[
  {
    "path": ".config/dotnet-tools.json",
    "content": "{\n  \"version\": 1,\n  \"isRoot\": true,\n  \"tools\": {\n    \"husky\": {\n      \"version\": \"0.8.0\",\n      \"commands\": [\n        \"husky\"\n      ],\n      \"rollForward\": false\n    },\n    \"run-script\": {\n      \"version\": \"0.6.0\",\n      \"commands\": [\n        \"r\"\n      ],\n      \"rollForward\": false\n    },\n    \"csharpier\": {\n      \"version\": \"1.2.1\",\n      \"commands\": [\n        \"csharpier\"\n      ],\n      \"rollForward\": false\n    }\n  }\n}"
  },
  {
    "path": ".csharpierignore",
    "content": "﻿*\n!**/*.cs\n!**/*.axaml\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n1. Description:\nA brief description of the error or issue you have encountered.\n\n2. Steps to Reproduce:\n[Step 1]\n[Step 2]\n[Step 3]\n...\n\n3. Expected Result:\nDescribe what you expected to see after performing the steps mentioned above.\n\n4. Actual Result:\nDescribe what actually happened after performing the steps mentioned above.\n\n5. Screenshots:\nIf applicable, add screenshots to help explain your problem.\n\n6. Environment:\nDesktop:\n    OS: [e.g., Windows 10]\n    Version: [e.g., 99.0]\n\n7. Additional Context:\n Include relevant console logs.[Your console logs here]❌Errors [], ⚠️Info [], 📜Log [] \n\nPlease ensure you have provided all necessary steps and information to reproduce the bug.\n"
  },
  {
    "path": ".github/workflows/api-release-dev.yml",
    "content": "name: Release Api Github only\n\non:\n  push:\n    tags:\n      - \"api-v[0-9]+.[0-9]+.[0-9]+-dev.[0-9]+\"\n      - \"api-v[0-9]+.[0-9]+.[0-9]+-dev\"\n\nenv:\n  GITHUB_PACKAGES_URL: 'https://nuget.pkg.github.com/asv-soft/index.json'\n  PROJECT_NAME: 'Asv.Drones'\n  PROPS_VERSION_VAR_NAME: 'ApiVersion'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    if: startsWith(github.ref, 'refs/tags/api-v')\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Setup .NET\n        uses: actions/setup-dotnet@v4\n        with:\n          dotnet-version: '10.x.x'\n          \n      - name: Add NuGet source\n        run: dotnet nuget add source ${{ env.GITHUB_PACKAGES_URL }} \\--username '${{secrets.USER_NAME}}' \\--password '${{secrets.GIHUB_NUGET_AUTH_TOKEN}}' \\--store-password-in-clear-text\n\n      - name: Install dependencies\n        run: |\n          dotnet restore ./src/${{env.PROJECT_NAME}}/${{env.PROJECT_NAME}}.csproj\n          dotnet restore ./src/${{env.PROJECT_NAME}}.Api/${{env.PROJECT_NAME}}.Api.csproj\n\n      - name: Build\n        run: |\n          dotnet build ./src/${{env.PROJECT_NAME}}/${{env.PROJECT_NAME}}.csproj --configuration Release --no-restore\n          dotnet build ./src/${{env.PROJECT_NAME}}.Api/${{env.PROJECT_NAME}}.Api.csproj --configuration Release --no-restore\n\n      - name: Set version variable\n        env:\n          TAG: ${{ github.ref_name }}\n        run: echo \"VERSION=${TAG#api-v}\" >> $GITHUB_ENV\n        \n      - name: Read version from Directory.Build.props\n        id: read-version\n        run: |\n            version=$(grep -oP '<${{env.PROPS_VERSION_VAR_NAME}}>\\K[^<]+' ./src/Directory.Build.props)\n            echo \"PropsVersion=${version}\" >> $GITHUB_ENV\n            \n      - name: Compare tag with NuGet package version\n        run: |\n          if [ \"${{ env.PropsVersion }}\" != \"${{ env.VERSION }}\" ]; then\n            echo \"Error: Tag does not match NuGet package version\"\n            exit 1\n          fi\n\n      - name: Pack package\n        run: |\n          dotnet pack ./src/${{env.PROJECT_NAME}}/${{env.PROJECT_NAME}}.csproj -c Release /p:ProductVersion=${VERSION} --no-build -o .\n          dotnet pack ./src/${{env.PROJECT_NAME}}.Api/${{env.PROJECT_NAME}}.Api.csproj -c Release /p:ProductVersion=${VERSION} --no-build -o .\n\n      - name: List output files\n        run: ls -la\n\n      - name: Push package to GitHub\n        run: |\n          dotnet nuget push ${{env.PROJECT_NAME}}.${{ env.VERSION }}.nupkg --api-key ${{ secrets.GIHUB_NUGET_AUTH_TOKEN }} --skip-duplicate --source ${{ env.GITHUB_PACKAGES_URL }}\n          dotnet nuget push ${{env.PROJECT_NAME}}.Api.${{ env.VERSION }}.nupkg --api-key ${{ secrets.GIHUB_NUGET_AUTH_TOKEN }} --skip-duplicate --source ${{ env.GITHUB_PACKAGES_URL }}\n"
  },
  {
    "path": ".github/workflows/api-release.yml",
    "content": "name: Release Api NuGet\n\non:\n  push:\n    tags:\n      - \"api-v[0-9]+.[0-9]+.[0-9]+\"\n      - \"api-v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+\"\n      - \"api-v[0-9]+.[0-9]+.[0-9]+-rc\"\n\nenv:\n  NUGET_SOURCE_URL: 'https://api.nuget.org/v3/index.json'\n  GITHUB_PACKAGES_URL: 'https://nuget.pkg.github.com/asv-soft/index.json'\n  PROJECT_NAME: 'Asv.Drones'\n  PROPS_VERSION_VAR_NAME: 'ApiVersion'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    if: startsWith(github.ref, 'refs/tags/api-v')\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Setup .NET\n        uses: actions/setup-dotnet@v4\n        with:\n          dotnet-version: '10.x.x'\n          \n      - name: Add NuGet source\n        run: dotnet nuget add source ${{ env.GITHUB_PACKAGES_URL }} \\--username '${{secrets.USER_NAME}}' \\--password '${{secrets.GIHUB_NUGET_AUTH_TOKEN}}' \\--store-password-in-clear-text\n\n      - name: Install dependencies\n        run: | \n          dotnet restore ./src/${{env.PROJECT_NAME}}/${{env.PROJECT_NAME}}.csproj\n          dotnet restore ./src/${{env.PROJECT_NAME}}.Api/${{env.PROJECT_NAME}}.Api.csproj\n\n      - name: Build\n        run: |\n          dotnet build ./src/${{env.PROJECT_NAME}}/${{env.PROJECT_NAME}}.csproj --configuration Release --no-restore\n          dotnet build ./src/${{env.PROJECT_NAME}}.Api/${{env.PROJECT_NAME}}.Api.csproj --configuration Release --no-restore\n\n      - name: Set version variable\n        env:\n          TAG: ${{ github.ref_name }}\n        run: echo \"VERSION=${TAG#api-v}\" >> $GITHUB_ENV\n        \n      - name: Read version from Directory.Build.props\n        id: read-version\n        run: |\n          version=$(grep -oP '<${{env.PROPS_VERSION_VAR_NAME}}>\\K[^<]+' ./src/Directory.Build.props)\n          echo \"PropsVersion=${version}\" >> $GITHUB_ENV\n          \n      - name: Compare tag with NuGet package version\n        run: |\n          if [ \"${{ env.PropsVersion }}\" != \"${{ env.VERSION }}\" ]; then\n            echo \"Error: Tag does not match NuGet package version\"\n            exit 1\n          fi\n\n      - name: Pack package\n        run: |\n          dotnet pack ./src/${{env.PROJECT_NAME}}/${{env.PROJECT_NAME}}.csproj -c Release /p:ProductVersion=${VERSION} --no-build -o .\n          dotnet pack ./src/${{env.PROJECT_NAME}}.Api/${{env.PROJECT_NAME}}.Api.csproj -c Release /p:ProductVersion=${VERSION} --no-build -o .\n\n      - name: List output files\n        run: ls -la\n\n      - name: Push package to GitHub\n        run: |\n          dotnet nuget push ${{env.PROJECT_NAME}}.${{ env.VERSION }}.nupkg --api-key ${{ secrets.GIHUB_NUGET_AUTH_TOKEN }} --skip-duplicate --source ${{ env.GITHUB_PACKAGES_URL }}\n          dotnet nuget push ${{env.PROJECT_NAME}}.Api.${{ env.VERSION }}.nupkg --api-key ${{ secrets.GIHUB_NUGET_AUTH_TOKEN }} --skip-duplicate --source ${{ env.GITHUB_PACKAGES_URL }}\n\n      - name: Push package to Nuget\n        run: |\n          dotnet nuget push ${{env.PROJECT_NAME}}.${{ env.VERSION }}.nupkg --api-key ${{ secrets.NUGET_AUTH_TOKEN }} --skip-duplicate --source ${{ env.NUGET_SOURCE_URL }}\n          dotnet nuget push ${{env.PROJECT_NAME}}.Api.${{ env.VERSION }}.nupkg --api-key ${{ secrets.NUGET_AUTH_TOKEN }} --skip-duplicate --source ${{ env.NUGET_SOURCE_URL }}"
  },
  {
    "path": ".github/workflows/drones-release-windows.yml",
    "content": "name: Build and Release Drones For Windows\n\non:\n  push:\n    tags:\n      - 'v[0-9]+.[0-9]+.[0-9]+'\n      - 'v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+'\n      - 'v[0-9]+.[0-9]+.[0-9]+-rc'\n\nenv:\n  NUGET_SOURCE_URL: 'https://api.nuget.org/v3/index.json'\n  GITHUB_PACKAGES_URL: 'https://nuget.pkg.github.com/asv-soft/index.json'\n  PROJECT_NAME: 'Asv.Drones'\n  PROPS_VERSION_VAR_NAME: 'ApiVersion'\n\njobs:\n  build-and-release:\n    runs-on: windows-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v3\n\n      - name: Setup .NET\n        uses: actions/setup-dotnet@v4\n        with:\n          dotnet-version: '10.x.x'\n\n      - name: Add NuGet source\n        run: dotnet nuget add source ${{ env.GITHUB_PACKAGES_URL }} --username '${{secrets.USER_NAME}}' --password '${{secrets.GIHUB_NUGET_AUTH_TOKEN}}' --store-password-in-clear-text\n\n      - name: Install dependencies\n        run: |\n          dotnet restore ./src/${{ env.PROJECT_NAME }}/${{ env.PROJECT_NAME }}.csproj\n          dotnet restore ./src/${{ env.PROJECT_NAME }}.Desktop/${{ env.PROJECT_NAME }}.Desktop.csproj\n\n      - name: Build\n        run: |\n          dotnet build ./src/${{ env.PROJECT_NAME }}/${{ env.PROJECT_NAME }}.csproj --no-restore\n          dotnet build ./src/${{ env.PROJECT_NAME }}.Desktop/${{ env.PROJECT_NAME }}.Desktop.csproj --no-restore\n\n      - name: Set version variable\n        env:\n          TAG: ${{ github.ref_name }}\n        run: echo \"VERSION=${TAG#v}\" >> $GITHUB_ENV\n\n\n      # here you must define path to your .csproj\n      - name: Publish project for installer\n        run: dotnet publish ./src/${{ env.PROJECT_NAME }}.Desktop/${{ env.PROJECT_NAME }}.Desktop.csproj -c Release -o ./publish/app\n\n      - name: Sign app files\n        uses: dlemstra/code-sign-action@v1\n        with:\n          certificate: '${{ secrets.WINDOWS_SIGNING_CERTIFICATE }}'\n          password: '${{ secrets.WINDOWS_SIGNING_PASSWORD }}'\n          folder: './publish/app'\n          recursive: true\n          description: 'Sign The App'\n\n      - name: Install NSIS\n        run: |\n          choco install nsis\n\n      #here you must define path to your .nsi file (it is used for installer setup and creation)\n      - name: Create EXE installer\n        uses: joncloud/makensis-action@v4\n        with:\n          script-file: win-64-install.nsi\n\n      - name: Sign the installer\n        uses: dlemstra/code-sign-action@v1\n        with:\n          certificate: '${{ secrets.WINDOWS_SIGNING_CERTIFICATE }}'\n          password: '${{ secrets.WINDOWS_SIGNING_PASSWORD }}'\n          files: |\n            AsvDronesInstaller.exe\n          description: 'Sign The Installer'\n\n      - name: List output files\n        run: Get-ChildItem -Path ./publish/app -Force\n\n      - name: Create Release\n        id: create_release\n        uses: actions/create-release@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GIHUB_NUGET_AUTH_TOKEN }}\n          RELEASE_BODY: ${{ steps.create-release-notes.outputs.release-notes }}\n        with:\n          tag_name: ${{ github.ref }}\n          release_name: Release ${{ github.ref }}\n          draft: false\n          prerelease: ${{ contains(github.ref, 'rc') }}\n\n\n      - name: Upload Release Asset\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GIHUB_NUGET_AUTH_TOKEN }}\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ./AsvDronesInstaller.exe\n          asset_name: asv-drones-${{ github.ref_name }}-setup-windows-64.exe\n          asset_content_type: application/vnd.microsoft.portable-executable"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n[Ll]ogs/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# Ionide (cross platform F# VS Code tools) working folder\n.ionide/\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\n## husky task runner examples -------------------\n## Note : for local installation use 'dotnet' prefix. e.g. 'dotnet husky'\n\n## run all tasks\n#husky run\n\n### run all tasks with group: 'group-name'\n#husky run --group group-name\n\n## run task with name: 'task-name'\n#husky run --name task-name\n\n## pass hook arguments to task\n#husky run --args \"$1\" \"$2\"\n\n## or put your custom commands -------------------\n#echo 'Husky.Net is awesome!'\n\ndotnet husky run --group check\ndotnet husky run --group check\n"
  },
  {
    "path": ".husky/task-runner.json",
    "content": "{\n   \"$schema\": \"https://alirezanet.github.io/Husky.Net/schema.json\",\n   \"tasks\": [\n      {\n         \"name\": \"Run csharpier\",\n         \"command\": \"dotnet\",\n         \"args\": [ \"csharpier\", \"format\", \".\" ],\n         \"group\": \"prettier\"\n      },\n      {\n         \"name\": \"Run csharpier check\",\n         \"command\": \"dotnet\",\n         \"args\": [ \"csharpier\", \"check\", \".\" ],\n         \"group\": \"check\"\n      }\n   ]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Asv Soft LLC\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "![linkedin](https://github.com/user-attachments/assets/4fa5221e-7ae5-4b6b-98a8-1c1e39b49afb)\n\n\n\n[//]: [![Release](https://github.com/asv-soft/asv-drones/actions/workflows/ReleaseDeployAction.yml/badge.svg)](https://github.com/asv-soft/asv-drones/actions/workflows/ReleaseDeployAction.yml)\n\n## 1. Introduction\n\nAsv.Drones: Empowering Innovation in Unmanned Aerial Systems\n\nWelcome to Asv.Drones, an advanced and modular open-source application designed to revolutionize the field of Unmanned Aerial Systems (UAS). \nCommitted to fostering innovation and collaboration, Asv.Drones is not just a drone application; it's a community-driven platform that opens the doors to limitless possibilities.\n\nKey Features:\n\n1. **Modularity:**\n   Asv.Drones embraces a modular architecture, allowing users to tailor the application to their specific needs. Each module serves a distinct purpose, contributing to the overall functionality and versatility of the platform.\n\n2. **Open Source Philosophy:**\n   Transparency and collaboration lie at the heart of Asv.Drones. The entire application, along with its constituent modules, is open source. This means that not only can users benefit from the software, but they can also actively contribute to its enhancement and evolution.\n\n3. **Modules Overview:**\n\n  - **[Asv.Drones.Gbs](https://github.com/asv-soft/asv-drones-gbs) (Ground Base Station Service):**\n    This module provides a robust ground base station service, ensuring seamless communication between the drone and the operator on the ground. Open source nature encourages customization for specific ground station requirements.\n\n  - **Obsolete [Asv.Drones.Sdr](https://github.com/asv-soft/asv-drones-sdr) (SDR Payload Example Project):**\n    Explore the possibilities of Software-Defined Radio (SDR) payloads with this open-source example project. Asv.Drones.Sdr serves as a foundation for integrating cutting-edge SDR technologies into unmanned aerial systems.\n\n  - **[Asv.Gnss](https://github.com/asv-soft/asv-gnss) (GNSS Library):**\n    The Asv.Gnss module is a comprehensive GNSS library that parses RTCMv2, RTCMv3 and NMEA protocols. It goes a step further by providing control over receivers through SBF, ComNav and UBX protocols, all tailored for .NET environments.\n\n  - **[Asv.Mavlink](https://github.com/asv-soft/asv-mavlink) (MAVLink Library for .NET 9.0):**\n    For seamless communication and control, Asv.MAVLink is a dedicated library compatible with .NET 9.0. It ensures that your drone's communication adheres to the MAVLink protocol standards.\n\n  - **[Asv.Common](https://github.com/asv-soft/asv-common):**\n    Asv.Common serves as the backbone, offering common types and extensions for all Asv-based libraries. It streamlines development, ensuring consistency and efficiency across different modules.\n\n  - **[Asv.Avalonia](https://github.com/asv-soft/asv-avalonia):**\n    Asv.Avalonia is a custom framework built on top of Avalonia. It defines the fundamental rules for cross-platform applications and allows for their rapid assembly. \n    Out of the box, the framework provides an event system for ViewModels, a powerful undo/redo mechanism for various user actions, a theme with diverse styles, a cross-platform dialog system and more. \n    Asv.Avalonia features various additional modules that extend its functionality.\n    \n  Here's a schematic representation of the whole project:\n<p align=\"center\">\n    <img src=\"img/screenshot-asv-drones-structure.png\" alt=\"structure\" width=\"650\" />\n</p>\n\n4. **Community Collaboration:**\n   Asv.Drones thrives on community collaboration. Developers, enthusiasts and innovators are encouraged to contribute, share insights and collectively shape the future of unmanned aerial systems.\n\nEmbark on a journey of exploration, experimentation and innovation with Asv.Drones. Whether you're a developer, researcher or drone enthusiast, this open-source platform invites you to redefine the possibilities of unmanned aerial systems.\n\n<p align=\"center\">\n    <img src=\"img/screenshot-windows-flights.png\" alt=\"win\" width=\"650\" />\n    <img src=\"img/screenshot-flight-many-drones.png\" alt=\"win\" width=\"650\" />\n    <img src=\"img/screenshot-packet-viewer.png\" alt=\"packet-viewer\" width=\"650\" />\n    <img src=\"img/screenshot-connections.png\" alt=\"connections\" width=\"650\" />\n    <img src=\"img/screenshot-settings-commands.png\" alt=\"settings\" width=\"650\" />\n</p>\n\n## 2. Different sets of components\nAsv.Drones can work with different combinations of its components\n\n### Example Of Usage With GBS\n\n**Ground Base Station Integration:** Asv.Drones offers seamless integration with ground base stations through our proprietary implementation called [Asv.Drones.Gbs](https://github.com/asv-soft/asv-drones-gbs). \nBuilt to operate via the MAVLink protocol, Asv.Drones.Gbs allows users to remotely manage and monitor drone operations from a centralized platform. \nMoreover, any other ground base station software compatible with MAVLink can seamlessly interface with our application, ensuring flexibility and interoperability across different systems (development of additional UI controls may be required).\nWith Asv.Drones.Gbs, users can plan missions, monitor telemetry data and adjust flight parameters with ease.\nTo connect to a gbs, create a new connection (usually tcp) in the connection settings.\n\n<div align=\"center\">\n   <img src=\"img/asv-drones-gbs-packets.png\" alt=\"gbs-packets\" width=\"650\" />\n</div>\n\n## 3. Getting Started\n\n### Setting Up the Development Environment\n\nTo ensure a smooth development experience, follow the steps below to set up your development environment:\n\n### 3.1 Prerequisites:\n- **Operating System:** This project is compatible with Windows, macOS and Linux. Ensure that your development machine runs one of these supported operating systems.\n- **IDE (Integrated Development Environment):** We recommend using [Visual Studio](https://visualstudio.microsoft.com/) or [JetBrains Rider](https://www.jetbrains.com/rider/) as your IDE for C# development. \n- Make sure to install the necessary extensions and plugins for a better development experience.\n\n### 3.2 .NET Installation:\n- This project is built using [.NET 9.0](https://dotnet.microsoft.com/download/dotnet/9.0), the latest version of the .NET platform. \nWe recommend installing .NET 9.0 by following the instructions provided on the official [.NET website](https://dotnet.microsoft.com/download/dotnet/9.0).\n\n   ```bash\n   # Check your current .NET version\n   dotnet --version\n   ```\n\n### 3.3 Version Control:\n- If you haven't already, install a version control system such as [Git](https://git-scm.com/) to track changes and collaborate with other developers.\n\n### 3.4 Clone the Repository:\n- Clone the project repository to your local machine using the following command:\n\n   ```bash\n   git clone https://github.com/asv-soft/asv-drones.git\n   ```\n\n### 3.5 Restore Dependencies:\n- Navigate to the platform project directory and restore the required dependencies. \nThere are three possible platform directories to build and debug our app: __Asv.Drones.Desktop__, __Asv.Drones.Android__, __Asv.Drones.iOS__.\nCurrently, we support only the desktop platform.\nFor example, we will use __Asv.Drones.Desktop__ platform, so you have to execute the following command:\n\n   ```bash\n   cd asv-drones/src/Asv.Drones.Desktop\n   dotnet workload restore\n   dotnet workload repair\n   ```\n\n### 3.6 Build and Run:\n- After restoring, you have to build the project to ensure that everything is set up correctly, and if it's not - try to restore workloads again:\n\n   ```bash\n   dotnet build\n   ```\n\n- Run the project:\n\n   ```bash\n   dotnet run\n   ```\n\nCongratulations! Your development environment is now set up, and you are ready to start contributing to the project. \nIf you encounter any issues during the setup process, refer to the project's documentation or reach out to the development team for assistance.\n\n### Building for Android\n\nComing soon...\n\n## 4. Code Structure\n\nThe organization of the codebase plays a crucial role in maintaining a clean, scalable and easily understandable project. \nThis section outlines the structure of our codebase, highlighting key directories and their purposes.\n\n### 4.1 Solution Organization\n\nOur solution is organized the following way:\n\n- **`src/`:** This directory contains the source code of the application. \nThe code is further organized into projects, each residing in its own subdirectory. The goal is to promote modularity and maintainability.\n\n  ```\n  src/\n  ├── Asv.Drones.Android/\n  ├── Asv.Drones.Desktop/\n  ├── Asv.Drones.iOS/\n  ├── Asv.Drones/\n  │   ├── Commands/\n  │   ├── Controls/\n  │   ├── Shell/\n  │   └── ...\n  └── Asv.Drones.Api/\n      ├── Commands/\n      ├── Controls/\n      ├── Shell/\n      └── ...\n\n### 4.2 Naming Conventions\n\nConsistent naming conventions are essential for code readability. \nThroughout the codebase, we follow the guidelines outlined [in our documentation](https://docs.asv.me/use-cases/for-developers)\n\nThese conventions contribute to a unified and coherent codebase.\n\nBy adhering to this organized structure and naming conventions, we aim to create a codebase that is easy to navigate, scalable and conducive to collaboration among developers.\n\n## 5. Coding Style\n\nMaintaining a consistent coding style across the project enhances readability, reduces errors and facilitates collaboration. \nThe following guidelines outline our preferred coding style for C#:\n\n**Note:** We have auto formatters in our project to make your life easier. Read more about them in the husky section.\n\n### 5.1 C# Coding Style\n\n#### 5.1.1 Formatting\n\n- **Indentation:** Use tabs for indentation. Each level of indentation should consist of one tab.\n- **Brace Placement:** Place opening braces on the same line as the statement they belong to, and closing braces on a new line.\n\n    ```c#\n    // Good\n    if (condition)\n    {\n        // Code here\n    }\n\n    // Bad\n    if (condition) {\n        // Code here\n    }\n    ```\n\n#### 5.1.2 Naming Conventions\n\n- **Pascal Case:** Use Pascal case for class names, method names and property names.\n\n    ```c#\n    public class MyClass\n    {\n        public void MyMethod()\n        {\n            // Code here\n        }\n\n        public int MyProperty { get; set; }\n    }\n    ```\n\n#### 5.1.3 Language Features\n\n- **Expression-bodied Members:** Utilize expression-bodied members for concise one-liners.\n\n    ```c#\n    // Good\n    public int CalculateSquare(int x) => x * x;\n\n    // Bad\n    public int CalculateSquare(int x)\n    {\n        return x * x;\n    }\n    ```\n\n- **Null Conditional Operator:** Use the null conditional operator (`?.`) for safe property or method access.\n\n    ```c#\n    // Good\n    int? length = text?.Length;\n\n    // Bad\n    int length = (text != null) ? text.Length : 0;\n    \n    // or\n    \n    int length = text!.Length;\n    ```\n\n#### 5.1.4 Special Cases\n\nUsually you place public members after private members, but we have some exceptions:\n- **Page constants:** We place page ids and viewmodel ids at the top of the class.\n  ```c#\n  public const string PageId = \"files.browser\";\n  public const MaterialIconKind PageIcon = MaterialIconKind.FolderEye;\n  ```\n- **Export information:** We place export information at the end of the class.\n  ```c#\n  \n  ```\n\n#### 5.1.5 Husky\n\nWe use husky to make sure that our code is formatted correctly before committing. You may use it for your own code.\n\n1. Go to the src folder\n2. run:\n    ```bash\n    dotnet tool restore\n    ```\n3. run (use husky-unix instead of husky if you are on linux or macOS):\n    ```bash\n    dotnet r husky\n    ```\n4. run the following command to format the code:\n    ```bash\n    dotnet husky run\n    ```\n\n### 5.2 Documentation\n\n#### 5.2.1 Comments\n\n- **XML Documentation:** Include XML comments for classes, methods, and properties to provide comprehensive documentation.\n\n    ```c#\n    /// <summary>\n    /// Represents a sample class.\n    /// </summary>\n    public class SampleClass\n    {\n        /// <summary>\n        /// Calculates the sum of two numbers.\n        /// </summary>\n        /// <param name=\"a\">The first number.</param>\n        /// <param name=\"b\">The second number.</param>\n        /// <returns>The sum of the two numbers.</returns>\n        public int Add(int a, int b)\n        {\n            // Code here\n        }\n    }\n    ```\n\n#### 5.2.2 Code Comments\n\n- Use comments sparingly and focus on explaining complex or non-intuitive code sections.\n\nBy adhering to these coding style guidelines, we aim to create code that is straightforward to read, understand, and maintain.\n\n## 6. Version Control\n\nVersion control is a fundamental aspect of our development process, providing a systematic way to track changes, collaborate with team members and manage the evolution of our codebase. \nWe use Git as our version control system.\n\n### 6.1 Branching Strategy\n\n#### 6.1.1 Feature Branches\n\nFor each new feature or bug fix, create a dedicated feature branch. \nThe branch name should be descriptive of the feature or issue it addresses.\n\n```bash\n# Example: Creating a new feature branch\ngit checkout -b feature/my-new-feature\n```\n\n#### 6.1.2 Hotfix Branches\n\nIn case of critical issues in the production environment, create a hotfix branch. \nThis allows for a quick resolution without affecting the main development branch.\n\n```bash\n# Example: Creating a hotfix branch\ngit checkout -b hotfix/1.0.1\n```\n\n### 6.2 Commit Messages\n\nWrite clear and concise commit messages that convey the purpose of the change. Follow these guidelines:\n\n- Start with a verb in the imperative mood (e.g., \"Add,\" \"Fix,\" \"Update\").\n- Keep messages short but descriptive.\n- Use present tense.\n- Use feat, fix or chore prefixes to indicate the type of change.\n\nExample:\n\n```bash\n# Good\ngit commit -m \"fix: add user authentication feature\"\n\n# Bad\ngit commit -m \"Updated stuff\"\n```\n\n### 6.3 Pull Requests\n\nBefore merging changes into the main branch, create a pull request (PR). \nThis allows for code review and ensures that changes adhere to coding standards.\n\n- Assign reviewers to the PR.\n- Include a clear description of the changes.\n- Ensure that automated tests pass before merging.\n\n### 6.4 Merging Strategy\n\nAdopt a merging strategy based on the nature of the changes:\n\n- **Feature Branches:** Merge feature branches into the main branch after code review and approval.\n- **Release Branches:** Merge release branches into the main branch and tag the commit for the release.\n\n```bash\n# Example: Merging a feature branch\ngit checkout main\ngit merge --no-ff feature/my-new-feature\n```\n\n### 6.5 Repository Hosting\n\nOur Git repository is hosted on [GitHub](https://github.com/asv-soft/asv-drones). \nEnsure that you have the necessary permissions and follow the best practices for repository management.\n\nBy following these version control practices, we aim to maintain a well-organized and collaborative development process. \n\n## 7. Build and Deployment\n\nThe build and deployment processes are crucial parts of our development workflow. \nThis section outlines the steps for building the project and deploying it using GitHub Actions.\n\n### 7.1 Build Process\n\nTo compile the project, use the following command:\n\n```bash\ndotnet build\n```\n\nThis command compiles the code and produces executable binaries.\n\n### 7.2 Deployment using GitHub Releases\n\nOur application is deployed using [GitHub Actions](https://docs.github.com/en/actions).\n\nThe latest release can be found [here](https://github.com/asv-soft/asv-drones/releases).\n\n## 8. Contributing\n\nWe welcome contributions from the community to help enhance and improve our project. \nBefore contributing, please take a moment to review this guide.\n\n### 8.1 Code Reviews\n\nAll code changes undergo a review process to ensure quality and consistency. Here are the steps to follow:\n\n1. **Fork the Repository:** Start by forking the repository to your own GitHub account.\n\n2. **Create a Feature Branch:** Create a new branch for your feature or bug fix.\n\n   ```bash\n   git checkout -b feature/my-feature\n   ```\n\n3. **Commit Changes:** Make your changes, commit them with clear and concise messages, and push the branch to your forked repository.\n\n   ```bash\n   git commit -m \"feat: add new feature\"\n   git push origin feature/my-feature\n   ```\n\n4. **Squash your commit:** Squash your commits into a single commit before submitting a pull request.\n\n ```bash\n   git rebase -i main\n   \n    # squash your commits into a single commit by leaving the first line as \"pick\" and changing the rest to \"squash\"\n   \n   git push --force\n```\n\n5**Open a Pull Request (PR):** Submit a pull request to the main repository, detailing the changes made and any relevant information. Ensure your PR adheres to the established coding standards.\n\n6**Code Review:** Participate in the code review process by responding to feedback and making necessary adjustments. \nAddressing comments promptly helps streamline the review process.\n\n7**Merge:** Once the code review is complete and the changes are approved, your pull request will be merged into the main branch.\n\n### 8.2 Submitting Changes\n\nBefore submitting changes, ensure the following:\n\n- **Coding Standards:** Adhere to the coding standards and guidelines outlined in this document.\n\n- **Tests:** If applicable, include tests for your changes and ensure that existing tests pass.\n\n- **Documentation:** Update relevant documentation, including code comments and external documentation, to reflect your changes.\n\n### 8.3 Communication\n\nFor larger changes or feature additions, it's beneficial to discuss the proposed changes beforehand. Engage with the community through:\n\n- **Opening an Issue:** Discuss your proposed changes by opening an issue. \nThis provides an opportunity for community input before investing significant time in development.\n\n- **Joining Discussions:** Participate in existing discussions related to the project. Your insights and feedback are valuable.\n\n### 8.4 Contributor License Agreement (CLA)\n\nBy contributing to this project, you agree that your contributions will be licensed under the project's license. \nIf a Contributor License Agreement (CLA) is required, it will be provided in the repository.\n\nWe appreciate your contributions, and together we can make this project even better!\n\n## 9. Code Documentation\n\nClear and comprehensive code documentation is essential for ensuring that developers can easily understand, use and contribute to the project. \nFollow these guidelines for documenting your code:\n\n### 9.1 Inline Comments\n\nUse inline comments to explain specific sections of your code, especially for complex logic or non-intuitive implementations. Follow these principles:\n\n- **Clarity:** Write comments that enhance code comprehension. If a piece of code is not self-explanatory, provide comments explaining the reasoning or intention.\n\n- **Conciseness:** Keep comments concise and to the point. Avoid unnecessary comments that do not add value.\n\n- **Update Comments:** Regularly review and update comments to reflect any changes in the code. Outdated comments can be misleading.\n\nExample:\n\n```c#\n// Calculate the sum of two numbers\nint CalculateSum(int a, int b)\n{\n    return a + b;\n}\n```\n\n### 9.2 XML Documentation\n\nFor classes, methods, properties and other significant code elements, use XML documentation comments to provide comprehensive information. Follow these guidelines:\n\n- **Summary:** Provide a summary that succinctly describes the purpose of the class or member.\n\n- **Parameters:** Document each parameter, specifying its purpose and any constraints.\n\n- **Returns:** If applicable, document the return value and its significance.\n\n- **Examples:** Include examples that demonstrate how to use the class or member.\n\nExample:\n\n```c#\n/// <summary>\n/// Represents a utility class for mathematical operations.\n/// </summary>\npublic class MathUtility\n{\n    /// <summary>\n    /// Calculates the sum of two numbers.\n    /// </summary>\n    /// <param name=\"a\">The first number.</param>\n    /// <param name=\"b\">The second number.</param>\n    /// <returns>The sum of the two numbers.</returns>\n    public int CalculateSum(int a, int b)\n    {\n        return a + b;\n    }\n}\n```\n\n### 9.3 Consistency\n\nEnsure consistency in your documentation style across the codebase. \nConsistent documentation makes it easier for developers to navigate and understand the project.\n\nBy following these documentation guidelines, we aim to create a codebase that is not only functional but also accessible and easily maintainable for all contributors.\n\n## 10. Security\n\nEnsuring the security of our software is paramount to maintaining the integrity and confidentiality of user data. \nDevelopers should adhere to best practices and follow guidelines outlined in this section.\n\n### 10.1 Code Security Practices\n\n#### 10.1.1 Input Validation\n\nAlways validate and sanitize user input to prevent injection attacks and ensure the integrity of your application.\n\n```c#\n// Example for C#\npublic ActionResult ProcessUserInput(string userInput)\n{\n    if (string.IsNullOrWhiteSpace(userInput))\n    {\n        // Handle invalid input\n    }\n\n    // Process input\n}\n```\n\n#### 10.1.2 Authentication and Authorization\n\nImplement secure authentication and authorization mechanisms to control access to sensitive functionalities and data. \nLeverage industry-standard protocols like OAuth when applicable.\n\n#### 10.1.3 Secure Communication\n\nEnsure that communication between components, APIs and external services is encrypted using secure protocols (e.g., HTTPS).\n\n### 10.2 Dependency Security\n\n#### 10.2.1 Dependency Scanning\n\nRegularly scan and update dependencies to identify and address security vulnerabilities. \nLeverage tools and services that provide automated dependency analysis.\n\n#### 10.2.2 Minimal Dependencies\n\nKeep dependencies to a minimum and only include libraries and packages that are actively maintained and have a good security track record.\n\n### 10.3 Data Protection\n\n#### 10.3.1 Encryption\n\nSensitive data, both at rest and in transit, should be encrypted. Use strong encryption algorithms and ensure proper key management.\n\n#### 10.3.2 Data Backups\n\nImplement regular data backup procedures to prevent data loss in the event of security incidents or system failures.\n\n### 10.4 Secure Coding Standards\n\nAdhere to secure coding standards to mitigate common vulnerabilities. \nFollow principles such as the [OWASP Top Ten](https://owasp.org/www-project-top-ten/) to address security concerns in your codebase.\n\n### 10.5 Reporting Security Issues\n\nIf you discover a security vulnerability or have concerns about the security of the project, please report it immediately to our team at [our telegram channel](https://t.me/asvsoft).\nDo not disclose security-related issues publicly until they have been addressed.\n\n### 9.6 Security Training\n\nEncourage ongoing security training for all team members to stay informed about the latest security threats and best practices. \nKnowledgeable developers are key to maintaining a secure codebase.\n\nBy incorporating security practices into our development process, we aim to create a robust and secure software environment for our users.\n\n## 11. License\n\nThis project is licensed under the terms of the MIT License. \nA copy of the MIT License is provided in the [LICENSE](https://github.com/asv-soft/asv-drones?tab=MIT-1-ov-file#) file.\n\n### MIT License\n\n```\nMIT License\n\nCopyright (c) 2023 Asv Soft LLC\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n```\n\n### Using the MIT License\n\nThe MIT License is a permissive open-source license that allows for the free use, modification and distribution of the software. \nIt is important to review and understand the terms of the license before using, contributing to or distributing this software.\n\nBy contributing to this project, you agree that your contributions will be licensed under the MIT License.\n\nFor more details about the MIT License, please visit [opensource.org/licenses/MIT](https://opensource.org/licenses/MIT).\n\n## 12. Contact\n\nIf you have questions, suggestions, or need assistance with the project, we encourage you to reach out through the following channels:\n\n### 12.1 Telegram Channel\n\nVisit our Telegram channel: [ASVSoft on Telegram](https://t.me/asvsoft)\n\nFeel free to join our Telegram community to engage in discussions, seek help, or share your insights.\n\n### 12.2 GitHub Issues\n\nFor bug reports, feature requests or any project-related discussions, please use our GitHub Issues:\n\n[Project Issues on GitHub](https://github.com/asv-soft/asv-drones/issues)\n\nOur GitHub repository is the central hub for project-related discussions and issue tracking. \nPlease check existing issues before creating new ones to avoid duplication.\n\n### 12.3 Security Concerns\n\nIf you discover a security vulnerability or have concerns about the security of the project, please report it immediately to our telegram channel: [ASVSoft on Telegram](https://t.me/asvsoft). \nDo not disclose security-related issues publicly until they have been addressed.\n\n### 12.4 General Inquiries\n\nFor general inquiries or if you prefer email communication, you can reach us at [me@asv.me](mailto:me@asv.me).\n\nWe value your feedback and contributions, and we look forward to hearing from you!\n"
  },
  {
    "path": "api_build.bat",
    "content": "@echo off\nsetlocal enabledelayedexpansion\nrem ====== projects ======\n\nset project=Asv.Drones.Gui.Api\n\n\nset \"file=.\\src\\Directory.Build.props\"\n\n\n::    ApiVersion   \nfor /f \"tokens=2 delims=> \" %%a in ('findstr /i /c:\"<ApiVersion>\" \"%file%\"') do (\n    set \"line=%%a\"\n    for /f \"tokens=1 delims=<\" %%b in (\"!line!\") do (\n        set \"ApiVersion=%%b\"\n    )\n)\n\n::    \nif defined ApiVersion (\n    echo ApiVersion: %ApiVersion%\n    \tdotnet restore ./src/%project%/%project%.csproj\n\tdotnet build /p:SolutionDir=../;ProductVersion=%ApiVersion% ./src/%project%/%project%.csproj -c Release\n\tdotnet pack ./src/%project%/%project%.csproj -c Release\n\n\n) else (\n    echo ApiVersion not found\n)\n\nendlocal\npause"
  },
  {
    "path": "api_publish_github.bat",
    "content": "@echo off\nsetlocal enabledelayedexpansion\nrem ====== projects ======\n\nset project=Asv.Drones.Gui.Api\n\n\nset \"file=.\\src\\Directory.Build.props\"\n\n\n::    ApiVersion   \nfor /f \"tokens=2 delims=> \" %%a in ('findstr /i /c:\"<ApiVersion>\" \"%file%\"') do (\n    set \"line=%%a\"\n    for /f \"tokens=1 delims=<\" %%b in (\"!line!\") do (\n        set \"ApiVersion=%%b\"\n    )\n)\n\n::    \nif defined ApiVersion (\n    echo ApiVersion: %ApiVersion%\n\tcd src\\%project%\\bin\\Release\\\nrem\tdotnet nuget push %project%.%ApiVersion%.nupkg --skip-duplicate --source https://api.nuget.org/v3/index.json\n\tdotnet nuget push %project%.%ApiVersion%.nupkg --skip-duplicate --source https://nuget.pkg.github.com/asv-soft/index.json\n\n\n) else (\n    echo ApiVersion not found\n)\n\nendlocal\npause\n\n\n\n"
  },
  {
    "path": "publish.bat",
    "content": "cd publish\n\nfor /d %%i in (\".\\*\") do (\n    rmdir /s /q \"%%i\"\n)\n\ncd ../src/Asv.Drones.Gui.Desktop\n\ndotnet publish -c Release -r win-arm --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true  -o ~/../../../publish/win-arm/app\ndotnet publish -c Release -r win-arm64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true  -o ~/../../../publish/win-arm64/app\ndotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -o ~/../../../publish/win-x64/app\ndotnet publish -c Release -r win-x86 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -o ~/../../../publish/win-x86/app\n\ndotnet publish -c Release -r linux-arm --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -o ~/../../../publish/linux-arm/app\ndotnet publish -c Release -r linux-arm64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -o ~/../../../publish/linux-arm64/app\ndotnet publish -c Release -r linux-musl-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -o ~/../../../publish/linux-musl-x64/app\ndotnet publish -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -o ~/../../../publish/linux-x64/app\n\ndotnet publish -c Release -r osx-arm64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -o ~/../../../publish/osx-arm64/app\ndotnet publish -c Release -r osx-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -o ~/../../../publish/osx-x64/app\n\ncd ../../publish\ndel /S *.pdb\n\ncd win-arm/app\nmove Asv.Drones.Gui.Desktop.exe asv-drones-win-arm.exe\ncd ../../win-arm64/app\nmove Asv.Drones.Gui.Desktop.exe asv-drones-win-arm64.exe\ncd ../../win-x64/app\nmove Asv.Drones.Gui.Desktop.exe asv-drones-win-x64.exe\ncd ../../win-x86/app\nmove Asv.Drones.Gui.Desktop.exe asv-drones-win-x86.exe\n\ncd ../../linux-arm/app\nmove Asv.Drones.Gui.Desktop asv-drones-linux-arm\ncd ../../linux-arm64/app\nmove Asv.Drones.Gui.Desktop asv-drones-linux-arm64 \ncd ../../linux-musl-x64/app\nmove Asv.Drones.Gui.Desktop asv-drones-linux-musl-x64\ncd ../../linux-x64/app\nmove Asv.Drones.Gui.Desktop asv-drones-linux-x64\n\ncd ../../osx-arm64/app\nmove Asv.Drones.Gui.Desktop asv-drones-osx-arm64\ncd ../../osx-x64/app\nmove Asv.Drones.Gui.Desktop asv-drones-osx-x64\ncd ../../..\n\nsetlocal enabledelayedexpansion\n\nset \"xmlFile=src\\Directory.Build.props\"\n\nfor /f \"tokens=2 delims=<> \" %%a in ('findstr /i \"<ProductVersion>\" \"%xmlFile%\"') do (\n    set \"productVersion=%%a\"\n)\n\nset \"issFile=win-arm-install.iss\"\n\nset \"tempFile=%temp%\\temp.iss\"\n\nfor /f \"tokens=*\" %%a in ('type \"%issFile%\"') do (\n    set \"line=%%a\"\n\n    echo !line! | findstr /C:\"#define MyAppVersion\" > nul\n\n    if !errorlevel! == 0 (\n        echo #define MyAppVersion \"%productVersion%\" >> \"%tempFile%\"\n    ) else (\n        echo !line! >> \"%tempFile%\"\n    )\n)\n\nmove /y \"%tempFile%\" \"%issFile%\" > nul\n\nset \"issFile=win-arm64-install.iss\"\n\nset \"tempFile=%temp%\\temp.iss\"\n\nfor /f \"tokens=*\" %%a in ('type \"%issFile%\"') do (\n    set \"line=%%a\"\n\n    echo !line! | findstr /C:\"#define MyAppVersion\" > nul\n\n    if !errorlevel! == 0 (\n        echo #define MyAppVersion \"%productVersion%\" >> \"%tempFile%\"\n    ) else (\n        echo !line! >> \"%tempFile%\"\n    )\n)\n\nmove /y \"%tempFile%\" \"%issFile%\" > nul\n\nset \"issFile=win-x64-install.iss\"\n\nset \"tempFile=%temp%\\temp.iss\"\n\nfor /f \"tokens=*\" %%a in ('type \"%issFile%\"') do (\n    set \"line=%%a\"\n\n    echo !line! | findstr /C:\"#define MyAppVersion\" > nul\n\n    if !errorlevel! == 0 (\n        echo #define MyAppVersion \"%productVersion%\" >> \"%tempFile%\"\n    ) else (\n        echo !line! >> \"%tempFile%\"\n    )\n)\n\nmove /y \"%tempFile%\" \"%issFile%\" > nul\n\nset \"issFile=win-x86-install.iss\"\n\nset \"tempFile=%temp%\\temp.iss\"\n\nfor /f \"tokens=*\" %%a in ('type \"%issFile%\"') do (\n    set \"line=%%a\"\n\n    echo !line! | findstr /C:\"#define MyAppVersion\" > nul\n\n    if !errorlevel! == 0 (\n        echo #define MyAppVersion \"%productVersion%\" >> \"%tempFile%\"\n    ) else (\n        echo !line! >> \"%tempFile%\"\n    )\n)\n\nmove /y \"%tempFile%\" \"%issFile%\" > nul\n\nendlocal\n\ncd publish\n\niscc ../win-arm-install.iss\niscc ../win-arm64-install.iss\niscc ../win-x86-install.iss\niscc ../win-x64-install.iss\n\n\nwsl sed -i 's/\\r//' linux_packages.sh\nwsl sed -i 's/\\r//' osx_packages.sh\n\nwsl ../linux_packages.sh\n\nwsl ../osx_packages.sh"
  },
  {
    "path": "src/.aiassistant/rules/comments.md",
    "content": "---\napply: always\n---\n\n## Comment and Documentation Language\n\n- Write all code comments in English.\n- Write all XML documentation, Markdown documentation, README content, and other developer-facing documentation in English.\n- Do not use Russian or mixed-language comments or documentation.\n- Keep terminology consistent across code, comments, and documentation.\n- Use clear English names for types, members, variables, files, modules, and public APIs.\n\n## Comment Quality\n\n- Prefer self-explanatory code over excessive comments.\n- Add comments only when they explain intent, constraints, assumptions, tradeoffs, or non-obvious behavior.\n- Do not add comments that only restate what the code already makes obvious.\n- Keep comments concise, accurate, and aligned with the current implementation.\n- Update or remove comments when the code changes so documentation never becomes misleading.\n\n## Architecture and Design\n\n- Keep the architecture clean, modular, and easy to maintain.\n- Follow SOLID principles in design and implementation.\n- Give each class, service, and module a single, well-defined responsibility.\n- Prefer composition over inheritance unless inheritance is clearly justified.\n- Minimize coupling and keep related behavior cohesive.\n- Separate domain logic from UI, infrastructure, persistence, and framework-specific concerns.\n- Depend on abstractions at system boundaries when this improves testability, extensibility, or clarity.\n- Keep public APIs explicit, stable, and easy to understand.\n- Eliminate duplicated logic through extraction or refactoring instead of copying behavior.\n- Avoid god objects, hidden side effects, and unclear ownership of responsibilities.\n\n---\napply: always\n---\n\nBehavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.\n\n**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.\n\n## 1. Think Before Coding\n\n**Don't assume. Don't hide confusion. Surface tradeoffs.**\n\nBefore implementing:\n- State your assumptions explicitly. If uncertain, ask.\n- If multiple interpretations exist, present them - don't pick silently.\n- If a simpler approach exists, say so. Push back when warranted.\n- If something is unclear, stop. Name what's confusing. Ask.\n\n## 2. Simplicity First\n\n**Minimum code that solves the problem. Nothing speculative.**\n\n- No features beyond what was asked.\n- No abstractions for single-use code.\n- No \"flexibility\" or \"configurability\" that wasn't requested.\n- No error handling for impossible scenarios.\n- If you write 200 lines and it could be 50, rewrite it.\n\nAsk yourself: \"Would a senior engineer say this is overcomplicated?\" If yes, simplify.\n\n## 3. Surgical Changes\n\n**Touch only what you must. Clean up only your own mess.**\n\nWhen editing existing code:\n- Don't \"improve\" adjacent code, comments, or formatting.\n- Don't refactor things that aren't broken.\n- Match existing style, even if you'd do it differently.\n- If you notice unrelated dead code, mention it - don't delete it.\n\nWhen your changes create orphans:\n- Remove imports/variables/functions that YOUR changes made unused.\n- Don't remove pre-existing dead code unless asked.\n\nThe test: Every changed line should trace directly to the user's request.\n\n## 4. Goal-Driven Execution\n\n**Define success criteria. Loop until verified.**\n\nTransform tasks into verifiable goals:\n- \"Add validation\" → \"Write tests for invalid inputs, then make them pass\"\n- \"Fix the bug\" → \"Write a test that reproduces it, then make it pass\"\n- \"Refactor X\" → \"Ensure tests pass before and after\"\n\nFor multi-step tasks, state a brief plan:\n```\n1. [Step] → verify: [check]\n2. [Step] → verify: [check]\n3. [Step] → verify: [check]\n```\n\nStrong success criteria let you loop independently. Weak criteria (\"make it work\") require constant clarification.\n\n---\n\n**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.\n\n"
  },
  {
    "path": "src/.editorconfig",
    "content": "[*.cs]\ndotnet_diagnostic.RCS1037.severity = none\ndotnet_diagnostic.RCS1251.severity = none\n\n[*.axaml]\ncsharpier_formatter = xml\nindent_size = 4"
  },
  {
    "path": "src/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Ww][Ii][Nn]32/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n[Ll]ogs/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# Tye\n.tye/\n\n# ASP.NET Scaffolding\nScaffoldingReadMe.txt\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Coverlet is a free, cross platform Code Coverage Tool\ncoverage*.json\ncoverage*.xml\ncoverage*.info\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# Ionide (cross platform F# VS Code tools) working folder\n.ionide/\n\n# Fody - auto-generated XML schema\nFodyWeavers.xsd\n\n##\n## Visual studio for Mac\n##\n\n\n# globs\nMakefile.in\n*.userprefs\n*.usertasks\nconfig.make\nconfig.status\naclocal.m4\ninstall-sh\nautom4te.cache/\n*.tar.gz\ntarballs/\ntest-results/\n\n# Mac bundle stuff\n*.dmg\n*.app\n\n# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore\n# Windows thumbnail cache files\nThumbs.db\nehthumbs.db\nehthumbs_vista.db\n\n# Dump file\n*.stackdump\n\n# Folder config file\n[Dd]esktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n# JetBrains Rider\n.idea/\n*.sln.iml\n\n##\n## Visual Studio Code\n##\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n.qodo\n"
  },
  {
    "path": "src/.run/Publish win-x64.run.xml",
    "content": "﻿<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"Publish win-x64\" type=\"DotNetFolderPublish\" factoryName=\"Publish to folder\">\n    <riderPublish configuration=\"Release\" delete_existing_files=\"true\" platform=\"Any CPU\" produce_single_file=\"true\" ready_to_run=\"true\" self_contained=\"true\" target_folder=\"$PROJECT_DIR$/Asv.Drones.Desktop/bin/Release/net10.0/publish\" target_framework=\"net10.0\" uuid_high=\"-6069974389149972757\" uuid_low=\"-5498245974063364756\">\n      <runtimes>\n        <item value=\"win-x64\" />\n      </runtimes>\n    </riderPublish>\n    <method v=\"2\" />\n  </configuration>\n</component>"
  },
  {
    "path": "src/Asv.Drones/App.axaml",
    "content": "<Application\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    x:Class=\"Asv.Drones.App\"\n    RequestedThemeVariant=\"Dark\"\n>\n    <!-- \"Default\" ThemeVariant follows system theme variant. \"Dark\" or \"Light\" are other available options. -->\n\n    <Application.Resources>\n        <ResourceDictionary>\n            <ResourceDictionary.MergedDictionaries>\n                <ResourceInclude Source=\"avares://Asv.Drones/Core/Controls/DeviceTelemetry/RouteUavIndicator/RouteUavIndicatorStyles.axaml\" />\n            </ResourceDictionary.MergedDictionaries>\n        </ResourceDictionary>\n    </Application.Resources>\n    <Application.Styles>\n        <StyleInclude Source=\"avares://Asv.Drones/Core/Controls/DeviceTelemetry/OldAttitudeIndicator/AttitudeIndicatorStyles.axaml\" />\n        <StyleInclude Source=\"avares://Asv.Drones/Core/Controls/DeviceTelemetry/AngleUavIndicator/UavAngleIndicatorStyles.axaml\" />\n        <StyleInclude Source=\"avares://Asv.Drones/Core/Controls/DeviceTelemetry/CompassUavIndicator/CompassUavIndicatorStyles.axaml\" />\n        <StyleInclude Source=\"avares://Asv.Avalonia/Styling/Theme.axaml\" />\n        <StyleInclude Source=\"avares://Asv.Avalonia.GeoMap/Theme.axaml\" />\n    </Application.Styles>\n</Application>\n"
  },
  {
    "path": "src/Asv.Drones/App.axaml.cs",
    "content": "using Asv.Avalonia;\nusing Avalonia.Markup.Xaml;\n\nnamespace Asv.Drones;\n\npublic partial class App : AsvApplication\n{\n    public override void Initialize()\n    {\n        AvaloniaXamlLoader.Load(this);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Asv.Drones.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n    <PropertyGroup>\n        <TargetFramework>$(TargetFramework)</TargetFramework>\n        <FileVersion>$(ProductVersion)</FileVersion>\n        <Version>$(ProductVersion)</Version>\n\n        <Authors>https://github.com/asv-soft</Authors>\n        <Company>https://github.com/asv-soft</Company>\n        <Copyright>https://github.com/asv-soft</Copyright>\n        \n        <Nullable>enable</Nullable>\n        <LangVersion>preview</LangVersion>\n        <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>\n        <AvaloniaUseCompilation>true</AvaloniaUseCompilation>\n        <CodeAnalysisRuleSet>../CodeStyle.ruleset</CodeAnalysisRuleSet>\n        <WarningsAsErrors>\n            CS0169,\n            CS0618,\n            CS1502,\n            CS1503,\n            CS8524,\n            CS8600,\n            CS8601,\n            CS8602,\n            CS8603,\n            CS8604,\n            CS8625,\n            CS8629,\n            CS8762,\n            CA1510,\n            CA1851\n        </WarningsAsErrors>\n        <GeneratePackageOnBuild>true</GeneratePackageOnBuild>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <AvaloniaResource Include=\"Assets\\**\"/>\n    </ItemGroup>\n    \n    <ItemGroup>\n        <PackageReference Include=\"Asv.Mavlink\" Version=\"$(AsvMavlinkVersion)\" />\n        <PackageReference Include=\"Asv.Gnss\" Version=\"$(AsvGnssVersion)\" />\n        \n        <PackageReference Include=\"Avalonia\" Version=\"$(AvaloniaVersion)\"/>\n        <PackageReference Include=\"Avalonia.Themes.Fluent\" Version=\"$(AvaloniaVersion)\"/>\n        <PackageReference Include=\"Avalonia.Fonts.Inter\" Version=\"$(AvaloniaVersion)\"/>\n        <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->\n        <PackageReference Condition=\"'$(Configuration)' == 'Debug'\" Include=\"Avalonia.Diagnostics\" Version=\"$(AvaloniaVersion)\"/>\n        <PackageReference Include=\"Roslynator.Analyzers\" Version=\"4.14.0\">\n          <PrivateAssets>all</PrivateAssets>\n          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n        </PackageReference>\n        <PackageReference Include=\"StyleCop.Analyzers\" Version=\"1.1.118\">\n          <PrivateAssets>all</PrivateAssets>\n          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n        </PackageReference>\n    </ItemGroup>\n\n    <ItemGroup>\n      <ProjectReference Include=\"..\\Asv.Drones.Api\\Asv.Drones.Api.csproj\" />\n    </ItemGroup>\n\n    <ItemGroup>\n      <EmbeddedResource Update=\"RS.resx\">\n        <Generator>PublicResXFileCodeGenerator</Generator>\n        <LastGenOutput>RS.Designer.cs</LastGenOutput>\n      </EmbeddedResource>\n    </ItemGroup>\n\n    <ItemGroup>\n      <Compile Update=\"RS.Designer.cs\">\n        <DesignTime>True</DesignTime>\n        <AutoGen>True</AutoGen>\n        <DependentUpon>RS.resx</DependentUpon>\n      </Compile>\n      <Compile Update=\"Pages\\FileBrowser\\Dialogs\\BurstDownloadDialogView.axaml.cs\">\n        <DependentUpon>BurstDownloadDialogView.axaml</DependentUpon>\n        <SubType>Code</SubType>\n      </Compile>\n    </ItemGroup>\n\n    <ItemGroup>\n      <Folder Include=\"Core\\\" />\n      <Folder Include=\"Core\\Controls\\DeviceTelemetry\\AngleUavIndicator\\Items\\\" />\n      <Folder Include=\"Core\\Controls\\DeviceTelemetry\\Rtt\\\" />\n    </ItemGroup>\n\n    <ItemGroup>\n      <AdditionalFiles Include=\"Core\\Controls\\DeviceTelemetry\\AngleUavIndicator\\UavAngleIndicatorStyles.axaml\" />\n      <AdditionalFiles Include=\"Core\\Controls\\DeviceTelemetry\\Rtt\\HeadingUavIndicator\\HeadingUavIndicator.axaml\" />\n      <AdditionalFiles Include=\"Core\\Controls\\DeviceTelemetry\\Rtt\\HomeAzimuthUavIndicator\\HomeAzimuthUavIndicator.axaml\" />\n      <AdditionalFiles Include=\"Shell\\Pages\\FlightMode\\Widgets\\Test\\PluginFlightItemView.axaml\" />\n    </ItemGroup>\n</Project>\n"
  },
  {
    "path": "src/Asv.Drones/Asv.Drones.csproj.DotSettings",
    "content": "﻿<wpf:ResourceDictionary xml:space=\"preserve\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:s=\"clr-namespace:System;assembly=mscorlib\" xmlns:ss=\"urn:shemas-jetbrains-com:settings-storage-xaml\" xmlns:wpf=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cbehaviour/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cbehaviour_005Cremovable/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cbehaviour_005Cremove/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cbehaviour_005Crenamable/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cbehaviour_005Crename/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cfilebrowser/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cfilebrowser_005Cfilebrowserviewmodel/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cfilebrowser_005Citems/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cfilebrowser_005Ctransfer/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cfilebrowser_005Ctransferable/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cflight/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cflight_005Cwidgets/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cflight_005Cwidgets_005Cuavwidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cflight_005Cwidgets_005Cuavwidget_005Cmissionprogress/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cmavlink/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cmavparams/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cmavparams_005Ccommandargs/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cmavparams_005Cmavparamspageviewmodel/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cmavparams_005Cparamitemviewmodel/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cpacketviewer/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cremovable/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Crenamable/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cpacketviewer_005Ccommandargs/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Csetup/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Csetup_005Cframetype/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Ctransferable/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cattitude/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cflight/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cflight_005Cuavwidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cflight_005Cuavwidget_005Cattitude/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cflight_005Cuavwidget_005Cattitude_005Citems/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cflight_005Cuavwidget_005Cattitude_005Citems_005Cheading/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cflight_005Cuavwidget_005Cattitude_005Citems_005Cpitch/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cflight_005Cuavwidget_005Cattitude_005Citems_005Croll/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cflight_005Cuavwidget_005Cattitude_005Citems_005Cscale/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cflight_005Cuavwidget_005Crouteindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cftpbrowser/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cftpbrowser_005Ctree/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cparams/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cftpbrowser_005Ctree_005Ccrc32/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cftpbrowser_005Ctree_005Cprimitives/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cftpbrowser_005Ctree_005Ctypes/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Crouteindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=converters/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=converters_005Ccrc32statustocolorconverter/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cbehaviour_005Cremove/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cbehaviour_005Crename/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cbehaviour/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cfilebrowser_005Cfilebrowserviewmodel/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cfilebrowser_005Citems/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cfilebrowser_005Ctransfer/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cfilebrowser/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cflight_005Cwidgets_005Cuavwidget_005Cmissionprogress/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cflight_005Cwidgets_005Cuavwidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cflight_005Cwidgets/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cflight/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cmavlink/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cmavparams_005Cmavparamspageviewmodel/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cmavparams/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cpacketviewer/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Csetup_005Cframetype/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Csetup/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Caltitudeuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Cangleuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Cangleuavindicator_005Citems/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Cangleuavindicator_005Citems_005Cpitch/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Cangleuavindicator_005Citems_005Croll/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Cangleuavrttindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Cattitude_005Citems_005Cheading/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Cattitude_005Citems_005Cpitch/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Cattitude_005Citems_005Croll/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Cattitude_005Citems_005Cscale/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Cattitude_005Citems/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Cattitude/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Cbatteryuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Ccompass/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Ccompassuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Cheadinguavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Chomeazimuthuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Coldattitudeindicator/@EntryIndexedValue\">False</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Crouteindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Crouteuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Crtt/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Crtt_005Caltitudeuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Crtt_005Cangleuavrttindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Crtt_005Cbatteryuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Crtt_005Cheadinguavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Crtt_005Chomeazimuthuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Crtt_005Cvelocityuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry_005Cvelocityuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cdevicetelemetry/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflightwidget_005Csection/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflightwidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Corderablewidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cuavwidget_005Cattitude_005Citems_005Cheading/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cuavwidget_005Cattitude_005Citems_005Cpitch/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cuavwidget_005Cattitude_005Citems_005Croll/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cuavwidget_005Cattitude_005Citems_005Cscale/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cuavwidget_005Cattitude_005Citems/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cuavwidget_005Cattitude/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cuavwidget_005Cbatteryuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cuavwidget_005Crouteindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cuavwidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cuavwidget_005Cvelocityuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cwidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cwidget_005Corderable/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cwidget_005Corderablewidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cwidget_005Csection/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflight_005Cwidget_005Csections/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Corderablewidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget_005Caltitudeuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget_005Cangleuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget_005Cangleuavrttindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget_005Cattitude_005Citems_005Cheading/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget_005Cattitude_005Citems_005Cpitch/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget_005Cattitude_005Citems_005Croll/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget_005Cattitude_005Citems_005Cscale/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget_005Cattitude_005Citems/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget_005Cattitude/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget_005Cbatteryuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget_005Cheadinguavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget_005Chomeazimuthuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget_005Crouteindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget_005Cvelocityuavindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cuavwidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cwidget_005Csection/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cwidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005C_005Fmovetoasvavalonia/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cconverters/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cservices_005Cclientdevicewidgetfactory/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cservices_005Cdevices_005Cgnss/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cservices_005Cdevices_005Cmavlink/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cservices_005Cdevices/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cservices_005Cfiles_005Clocal/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cservices_005Cfiles_005Cremote/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cservices_005Cfiles/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cservices/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Cfilebrowser_005Ccontracts/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Cfilebrowser_005Cdialogs/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Cfilebrowser_005Cio_005Ctypes/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Cfilebrowser_005Cio/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Cfilebrowser_005Ctree_005Citems/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Cfilebrowser_005Ctree/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Cfilebrowser/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Cmavparams_005Cdialog/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Cmavparams_005Cparamitem_005Croutedevents/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Cmavparams_005Cparamitem/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Cmavparams/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Csetup_005Csubpage_005Cframetype/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Csetup_005Csubpage/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Csetup/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Csetup_005Csubpage_005Cframetype_005Cdroneframeitem/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Csetup_005Csubpage_005Cframetype_005Cdroneframeitem_005Croutedevents/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cdevice_005Csetup_005Csubpage_005Cframetype_005Croutedevents/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cfilebrowser/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cfilebrowser_005Ccommands/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cfilebrowser_005Ccontracts/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cfilebrowser_005Cdialogs/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cfilebrowser_005Cio/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cfilebrowser_005Cio_005Ctypes/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cfilebrowser_005Cmisc/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cfilebrowser_005Cservices/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cfilebrowser_005Ctree/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cfilebrowser_005Ctree_005Citems/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cfilebrowser_005Ctree_005Cprimitives/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Canchors/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Ccommands/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Cmove_005Fto_005Fasv_005Favalonia_005Fio/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Cwidgets/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Cwidgets_005Cuavwidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Cwidgets_005Cuavwidget_005Cactions/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Cwidgets_005Cuavwidget_005Ccommands/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Cwidgets_005Cuavwidget_005Ccontrols/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Cwidgets_005Cuavwidget_005Ccontrols_005Cattitude/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Cwidgets_005Cuavwidget_005Ccontrols_005Crouteindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Cwidgets_005Cuavwidget_005Cdialogs/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Cwidgets_005Cuavwidget_005Cmissionprogress/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Cwidgets_005Cuavwidget_005Cmissionprogress_005Ccommands/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005C_005Fmove_005Fto_005Fasv_005Favalonia_005Fio/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005C_005Fmove_005Fto_005Fasv_005Favalonia_005Fio_005Cdesigntime/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cmavparams/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cmavparams_005Ccommands/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cmavparams_005Ccommands_005Cmavparamspageviewmodel/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cmavparams_005Ccommands_005Cparamitemviewmodel/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cmavparams_005Cdialog/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cmavparams_005Cparamitem/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cmavparams_005Cparamitem_005Croutedevents/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cpacketviewer/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cpacketviewer_005Ccommand/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cpacketviewer_005Cdialogs/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cpacketviewer_005Cfilters/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cpacketviewer_005Cfilters_005Ccomparers/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cpacketviewer_005Cpacketmessage/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Csetup/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Csetup_005Csubpage/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Csetup_005Csubpages/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Csetup_005Csubpages_005Cframetype/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Csetup_005Csubpage_005Cframetype/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Csetup_005Csubpage_005Cframetype_005Cardu/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Csetup_005Csubpage_005Cframetype_005Ccommands/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Csetup_005Csubpage_005Cframetype_005Cdefault/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cconverters/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cdevices/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cdevices_005Cgnss/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cdevices_005Cmavlink/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cfiles/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cfiles_005Clocal/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cfiles_005Cremote/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cfiles_005Ctransfer/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Cfilebrowser_005Ccontracts/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Cfilebrowser_005Cdialogs/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Cfilebrowser_005Cio_005Ctypes/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Cfilebrowser_005Cio/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Cfilebrowser_005Ctree_005Citems/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Cfilebrowser_005Ctree/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Cfilebrowser/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Cmavparams_005Cdialog/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Cmavparams_005Cparamitem_005Croutedevents/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Cmavparams_005Cparamitem/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Cmavparams/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Csetup_005Csubpage_005Cframetype_005Cdroneframeitem_005Croutedevents/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Csetup_005Csubpage_005Cframetype_005Cdroneframeitem/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Csetup_005Csubpage_005Cframetype/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Csetup_005Csubpage/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Csetup/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Csetup_005Csubpage_005Cmotors/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cdevice_005Csetup_005Csubpage_005Cmotors_005Cmotoritem/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Canchors/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Canchors/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevices/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevices_005Cdrones/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevices_005Cdrones_005Cattitudeindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevices_005Cdrones_005Cflightcontrol/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevices_005Cdrones_005Ctelemetry/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevices_005Cmavlink/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevices_005Cmavlink_005Cdrones_005Cattitudeindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevices_005Cmavlink_005Cdrones_005Cflightcontrol/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevices_005Cmavlink_005Cdrones_005Csections/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevices_005Cmavlink_005Cdrones_005Csections_005Cattitudeindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevices_005Cmavlink_005Cdrones_005Csections_005Cflightcontrol/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevices_005Cmavlink_005Cdrones_005Csections_005Ctelemetry/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevices_005Cmavlink_005Cdrones_005Ctelemetry/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevices_005Cmavlink_005Cdrones/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink_005Cdrones_005Csections_005Cattitudeindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink_005Cdrones_005Csections_005Cflightcontrol/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink_005Cdrones_005Csections_005Ctelemetry/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink_005Cdrones_005Csections/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink_005Cdrones/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink_005Cdrone_005Cplane_005Csections/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink_005Cdrone_005Csections_005Casyncsectionfortest/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink_005Cdrone_005Csections_005Cattitudeindicator/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink_005Cdrone_005Csections_005Cflightcontrol/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink_005Cdrone_005Csections_005Ctelemetry/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink_005Cdrone_005Csections/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink_005Cdrone/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Corderable/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Ctest/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Ctest_005Casync/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflight_005Canchors/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflight_005Cwidgets_005Cuavwidget_005Cdialogs/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflight_005Cwidgets_005Cuavwidget_005Cmissionprogress/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflight_005Cwidgets_005Cuavwidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflight_005Cwidgets/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflight/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cpacketviewer_005Cdialogs/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cpacketviewer_005Cfilters_005Ccomparers/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cpacketviewer_005Cfilters/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cpacketviewer_005Cpacketconverter/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cpacketviewer_005Cpacketmessage/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cpacketviewer/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages/@EntryIndexedValue\">True</s:Boolean></wpf:ResourceDictionary>"
  },
  {
    "path": "src/Asv.Drones/AsvDronesMixin.cs",
    "content": "﻿using System;\nusing Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Drones.Api;\nusing Asv.Drones.Plane;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\n\nnamespace Asv.Drones;\n\npublic static class AsvDronesMixin\n{\n    extension(IHostApplicationBuilder builder)\n    {\n        public IHostApplicationBuilder UseDronesApp(Action<Builder>? configure = null)\n        {\n            configure ??= b => b.UseDefault();\n            configure(new Builder(builder));\n            return builder;\n        }\n    }\n\n    public class Builder(IHostApplicationBuilder builder)\n    {\n        public IHostApplicationBuilder Parent => builder;\n\n        public Builder UseDefault()\n        {\n            builder.Services.AddSingleton<IPacketSequenceCalculator, PacketSequenceCalculator>();\n            builder.Services.AddSingleton<IDeviceManagerExtension, GnssDeviceManagerExtension>();\n            return UseMavlinkHost()\n                .UseMavParams()\n                .UseOptionalPacketViewer()\n                .UseFlightMode()\n                .UseExtendableFlightMode()\n                .UseCommands()\n                .UseFileBrowser()\n                .UseSetupPage();\n        }\n\n        public Builder UseControls()\n        {\n            builder.ViewLocator.RegisterViewFor<UavWidgetViewModel, UavWidgetView>();\n            builder.ViewLocator.RegisterViewFor<\n                VelocityUavIndicatorViewModel,\n                VelocityUavIndicator\n            >();\n            builder.ViewLocator.RegisterViewFor<\n                BatteryUavIndicatorViewModel,\n                BatteryUavIndicator\n            >();\n            return this;\n        }\n\n        public Builder UseCommands()\n        {\n            builder.Services.AddSingleton<IMavlinkCommands, MavlinkCommands>();\n            builder\n                .RegisterMavlinkCommands()\n                .Commands.Register<OpenSetupCommand>()\n                .Register<ChangeFrameTypeCommand>()\n                .Register<OpenPacketViewerCommand>()\n                .Register<ExportPacketsToCsvCommand>()\n                .Register<ClearAllPacketsCommand>()\n                .Register<OpenMavParamsCommand>()\n                .Register<UpdateParamsCommand>()\n                .Register<StopUpdateParamsCommand>()\n                .Register<RemoveAllPinsCommand>()\n                .Register<MavlinkParamsWriteCommand>()\n                .Register<MavlinkParamReadCommand>()\n                .Register<OpenFlightModeCommand>()\n                .Register<OpenFlightCommand>()\n                .Register<TakeOffCommand>()\n                .Register<StartMissionCommand>()\n                .Register<RTLCommand>()\n                .Register<LandCommand>()\n                .Register<GuidedModeCommand>()\n                .Register<AutoModeCommand>()\n                .Register<UpdateMissionCommand>()\n                .Register<OpenFileBrowserCommand>()\n                .Register<UploadItemCommand>()\n                .Register<DownloadItemCommand>()\n                .Register<BurstDownloadItemCommand>()\n                .Register<CreateDirectoryCommand>()\n                .Register<CalculateCrc32Command>()\n                .Register<FindFileCommand>()\n                .Register<CommitRenameCommand>()\n                .Register<RemoveItemCommand>();\n            return this;\n        }\n\n        public Builder UseMavlinkHost()\n        {\n            builder.Services.AddSingleton<MavlinkHost>();\n            builder.Services.AddHostedService(svc => svc.GetRequiredService<MavlinkHost>());\n            builder.Services.AddSingleton<IMavlinkHost>(svc =>\n                svc.GetRequiredService<MavlinkHost>()\n            );\n            builder.Services.AddSingleton<IDeviceManagerExtension>(svc =>\n                svc.GetRequiredService<MavlinkHost>()\n            );\n            return this;\n        }\n\n        public Builder UseMavParams()\n        {\n            builder.ViewLocator.RegisterViewFor<MavParamTextBoxViewModel, MavParamTextBoxView>();\n            builder.ViewLocator.RegisterViewFor<MavParamButtonViewModel, MavParamButtonView>();\n            builder.ViewLocator.RegisterViewFor<MavParamComboBoxViewModel, MavParamComboBoxView>();\n            builder.ViewLocator.RegisterViewFor<\n                MavParamAltitudeTextBoxViewModel,\n                MavParamAltitudeTextBoxView\n            >();\n            builder.ViewLocator.RegisterViewFor<\n                MavParamLatLonTextBoxViewModel,\n                MavParamLatLonTextBoxView\n            >();\n            builder.ViewLocator.RegisterViewFor<\n                MavParamAsciiCharViewModel,\n                MavParamAsciiCharView\n            >();\n            builder.ViewLocator.RegisterViewFor<MavParamButtonViewModel, MavParamButtonView>();\n\n            builder.Shell.Pages.Register<MavParamsPageViewModel, MavParamsPageView>(\n                MavParamsPageViewModel.PageId\n            );\n            builder.Shell.Pages.Home.UseItemExtension<HomePageParamsDeviceItemAction>();\n            builder.ViewLocator.RegisterViewFor<ParamItemViewModel, ParamItemView>();\n            builder.ViewLocator.RegisterViewFor<\n                TryCloseWithApprovalDialogViewModel,\n                TryCloseWithApprovalDialogView\n            >();\n            return this;\n        }\n\n        public Builder UseFileBrowser()\n        {\n            builder.Shell.Pages.Register<FileBrowserViewModel, FileBrowserView>(\n                FileBrowserViewModel.PageId\n            );\n            builder.Shell.Pages.Home.UseItemExtension<HomePageFileBrowserDeviceItemAction>();\n            builder.ViewLocator.RegisterViewFor<\n                BurstDownloadDialogViewModel,\n                BurstDownloadDialogView\n            >();\n            return this;\n        }\n\n        public Builder UseFlightMode()\n        {\n            builder.Shell.Pages.Register<FlightPageViewModel, FlightPageView>(\n                FlightPageViewModel.PageId\n            );\n            builder.Shell.Pages.Home.UseExtension<HomePageFlightExtension>();\n            builder.Extensions.Register<IFlightMode, FlightUavAnchorsExtension>();\n            builder.Extensions.Register<IFlightMode, FlightWidgetsExtension>();\n            builder.ViewLocator.RegisterViewFor<UavWidgetViewModel, UavWidgetView>();\n            builder.ViewLocator.RegisterViewFor<MissionProgressViewModel, MissionProgressView>();\n            builder.ViewLocator.RegisterViewFor<\n                SetAltitudeDialogViewModel,\n                SetAltitudeDialogView\n            >();\n            return this;\n        }\n\n        public Builder UseExtendableFlightMode()\n        {\n            // FlightMode\n            builder.Shell.Pages.Register<FlightModePageViewModel, FlightModePageView>(\n                FlightModePageViewModel.PageId\n            );\n            builder.Shell.Pages.Home.UseExtension<HomePageFlightModeExtension>();\n\n            // Anchors\n            builder.Extensions.Register<IFlightModePage, FlightModeAnchorsExtension>();\n\n            // Factory for client device widgets\n            builder.Services.AddSingleton<IClientDeviceWidgetFactory, ClientDeviceWidgetFactory>();\n\n            // Create widgets for client devices\n            builder.Extensions.Register<IFlightModePage, FlightModeClientDeviceWidgetExtension>();\n\n            // Widget for all drones\n            builder.Services.AddSingleton<\n                IClientDeviceWidgetCreationHandler,\n                DroneWidgetCreationHandler\n            >();\n            builder.Services.AddTransient<IDroneFlightWidget, DroneFlightWidgetViewModel>();\n            builder.ViewLocator.RegisterViewFor<DroneFlightWidgetViewModel, FlightWidgetView>();\n\n            // Sections for the drone Widget\n            builder.Services.AddKeyedTransient<TelemetrySectionViewModel>(\n                TelemetrySectionViewModel.SectionId\n            );\n            builder.Extensions.Register<\n                IDroneFlightWidget,\n                DroneFlightWidgetTelemetrySectionExtension\n            >();\n            builder.ViewLocator.RegisterViewFor<TelemetrySectionViewModel, TelemetrySectionView>();\n\n            builder.Services.AddKeyedTransient<AttitudeIndicatorSectionViewModel>(\n                AttitudeIndicatorSectionViewModel.SectionId\n            );\n            builder.Extensions.Register<\n                IDroneFlightWidget,\n                DroneFlightWidgetExtensionAttitudeIndicatorSection\n            >();\n            builder.ViewLocator.RegisterViewFor<\n                AttitudeIndicatorSectionViewModel,\n                AttitudeIndicatorSectionView\n            >();\n\n            builder.Services.AddKeyedTransient<FlightControlSectionViewModel>(\n                FlightControlSectionViewModel.SectionId\n            );\n            builder.Extensions.Register<\n                IDroneFlightWidget,\n                DroneFlightWidgetFlightControlSectionExtension\n            >();\n            builder.ViewLocator.RegisterViewFor<\n                FlightControlSectionViewModel,\n                FlightControlSectionView\n            >();\n\n            // Test plugin widget\n            builder.ViewLocator.RegisterViewFor<PluginFlightItemViewModel, PluginFlightItemView>();\n            builder.Extensions.Register<IFlightModePage, PluginFlightItemWidgetExtension>();\n\n            // Test plane widget\n            builder.Services.AddSingleton<\n                IClientDeviceWidgetCreationHandler,\n                PlaneWidgetCreationHandler\n            >();\n            builder.Services.AddTransient<IPlaneWidget, PlaneWidgetViewModel>();\n            builder.ViewLocator.RegisterViewFor<PlaneWidgetViewModel, FlightWidgetView>();\n\n            // Test plane section\n            builder.Services.AddKeyedTransient<PlaneSectionViewModel>(\n                PlaneSectionViewModel.SectionId\n            );\n            builder.Extensions.Register<IPlaneWidget, PlaneSectionExtension>();\n            builder.ViewLocator.RegisterViewFor<PlaneSectionViewModel, PlaneSectionView>();\n\n            return this;\n        }\n\n        public Builder UseOptionalPacketViewer()\n        {\n            builder.Shell.Pages.Register<PacketViewerViewModel, PacketViewerView>(\n                PacketViewerViewModel.PageId\n            );\n            builder.Shell.Pages.Home.UseExtension<HomePacketViewerExtension>();\n            builder.ViewLocator.RegisterViewFor<PacketMessageViewModel, PacketMessageView>();\n            builder.Services.AddSingleton<IPacketConverter, DefaultMavlinkPacketConverter>();\n            builder.ViewLocator.RegisterViewFor<\n                SavePacketMessagesDialogViewModel,\n                SavePacketMessagesDialogView\n            >();\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Behaviour/Remove/RemoveItemCommand.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class RemoveItemCommand : ContextCommand<ISupportRemove>, IRemoveItemCommand\n{\n    public const string Id = IRemoveItemCommand.CommandId;\n\n    private static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.RemoveItemCommand_CommandInfo_Name,\n        Description = RS.RemoveItemCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.Delete,\n        DefaultHotKey = \"Shift + Delete\",\n    };\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override async ValueTask<CommandArg?> InternalExecute(\n        ISupportRemove context,\n        CommandArg newValue,\n        CancellationToken cancel\n    )\n    {\n        // TODO: make removing items command undoable\n        await context.RemoveAsync(cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Behaviour/Rename/CommitRenameCommand.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\n/// <summary>\n/// <para>Executes with:</para>\n/// <para>- <c>arg[\"old\"]</c> as Old Value.</para>\n/// <para>- <c>arg[\"new\"]</c> as New Value.</para>\n/// </summary>\npublic class CommitRenameCommand : ContextCommand<ISupportRename, DictArg>, ICommitRenameCommand\n{\n    public const string Id = ICommitRenameCommand.CommandId;\n\n    public const string OldValue = \"old\";\n    public const string NewValue = \"new\";\n\n    private static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.RenameItemCommand_CommandInfo_Name,\n        Description = RS.RenameItemCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.Check,\n        DefaultHotKey = \"Enter\",\n    };\n\n    public override ICommandInfo Info => StaticInfo;\n\n    public override async ValueTask<DictArg?> InternalExecute(\n        ISupportRename context,\n        DictArg arg,\n        CancellationToken cancel\n    )\n    {\n        arg.TryGetValue(OldValue, out var oldValue);\n        arg.TryGetValue(NewValue, out var newValue);\n\n        if (oldValue is not StringArg || newValue is not StringArg)\n        {\n            return null;\n        }\n\n        var oldString = oldValue.AsString();\n        var newString = newValue.AsString();\n\n        if (string.IsNullOrWhiteSpace(oldString) || string.IsNullOrWhiteSpace(newString))\n        {\n            return null;\n        }\n        try\n        {\n            await context.RenameAsync(oldString, newString, cancel);\n        }\n        catch\n        {\n            return null;\n        }\n        return CommandArg.CreateDictionary(\n            new Dictionary<string, CommandArg>\n            {\n                { NewValue, CommandArg.CreateString(oldString) },\n                { OldValue, CommandArg.CreateString(newString) },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/FileBrowser/FileBrowserViewModel/FindFileCommand.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class FindFileCommand : ContextCommand<FileBrowserViewModel>\n{\n    public const string Id = $\"{BaseId}.find_file\";\n\n    private static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.FindFileOnLocalCommand_CommandInfo_Name,\n        Description = RS.FindFileOnLocalCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.Magnify,\n        DefaultHotKey = null,\n    };\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override ValueTask<CommandArg?> InternalExecute(\n        FileBrowserViewModel context,\n        CommandArg arg,\n        CancellationToken cancel\n    )\n    {\n        context.FindFileOnLocal();\n        return ValueTask.FromResult<CommandArg?>(null);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/FileBrowser/Items/CalculateCrc32Command.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class CalculateCrc32Command : ContextCommand<IBrowserItemViewModel>\n{\n    public const string Id = $\"{BaseId}.crc32\";\n\n    private static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.CalculateCrc32Command_CommandInfo_Name,\n        Description = RS.CalculateCrc32Command_CommandInfo_Description,\n        Icon = MaterialIconKind.KeyOutline,\n        DefaultHotKey = \"Ctrl + Q\",\n    };\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override async ValueTask<CommandArg?> InternalExecute(\n        IBrowserItemViewModel context,\n        CommandArg newValue,\n        CancellationToken cancel\n    )\n    {\n        await context.CalculateCrc32Async(cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/FileBrowser/Items/CreateDirectoryCommand.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class CreateDirectoryCommand : ContextCommand<IBrowserItemViewModel>\n{\n    public const string Id = $\"{BaseId}.create_directory\";\n\n    private static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.CreateDirectoryCommand_CommandInfo_Name,\n        Description = RS.CreateDirectoryCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.FolderAdd,\n        DefaultHotKey = \"Ctrl + N\",\n    };\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override async ValueTask<CommandArg?> InternalExecute(\n        IBrowserItemViewModel context,\n        CommandArg newValue,\n        CancellationToken cancel\n    )\n    {\n        await context.CreateDirectoryAsync(cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/FileBrowser/OpenFileBrowserCommand.cs",
    "content": "﻿using Asv.Avalonia;\n\nnamespace Asv.Drones;\n\npublic class OpenFileBrowserCommand(INavigationService nav)\n    : OpenPageCommandBase(FileBrowserViewModel.PageId, nav)\n{\n    #region Static\n\n    public const string Id = $\"{BaseId}.open.{FileBrowserViewModel.PageId}\";\n\n    public static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.OpenFileBrowserCommand_CommandInfo_Name,\n        Description = RS.OpenFileBrowserCommand_CommandInfo_Description,\n        Icon = FileBrowserViewModel.PageIcon,\n        DefaultHotKey = null,\n    };\n\n    #endregion\n\n    public override ICommandInfo Info => StaticInfo;\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/FileBrowser/Transfer/BurstDownloadItemCommand.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Mavlink;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\n/// <summary>\n/// <para>Executes with:</para>\n/// <para>- <c>arg[\"src\"]</c> as <c>string</c> sourcePath.</para>\n/// <para>- <c>arg[\"dst\"]</c> as <c>string</c> destinationPath.</para>\n/// <para>- <c>arg[\"prt\"]</c> as <c>int</c> partSize.</para>\n/// <para>- <c>arg[\"typ\"]</c> as <c>string</c> entryType.</para>\n/// </summary>\npublic class BurstDownloadItemCommand : TransferCommandBase\n{\n    public const string Id = $\"{BaseIdTransferCmd}.burst_download\";\n\n    private static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.BurstDownloadItemCommand_CommandInfo_Name,\n        Description = RS.BurstDownloadItemCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.TransferLeft,\n        DefaultHotKey = null,\n    };\n\n    public override ICommandInfo Info => StaticInfo;\n\n    public override async ValueTask<DictArg?> InternalExecute(\n        ITransferFtpEntries context,\n        DictArg newValue,\n        CancellationToken cancel\n    )\n    {\n        if (!TryReadRequiredString(newValue, SourcePath, out var src))\n        {\n            return null;\n        }\n\n        if (!TryReadRequiredString(newValue, DestinationPath, out var dst))\n        {\n            return null;\n        }\n\n        if (!TryReadRequiredByte(newValue, PartSize, out var part))\n        {\n            return null;\n        }\n\n        if (!TryReadRequiredEntryType(newValue, EntryType, out var type))\n        {\n            return null;\n        }\n\n        await context.BurstDownloadItem(src, dst, part, type, cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/FileBrowser/Transfer/DownloadItemCommand.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Mavlink;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\n/// <summary>\n/// <para>Executes with:</para>\n/// <para>- <c>arg[\"src\"]</c> as <c>string</c> sourcePath.</para>\n/// <para>- <c>arg[\"dst\"]</c> as <c>string</c> destinationPath.</para>\n/// <para>- <c>arg[\"prt\"]</c> as <c>int</c> partSize.</para>\n/// <para>- <c>arg[\"typ\"]</c> as <c>string</c> entryType.</para>\n/// </summary>\npublic class DownloadItemCommand : TransferCommandBase\n{\n    public const string Id = $\"{BaseIdTransferCmd}.download\";\n\n    private static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.DownloadItemCommand_CommandInfo_Name,\n        Description = RS.DownloadItemCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.TransferLeft,\n        DefaultHotKey = null,\n    };\n\n    public override ICommandInfo Info => StaticInfo;\n\n    public override async ValueTask<DictArg?> InternalExecute(\n        ITransferFtpEntries context,\n        DictArg newValue,\n        CancellationToken cancel\n    )\n    {\n        if (!TryReadRequiredString(newValue, SourcePath, out var src))\n        {\n            return null;\n        }\n\n        if (!TryReadRequiredString(newValue, DestinationPath, out var dst))\n        {\n            return null;\n        }\n\n        if (!TryReadRequiredByte(newValue, PartSize, out var part))\n        {\n            return null;\n        }\n\n        if (!TryReadRequiredEntryType(newValue, EntryType, out var type))\n        {\n            return null;\n        }\n\n        await context.DownloadItem(src, dst, part, type, cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/FileBrowser/Transfer/ITransferFtpEntries.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Mavlink;\n\nnamespace Asv.Drones;\n\npublic interface ITransferFtpEntries : IRoutable\n{\n    ValueTask UploadItem(\n        string source,\n        string destination,\n        FtpEntryType type,\n        CancellationToken ct\n    );\n    ValueTask DownloadItem(\n        string source,\n        string destination,\n        byte partSize,\n        FtpEntryType type,\n        CancellationToken ct\n    );\n    ValueTask BurstDownloadItem(\n        string source,\n        string destination,\n        byte partSize,\n        FtpEntryType type,\n        CancellationToken ct\n    );\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/FileBrowser/Transfer/TransferCommandBase.cs",
    "content": "﻿using System;\nusing Asv.Avalonia;\nusing Asv.Mavlink;\n\nnamespace Asv.Drones;\n\npublic abstract class TransferCommandBase : ContextCommand<ITransferFtpEntries, DictArg>\n{\n    protected const string BaseIdTransferCmd = $\"{BaseId}.transfer\";\n\n    public const string SourcePath = \"src\";\n    public const string DestinationPath = \"dst\";\n    public const string PartSize = \"prt\";\n    public const string EntryType = \"typ\";\n\n    protected static bool TryReadRequiredString(DictArg args, string key, out string value)\n    {\n        value = string.Empty;\n        if (!args.TryGetValue(key, out var v))\n        {\n            return false;\n        }\n\n        value = v.AsString();\n        return value.Length > 0;\n    }\n\n    protected static bool TryReadRequiredByte(DictArg args, string key, out byte value)\n    {\n        value = 0;\n        if (!args.TryGetValue(key, out var v))\n        {\n            return false;\n        }\n\n        var i = v.AsInt();\n        if (i is < byte.MinValue or > byte.MaxValue)\n        {\n            return false;\n        }\n\n        value = (byte)i;\n        return true;\n    }\n\n    protected static bool TryReadRequiredEntryType(\n        DictArg args,\n        string key,\n        out FtpEntryType entryType\n    )\n    {\n        entryType = default;\n        return args.TryGetValue(key, out var v)\n            && Enum.TryParse(v.AsString(), ignoreCase: true, out entryType);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/FileBrowser/Transfer/UploadItemCommand.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Mavlink;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\n/// <summary>\n/// <para>Executes with:</para>\n/// <para>- <c>arg[\"src\"]</c> as <c>string</c> sourcePath.</para>\n/// <para>- <c>arg[\"dst\"]</c> as <c>string</c> destinationPath.</para>\n/// <para>- <c>arg[\"typ\"]</c> as <c>string</c> entryType.</para>\n/// </summary>\npublic class UploadItemCommand : TransferCommandBase\n{\n    public const string Id = $\"{BaseIdTransferCmd}.upload\";\n\n    private static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.UploadItemCommand_CommandInfo_Name,\n        Description = RS.UploadItemCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.TransferRight,\n        DefaultHotKey = null,\n    };\n\n    public override ICommandInfo Info => StaticInfo;\n\n    public override async ValueTask<DictArg?> InternalExecute(\n        ITransferFtpEntries context,\n        DictArg newValue,\n        CancellationToken cancel\n    )\n    {\n        if (!TryReadRequiredString(newValue, SourcePath, out var src))\n        {\n            return null;\n        }\n\n        if (!TryReadRequiredString(newValue, DestinationPath, out var dst))\n        {\n            return null;\n        }\n\n        if (!TryReadRequiredEntryType(newValue, EntryType, out var type))\n        {\n            return null;\n        }\n\n        await context.UploadItem(src, dst, type, cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Flight/OpenFlight.cs",
    "content": "﻿using Asv.Avalonia;\n\nnamespace Asv.Drones;\n\npublic class OpenFlightCommand(INavigationService nav)\n    : OpenPageCommandBase(FlightModePageViewModel.PageId, nav)\n{\n    #region Static\n\n    public const string Id = $\"{BaseId}.open.{FlightModePageViewModel.PageId}\";\n\n    public static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = \"Open Flight Mode (BETA)\",\n        Description = \"Command opens Flight Mode (BETA)\",\n        Icon = FlightModePageViewModel.PageIcon,\n        DefaultHotKey = null, // TODO: add after BETA\n    };\n\n    #endregion\n    public override ICommandInfo Info => StaticInfo;\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Flight/OpenFlightMode.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Drones.Api;\n\nnamespace Asv.Drones;\n\npublic class OpenFlightModeCommand(INavigationService nav)\n    : OpenPageCommandBase(FlightMode.PageId, nav)\n{\n    #region Static\n\n    public const string Id = $\"{BaseId}.open.{FlightMode.PageId}\";\n\n    public static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.OpenFlightModeCommand_CommandInfo_Name,\n        Description = RS.OpenFlightModeCommand_CommandInfo_Description,\n        Icon = FlightMode.PageIcon,\n        DefaultHotKey = \"Ctrl+F2\",\n    };\n\n    #endregion\n    public override ICommandInfo Info => StaticInfo;\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Flight/Widgets/UavWidget/AutoModeCommand.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class AutoModeCommand : ContextCommand<UavWidgetViewModel> // TODO: make basic class for commands that change the uav mode\n{\n    #region Static\n\n    public const string Id = $\"{BaseId}.change.mode.auto\";\n\n    internal static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.UavAction_AutoMode_Name,\n        Description = RS.UavAction_AutoMode_Description,\n        Icon = MaterialIconKind.Automatic,\n        DefaultHotKey = null,\n    };\n\n    #endregion\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override async ValueTask<CommandArg?> InternalExecute(\n        UavWidgetViewModel context,\n        CommandArg newValue,\n        CancellationToken cancel\n    )\n    {\n        var control = context.Device.GetMicroservice<ControlClient>();\n\n        if (control == null)\n        {\n            return null;\n        }\n\n        await control.SetAutoMode(cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Flight/Widgets/UavWidget/GuidedModeCommand.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class GuidedModeCommand : ContextCommand<UavWidgetViewModel>\n{\n    #region Static\n\n    public const string Id = $\"{BaseId}.change.mode.guided\";\n\n    internal static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.UavAction_GuidedMode,\n        Description = RS.UavAction_GuidedMode_Description,\n        Icon = MaterialIconKind.Controller,\n        DefaultHotKey = null,\n    };\n\n    #endregion\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override async ValueTask<CommandArg?> InternalExecute(\n        UavWidgetViewModel context,\n        CommandArg newValue,\n        CancellationToken cancel\n    )\n    {\n        var control = context.Device.GetMicroservice<ControlClient>();\n\n        if (control == null)\n        {\n            return null;\n        }\n\n        await control.SetGuidedMode(cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Flight/Widgets/UavWidget/LandCommand.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class LandCommand : ContextCommand<UavWidgetViewModel>\n{\n    #region Static\n\n    public const string Id = $\"{BaseId}.uav.land\";\n\n    internal static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.UavAction_Land,\n        Description = RS.UavAction_Land_Description,\n        Icon = MaterialIconKind.AeroplaneLanding,\n        DefaultHotKey = null,\n    };\n\n    #endregion\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override async ValueTask<CommandArg?> InternalExecute(\n        UavWidgetViewModel context,\n        CommandArg newValue,\n        CancellationToken cancel\n    )\n    {\n        var control = context.Device.GetMicroservice<ControlClient>();\n\n        if (control == null)\n        {\n            return null;\n        }\n\n        await control.EnsureGuidedMode(cancel: cancel);\n        await control.DoLand(cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Flight/Widgets/UavWidget/MissionProgress/UpdateMissionCommand.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class UpdateMissionCommand : ContextCommand<MissionProgressViewModel>\n{\n    #region StaticInfo\n\n    public const string Id = $\"{BaseId}.mission-items.update\";\n\n    internal static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.UavAction_Land,\n        Description = RS.UavAction_Land_Description,\n        Icon = MaterialIconKind.Reload,\n        DefaultHotKey = null,\n    };\n\n    #endregion\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override async ValueTask<CommandArg?> InternalExecute(\n        MissionProgressViewModel context,\n        CommandArg newValue,\n        CancellationToken cancel\n    )\n    {\n        await context.InitiateMissionPoints(cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Flight/Widgets/UavWidget/RTLCommand.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class RTLCommand : ContextCommand<UavWidgetViewModel>\n{\n    #region Static\n\n    public const string Id = $\"{BaseId}.uav.rtl\";\n\n    internal static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.UavAction_Rtl_Name,\n        Description = RS.UavAction_Rtl_Description,\n        Icon = MaterialIconKind.Home,\n        DefaultHotKey = null,\n    };\n\n    #endregion\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override async ValueTask<CommandArg?> InternalExecute(\n        UavWidgetViewModel context,\n        CommandArg newValue,\n        CancellationToken cancel\n    )\n    {\n        var control = context.Device.GetMicroservice<ControlClient>();\n\n        if (control is null)\n        {\n            return null;\n        }\n\n        await control.EnsureGuidedMode(cancel: cancel);\n        await control.DoRtl(cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Flight/Widgets/UavWidget/StartMissionCommand.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Material.Icons;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class StartMissionCommand : ContextCommand<UavWidgetViewModel>\n{\n    #region Static\n\n    public const string Id = $\"{BaseId}.uav.start\";\n\n    internal static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.UavAction_StartMission,\n        Description = RS.UavAction_StartMission_Description,\n        Icon = MaterialIconKind.MapMarkerPath,\n        DefaultHotKey = null,\n    };\n\n    #endregion\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override async ValueTask<CommandArg?> InternalExecute(\n        UavWidgetViewModel context,\n        CommandArg newValue,\n        CancellationToken cancel\n    )\n    {\n        var control = context.Device.GetMicroservice<ControlClient>();\n        var mission = context.Device.GetMicroservice<MissionClientEx>();\n\n        if (control is null || mission is null)\n        {\n            return null;\n        }\n\n        context.MissionProgress.UpdateMission.Execute(Unit.Default);\n\n        await mission.SetCurrent(0, cancel);\n        await control.SetAutoMode(cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Flight/Widgets/UavWidget/TakeOffCommand.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class TakeOffCommand : ContextCommand<UavWidgetViewModel, DoubleArg>\n{\n    #region Static\n\n    public const string Id = $\"{BaseId}.uav.takeOff\";\n\n    internal static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.UavAction_TakeOff,\n        Description = RS.UavAction_TakeOff_Description,\n        Icon = MaterialIconKind.AeroplaneTakeoff,\n        DefaultHotKey = null,\n    };\n\n    #endregion\n\n    public override ICommandInfo Info => StaticInfo;\n\n    public override async ValueTask<DoubleArg?> InternalExecute(\n        UavWidgetViewModel context,\n        DoubleArg arg,\n        CancellationToken cancel\n    )\n    {\n        var device = context.Device;\n        var controlClient = device.GetMicroservice<ControlClient>();\n\n        if (controlClient == null)\n        {\n            return null;\n        }\n\n        await controlClient.SetGuidedMode(cancel);\n        await controlClient.TakeOff(arg.Value, cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/MavParams/MavParamsPageViewModel/RemoveAllPinsCommand.cs",
    "content": "using System.Collections.Generic;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class RemoveAllPinsCommand : ContextCommand<MavParamsPageViewModel, DictArg>\n{\n    public const string Id = $\"{BaseId}.params.remove-all-pins\";\n\n    internal static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.UnpinAllParamsCommand_CommandInfo_Name,\n        Description = RS.UnpinAllParamsCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.PinOff,\n        DefaultHotKey = null, // TODO: make a key bind when new key listener system appears\n    };\n\n    public override ICommandInfo Info => StaticInfo;\n\n    public override ValueTask<DictArg?> InternalExecute(\n        MavParamsPageViewModel context,\n        DictArg arg,\n        CancellationToken cancel\n    )\n    {\n        if (context.AllParams is null)\n        {\n            return ValueTask.FromResult<DictArg?>(null);\n        }\n\n        if (arg.Count == 0)\n        {\n            var oldValue = new DictArg();\n            foreach (var item in context.AllParams.Where(item => item.IsPinned.ViewValue.Value))\n            {\n                oldValue.Add(\n                    new KeyValuePair<string, CommandArg>(item.Id.ToString(), new BoolArg(true))\n                );\n                item.IsPinned.ModelValue.Value = false;\n            }\n\n            var notSelected = context\n                .ViewedParams.Where(it => it.Id != context.SelectedItem.Value?.Id)\n                .ToArray();\n\n            foreach (var item in notSelected)\n            {\n                context.ViewedParams.Remove(item);\n            }\n\n            return ValueTask.FromResult<DictArg?>(oldValue);\n        }\n\n        foreach (var item in context.AllParams.Where(item => arg.ContainsKey(item.Id.ToString())))\n        {\n            item.IsPinned.ModelValue.Value = !item.IsPinned.ModelValue.Value;\n        }\n\n        return ValueTask.FromResult<DictArg?>(arg);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/MavParams/MavParamsPageViewModel/StopUpdateParamsCommand.cs",
    "content": "using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class StopUpdateParamsCommand : ContextCommand<MavParamsPageViewModel>\n{\n    public const string Id = $\"{BaseId}.params.stop-update\";\n\n    internal static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.StopUpdateParamsCommand_CommandInfo_Name,\n        Description = RS.StopUpdateParamsCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.CancelCircle,\n        DefaultHotKey = null, // TODO: make a key bind when new key listener system appears\n    };\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override ValueTask<CommandArg?> InternalExecute(\n        MavParamsPageViewModel context,\n        CommandArg newValue,\n        CancellationToken cancel\n    )\n    {\n        context.StopUpdateParamsImpl();\n        return default;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/MavParams/MavParamsPageViewModel/UpdateParamsCommand.cs",
    "content": "using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class UpdateParamsCommand : ContextCommand<MavParamsPageViewModel>\n{\n    public const string Id = $\"{BaseId}.params.update\";\n\n    internal static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.UpdateParamsCommand_CommandInfo_Name,\n        Description = RS.UpdateParamsCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.Refresh,\n        DefaultHotKey = null, // TODO: make a key bind when new key listener system appears\n    };\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override async ValueTask<CommandArg?> InternalExecute(\n        MavParamsPageViewModel context,\n        CommandArg newValue,\n        CancellationToken cancel\n    )\n    {\n        await context.UpdateParamsImpl(cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/MavParams/OpenMavParamsCommand.cs",
    "content": "﻿using Asv.Avalonia;\n\nnamespace Asv.Drones;\n\npublic class OpenMavParamsCommand(INavigationService nav)\n    : OpenPageCommandBase(MavParamsPageViewModel.PageId, nav)\n{\n    #region Static\n\n    public const string Id = $\"{BaseId}.open.{MavParamsPageViewModel.PageId}\";\n\n    public static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.OpenMavParamsCommand_CommandInfo_Name,\n        Description = RS.OpenMavParamsCommand_CommandInfo_Description,\n        Icon = MavParamsPageViewModel.PageIcon,\n        DefaultHotKey = null,\n    };\n\n    #endregion\n    public override ICommandInfo Info => StaticInfo;\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Mavlink/MavlinkCommands.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Asv.Mavlink;\n\nnamespace Asv.Drones;\n\npublic class MavlinkCommands : IMavlinkCommands\n{\n    public ICommandInfo WriteParamInfo => MavlinkParamsWriteCommand.StaticInfo;\n\n    public ValueTask WriteParam(\n        IRoutable context,\n        string name,\n        MavParamValue value,\n        CancellationToken cancel = default\n    ) => MavlinkParamsWriteCommand.Execute(context, name, value, cancel);\n\n    public ICommandInfo ReadParamInfo => MavlinkParamReadCommand.StaticInfo;\n\n    public ValueTask ReadParam(\n        IRoutable context,\n        string name,\n        CancellationToken cancel = default\n    ) => MavlinkParamReadCommand.Execute(context, name, cancel);\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Mavlink/MavlinkCommandsMixin.cs",
    "content": "using Asv.Drones.Api;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\n\nnamespace Asv.Drones;\n\npublic static class MavlinkCommandsMixin\n{\n    public static IHostApplicationBuilder RegisterMavlinkCommands(\n        this IHostApplicationBuilder builder\n    )\n    {\n        builder.Services.AddSingleton<IMavlinkCommands, MavlinkCommands>();\n        return builder;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Mavlink/MavlinkParamReadCommand.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Asv.Mavlink;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class MavlinkParamReadCommand : MavlinkMicroserviceCommand<IParamsClientEx, StringArg>\n{\n    public const string Id = $\"{BaseId}.mavlink.param.read\";\n    public static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.ReadParamCommand_CommandInfo_Name,\n        Description = RS.ReadParamCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.Set,\n\n        DefaultHotKey = null,\n    };\n\n    public static ValueTask Execute(\n        IRoutable context,\n        string name,\n        CancellationToken cancel = default\n    )\n    {\n        return context.ExecuteCommand(Id, CommandArg.CreateString(name), cancel: cancel);\n    }\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override async ValueTask<StringArg?> InternalExecute(\n        IParamsClientEx microservice,\n        StringArg arg,\n        CancellationToken cancel\n    )\n    {\n        await microservice.ReadOnce(arg.Value, cancel);\n        return null; // this is command without undo, so we return null\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Mavlink/MavlinkParamsWriteCommand.cs",
    "content": "using System;\nusing System.Diagnostics;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Asv.Mavlink;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class MavlinkParamsWriteCommand : MavlinkMicroserviceCommand<IParamsClientEx, ActionArg>\n{\n    public const string Id = $\"{BaseId}.mavlink.param.write\";\n    public static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.WritePatamCommand_CommandInfo_Name,\n        Description = RS.WriteParamCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.Set,\n\n        DefaultHotKey = null,\n    };\n\n    public static ValueTask Execute(\n        IRoutable context,\n        string name,\n        MavParamValue value,\n        CancellationToken cancel = default\n    )\n    {\n        return context.ExecuteCommand(\n            Id,\n            CommandArg.ChangeAction(name, CommandArg.CreateString(value.PrintValue())),\n            cancel\n        );\n    }\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override async ValueTask<ActionArg?> InternalExecute(\n        IParamsClientEx microservice,\n        ActionArg arg,\n        CancellationToken cancel\n    )\n    {\n        if (string.IsNullOrWhiteSpace(arg.SubjectId))\n        {\n            throw new ArgumentException(\n                $@\"{nameof(arg.SubjectId)} cannot be null or empty.\",\n                nameof(arg.SubjectId)\n            );\n        }\n\n        MavParamValue prevValue;\n        if (!microservice.Items.TryGetValue(arg.SubjectId, out var param))\n        {\n            prevValue = await microservice.ReadOnce(arg.SubjectId, cancel);\n        }\n        else\n        {\n            prevValue = param.Value.Value;\n        }\n\n        var stringValue = arg.Value?.AsString();\n        if (string.IsNullOrWhiteSpace(stringValue))\n        {\n            throw new ArgumentException(\n                $@\"{nameof(arg.Value)} must be of type {CommandArg.Id.String}.\",\n                nameof(arg.Value)\n            );\n        }\n\n        var result = MavParamValue.TryParseValue(stringValue, prevValue.Type, out var value);\n        if (!result.IsSuccess)\n        {\n            Debug.Assert(result.ValidationException != null, \"result.ValidationException != null\");\n            throw new ArgumentException(\n                $\"Cannot parse value '{stringValue}' to type {prevValue.Type}: {result.ValidationException.Message}\",\n                nameof(arg.Value),\n                result.ValidationException\n            );\n        }\n\n        Debug.Assert(value != null, nameof(value) + \" != null\");\n        await microservice.WriteOnce(arg.SubjectId, value.Value, cancel);\n\n        return CommandArg.ChangeAction(\n            arg.SubjectId,\n            CommandArg.CreateString(prevValue.PrintValue())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Mavlink/NullMavlinkCommands.cs",
    "content": "using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Asv.Mavlink;\n\nnamespace Asv.Drones;\n\npublic sealed class NullMavlinkCommands : IMavlinkCommands\n{\n    public static NullMavlinkCommands Instance { get; } = new();\n\n    private NullMavlinkCommands() { }\n\n    public ICommandInfo WriteParamInfo => MavlinkParamsWriteCommand.StaticInfo;\n\n    public ValueTask WriteParam(\n        IRoutable context,\n        string name,\n        MavParamValue value,\n        CancellationToken cancel = default\n    )\n    {\n        return ValueTask.CompletedTask;\n    }\n\n    public ICommandInfo ReadParamInfo => MavlinkParamReadCommand.StaticInfo;\n\n    public ValueTask ReadParam(IRoutable context, string name, CancellationToken cancel = default)\n    {\n        return ValueTask.CompletedTask;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/PacketViewer/ClearAllPacketsCommand.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic sealed class ClearAllPacketsCommand : ContextCommand<PacketViewerViewModel>\n{\n    public const string Id = $\"{BaseId}.packet-viewer.clear-all\";\n\n    internal static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.ClearAllPacketsCommand_CommandInfo_Name,\n        Description = RS.ClearAllPacketsCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.Bin,\n        DefaultHotKey = null, // TODO: make a key bind later\n    };\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override ValueTask<CommandArg?> InternalExecute(\n        PacketViewerViewModel context,\n        CommandArg newValue,\n        CancellationToken cancel\n    )\n    {\n        context.ClearAllImpl();\n        return ValueTask.FromResult<CommandArg?>(null);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/PacketViewer/ExportPacketsToCsvCommand.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class ExportPacketsToCsvCommand : ContextCommand<PacketViewerViewModel>\n{\n    public const string Id = $\"{BaseId}.packet-viewer.export-to-csv\";\n\n    internal static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.ExportPacketsToCsvCommand_CommandInfo_Name,\n        Description = RS.ExportPacketsToCsvCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.ContentSave,\n        DefaultHotKey = null, // TODO: make a key bind later\n    };\n\n    public override ICommandInfo Info => StaticInfo;\n\n    protected override async ValueTask<CommandArg?> InternalExecute(\n        PacketViewerViewModel context,\n        CommandArg newValue,\n        CancellationToken cancel\n    )\n    {\n        await context.ExportToCsvImpl(cancel);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/PacketViewer/OpenPacketViewer.cs",
    "content": "using Asv.Avalonia;\n\nnamespace Asv.Drones;\n\npublic class OpenPacketViewerCommand(INavigationService nav)\n    : OpenPageCommandBase(PacketViewerViewModel.PageId, nav)\n{\n    #region Static\n    public const string Id = $\"{BaseId}.open.{PacketViewerViewModel.PageId}\";\n\n    public static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.OpenPacketViewerCommand_CommandInfo_Name,\n        Description = RS.OpenPacketViewerCommand_CommandInfo_Description,\n        Icon = PacketViewerViewModel.PageIcon,\n        DefaultHotKey = null,\n    };\n    #endregion\n\n    public override ICommandInfo Info => StaticInfo;\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Setup/FrameType/ChangeFrameTypeCommand.cs",
    "content": "using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class ChangeFrameTypeCommand : ContextCommand<SetupFrameTypeViewModel, StringArg>\n{\n    public const string Id = $\"{BaseId}.setup.frame-type.change\";\n\n    internal static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.ChangeFrameTypeCommand_CommandInfo_Name,\n        Description = RS.ChangeFrameTypeCommand_CommandInfo_Description,\n        Icon = MaterialIconKind.KeyChange,\n        DefaultHotKey = null,\n    };\n\n    public override ICommandInfo Info => StaticInfo;\n\n    public override async ValueTask<StringArg?> InternalExecute(\n        SetupFrameTypeViewModel context,\n        StringArg newValue,\n        CancellationToken cancel\n    )\n    {\n        var currentFrameId = context.CurrentFrame?.Value?.Id;\n\n        if (currentFrameId is null)\n        {\n            return null;\n        }\n\n        await context.ChangeFrameType(newValue.Value, cancel);\n\n        return new StringArg(currentFrameId);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Commands/Setup/OpenSetupCommand.cs",
    "content": "using Asv.Avalonia;\n\nnamespace Asv.Drones;\n\npublic class OpenSetupCommand(INavigationService nav)\n    : OpenPageCommandBase(SetupPageViewModel.PageId, nav)\n{\n    #region Static\n\n    public const string Id = $\"{BaseId}.open.{SetupPageViewModel.PageId}\";\n\n    public static readonly ICommandInfo StaticInfo = new CommandInfo\n    {\n        Id = Id,\n        Name = RS.OpenSetupCommand_CommandInfo_Name,\n        Description = RS.OpenSetupCommand_CommandInfo_Description,\n        Icon = SetupPageViewModel.PageIcon,\n        DefaultHotKey = null,\n    };\n\n    #endregion\n\n    public override ICommandInfo Info => StaticInfo;\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/AngleUavIndicator/Items/Pitch/PitchItem.cs",
    "content": "using System;\nusing Avalonia;\n\nnamespace Asv.Drones;\n\npublic partial class PitchItem : AvaloniaObject\n{\n    private readonly int _pitch;\n\n    public PitchItem(\n        int pitch,\n        double scale,\n        bool titleIsVisible = true,\n        double controlHeight = 284\n    )\n    {\n        _pitch = pitch;\n        Value = ((controlHeight / 2) - pitch) * scale;\n        if (titleIsVisible)\n        {\n            Title = pitch.ToString();\n            StartLine = new Point(0 * scale, 0 * scale);\n            StopLine = new Point(20 * scale, 0 * scale);\n        }\n        else\n        {\n            Title = string.Empty;\n            StartLine = new Point(4 * scale, 0 * scale);\n            StopLine = new Point(16 * scale, 0 * scale);\n        }\n\n        IsVisible = Math.Abs(pitch) <= 20;\n    }\n\n    public void UpdateVisibility(double pitch)\n    {\n        IsVisible = pitch >= _pitch - 20 && pitch <= _pitch + 20;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/AngleUavIndicator/Items/Pitch/PitchItem.properties.cs",
    "content": "using Avalonia;\n\nnamespace Asv.Drones;\n\npublic partial class PitchItem\n{\n    public static readonly DirectProperty<OldAttitudeIndicator.PitchItem, string> TitleProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.PitchItem, string>(\n            nameof(Title),\n            _ => _.Title,\n            (_, value) => _.Title = value\n        );\n\n    public string Title\n    {\n        get;\n        set => SetAndRaise(TitleProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<OldAttitudeIndicator.PitchItem, double> ValueProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.PitchItem, double>(\n            nameof(Value),\n            _ => _.Value,\n            (_, value) => _.Value = value\n        );\n\n    public double Value\n    {\n        get;\n        set => SetAndRaise(ValueProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<OldAttitudeIndicator.PitchItem, bool> IsVisibleProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.PitchItem, bool>(\n            nameof(IsVisible),\n            _ => _.IsVisible,\n            (_, value) => _.IsVisible = value\n        );\n\n    public bool IsVisible\n    {\n        get;\n        set => SetAndRaise(IsVisibleProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<OldAttitudeIndicator.PitchItem, Point> StartLineProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.PitchItem, Point>(\n            nameof(StartLine),\n            _ => _.StartLine,\n            (_, value) => _.StartLine = value\n        );\n\n    public Point StartLine\n    {\n        get;\n        set => SetAndRaise(StartLineProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<OldAttitudeIndicator.PitchItem, Point> StopLineProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.PitchItem, Point>(\n            nameof(StopLine),\n            _ => _.StopLine,\n            (_, value) => _.StopLine = value\n        );\n\n    public Point StopLine\n    {\n        get;\n        set => SetAndRaise(StopLineProperty, ref field, value);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/AngleUavIndicator/Items/Roll/RollItem.cs",
    "content": "using System;\nusing Avalonia;\n\nnamespace Asv.Drones;\n\npublic partial class RollItem : AvaloniaObject\n{\n    public RollItem(int angle)\n    {\n        Value = angle;\n        Title =\n            Math.Abs(angle) > 180 ? (360 - Math.Abs(angle)).ToString() : Math.Abs(angle).ToString();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/AngleUavIndicator/Items/Roll/RollItem.properties.cs",
    "content": "using Avalonia;\n\nnamespace Asv.Drones;\n\npublic partial class RollItem\n{\n    public static readonly DirectProperty<OldAttitudeIndicator.RollItem, string> TitleProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.RollItem, string>(\n            nameof(Title),\n            _ => _.Title,\n            (_, value) => _.Title = value\n        );\n\n    public string Title\n    {\n        get;\n        set => SetAndRaise(TitleProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<OldAttitudeIndicator.RollItem, double> ValueProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.RollItem, double>(\n            nameof(Value),\n            _ => _.Value,\n            (_, value) => _.Value = value\n        );\n\n    public double Value\n    {\n        get;\n        set => SetAndRaise(ValueProperty, ref field, value);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/AngleUavIndicator/UavAngleIndicator.cs",
    "content": "using System;\nusing Avalonia;\nusing Avalonia.Collections;\nusing Avalonia.Controls.Primitives;\n\nnamespace Asv.Drones;\n\npublic partial class UavAngleIndicator : TemplatedControl\n{\n    public UavAngleIndicator()\n    {\n        Scale = Math.Min(InternalWidth, InternalHeight) / 100;\n\n        RollItems = new AvaloniaList<OldAttitudeIndicator.RollItem>(\n            new OldAttitudeIndicator.RollItem(0),\n            new OldAttitudeIndicator.RollItem(10),\n            new OldAttitudeIndicator.RollItem(20),\n            new OldAttitudeIndicator.RollItem(30),\n            new OldAttitudeIndicator.RollItem(45),\n            new OldAttitudeIndicator.RollItem(60),\n            new OldAttitudeIndicator.RollItem(300),\n            new OldAttitudeIndicator.RollItem(315),\n            new OldAttitudeIndicator.RollItem(330),\n            new OldAttitudeIndicator.RollItem(340),\n            new OldAttitudeIndicator.RollItem(350)\n        );\n\n        PitchItems = new AvaloniaList<OldAttitudeIndicator.PitchItem>(\n            new OldAttitudeIndicator.PitchItem(135, Scale, false),\n            new OldAttitudeIndicator.PitchItem(130, Scale),\n            new OldAttitudeIndicator.PitchItem(125, Scale, false),\n            new OldAttitudeIndicator.PitchItem(120, Scale),\n            new OldAttitudeIndicator.PitchItem(115, Scale, false),\n            new OldAttitudeIndicator.PitchItem(110, Scale),\n            new OldAttitudeIndicator.PitchItem(105, Scale, false),\n            new OldAttitudeIndicator.PitchItem(100, Scale),\n            new OldAttitudeIndicator.PitchItem(95, Scale, false),\n            new OldAttitudeIndicator.PitchItem(90, Scale),\n            new OldAttitudeIndicator.PitchItem(85, Scale, false),\n            new OldAttitudeIndicator.PitchItem(80, Scale),\n            new OldAttitudeIndicator.PitchItem(75, Scale, false),\n            new OldAttitudeIndicator.PitchItem(70, Scale),\n            new OldAttitudeIndicator.PitchItem(65, Scale, false),\n            new OldAttitudeIndicator.PitchItem(60, Scale),\n            new OldAttitudeIndicator.PitchItem(55, Scale, false),\n            new OldAttitudeIndicator.PitchItem(50, Scale),\n            new OldAttitudeIndicator.PitchItem(45, Scale, false),\n            new OldAttitudeIndicator.PitchItem(40, Scale),\n            new OldAttitudeIndicator.PitchItem(35, Scale, false),\n            new OldAttitudeIndicator.PitchItem(30, Scale),\n            new OldAttitudeIndicator.PitchItem(25, Scale, false),\n            new OldAttitudeIndicator.PitchItem(20, Scale),\n            new OldAttitudeIndicator.PitchItem(15, Scale, false),\n            new OldAttitudeIndicator.PitchItem(10, Scale),\n            new OldAttitudeIndicator.PitchItem(5, Scale, false),\n            new OldAttitudeIndicator.PitchItem(0, Scale),\n            new OldAttitudeIndicator.PitchItem(-5, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-10, Scale),\n            new OldAttitudeIndicator.PitchItem(-15, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-20, Scale),\n            new OldAttitudeIndicator.PitchItem(-25, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-30, Scale),\n            new OldAttitudeIndicator.PitchItem(-35, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-40, Scale),\n            new OldAttitudeIndicator.PitchItem(-45, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-50, Scale),\n            new OldAttitudeIndicator.PitchItem(-55, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-60, Scale),\n            new OldAttitudeIndicator.PitchItem(-65, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-70, Scale),\n            new OldAttitudeIndicator.PitchItem(-75, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-80, Scale),\n            new OldAttitudeIndicator.PitchItem(-85, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-90, Scale),\n            new OldAttitudeIndicator.PitchItem(-95, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-100, Scale),\n            new OldAttitudeIndicator.PitchItem(-105, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-110, Scale),\n            new OldAttitudeIndicator.PitchItem(-115, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-120, Scale),\n            new OldAttitudeIndicator.PitchItem(-125, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-130, Scale),\n            new OldAttitudeIndicator.PitchItem(-135, Scale, false)\n        );\n    }\n\n    public double Scale { get; }\n\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        if (change.Property == RollAngleProperty)\n        {\n            UpdateRollAngle(change.Sender);\n        }\n        else if (change.Property == PitchAngleProperty)\n        {\n            UpdateAngle(change.Sender);\n        }\n    }\n\n    private static void UpdateAngle(AvaloniaObject source)\n    {\n        if (source is not UavAngleIndicator indicator)\n        {\n            return;\n        }\n\n        var pitch = indicator.PitchAngle;\n        UpdateRollAngle(source);\n        foreach (var item in indicator.PitchItems)\n        {\n            item.UpdateVisibility(pitch);\n        }\n    }\n\n    private static void UpdateRollAngle(AvaloniaObject source)\n    {\n        if (source is not UavAngleIndicator indicator)\n        {\n            return;\n        }\n\n        var roll = indicator.RollAngle;\n        var pitch = indicator.PitchAngle;\n        indicator.PitchTranslateX =\n            -pitch * indicator.Scale * Math.Cos((roll - 90.0) * Math.PI / 180.0);\n        indicator.PitchTranslateY =\n            pitch * indicator.Scale * Math.Sin((90 - roll) * Math.PI / 180.0);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/AngleUavIndicator/UavAngleIndicator.properties.cs",
    "content": "using System.Collections.Generic;\nusing Avalonia;\n\nnamespace Asv.Drones;\n\npublic partial class UavAngleIndicator\n{\n    public static readonly StyledProperty<double> RollAngleProperty = AvaloniaProperty.Register<\n        UavAngleIndicator,\n        double\n    >(nameof(RollAngle));\n\n    public double RollAngle\n    {\n        get => GetValue(RollAngleProperty);\n        set => SetValue(RollAngleProperty, value);\n    }\n\n    public static readonly StyledProperty<double> PitchAngleProperty = AvaloniaProperty.Register<\n        UavAngleIndicator,\n        double\n    >(nameof(PitchAngle));\n\n    public double PitchAngle\n    {\n        get => GetValue(PitchAngleProperty);\n        set => SetValue(PitchAngleProperty, value);\n    }\n\n    #region Internal direct property\n\n    public static readonly DirectProperty<UavAngleIndicator, double> InternalWidthProperty =\n        AvaloniaProperty.RegisterDirect<UavAngleIndicator, double>(\n            nameof(InternalWidth),\n            _ => _.InternalWidth,\n            (_, value) => _.InternalWidth = value\n        );\n\n    public double InternalWidth\n    {\n        get;\n        set => SetAndRaise(InternalWidthProperty, ref field, value);\n    } = 1000;\n\n    public static readonly DirectProperty<UavAngleIndicator, double> InternalHeightProperty =\n        AvaloniaProperty.RegisterDirect<UavAngleIndicator, double>(\n            nameof(InternalHeight),\n            _ => _.InternalHeight,\n            (_, value) => _.InternalHeight = value\n        );\n\n    public double InternalHeight\n    {\n        get;\n        set => SetAndRaise(InternalHeightProperty, ref field, value);\n    } = 1000;\n\n    public static readonly DirectProperty<UavAngleIndicator, double> PitchTranslateXProperty =\n        AvaloniaProperty.RegisterDirect<UavAngleIndicator, double>(\n            nameof(PitchTranslateX),\n            _ => _.PitchTranslateX,\n            (_, value) => _.PitchTranslateX = value\n        );\n\n    private double PitchTranslateX\n    {\n        get;\n        set => SetAndRaise(PitchTranslateXProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<UavAngleIndicator, double> PitchTranslateYProperty =\n        AvaloniaProperty.RegisterDirect<UavAngleIndicator, double>(\n            nameof(PitchTranslateY),\n            _ => _.PitchTranslateY,\n            (_, value) => _.PitchTranslateY = value\n        );\n\n    public double PitchTranslateY\n    {\n        get;\n        set => SetAndRaise(PitchTranslateYProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<\n        UavAngleIndicator,\n        IEnumerable<OldAttitudeIndicator.RollItem>\n    > RollItemsProperty = AvaloniaProperty.RegisterDirect<\n        UavAngleIndicator,\n        IEnumerable<OldAttitudeIndicator.RollItem>\n    >(nameof(RollItems), _ => _.RollItems, (_, value) => _.RollItems = value);\n\n    public IEnumerable<OldAttitudeIndicator.RollItem> RollItems\n    {\n        get;\n        set => SetAndRaise(RollItemsProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<\n        UavAngleIndicator,\n        IEnumerable<OldAttitudeIndicator.PitchItem>\n    > PitchItemsProperty = AvaloniaProperty.RegisterDirect<\n        UavAngleIndicator,\n        IEnumerable<OldAttitudeIndicator.PitchItem>\n    >(nameof(PitchItems), _ => _.PitchItems, (_, value) => _.PitchItems = value);\n\n    public IEnumerable<OldAttitudeIndicator.PitchItem> PitchItems\n    {\n        get;\n        set => SetAndRaise(PitchItemsProperty, ref field, value);\n    }\n    #endregion\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/AngleUavIndicator/UavAngleIndicatorStyles.axaml",
    "content": "<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n>\n    <Design.PreviewWith>\n        <drones:UavAngleIndicator\n            Width=\"500\"\n            Height=\"500\"\n            RollAngle=\"0\"\n            PitchAngle=\"0\"\n            CornerRadius=\"500\"\n        />\n    </Design.PreviewWith>\n\n    <Style Selector=\"drones|UavAngleIndicator\">\n        <Setter Property=\"VerticalAlignment\" Value=\"Center\" />\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <Viewbox>\n                    <Border CornerRadius=\"{TemplateBinding CornerRadius}\" ClipToBounds=\"True\">\n                        <Canvas\n                            x:Name=\"Canvas\"\n                            Width=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=InternalWidth}\"\n                            Height=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=InternalHeight}\"\n                            ClipToBounds=\"True\"\n                            Background=\"Transparent\"\n                        >\n                            <Rectangle\n                                x:Name=\"Sky\"\n                                Canvas.Top=\"-920\"\n                                Canvas.Left=\"-210\"\n                                Width=\"1420\"\n                                Height=\"1420\"\n                            >\n                                <Rectangle.Fill>\n                                    <LinearGradientBrush EndPoint=\"0,0\" StartPoint=\"0,1420\">\n                                        <GradientStop Color=\"#64b5f6\" Offset=\"0\" />\n                                        <GradientStop Color=\"#1565c0\" Offset=\"0.3\" />\n                                    </LinearGradientBrush>\n                                </Rectangle.Fill>\n                                <Rectangle.RenderTransform>\n                                    <TransformGroup>\n                                        <ScaleTransform />\n                                        <SkewTransform />\n                                        <RotateTransform\n                                            Angle=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RollAngle}\"\n                                            CenterX=\"0\"\n                                            CenterY=\"710\"\n                                        />\n                                        <TranslateTransform\n                                            X=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PitchTranslateX}\"\n                                            Y=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PitchTranslateY}\"\n                                        />\n                                    </TransformGroup>\n                                </Rectangle.RenderTransform>\n                            </Rectangle>\n                            <Rectangle\n                                Canvas.Top=\"500\"\n                                Canvas.Left=\"-210\"\n                                Width=\"2000\"\n                                Height=\"2000\"\n                            >\n                                <Rectangle.Fill>\n                                    <LinearGradientBrush\n                                        StartPoint=\"50%,0%\"\n                                        EndPoint=\"50%,100%\"\n                                        Opacity=\"0.2\"\n                                    >\n                                        <LinearGradientBrush.GradientStops>\n                                            <GradientStop Offset=\"0\" Color=\"#4679ba\" />\n                                            <GradientStop Offset=\"0.5\" Color=\"#a2acc5\" />\n                                            <GradientStop Offset=\"0.7\" Color=\"#081b5d\" />\n                                            <GradientStop Offset=\"1\" Color=\"#2e4469\" />\n                                        </LinearGradientBrush.GradientStops>\n                                    </LinearGradientBrush>\n                                </Rectangle.Fill>\n                                <Rectangle.RenderTransform>\n                                    <TransformGroup>\n                                        <ScaleTransform />\n                                        <SkewTransform />\n                                        <RotateTransform\n                                            Angle=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RollAngle}\"\n                                            CenterX=\"0\"\n                                            CenterY=\"-710\"\n                                        />\n                                        <TranslateTransform\n                                            X=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PitchTranslateX}\"\n                                            Y=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PitchTranslateY}\"\n                                        />\n                                    </TransformGroup>\n                                </Rectangle.RenderTransform>\n                            </Rectangle>\n\n                            <Path Width=\"500\" Height=\"500\" Stroke=\"#e0e0e0\" StrokeThickness=\"5\">\n                                <Path.RenderTransform>\n                                    <TransformGroup>\n                                        <ScaleTransform />\n                                        <SkewTransform />\n                                        <RotateTransform\n                                            Angle=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RollAngle}\"\n                                            CenterX=\"250\"\n                                            CenterY=\"250\"\n                                        />\n                                    </TransformGroup>\n                                </Path.RenderTransform>\n                                <Path.Data>\n                                    <PathGeometry>\n                                        <PathFigure StartPoint=\"240,350\" IsClosed=\"False\">\n                                            <ArcSegment\n                                                Size=\"300,300\"\n                                                IsLargeArc=\"False\"\n                                                SweepDirection=\"Clockwise\"\n                                                Point=\"760,350\"\n                                            />\n                                        </PathFigure>\n                                    </PathGeometry>\n                                </Path.Data>\n                            </Path>\n\n                            <ItemsControl\n                                x:Name=\"Ticks\"\n                                Canvas.Left=\"150\"\n                                Canvas.Top=\"150\"\n                                Width=\"700\"\n                                Height=\"700\"\n                                ItemsSource=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RollItems}\"\n                                HorizontalAlignment=\"Center\"\n                                VerticalAlignment=\"Center\"\n                            >\n                                <ItemsControl.RenderTransform>\n                                    <TransformGroup>\n                                        <RotateTransform\n                                            Angle=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RollAngle}\"\n                                            CenterX=\"0\"\n                                            CenterY=\"0\"\n                                        />\n                                    </TransformGroup>\n                                </ItemsControl.RenderTransform>\n                                <ItemsControl.ItemsPanel>\n                                    <ItemsPanelTemplate>\n                                        <Grid />\n                                    </ItemsPanelTemplate>\n                                </ItemsControl.ItemsPanel>\n                                <ItemsControl.ItemTemplate>\n                                    <DataTemplate>\n                                        <StackPanel\n                                            x:DataType=\"drones:RollItem\"\n                                            Orientation=\"Vertical\"\n                                        >\n                                            <StackPanel.RenderTransform>\n                                                <TransformGroup>\n                                                    <RotateTransform\n                                                        CenterX=\"0\"\n                                                        CenterY=\"0\"\n                                                        Angle=\"{CompiledBinding Value}\"\n                                                    />\n                                                </TransformGroup>\n                                            </StackPanel.RenderTransform>\n                                            <TextBlock\n                                                Text=\"{CompiledBinding Title}\"\n                                                TextAlignment=\"Center\"\n                                                Foreground=\"#e0e0e0\"\n                                                Margin=\"0,-5\"\n                                                Width=\"100\"\n                                                FontSize=\"37\"\n                                            />\n                                            <Line\n                                                Stroke=\"#e0e0e0\"\n                                                StartPoint=\"350,0\"\n                                                EndPoint=\"350,10\"\n                                                Margin=\"0,0,0,0\"\n                                                StrokeThickness=\"5\"\n                                            />\n                                        </StackPanel>\n                                    </DataTemplate>\n                                </ItemsControl.ItemTemplate>\n                            </ItemsControl>\n\n                            <ItemsControl\n                                x:Name=\"Ticks2\"\n                                Canvas.Left=\"300\"\n                                Canvas.Top=\"-920\"\n                                Width=\"400\"\n                                Height=\"2840\"\n                                ItemsSource=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PitchItems}\"\n                                HorizontalAlignment=\"Center\"\n                                VerticalAlignment=\"Center\"\n                            >\n                                <ItemsControl.RenderTransform>\n                                    <TransformGroup>\n                                        <RotateTransform\n                                            Angle=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RollAngle}\"\n                                            CenterX=\"0\"\n                                            CenterY=\"0\"\n                                        />\n                                        <TranslateTransform\n                                            X=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PitchTranslateX}\"\n                                            Y=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PitchTranslateY}\"\n                                        />\n                                    </TransformGroup>\n                                </ItemsControl.RenderTransform>\n                                <ItemsControl.ItemsPanel>\n                                    <ItemsPanelTemplate>\n                                        <Grid />\n                                    </ItemsPanelTemplate>\n                                </ItemsControl.ItemsPanel>\n                                <ItemsControl.ItemTemplate>\n                                    <DataTemplate>\n                                        <StackPanel\n                                            x:DataType=\"drones:PitchItem\"\n                                            Orientation=\"Horizontal\"\n                                            HorizontalAlignment=\"Center\"\n                                            IsVisible=\"{CompiledBinding IsVisible}\"\n                                        >\n                                            <StackPanel.RenderTransform>\n                                                <TransformGroup>\n                                                    <TranslateTransform\n                                                        X=\"0\"\n                                                        Y=\"{CompiledBinding Value}\"\n                                                    />\n                                                </TransformGroup>\n                                            </StackPanel.RenderTransform>\n                                            <TextBlock\n                                                Margin=\"0,-25,10,0\"\n                                                Foreground=\"#e0e0e0\"\n                                                Text=\"{CompiledBinding Title}\"\n                                                TextAlignment=\"Right\"\n                                                Width=\"90\"\n                                                FontSize=\"35\"\n                                            />\n                                            <Line\n                                                Stroke=\"#e0e0e0\"\n                                                Width=\"300\"\n                                                HorizontalAlignment=\"Center\"\n                                                StartPoint=\"{Binding StartLine}\"\n                                                EndPoint=\"{CompiledBinding StopLine}\"\n                                                StrokeThickness=\"3\"\n                                            />\n                                        </StackPanel>\n                                    </DataTemplate>\n                                </ItemsControl.ItemTemplate>\n                            </ItemsControl>\n                            <Path\n                                Canvas.Left=\"480\"\n                                Canvas.Top=\"200\"\n                                Width=\"40\"\n                                Height=\"20\"\n                                Stretch=\"Fill\"\n                                Fill=\"#e53935\"\n                                StrokeThickness=\"0.5\"\n                            >\n                                <Path.Data>\n                                    <PathGeometry>\n                                        <PathFigure StartPoint=\"0,1\" IsClosed=\"True\">\n                                            <LineSegment Point=\"1,0\" />\n                                            <LineSegment Point=\"2,1\" />\n                                        </PathFigure>\n                                    </PathGeometry>\n                                </Path.Data>\n                            </Path>\n\n                            <Line\n                                StartPoint=\"150,500\"\n                                EndPoint=\"250,500\"\n                                Stroke=\"#e53935\"\n                                StrokeThickness=\"10\"\n                            />\n                            <Line\n                                StartPoint=\"750,500\"\n                                EndPoint=\"850,500\"\n                                Stroke=\"#e53935\"\n                                StrokeThickness=\"10\"\n                            />\n                            <Line\n                                StartPoint=\"400,500\"\n                                EndPoint=\"600,500\"\n                                Stroke=\"#e0e0e0\"\n                                StrokeThickness=\"5\"\n                            />\n                            <Line\n                                StartPoint=\"400,540\"\n                                EndPoint=\"502,500\"\n                                Stroke=\"#e53935\"\n                                StrokeThickness=\"10\"\n                            />\n                            <Line\n                                StartPoint=\"498,500\"\n                                EndPoint=\"600,540\"\n                                Stroke=\"#e53935\"\n                                StrokeThickness=\"10\"\n                            />\n                        </Canvas>\n                    </Border>\n                </Viewbox>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/CompassUavIndicator/CompassScaleItem.cs",
    "content": "using System;\nusing Avalonia;\n\nnamespace Asv.Drones;\n\npublic class CompassScaleItem : AvaloniaObject\n{\n    private const double Center = 150.0;\n    private const double OuterRadius = 128.0;\n    private const double LabelRadius = 88.0;\n    private const double LabelWidth = 44.0;\n    private const double LabelHeight = 24.0;\n\n    public CompassScaleItem(double angle, string? title, bool isMajor)\n    {\n        Angle = angle;\n        Title = title;\n        HasTitle = !string.IsNullOrWhiteSpace(title);\n        TickLength = isMajor ? 20 : 10;\n        TickWidth = isMajor ? 3 : 2;\n        Update(0);\n    }\n\n    public double Angle { get; }\n    public string? Title { get; }\n    public bool HasTitle { get; }\n    public double TickLength { get; }\n    public double TickWidth { get; }\n\n    public static readonly DirectProperty<CompassScaleItem, Point> TickStartProperty =\n        AvaloniaProperty.RegisterDirect<CompassScaleItem, Point>(\n            nameof(TickStart),\n            item => item.TickStart,\n            (item, value) => item.TickStart = value\n        );\n\n    public Point TickStart\n    {\n        get;\n        private set => SetAndRaise(TickStartProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<CompassScaleItem, Point> TickEndProperty =\n        AvaloniaProperty.RegisterDirect<CompassScaleItem, Point>(\n            nameof(TickEnd),\n            item => item.TickEnd,\n            (item, value) => item.TickEnd = value\n        );\n\n    public Point TickEnd\n    {\n        get;\n        private set => SetAndRaise(TickEndProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<CompassScaleItem, double> LabelLeftProperty =\n        AvaloniaProperty.RegisterDirect<CompassScaleItem, double>(\n            nameof(LabelLeft),\n            item => item.LabelLeft,\n            (item, value) => item.LabelLeft = value\n        );\n\n    public double LabelLeft\n    {\n        get;\n        private set => SetAndRaise(LabelLeftProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<CompassScaleItem, double> LabelTopProperty =\n        AvaloniaProperty.RegisterDirect<CompassScaleItem, double>(\n            nameof(LabelTop),\n            item => item.LabelTop,\n            (item, value) => item.LabelTop = value\n        );\n\n    public double LabelTop\n    {\n        get;\n        private set => SetAndRaise(LabelTopProperty, ref field, value);\n    }\n\n    public void Update(double heading)\n    {\n        var visualAngle = NormalizeSignedAngle(Angle - heading);\n        var radians = visualAngle * Math.PI / 180.0;\n        var sin = Math.Sin(radians);\n        var cos = Math.Cos(radians);\n\n        TickStart = new Point(\n            Center + ((OuterRadius - TickLength) * sin),\n            Center - ((OuterRadius - TickLength) * cos)\n        );\n        TickEnd = new Point(Center + (OuterRadius * sin), Center - (OuterRadius * cos));\n        LabelLeft = Center + (LabelRadius * sin) - (LabelWidth / 2.0);\n        LabelTop = Center - (LabelRadius * cos) - (LabelHeight / 2.0);\n    }\n\n    private static double NormalizeSignedAngle(double value)\n    {\n        var angle = value % 360.0;\n        if (angle <= -180.0)\n        {\n            angle += 360.0;\n        }\n        else if (angle > 180.0)\n        {\n            angle -= 360.0;\n        }\n\n        return angle;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/CompassUavIndicator/CompassUavIndicator.cs",
    "content": "using System.Linq;\nusing Avalonia;\nusing Avalonia.Collections;\nusing Avalonia.Controls.Primitives;\n\nnamespace Asv.Drones;\n\npublic partial class CompassUavIndicator : TemplatedControl\n{\n    public CompassUavIndicator()\n    {\n        CompassItems = new AvaloniaList<CompassScaleItem>(\n            Enumerable\n                .Range(0, 24)\n                .Select(index =>\n                {\n                    var angle = index * 15.0;\n                    var title = angle switch\n                    {\n                        0 => RS.HeadingScaleItem_Direction_N,\n                        90 => RS.HeadingScaleItem_Direction_E,\n                        180 => RS.HeadingScaleItem_Direction_S,\n                        270 => RS.HeadingScaleItem_Direction_W,\n                        _ when angle % 30 == 0 => angle.ToString(\"F0\"),\n                        _ => null,\n                    };\n                    return new CompassScaleItem(angle, title, angle % 30 == 0);\n                })\n        );\n        UpdateCompass();\n    }\n\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n\n        if (change.Property == HeadingProperty || change.Property == HomeAzimuthProperty)\n        {\n            UpdateCompass();\n        }\n    }\n\n    private void UpdateCompass()\n    {\n        var heading = NormalizeAngle(Heading);\n        HeadingText = $\"{heading:F0}°\";\n\n        foreach (var item in CompassItems)\n        {\n            item.Update(heading);\n        }\n\n        HomeMarkerRotation = double.IsNaN(HomeAzimuth)\n            ? 0.0\n            : NormalizeSignedAngle(HomeAzimuth - heading);\n    }\n\n    private static double NormalizeAngle(double value)\n    {\n        if (double.IsNaN(value) || double.IsInfinity(value))\n        {\n            return 0.0;\n        }\n\n        var angle = value % 360.0;\n        return angle < 0.0 ? angle + 360.0 : angle;\n    }\n\n    private static double NormalizeSignedAngle(double value)\n    {\n        var angle = value % 360.0;\n        if (angle <= -180.0)\n        {\n            angle += 360.0;\n        }\n        else if (angle > 180.0)\n        {\n            angle -= 360.0;\n        }\n\n        return angle;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/CompassUavIndicator/CompassUavIndicator.properties.cs",
    "content": "using System.Collections.Generic;\nusing Avalonia;\n\nnamespace Asv.Drones;\n\npublic partial class CompassUavIndicator\n{\n    public static readonly StyledProperty<double> HeadingProperty = AvaloniaProperty.Register<\n        CompassUavIndicator,\n        double\n    >(nameof(Heading));\n\n    public double Heading\n    {\n        get => GetValue(HeadingProperty);\n        set => SetValue(HeadingProperty, value);\n    }\n\n    public static readonly StyledProperty<double> HomeAzimuthProperty = AvaloniaProperty.Register<\n        CompassUavIndicator,\n        double\n    >(nameof(HomeAzimuth), double.NaN);\n\n    public double HomeAzimuth\n    {\n        get => GetValue(HomeAzimuthProperty);\n        set => SetValue(HomeAzimuthProperty, value);\n    }\n\n    public static readonly DirectProperty<\n        CompassUavIndicator,\n        IEnumerable<CompassScaleItem>\n    > CompassItemsProperty = AvaloniaProperty.RegisterDirect<\n        CompassUavIndicator,\n        IEnumerable<CompassScaleItem>\n    >(nameof(CompassItems), indicator => indicator.CompassItems);\n\n    public IEnumerable<CompassScaleItem> CompassItems { get; }\n\n    public static readonly DirectProperty<CompassUavIndicator, string> HeadingTextProperty =\n        AvaloniaProperty.RegisterDirect<CompassUavIndicator, string>(\n            nameof(HeadingText),\n            indicator => indicator.HeadingText,\n            (indicator, value) => indicator.HeadingText = value\n        );\n\n    public string HeadingText\n    {\n        get;\n        private set => SetAndRaise(HeadingTextProperty, ref field, value);\n    } = \"0°\";\n\n    public static readonly DirectProperty<CompassUavIndicator, double> HomeMarkerRotationProperty =\n        AvaloniaProperty.RegisterDirect<CompassUavIndicator, double>(\n            nameof(HomeMarkerRotation),\n            indicator => indicator.HomeMarkerRotation,\n            (indicator, value) => indicator.HomeMarkerRotation = value\n        );\n\n    public double HomeMarkerRotation\n    {\n        get;\n        private set => SetAndRaise(HomeMarkerRotationProperty, ref field, value);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/CompassUavIndicator/CompassUavIndicatorStyles.axaml",
    "content": "<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n>\n    <Design.PreviewWith>\n        <UniformGrid Rows=\"2\" Columns=\"2\">\n            <drones:CompassUavIndicator Width=\"300\" Height=\"300\" Heading=\"10\" HomeAzimuth=\"300\" />\n            <drones:CompassUavIndicator Width=\"300\" Height=\"300\" Heading=\"210\" HomeAzimuth=\"60\" />\n            <drones:CompassUavIndicator Width=\"300\" Height=\"300\" Heading=\"359\" HomeAzimuth=\"1\" />\n            <drones:CompassUavIndicator Width=\"300\" Height=\"300\" Heading=\"-45\" HomeAzimuth=\"NaN\" />\n        </UniformGrid>\n    </Design.PreviewWith>\n\n    <Style Selector=\"drones|CompassUavIndicator\">\n        <Setter Property=\"Width\" Value=\"300\" />\n        <Setter Property=\"Height\" Value=\"300\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <Viewbox>\n                    <Canvas Width=\"300\" Height=\"300\">\n                        <Ellipse\n                            Canvas.Left=\"8\"\n                            Canvas.Top=\"8\"\n                            Width=\"284\"\n                            Height=\"284\"\n                            Fill=\"#9bd2e0\"\n                            Stroke=\"#f4fbff\"\n                            StrokeThickness=\"3\"\n                        />\n                        <Ellipse\n                            Canvas.Left=\"18\"\n                            Canvas.Top=\"18\"\n                            Width=\"264\"\n                            Height=\"264\"\n                            Fill=\"#78c3d5\"\n                            Opacity=\"0.55\"\n                        />\n                        <ItemsControl\n                            Width=\"300\"\n                            Height=\"300\"\n                            ItemsSource=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=CompassItems}\"\n                        >\n                            <ItemsControl.ItemsPanel>\n                                <ItemsPanelTemplate>\n                                    <Canvas Width=\"300\" Height=\"300\" />\n                                </ItemsPanelTemplate>\n                            </ItemsControl.ItemsPanel>\n                            <ItemsControl.ItemTemplate>\n                                <DataTemplate x:DataType=\"drones:CompassScaleItem\">\n                                    <Canvas Width=\"300\" Height=\"300\">\n                                        <Line\n                                            StartPoint=\"{CompiledBinding TickStart}\"\n                                            EndPoint=\"{CompiledBinding TickEnd}\"\n                                            Stroke=\"#f4fbff\"\n                                            StrokeThickness=\"{CompiledBinding TickWidth}\"\n                                            StrokeLineCap=\"Round\"\n                                        />\n                                        <TextBlock\n                                            Canvas.Left=\"{CompiledBinding LabelLeft}\"\n                                            Canvas.Top=\"{CompiledBinding LabelTop}\"\n                                            Width=\"44\"\n                                            Height=\"24\"\n                                            IsVisible=\"{CompiledBinding HasTitle}\"\n                                            Text=\"{CompiledBinding Title}\"\n                                            Foreground=\"#f4fbff\"\n                                            FontWeight=\"SemiBold\"\n                                            FontSize=\"18\"\n                                            TextAlignment=\"Center\"\n                                        />\n                                    </Canvas>\n                                </DataTemplate>\n                            </ItemsControl.ItemTemplate>\n                        </ItemsControl>\n\n                        <Path\n                            Fill=\"#ff3d00\"\n                            Stroke=\"#f4fbff\"\n                            StrokeThickness=\"1.5\"\n                            Data=\"M150,82 L147,10 L153,10 Z\"\n                        />\n                        <Canvas Width=\"300\" Height=\"300\" RenderTransformOrigin=\"50%,50%\">\n                            <Canvas.RenderTransform>\n                                <RotateTransform Angle=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=HomeMarkerRotation}\" />\n                            </Canvas.RenderTransform>\n                            <Path\n                                Fill=\"#ff8a00\"\n                                Stroke=\"#f4fbff\"\n                                StrokeThickness=\"2\"\n                                Data=\"M150,42 L140,23 L160,23 Z\"\n                            />\n                        </Canvas>\n\n                        <Ellipse\n                            Canvas.Left=\"88\"\n                            Canvas.Top=\"88\"\n                            Width=\"124\"\n                            Height=\"124\"\n                            Fill=\"#f7fbff\"\n                            Stroke=\"#1682ad\"\n                            StrokeThickness=\"3\"\n                        />\n                        <TextBlock\n                            Canvas.Left=\"94\"\n                            Canvas.Top=\"125\"\n                            Width=\"112\"\n                            Height=\"52\"\n                            Text=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=HeadingText}\"\n                            Foreground=\"#4c95b4\"\n                            FontSize=\"44\"\n                            FontWeight=\"Regular\"\n                            TextAlignment=\"Center\"\n                            VerticalAlignment=\"Center\"\n                        />\n                    </Canvas>\n                </Viewbox>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/DeviceTelemetryDesignPreview.cs",
    "content": "using Asv.Avalonia;\n\nnamespace Asv.Drones;\n\ninternal static class DeviceTelemetryDesignPreview\n{\n    public const AsvColorKind DefaultStatusColor = AsvColorKind.Info5;\n\n    private static bool _isConfigured;\n\n    public static IUnitService UnitService\n    {\n        get\n        {\n            ConfigureUnits();\n            return NullUnitService.Instance;\n        }\n    }\n\n    public static IUnit Unit(string id) => UnitService.Units[id];\n\n    private static void ConfigureUnits()\n    {\n        if (_isConfigured)\n        {\n            return;\n        }\n\n        var unitService = NullUnitService.Instance;\n\n        unitService.Extend(\n            new VelocityUnit(\n                DesignTime.Configuration,\n                [new VelocityMetersPerSecondUnitItem(), new VelocityMilesPerHourUnitItem()]\n            )\n        );\n        unitService.Extend(\n            new ProgressUnit(\n                DesignTime.Configuration,\n                [new ProgressPercentUnitItem(), new ProgressInPartsUnitItem()]\n            )\n        );\n        unitService.Extend(\n            new CapacityUnit(DesignTime.Configuration, [new CapacityMilliAmperePerHourUnitItem()])\n        );\n        unitService.Extend(\n            new AmperageUnit(\n                DesignTime.Configuration,\n                [new AmperageAmpereUnitItem(), new AmperageMilliAmpereUnitItem()]\n            )\n        );\n        unitService.Extend(\n            new VoltageUnit(\n                DesignTime.Configuration,\n                [new VoltageVoltUnitItem(), new VoltageMilliVoltUnitItem()]\n            )\n        );\n\n        _isConfigured = true;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/OldAttitudeIndicator/AttitudeIndicator.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing Avalonia;\nusing Avalonia.Collections;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Primitives;\nusing Avalonia.Media;\nusing R3;\n\nnamespace Asv.Drones.OldAttitudeIndicator;\n\npublic partial class AttitudeIndicator : TemplatedControl\n{\n    private const int VelocityItemCount = 6;\n    private const int VelocityValueRange = 5;\n    private const double VelocityControlLengthPrc = 0.4;\n    private const int AltitudeItemCount = 6;\n    private const int AltitudeValueRange = 5;\n    private const double AltitudeControlLengthPrc = 0.4;\n    private const int HeadingItemCount = 10;\n    private const double HeadingControlLengthPrc = 1.0;\n    private const int HeadingValueRange = 15;\n\n    private static double _headingPositionStep;\n    private static double _headingCenterPosition;\n\n    public double Scale { get; }\n\n    public AttitudeIndicator()\n    {\n        if (Design.IsDesignMode)\n        {\n            var status = new[] { \"Armed\", \"Disarmed\" };\n            Observable\n                .Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1), TimeProvider.System)\n                .Subscribe(_ =>\n                {\n                    StatusText = status[1 % 2];\n                });\n            StatusText = status[1];\n        }\n\n        var smallerSide = Math.Min((double)InternalWidth, InternalHeight);\n        Scale = smallerSide / 100;\n\n        RollItems = new AvaloniaList<OldAttitudeIndicator.RollItem>(\n            new OldAttitudeIndicator.RollItem(0),\n            new OldAttitudeIndicator.RollItem(10),\n            new OldAttitudeIndicator.RollItem(20),\n            new OldAttitudeIndicator.RollItem(30),\n            new OldAttitudeIndicator.RollItem(45),\n            new OldAttitudeIndicator.RollItem(60),\n            new OldAttitudeIndicator.RollItem(300),\n            new OldAttitudeIndicator.RollItem(315),\n            new OldAttitudeIndicator.RollItem(330),\n            new OldAttitudeIndicator.RollItem(340),\n            new OldAttitudeIndicator.RollItem(350)\n        );\n\n        PitchItems = new AvaloniaList<OldAttitudeIndicator.PitchItem>(\n            new OldAttitudeIndicator.PitchItem(135, Scale, false),\n            new OldAttitudeIndicator.PitchItem(130, Scale),\n            new OldAttitudeIndicator.PitchItem(125, Scale, false),\n            new OldAttitudeIndicator.PitchItem(120, Scale),\n            new OldAttitudeIndicator.PitchItem(115, Scale, false),\n            new OldAttitudeIndicator.PitchItem(110, Scale),\n            new OldAttitudeIndicator.PitchItem(105, Scale, false),\n            new OldAttitudeIndicator.PitchItem(100, Scale),\n            new OldAttitudeIndicator.PitchItem(95, Scale, false),\n            new OldAttitudeIndicator.PitchItem(90, Scale),\n            new OldAttitudeIndicator.PitchItem(85, Scale, false),\n            new OldAttitudeIndicator.PitchItem(80, Scale),\n            new OldAttitudeIndicator.PitchItem(75, Scale, false),\n            new OldAttitudeIndicator.PitchItem(70, Scale),\n            new OldAttitudeIndicator.PitchItem(65, Scale, false),\n            new OldAttitudeIndicator.PitchItem(60, Scale),\n            new OldAttitudeIndicator.PitchItem(55, Scale, false),\n            new OldAttitudeIndicator.PitchItem(50, Scale),\n            new OldAttitudeIndicator.PitchItem(45, Scale, false),\n            new OldAttitudeIndicator.PitchItem(40, Scale),\n            new OldAttitudeIndicator.PitchItem(35, Scale, false),\n            new OldAttitudeIndicator.PitchItem(30, Scale),\n            new OldAttitudeIndicator.PitchItem(25, Scale, false),\n            new OldAttitudeIndicator.PitchItem(20, Scale),\n            new OldAttitudeIndicator.PitchItem(15, Scale, false),\n            new OldAttitudeIndicator.PitchItem(10, Scale),\n            new OldAttitudeIndicator.PitchItem(5, Scale, false),\n            new OldAttitudeIndicator.PitchItem(0, Scale),\n            new OldAttitudeIndicator.PitchItem(-5, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-10, Scale),\n            new OldAttitudeIndicator.PitchItem(-15, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-20, Scale),\n            new OldAttitudeIndicator.PitchItem(-25, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-30, Scale),\n            new OldAttitudeIndicator.PitchItem(-35, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-40, Scale),\n            new OldAttitudeIndicator.PitchItem(-45, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-50, Scale),\n            new OldAttitudeIndicator.PitchItem(-55, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-60, Scale),\n            new OldAttitudeIndicator.PitchItem(-65, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-70, Scale),\n            new OldAttitudeIndicator.PitchItem(-75, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-80, Scale),\n            new OldAttitudeIndicator.PitchItem(-85, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-90, Scale),\n            new OldAttitudeIndicator.PitchItem(-95, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-100, Scale),\n            new OldAttitudeIndicator.PitchItem(-105, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-110, Scale),\n            new OldAttitudeIndicator.PitchItem(-115, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-120, Scale),\n            new OldAttitudeIndicator.PitchItem(-125, Scale, false),\n            new OldAttitudeIndicator.PitchItem(-130, Scale),\n            new OldAttitudeIndicator.PitchItem(-135, Scale, false)\n        );\n\n        var velocityControlLength = smallerSide * VelocityControlLengthPrc;\n        var velocityItemLength = velocityControlLength / (VelocityItemCount - 1);\n        VelocityItems = new AvaloniaList<OldAttitudeIndicator.ScaleItem>(\n            Enumerable\n                .Range(0, VelocityItemCount)\n                .Select(_ => new OldAttitudeIndicator.ScaleItem(\n                    0,\n                    VelocityValueRange,\n                    _,\n                    VelocityItemCount,\n                    velocityControlLength + velocityItemLength,\n                    velocityControlLength,\n                    showNegative: false\n                ))\n        );\n\n        var altitudeControlLength = smallerSide * AltitudeControlLengthPrc;\n        var altitudeItemLength = altitudeControlLength / (AltitudeItemCount - 1);\n        AltitudeItems = new AvaloniaList<OldAttitudeIndicator.ScaleItem>(\n            Enumerable\n                .Range(0, AltitudeItemCount)\n                .Select(_ => new OldAttitudeIndicator.ScaleItem(\n                    0,\n                    AltitudeValueRange,\n                    _,\n                    AltitudeItemCount,\n                    altitudeControlLength + altitudeItemLength,\n                    altitudeControlLength\n                ))\n        );\n\n        var headingControlLength = smallerSide * HeadingControlLengthPrc;\n        var headingItemLength = headingControlLength / (HeadingItemCount - 1);\n        HeadingItems = new AvaloniaList<OldAttitudeIndicator.ScaleItem>(\n            Enumerable\n                .Range(0, HeadingItemCount)\n                .Select(_ => new HeadingScaleItem(\n                    0,\n                    HeadingValueRange,\n                    _,\n                    HeadingItemCount,\n                    headingControlLength + headingItemLength,\n                    headingControlLength\n                ))\n        );\n\n        var headingItemStep =\n            (headingControlLength + headingItemLength)\n            / (HeadingItemCount % 2 != 0 ? HeadingItemCount - 1 : HeadingItemCount);\n        _headingPositionStep = -1 * headingItemStep / HeadingValueRange;\n        _headingCenterPosition = headingControlLength / 2;\n    }\n\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n        if (change.Property == AttitudeIndicator.VibrationXProperty)\n        {\n            UpdateColorX(change.Sender);\n        }\n        else if (change.Property == AttitudeIndicator.VibrationYProperty)\n        {\n            UpdateColorY(change.Sender);\n        }\n        else if (change.Property == AttitudeIndicator.VibrationZProperty)\n        {\n            UpdateColorZ(change.Sender);\n        }\n        else if (change.Property == AttitudeIndicator.VelocityProperty)\n        {\n            UpdateVelocityItems(change.Sender);\n        }\n        else if (change.Property == AttitudeIndicator.RollAngleProperty)\n        {\n            UpdateRollAngle(change.Sender);\n        }\n        else if (change.Property == AttitudeIndicator.PitchAngleProperty)\n        {\n            UpdateAngle(change.Sender);\n        }\n        else if (change.Property == AttitudeIndicator.AltitudeProperty)\n        {\n            UpdateAltitudeItems(change.Sender);\n        }\n        else if (change.Property == AttitudeIndicator.HeadingProperty)\n        {\n            UpdateHeadingItems(change.Sender);\n        }\n        else if (change.Property == AttitudeIndicator.HomeAzimuthProperty)\n        {\n            UpdateHomeAzimuthPosition(change.Sender);\n        }\n    }\n\n    private static void UpdateColorX(AvaloniaObject source)\n    {\n        if (source is not AttitudeIndicator indicator)\n        {\n            return;\n        }\n\n        if (indicator.VibrationX < 30)\n        {\n            indicator.BrushVibrationX = Colors.Red;\n        }\n        else if (indicator.VibrationX is > 30 and < 60)\n        {\n            indicator.BrushVibrationX = Colors.Yellow;\n        }\n        else if (indicator.VibrationX > 60)\n        {\n            indicator.BrushVibrationX = Colors.GreenYellow;\n        }\n    }\n\n    private static void UpdateColorY(AvaloniaObject source)\n    {\n        if (source is not AttitudeIndicator indicator)\n        {\n            return;\n        }\n\n        if (indicator.VibrationY < 30)\n        {\n            indicator.BrushVibrationY = Colors.Red;\n        }\n        else if (indicator.VibrationY > 30 & indicator.VibrationY < 60)\n        {\n            indicator.BrushVibrationY = Colors.Yellow;\n        }\n        else if (indicator.VibrationY > 60)\n        {\n            indicator.BrushVibrationY = Colors.GreenYellow;\n        }\n    }\n\n    private static void UpdateColorZ(AvaloniaObject source)\n    {\n        if (source is not AttitudeIndicator indicator)\n        {\n            return;\n        }\n\n        if (indicator.VibrationZ < 30)\n        {\n            indicator.BrushVibrationZ = Colors.Red;\n        }\n        else if (indicator.VibrationZ > 30 & indicator.VibrationZ < 60)\n        {\n            indicator.BrushVibrationZ = Colors.Yellow;\n        }\n        else if (indicator.VibrationZ > 60)\n        {\n            indicator.BrushVibrationZ = Colors.GreenYellow;\n        }\n    }\n\n    private static void UpdateAngle(AvaloniaObject source)\n    {\n        if (source is not AttitudeIndicator indicator)\n        {\n            return;\n        }\n\n        var pitch = indicator.PitchAngle;\n        UpdateRollAngle(source);\n        foreach (var item in indicator.PitchItems)\n        {\n            item.UpdateVisibility(pitch);\n        }\n    }\n\n    private static void UpdateRollAngle(AvaloniaObject source)\n    {\n        if (source is not AttitudeIndicator indicator)\n        {\n            return;\n        }\n\n        var roll = indicator.RollAngle;\n        var pitch = indicator.PitchAngle;\n        indicator.PitchTranslateX =\n            -pitch * indicator.Scale * Math.Cos((roll - 90.0) * Math.PI / 180.0);\n        indicator.PitchTranslateY =\n            pitch * indicator.Scale * Math.Sin((90 - roll) * Math.PI / 180.0);\n    }\n\n    private static void UpdateVelocityItems(AvaloniaObject source)\n    {\n        if (source is not AttitudeIndicator indicator)\n        {\n            return;\n        }\n\n        foreach (var item in indicator.VelocityItems)\n        {\n            item.UpdateValue(indicator.Velocity);\n        }\n    }\n\n    private static void UpdateAltitudeItems(AvaloniaObject source)\n    {\n        if (source is not AttitudeIndicator indicator)\n        {\n            return;\n        }\n\n        foreach (var item in indicator.AltitudeItems)\n        {\n            item.UpdateValue(indicator.Altitude);\n        }\n    }\n\n    private static void UpdateHeadingItems(AvaloniaObject source)\n    {\n        if (source is not AttitudeIndicator indicator)\n        {\n            return;\n        }\n\n        foreach (var item in indicator.HeadingItems)\n        {\n            item.UpdateValue(indicator.Heading);\n        }\n\n        indicator.HomeAzimuthPosition = GetHomeAzimuthPosition(\n            indicator.HomeAzimuth,\n            indicator.Heading\n        );\n    }\n\n    private static void UpdateHomeAzimuthPosition(AvaloniaObject source)\n    {\n        if (source is not AttitudeIndicator indicator)\n        {\n            return;\n        }\n\n        foreach (var item in indicator.HeadingItems)\n        {\n            item.UpdateValue(indicator.Heading);\n        }\n\n        indicator.HomeAzimuthPosition = GetHomeAzimuthPosition(\n            indicator.HomeAzimuth,\n            indicator.Heading\n        );\n    }\n\n    private static double GetHomeAzimuthPosition(double? value, double headingValue)\n    {\n        if (value == null)\n        {\n            return -100;\n        }\n\n        var distance = (headingValue - value.Value) % 360;\n        if (distance < -180)\n        {\n            distance += 360;\n        }\n        else if (distance > 179)\n        {\n            distance -= 360;\n        }\n\n        return _headingCenterPosition + (distance * _headingPositionStep);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/OldAttitudeIndicator/AttitudeIndicator.properties.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing Avalonia;\nusing Avalonia.Media;\n\nnamespace Asv.Drones.OldAttitudeIndicator;\n\npublic partial class AttitudeIndicator\n{\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        Color\n    > BrushVibrationXProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        Color\n    >(nameof(BrushVibrationX), o => o.BrushVibrationX, (o, v) => o.BrushVibrationX = v);\n\n    public Color BrushVibrationX\n    {\n        get;\n        set => SetAndRaise(BrushVibrationXProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        Color\n    > BrushVibrationYProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        Color\n    >(nameof(BrushVibrationY), o => o.BrushVibrationY, (o, v) => o.BrushVibrationY = v);\n\n    public Color BrushVibrationY\n    {\n        get;\n        set => SetAndRaise(BrushVibrationYProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        Color\n    > BrushVibrationZProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        Color\n    >(nameof(BrushVibrationZ), o => o.BrushVibrationZ, (o, v) => o.BrushVibrationZ = v);\n\n    public Color BrushVibrationZ\n    {\n        get;\n        set => SetAndRaise(BrushVibrationZProperty, ref field, value);\n    }\n\n    public static readonly StyledProperty<float> VibrationXProperty = AvaloniaProperty.Register<\n        OldAttitudeIndicator.AttitudeIndicator,\n        float\n    >(nameof(VibrationX), defaultValue: -1);\n\n    public float VibrationX\n    {\n        get => GetValue(VibrationXProperty);\n        set => SetValue(VibrationXProperty, value);\n    }\n\n    public static readonly StyledProperty<float> VibrationYProperty = AvaloniaProperty.Register<\n        OldAttitudeIndicator.AttitudeIndicator,\n        float\n    >(nameof(VibrationY), defaultValue: -1);\n\n    public float VibrationY\n    {\n        get => GetValue(VibrationYProperty);\n        set => SetValue(VibrationYProperty, value);\n    }\n\n    public static readonly StyledProperty<float> VibrationZProperty = AvaloniaProperty.Register<\n        OldAttitudeIndicator.AttitudeIndicator,\n        float\n    >(nameof(VibrationZ), defaultValue: -1);\n\n    public float VibrationZ\n    {\n        get => GetValue(VibrationZProperty);\n        set => SetValue(VibrationZProperty, value);\n    }\n\n    public static readonly StyledProperty<uint> Clipping0Property = AvaloniaProperty.Register<\n        OldAttitudeIndicator.AttitudeIndicator,\n        uint\n    >(nameof(Clipping0));\n\n    public uint Clipping0\n    {\n        get => GetValue(Clipping0Property);\n        set => SetValue(Clipping0Property, value);\n    }\n\n    public static readonly StyledProperty<uint> Clipping1Property = AvaloniaProperty.Register<\n        OldAttitudeIndicator.AttitudeIndicator,\n        uint\n    >(nameof(Clipping1));\n\n    public uint Clipping1\n    {\n        get => GetValue(Clipping1Property);\n        set => SetValue(Clipping1Property, value);\n    }\n\n    public static readonly StyledProperty<uint> Clipping2Property = AvaloniaProperty.Register<\n        OldAttitudeIndicator.AttitudeIndicator,\n        uint\n    >(nameof(Clipping2));\n\n    public uint Clipping2\n    {\n        get => GetValue(Clipping2Property);\n        set => SetValue(Clipping2Property, value);\n    }\n\n    public static readonly StyledProperty<double> RollAngleProperty = AvaloniaProperty.Register<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    >(nameof(RollAngle));\n\n    public double RollAngle\n    {\n        get => GetValue(RollAngleProperty);\n        set => SetValue(RollAngleProperty, value);\n    }\n\n    public static readonly StyledProperty<double> PitchAngleProperty = AvaloniaProperty.Register<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    >(nameof(PitchAngle));\n\n    public double PitchAngle\n    {\n        get => GetValue(PitchAngleProperty);\n        set => SetValue(PitchAngleProperty, value);\n    }\n\n    public static readonly StyledProperty<double> VelocityProperty = AvaloniaProperty.Register<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    >(nameof(Velocity));\n\n    public double Velocity\n    {\n        get => GetValue(VelocityProperty);\n        set => SetValue(VelocityProperty, value);\n    }\n\n    public static readonly StyledProperty<double> AltitudeProperty = AvaloniaProperty.Register<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    >(nameof(Altitude));\n\n    public double Altitude\n    {\n        get => GetValue(AltitudeProperty);\n        set => SetValue(AltitudeProperty, value);\n    }\n\n    public static readonly StyledProperty<double> HeadingProperty = AvaloniaProperty.Register<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    >(nameof(Heading));\n\n    public double Heading\n    {\n        get => GetValue(HeadingProperty);\n        set => SetValue(HeadingProperty, value);\n    }\n\n    public static readonly StyledProperty<double> HomeAzimuthProperty = AvaloniaProperty.Register<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    >(nameof(HomeAzimuth));\n\n    public double HomeAzimuth\n    {\n        get => GetValue(HomeAzimuthProperty);\n        set => SetValue(HomeAzimuthProperty, value);\n    }\n\n    public static readonly StyledProperty<bool> IsArmedProperty = AvaloniaProperty.Register<\n        OldAttitudeIndicator.AttitudeIndicator,\n        bool\n    >(nameof(IsArmed));\n\n    public bool IsArmed\n    {\n        get => GetValue(IsArmedProperty);\n        set => SetValue(IsArmedProperty, value);\n    }\n\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        string\n    > StatusTextProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        string\n    >(nameof(StatusText), _ => _.StatusText, (_, value) => _.StatusText = value);\n\n    public string StatusText\n    {\n        get;\n        set => SetAndRaise(StatusTextProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        string\n    > RightStatusTextProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        string\n    >(nameof(RightStatusText), _ => _.RightStatusText, (_, value) => _.RightStatusText = value);\n\n    public string RightStatusText\n    {\n        get;\n        set => SetAndRaise(RightStatusTextProperty, ref field, value);\n    }\n\n    public static readonly StyledProperty<TimeSpan> ArmedTimeProperty = AvaloniaProperty.Register<\n        OldAttitudeIndicator.AttitudeIndicator,\n        TimeSpan\n    >(nameof(ArmedTime));\n\n    public TimeSpan ArmedTime\n    {\n        get => GetValue(ArmedTimeProperty);\n        set => SetValue(ArmedTimeProperty, value);\n    }\n\n    #region Internal direct property\n\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    > InternalWidthProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    >(nameof(InternalWidth), _ => _.InternalWidth, (_, value) => _.InternalWidth = value);\n\n    public double InternalWidth\n    {\n        get;\n        set => SetAndRaise(InternalWidthProperty, ref field, value);\n    } = 1000;\n\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    > InternalHeightProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    >(nameof(InternalHeight), _ => _.InternalHeight, (_, value) => _.InternalHeight = value);\n\n    public double InternalHeight\n    {\n        get;\n        set => SetAndRaise(InternalHeightProperty, ref field, value);\n    } = 1000;\n\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    > PitchTranslateXProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    >(nameof(PitchTranslateX), _ => _.PitchTranslateX, (_, value) => _.PitchTranslateX = value);\n\n    private double PitchTranslateX\n    {\n        get;\n        set => SetAndRaise(PitchTranslateXProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    > PitchTranslateYProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    >(nameof(PitchTranslateY), _ => _.PitchTranslateY, (_, value) => _.PitchTranslateY = value);\n\n    public double PitchTranslateY\n    {\n        get;\n        set => SetAndRaise(PitchTranslateYProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        IEnumerable<OldAttitudeIndicator.RollItem>\n    > RollItemsProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        IEnumerable<OldAttitudeIndicator.RollItem>\n    >(nameof(RollItems), _ => _.RollItems, (_, value) => _.RollItems = value);\n\n    public IEnumerable<OldAttitudeIndicator.RollItem> RollItems\n    {\n        get;\n        set => SetAndRaise(RollItemsProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        IEnumerable<OldAttitudeIndicator.PitchItem>\n    > PitchItemsProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        IEnumerable<OldAttitudeIndicator.PitchItem>\n    >(nameof(PitchItems), _ => _.PitchItems, (_, value) => _.PitchItems = value);\n\n    public IEnumerable<OldAttitudeIndicator.PitchItem> PitchItems\n    {\n        get;\n        set => SetAndRaise(PitchItemsProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        IEnumerable<OldAttitudeIndicator.ScaleItem>\n    > VelocityItemsProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        IEnumerable<OldAttitudeIndicator.ScaleItem>\n    >(nameof(VelocityItems), _ => _.VelocityItems, (_, value) => _.VelocityItems = value);\n\n    public IEnumerable<OldAttitudeIndicator.ScaleItem> VelocityItems\n    {\n        get;\n        set => SetAndRaise(VelocityItemsProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        IEnumerable<OldAttitudeIndicator.ScaleItem>\n    > AltitudeItemsProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        IEnumerable<OldAttitudeIndicator.ScaleItem>\n    >(nameof(AltitudeItems), _ => _.AltitudeItems, (_, value) => _.AltitudeItems = value);\n\n    public IEnumerable<OldAttitudeIndicator.ScaleItem> AltitudeItems\n    {\n        get;\n        set => SetAndRaise(AltitudeItemsProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        IEnumerable<OldAttitudeIndicator.ScaleItem>\n    > HeadingItemsProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        IEnumerable<OldAttitudeIndicator.ScaleItem>\n    >(nameof(HeadingItems), _ => _.HeadingItems, (_, value) => _.HeadingItems = value);\n\n    public IEnumerable<OldAttitudeIndicator.ScaleItem> HeadingItems\n    {\n        get;\n        set => SetAndRaise(HeadingItemsProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    > HomeAzimuthPositionProperty = AvaloniaProperty.RegisterDirect<\n        OldAttitudeIndicator.AttitudeIndicator,\n        double\n    >(\n        nameof(HomeAzimuthPosition),\n        _ => _.HomeAzimuthPosition,\n        (_, value) => _.HomeAzimuthPosition = value\n    );\n\n    public double HomeAzimuthPosition\n    {\n        get;\n        set => SetAndRaise(HomeAzimuthPositionProperty, ref field, value);\n    } = -100;\n\n    #endregion\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/OldAttitudeIndicator/AttitudeIndicatorStyles.axaml",
    "content": "﻿<Styles\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    xmlns:oldAttitudeIndicator=\"clr-namespace:Asv.Drones.OldAttitudeIndicator\"\n>\n    <Design.PreviewWith>\n        <oldAttitudeIndicator:AttitudeIndicator\n            Width=\"500\"\n            Height=\"500\"\n            RollAngle=\"0\"\n            PitchAngle=\"0\"\n            Velocity=\"13\"\n            Altitude=\"160\"\n            Heading=\"57\"\n            HomeAzimuth=\"37\"\n            VibrationX=\"0.6150423\"\n            VibrationY=\"0.4214542\"\n            CornerRadius=\"55\"\n            VibrationZ=\"0.234787\"\n        />\n    </Design.PreviewWith>\n\n    <Style Selector=\"oldAttitudeIndicator|AttitudeIndicator\">\n        <Setter Property=\"VerticalAlignment\" Value=\"Center\" />\n        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <Viewbox>\n                    <Border CornerRadius=\"{TemplateBinding CornerRadius}\">\n                        <Canvas\n                            x:Name=\"Canvas\"\n                            Width=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=InternalWidth}\"\n                            Height=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=InternalHeight}\"\n                            ClipToBounds=\"True\"\n                            Background=\"Transparent\"\n                        >\n                            <Rectangle\n                                x:Name=\"Sky\"\n                                Canvas.Top=\"-920\"\n                                Canvas.Left=\"-210\"\n                                Width=\"1420\"\n                                Height=\"1420\"\n                            >\n                                <Rectangle.Fill>\n                                    <LinearGradientBrush EndPoint=\"0,0\" StartPoint=\"0,1420\">\n                                        <GradientStop Color=\"#64b5f6\" Offset=\"0\" />\n                                        <GradientStop Color=\"#1565c0\" Offset=\"0.3\" />\n                                    </LinearGradientBrush>\n                                </Rectangle.Fill>\n                                <Rectangle.RenderTransform>\n                                    <TransformGroup>\n                                        <ScaleTransform />\n                                        <SkewTransform />\n                                        <RotateTransform\n                                            Angle=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RollAngle}\"\n                                            CenterX=\"0\"\n                                            CenterY=\"710\"\n                                        />\n                                        <TranslateTransform\n                                            X=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PitchTranslateX}\"\n                                            Y=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PitchTranslateY}\"\n                                        />\n                                    </TransformGroup>\n                                </Rectangle.RenderTransform>\n                            </Rectangle>\n                            <Rectangle\n                                Canvas.Top=\"500\"\n                                Canvas.Left=\"-210\"\n                                Width=\"2000\"\n                                Height=\"2000\"\n                            >\n                                <Rectangle.Fill>\n                                    <LinearGradientBrush\n                                        StartPoint=\"50%,0%\"\n                                        EndPoint=\"50%,100%\"\n                                        Opacity=\"0.2\"\n                                    >\n                                        <LinearGradientBrush.GradientStops>\n                                            <GradientStop Offset=\"0\" Color=\"#4679ba\" />\n                                            <GradientStop Offset=\"0.5\" Color=\"#a2acc5\" />\n                                            <GradientStop Offset=\"0.7\" Color=\"#081b5d\" />\n                                            <GradientStop Offset=\"1\" Color=\"#2e4469\" />\n                                        </LinearGradientBrush.GradientStops>\n                                    </LinearGradientBrush>\n                                </Rectangle.Fill>\n                                <Rectangle.RenderTransform>\n                                    <TransformGroup>\n                                        <ScaleTransform />\n                                        <SkewTransform />\n                                        <RotateTransform\n                                            Angle=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RollAngle}\"\n                                            CenterX=\"0\"\n                                            CenterY=\"-710\"\n                                        />\n                                        <TranslateTransform\n                                            X=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PitchTranslateX}\"\n                                            Y=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PitchTranslateY}\"\n                                        />\n                                    </TransformGroup>\n                                </Rectangle.RenderTransform>\n                            </Rectangle>\n\n                            <Path Width=\"500\" Height=\"500\" Stroke=\"#e0e0e0\" StrokeThickness=\"5\">\n                                <Path.RenderTransform>\n                                    <TransformGroup>\n                                        <ScaleTransform />\n                                        <SkewTransform />\n                                        <RotateTransform\n                                            Angle=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RollAngle}\"\n                                            CenterX=\"250\"\n                                            CenterY=\"250\"\n                                        />\n                                    </TransformGroup>\n                                </Path.RenderTransform>\n                                <Path.Data>\n                                    <PathGeometry>\n                                        <PathFigure StartPoint=\"240,350\" IsClosed=\"False\">\n                                            <ArcSegment\n                                                Size=\"300,300\"\n                                                IsLargeArc=\"False\"\n                                                SweepDirection=\"Clockwise\"\n                                                Point=\"760,350\"\n                                            />\n                                        </PathFigure>\n                                    </PathGeometry>\n                                </Path.Data>\n                            </Path>\n\n                            <ItemsControl\n                                x:Name=\"Ticks\"\n                                Canvas.Left=\"150\"\n                                Canvas.Top=\"150\"\n                                Width=\"700\"\n                                Height=\"700\"\n                                ItemsSource=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RollItems}\"\n                                HorizontalAlignment=\"Center\"\n                                VerticalAlignment=\"Center\"\n                            >\n                                <ItemsControl.RenderTransform>\n                                    <TransformGroup>\n                                        <RotateTransform\n                                            Angle=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RollAngle}\"\n                                            CenterX=\"0\"\n                                            CenterY=\"0\"\n                                        />\n                                    </TransformGroup>\n                                </ItemsControl.RenderTransform>\n                                <ItemsControl.ItemsPanel>\n                                    <ItemsPanelTemplate>\n                                        <Grid />\n                                    </ItemsPanelTemplate>\n                                </ItemsControl.ItemsPanel>\n                                <ItemsControl.ItemTemplate>\n                                    <DataTemplate>\n                                        <StackPanel\n                                            x:DataType=\"oldAttitudeIndicator:RollItem\"\n                                            Orientation=\"Vertical\"\n                                        >\n                                            <StackPanel.RenderTransform>\n                                                <TransformGroup>\n                                                    <RotateTransform\n                                                        CenterX=\"0\"\n                                                        CenterY=\"0\"\n                                                        Angle=\"{CompiledBinding Value}\"\n                                                    />\n                                                </TransformGroup>\n                                            </StackPanel.RenderTransform>\n                                            <TextBlock\n                                                Text=\"{CompiledBinding Title}\"\n                                                TextAlignment=\"Center\"\n                                                Foreground=\"#e0e0e0\"\n                                                Margin=\"0,-5\"\n                                                Width=\"100\"\n                                                FontSize=\"37\"\n                                            />\n                                            <Line\n                                                Stroke=\"#e0e0e0\"\n                                                StartPoint=\"350,0\"\n                                                EndPoint=\"350,10\"\n                                                Margin=\"0,0,0,0\"\n                                                StrokeThickness=\"5\"\n                                            ></Line>\n                                        </StackPanel>\n                                    </DataTemplate>\n                                </ItemsControl.ItemTemplate>\n                            </ItemsControl>\n\n                            <ItemsControl\n                                x:Name=\"Ticks2\"\n                                Canvas.Left=\"300\"\n                                Canvas.Top=\"-920\"\n                                Width=\"400\"\n                                Height=\"2840\"\n                                ItemsSource=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PitchItems}\"\n                                HorizontalAlignment=\"Center\"\n                                VerticalAlignment=\"Center\"\n                            >\n                                <ItemsControl.RenderTransform>\n                                    <TransformGroup>\n                                        <RotateTransform\n                                            Angle=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RollAngle}\"\n                                            CenterX=\"0\"\n                                            CenterY=\"0\"\n                                        />\n                                        <TranslateTransform\n                                            X=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PitchTranslateX}\"\n                                            Y=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PitchTranslateY}\"\n                                        />\n                                    </TransformGroup>\n                                </ItemsControl.RenderTransform>\n                                <ItemsControl.ItemsPanel>\n                                    <ItemsPanelTemplate>\n                                        <Grid />\n                                    </ItemsPanelTemplate>\n                                </ItemsControl.ItemsPanel>\n                                <ItemsControl.ItemTemplate>\n                                    <DataTemplate>\n                                        <StackPanel\n                                            x:DataType=\"oldAttitudeIndicator:PitchItem\"\n                                            Orientation=\"Horizontal\"\n                                            HorizontalAlignment=\"Center\"\n                                            IsVisible=\"{CompiledBinding IsVisible}\"\n                                        >\n                                            <StackPanel.RenderTransform>\n                                                <TransformGroup>\n                                                    <TranslateTransform\n                                                        X=\"0\"\n                                                        Y=\"{CompiledBinding Value}\"\n                                                    />\n                                                </TransformGroup>\n                                            </StackPanel.RenderTransform>\n                                            <TextBlock\n                                                Margin=\"0,-25,10,0\"\n                                                Foreground=\"#e0e0e0\"\n                                                Text=\"{CompiledBinding Title}\"\n                                                TextAlignment=\"Right\"\n                                                Width=\"90\"\n                                                FontSize=\"35\"\n                                            />\n                                            <Line\n                                                Stroke=\"#e0e0e0\"\n                                                Width=\"300\"\n                                                HorizontalAlignment=\"Center\"\n                                                StartPoint=\"{Binding StartLine}\"\n                                                EndPoint=\"{CompiledBinding StopLine}\"\n                                                StrokeThickness=\"3\"\n                                            />\n                                        </StackPanel>\n                                    </DataTemplate>\n                                </ItemsControl.ItemTemplate>\n                            </ItemsControl>\n                            <Path\n                                Canvas.Left=\"480\"\n                                Canvas.Top=\"200\"\n                                Width=\"40\"\n                                Height=\"20\"\n                                Stretch=\"Fill\"\n                                Fill=\"#e53935\"\n                                StrokeThickness=\"0.5\"\n                            >\n                                <Path.Data>\n                                    <PathGeometry>\n                                        <PathFigure StartPoint=\"0,1\" IsClosed=\"True\">\n                                            <LineSegment Point=\"1,0\" />\n                                            <LineSegment Point=\"2,1\" />\n                                        </PathFigure>\n                                    </PathGeometry>\n                                </Path.Data>\n                            </Path>\n\n                            <Line\n                                StartPoint=\"150,500\"\n                                EndPoint=\"250,500\"\n                                Stroke=\"#e53935\"\n                                StrokeThickness=\"10\"\n                            />\n                            <Line\n                                StartPoint=\"750,500\"\n                                EndPoint=\"850,500\"\n                                Stroke=\"#e53935\"\n                                StrokeThickness=\"10\"\n                            />\n                            <Line\n                                StartPoint=\"400,500\"\n                                EndPoint=\"600,500\"\n                                Stroke=\"#e0e0e0\"\n                                StrokeThickness=\"5\"\n                            />\n                            <Line\n                                StartPoint=\"400,540\"\n                                EndPoint=\"502,500\"\n                                Stroke=\"#e53935\"\n                                StrokeThickness=\"10\"\n                            />\n                            <Line\n                                StartPoint=\"498,500\"\n                                EndPoint=\"600,540\"\n                                Stroke=\"#e53935\"\n                                StrokeThickness=\"10\"\n                            />\n\n                            <!-- Velocity -->\n                            <Rectangle\n                                ToolTip.Tip=\"Velocity\"\n                                Canvas.Top=\"301\"\n                                Canvas.Left=\"-3\"\n                                Opacity=\"0.3\"\n                                StrokeThickness=\"0\"\n                                HorizontalAlignment=\"Stretch\"\n                                Width=\"111\"\n                                Height=\"398\"\n                            />\n                            <ItemsControl\n                                ToolTip.Tip=\"Velocity\"\n                                x:Name=\"Velocity\"\n                                Canvas.Left=\"0\"\n                                Canvas.Top=\"300\"\n                                Width=\"110\"\n                                Height=\"400\"\n                                HorizontalAlignment=\"Center\"\n                                VerticalAlignment=\"Center\"\n                                ItemsSource=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=VelocityItems}\"\n                            >\n                                <ItemsControl.ItemsPanel>\n                                    <ItemsPanelTemplate>\n                                        <Canvas\n                                            Width=\"110\"\n                                            Height=\"400\"\n                                            ClipToBounds=\"True\"\n                                            Background=\"Transparent\"\n                                        />\n                                    </ItemsPanelTemplate>\n                                </ItemsControl.ItemsPanel>\n                                <ItemsControl.Styles>\n                                    <Style Selector=\"ContentPresenter\">\n                                        <Setter\n                                            Property=\"Canvas.Top\"\n                                            Value=\"{ReflectionBinding Position}\"\n                                        />\n                                    </Style>\n                                </ItemsControl.Styles>\n                                <ItemsControl.ItemTemplate>\n                                    <DataTemplate>\n                                        <Grid\n                                            x:DataType=\"oldAttitudeIndicator:ScaleItem\"\n                                            ColumnDefinitions=\"90,20\"\n                                            IsVisible=\"{CompiledBinding IsVisible}\"\n                                            Width=\"110\"\n                                            HorizontalAlignment=\"Stretch\"\n                                        >\n                                            <TextBlock\n                                                Grid.Column=\"0\"\n                                                Foreground=\"White\"\n                                                Text=\"{CompiledBinding Title}\"\n                                                Margin=\"0,-25,0,0\"\n                                                TextAlignment=\"Right\"\n                                                Width=\"85\"\n                                                FontSize=\"35\"\n                                            />\n                                            <Line\n                                                Grid.Column=\"1\"\n                                                StartPoint=\"0,0\"\n                                                EndPoint=\"20,0\"\n                                                Stroke=\"LightGreen\"\n                                                StrokeThickness=\"5\"\n                                            />\n                                        </Grid>\n                                    </DataTemplate>\n                                </ItemsControl.ItemTemplate>\n                            </ItemsControl>\n                            <Border\n                                Canvas.Top=\"450\"\n                                Canvas.Left=\"-1\"\n                                Width=\"110\"\n                                Height=\"90\"\n                                BorderBrush=\"White\"\n                                BorderThickness=\"3\"\n                                CornerRadius=\"5\"\n                                Background=\"Black\"\n                            >\n                                <Path\n                                    ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Velocity}\"\n                                    HorizontalAlignment=\"Left\"\n                                    Width=\"20\"\n                                    Height=\"30\"\n                                    Stretch=\"Fill\"\n                                    Fill=\"White\"\n                                    Stroke=\"White\"\n                                    StrokeThickness=\"3\"\n                                    Opacity=\"0.7\"\n                                >\n                                    <Path.Data>\n                                        <PathGeometry>\n                                            <PathFigure StartPoint=\"0,0\">\n                                                <LineSegment Point=\"1,1\" />\n                                                <LineSegment Point=\"0,2\" />\n                                            </PathFigure>\n                                        </PathGeometry>\n                                    </Path.Data>\n                                </Path>\n                            </Border>\n\n                            <TextBlock\n                                ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Velocity}\"\n                                Canvas.Left=\"0\"\n                                Canvas.Top=\"470\"\n                                Margin=\"0,0,0,0\"\n                                Foreground=\"White\"\n                                Text=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Velocity}\"\n                                TextAlignment=\"Right\"\n                                FontSize=\"40\"\n                                Width=\"87\"\n                                FontWeight=\"DemiBold\"\n                            />\n                            <Border\n                                ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Velocity}\"\n                                Canvas.Top=\"300\"\n                                Canvas.Left=\"-3\"\n                                BorderThickness=\"5\"\n                                CornerRadius=\"3\"\n                                Width=\"113\"\n                                Height=\"400\"\n                            />\n\n                            <!-- Altitude -->\n                            <Rectangle\n                                ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Altitude}\"\n                                Canvas.Top=\"301\"\n                                Canvas.Right=\"-3\"\n                                Opacity=\"0.3\"\n                                StrokeThickness=\"0\"\n                                HorizontalAlignment=\"Stretch\"\n                                Width=\"111\"\n                                Height=\"398\"\n                            />\n                            <ItemsControl\n                                ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Altitude}\"\n                                x:Name=\"Altitude\"\n                                Canvas.Right=\"0\"\n                                Canvas.Top=\"300\"\n                                Width=\"110\"\n                                Height=\"400\"\n                                HorizontalAlignment=\"Center\"\n                                VerticalAlignment=\"Center\"\n                                ItemsSource=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=AltitudeItems}\"\n                            >\n                                <ItemsControl.ItemsPanel>\n                                    <ItemsPanelTemplate>\n                                        <Canvas\n                                            Width=\"110\"\n                                            Height=\"400\"\n                                            HorizontalAlignment=\"Center\"\n                                            VerticalAlignment=\"Center\"\n                                        />\n                                    </ItemsPanelTemplate>\n                                </ItemsControl.ItemsPanel>\n                                <ItemsControl.Styles>\n                                    <Style Selector=\"ContentPresenter\">\n                                        <Setter\n                                            Property=\"Canvas.Top\"\n                                            Value=\"{ReflectionBinding Position}\"\n                                        />\n                                    </Style>\n                                </ItemsControl.Styles>\n                                <ItemsControl.ItemTemplate>\n                                    <DataTemplate>\n                                        <Grid\n                                            x:DataType=\"oldAttitudeIndicator:ScaleItem\"\n                                            ColumnDefinitions=\"20,90\"\n                                            IsVisible=\"{CompiledBinding IsVisible}\"\n                                            Width=\"110\"\n                                            HorizontalAlignment=\"Stretch\"\n                                        >\n                                            <TextBlock\n                                                Grid.Column=\"1\"\n                                                Foreground=\"White\"\n                                                Text=\"{CompiledBinding Title}\"\n                                                Margin=\"0,-25,0,0\"\n                                                TextAlignment=\"Left\"\n                                                Width=\"85\"\n                                                FontSize=\"35\"\n                                            />\n                                            <Line\n                                                Grid.Column=\"0\"\n                                                StartPoint=\"0,0\"\n                                                EndPoint=\"20,0\"\n                                                Stroke=\"LightGreen\"\n                                                StrokeThickness=\"5\"\n                                            />\n                                        </Grid>\n                                    </DataTemplate>\n                                </ItemsControl.ItemTemplate>\n                            </ItemsControl>\n                            <Border\n                                Canvas.Top=\"450\"\n                                Canvas.Left=\"890\"\n                                Width=\"110\"\n                                Height=\"90\"\n                                BorderBrush=\"White\"\n                                BorderThickness=\"3\"\n                                CornerRadius=\"5\"\n                                Background=\"Black\"\n                            >\n                                <Path\n                                    ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Altitude}\"\n                                    HorizontalAlignment=\"Left\"\n                                    Width=\"20\"\n                                    Height=\"30\"\n                                    Stretch=\"Fill\"\n                                    Fill=\"White\"\n                                    Stroke=\"White\"\n                                    StrokeThickness=\"3\"\n                                    Opacity=\"0.7\"\n                                >\n                                    <Path.Data>\n                                        <PathGeometry>\n                                            <PathFigure StartPoint=\"0,0\">\n                                                <LineSegment Point=\"1,1\" />\n                                                <LineSegment Point=\"0,2\" />\n                                            </PathFigure>\n                                        </PathGeometry>\n                                    </Path.Data>\n                                </Path>\n                            </Border>\n                            <!-- <Polygon/> -->\n                            <TextBlock\n                                ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Altitude}\"\n                                Canvas.Right=\"-1\"\n                                Canvas.Top=\"470\"\n                                Margin=\"0,0,0,0\"\n                                Foreground=\"White\"\n                                FontWeight=\"DemiBold\"\n                                Text=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Altitude}\"\n                                VerticalAlignment=\"Center\"\n                                TextAlignment=\"Left\"\n                                FontSize=\"40\"\n                                Width=\"87\"\n                            />\n                            <Border\n                                ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Altitude}\"\n                                Canvas.Right=\"-3\"\n                                Canvas.Top=\"300\"\n                                BorderThickness=\"5\"\n                                CornerRadius=\"3\"\n                                Width=\"113\"\n                                Height=\"400\"\n                            />\n\n                            <!-- Сompass -->\n                            <Rectangle\n                                ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Compass}\"\n                                Canvas.Left=\"-3\"\n                                Canvas.Top=\"-3\"\n                                Width=\"1006\"\n                                Height=\"93\"\n                                Fill=\"White\"\n                                Opacity=\"0.3\"\n                                Stroke=\"Black\"\n                                StrokeThickness=\"3\"\n                                HorizontalAlignment=\"Stretch\"\n                            />\n                            <Line\n                                ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Compass}\"\n                                StartPoint=\"0,80\"\n                                EndPoint=\"1000,80\"\n                                Stroke=\"#e0e0e0\"\n                                StrokeThickness=\"5\"\n                            />\n                            <ItemsControl\n                                x:Name=\"Heading\"\n                                Canvas.Left=\"0\"\n                                Canvas.Top=\"0\"\n                                Width=\"1000\"\n                                Height=\"80\"\n                                ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Compass}\"\n                                HorizontalAlignment=\"Center\"\n                                VerticalAlignment=\"Center\"\n                                ItemsSource=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=HeadingItems}\"\n                            >\n                                <ItemsControl.ItemsPanel>\n                                    <ItemsPanelTemplate>\n                                        <Canvas\n                                            Width=\"1000\"\n                                            Height=\"80\"\n                                            ClipToBounds=\"True\"\n                                            Background=\"Transparent\"\n                                        />\n                                    </ItemsPanelTemplate>\n                                </ItemsControl.ItemsPanel>\n                                <ItemsControl.Styles>\n                                    <Style Selector=\"ContentPresenter\">\n                                        <Setter\n                                            Property=\"Canvas.Left\"\n                                            Value=\"{ReflectionBinding Position}\"\n                                        />\n                                    </Style>\n                                </ItemsControl.Styles>\n                                <ItemsControl.ItemTemplate>\n                                    <DataTemplate>\n                                        <Grid\n                                            x:DataType=\"oldAttitudeIndicator:HeadingScaleItem\"\n                                            RowDefinitions=\"40,20\"\n                                            IsVisible=\"{CompiledBinding IsVisible}\"\n                                            HorizontalAlignment=\"Stretch\"\n                                            Margin=\"-40,15,0,0\"\n                                        >\n                                            <TextBlock\n                                                Grid.Row=\"0\"\n                                                Foreground=\"White\"\n                                                Text=\"{CompiledBinding Title}\"\n                                                HorizontalAlignment=\"Center\"\n                                                TextAlignment=\"Center\"\n                                                Width=\"80\"\n                                                FontSize=\"35\"\n                                            />\n                                            <Line\n                                                Grid.Row=\"1\"\n                                                StartPoint=\"40,25\"\n                                                EndPoint=\"40,5\"\n                                                Stroke=\"#e0e0e0\"\n                                                StrokeThickness=\"5\"\n                                            />\n                                        </Grid>\n                                    </DataTemplate>\n                                </ItemsControl.ItemTemplate>\n                            </ItemsControl>\n                            <Rectangle\n                                ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Compass}\"\n                                Canvas.Left=\"450\"\n                                Canvas.Top=\"-3\"\n                                Width=\"100\"\n                                Height=\"80\"\n                                Fill=\"#000a12\"\n                                Opacity=\"0.7\"\n                                Stroke=\"#eceff1\"\n                                StrokeThickness=\"3\"\n                                HorizontalAlignment=\"Stretch\"\n                            />\n                            <TextBlock\n                                ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Compass}\"\n                                Canvas.Top=\"12\"\n                                Canvas.Left=\"460\"\n                                Foreground=\"White\"\n                                Text=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Heading}\"\n                                VerticalAlignment=\"Center\"\n                                TextAlignment=\"Center\"\n                                FontWeight=\"DemiBold\"\n                                FontSize=\"45\"\n                                Width=\"80\"\n                            />\n\n                            <!-- Home -->\n                            <Path\n                                Canvas.Top=\"2\"\n                                Canvas.Left=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=HomeAzimuthPosition}\"\n                                Fill=\"#e53935\"\n                                StrokeThickness=\"3\"\n                                Stroke=\"Red\"\n                            >\n                                <Path.Data>\n                                    <PathGeometry>\n                                        <PathFigure StartPoint=\"-15,0\" IsClosed=\"True\">\n                                            <LineSegment Point=\"0,15\" />\n                                            <LineSegment Point=\"15,0\" />\n                                        </PathFigure>\n                                    </PathGeometry>\n                                </Path.Data>\n                            </Path>\n\n                            <!-- Vibration -->\n                            <Border\n                                IsVisible=\"False\"\n                                ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Vibration}\"\n                                BorderBrush=\"#e0e0e0\"\n                                BorderThickness=\"5\"\n                                CornerRadius=\"3\"\n                                Canvas.Top=\"105\"\n                                Canvas.Left=\"-3\"\n                                Height=\"185\"\n                                Width=\"170\"\n                            />\n                            <Border\n                                IsVisible=\"False\"\n                                ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Vibration}\"\n                                Background=\"White\"\n                                Opacity=\"0.3\"\n                                CornerRadius=\"3\"\n                                Canvas.Top=\"105\"\n                                Canvas.Left=\"-3\"\n                                Height=\"185\"\n                                Width=\"170\"\n                            />\n                            <Grid\n                                IsVisible=\"False\"\n                                ToolTip.Tip=\"{x:Static drones:RS.AltitudeIndicatorStyles_ToolTip_Vibration}\"\n                                Canvas.Top=\"110\"\n                                Canvas.Left=\"10\"\n                                Height=\"175\"\n                                ColumnDefinitions=\"*, 10, *, 10, *\"\n                                RowDefinitions=\"40, *, 2, *, 2, *, 40\"\n                            >\n                                <TextBlock\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"0\"\n                                    FontSize=\"25\"\n                                    HorizontalAlignment=\"Center\"\n                                    Text=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Clipping0}\"\n                                />\n                                <TextBlock\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"2\"\n                                    FontSize=\"25\"\n                                    HorizontalAlignment=\"Center\"\n                                    Text=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Clipping1}\"\n                                />\n                                <TextBlock\n                                    Grid.Row=\"0\"\n                                    Grid.Column=\"4\"\n                                    FontSize=\"25\"\n                                    HorizontalAlignment=\"Center\"\n                                    Text=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Clipping2}\"\n                                />\n                                <TextBlock\n                                    Grid.Row=\"6\"\n                                    Grid.Column=\"0\"\n                                    FontSize=\"25\"\n                                    HorizontalAlignment=\"Center\"\n                                    Text=\"X\"\n                                />\n                                <TextBlock\n                                    Grid.Row=\"6\"\n                                    Grid.Column=\"2\"\n                                    FontSize=\"25\"\n                                    HorizontalAlignment=\"Center\"\n                                    Text=\"Y\"\n                                />\n                                <TextBlock\n                                    Grid.Row=\"6\"\n                                    Grid.Column=\"4\"\n                                    FontSize=\"25\"\n                                    HorizontalAlignment=\"Center\"\n                                    Text=\"Z\"\n                                />\n\n                                <Rectangle Grid.Column=\"0\" Grid.Row=\"1\" Width=\"40\" Grid.RowSpan=\"5\">\n                                    <Rectangle.Fill>\n                                        <LinearGradientBrush>\n                                            <LinearGradientBrush.Transform>\n                                                <RotateTransform Angle=\"22.5\" />\n                                            </LinearGradientBrush.Transform>\n                                            <GradientStops>\n                                                <GradientStop\n                                                    Color=\"White\"\n                                                    Offset=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=VibrationX}\"\n                                                />\n                                                <GradientStop\n                                                    Color=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BrushVibrationX}\"\n                                                    Offset=\"0\"\n                                                />\n                                            </GradientStops>\n                                        </LinearGradientBrush>\n                                    </Rectangle.Fill>\n                                </Rectangle>\n                                <Rectangle Grid.Column=\"2\" Grid.Row=\"1\" Width=\"40\" Grid.RowSpan=\"5\">\n                                    <Rectangle.Fill>\n                                        <LinearGradientBrush>\n                                            <LinearGradientBrush.Transform>\n                                                <RotateTransform Angle=\"22.5\" />\n                                            </LinearGradientBrush.Transform>\n                                            <GradientStops>\n                                                <GradientStop\n                                                    Color=\"White\"\n                                                    Offset=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=VibrationY}\"\n                                                />\n                                                <GradientStop\n                                                    Color=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BrushVibrationY}\"\n                                                    Offset=\"0\"\n                                                />\n                                            </GradientStops>\n                                        </LinearGradientBrush>\n                                    </Rectangle.Fill>\n                                </Rectangle>\n                                <Rectangle Grid.Column=\"4\" Grid.Row=\"1\" Width=\"40\" Grid.RowSpan=\"5\">\n                                    <Rectangle.Fill>\n                                        <LinearGradientBrush>\n                                            <LinearGradientBrush.Transform>\n                                                <RotateTransform Angle=\"22.5\" />\n                                            </LinearGradientBrush.Transform>\n                                            <GradientStops>\n                                                <GradientStop\n                                                    Color=\"White\"\n                                                    Offset=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=VibrationZ}\"\n                                                />\n                                                <GradientStop\n                                                    Color=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BrushVibrationZ}\"\n                                                    Offset=\"0\"\n                                                />\n                                            </GradientStops>\n                                        </LinearGradientBrush>\n                                    </Rectangle.Fill>\n                                </Rectangle>\n\n                                <Rectangle Grid.Row=\"2\" Grid.Column=\"0\" Fill=\"Red\" />\n                                <Rectangle Grid.Row=\"4\" Grid.Column=\"0\" Fill=\"Red\" />\n                                <Rectangle Grid.Row=\"2\" Grid.Column=\"2\" Fill=\"Red\" />\n                                <Rectangle Grid.Row=\"4\" Grid.Column=\"2\" Fill=\"Red\" />\n                                <Rectangle Grid.Row=\"2\" Grid.Column=\"4\" Fill=\"Red\" />\n                                <Rectangle Grid.Row=\"4\" Grid.Column=\"4\" Fill=\"Red\" />\n                            </Grid>\n\n                            <TextBlock\n                                Canvas.Left=\"0\"\n                                Canvas.Top=\"550\"\n                                Foreground=\"#e53935\"\n                                FontSize=\"100\"\n                                Text=\"{Binding RelativeSource={RelativeSource TemplatedParent}, Path=StatusText, Mode=TwoWay}\"\n                                Width=\"1000\"\n                                TextAlignment=\"Center\"\n                                HorizontalAlignment=\"Center\"\n                            />\n                        </Canvas>\n                    </Border>\n                </Viewbox>\n            </ControlTemplate>\n        </Setter>\n    </Style>\n</Styles>\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/OldAttitudeIndicator/Items/Heading/HeadingScaleItem.cs",
    "content": "using System;\n\nnamespace Asv.Drones.OldAttitudeIndicator;\n\npublic class HeadingScaleItem : ScaleItem\n{\n    public HeadingScaleItem(\n        double value,\n        double valueRange,\n        int index,\n        int itemCount,\n        double fullLength,\n        double length\n    )\n        : base(value, valueRange, index, itemCount, fullLength, length, true) { }\n\n    protected override string GetTitle(double value)\n    {\n        var v = value < 0 ? ((int)Math.Round(value) % 360) + 360 : (int)Math.Round(value) % 360;\n        return v switch\n        {\n            0 => RS.HeadingScaleItem_Direction_N,\n            45 => RS.HeadingScaleItem_Direction_NE,\n            90 => RS.HeadingScaleItem_Direction_E,\n            135 => RS.HeadingScaleItem_Direction_SE,\n            180 => RS.HeadingScaleItem_Direction_S,\n            225 => RS.HeadingScaleItem_Direction_SW,\n            270 => RS.HeadingScaleItem_Direction_W,\n            315 => RS.HeadingScaleItem_Direction_NW,\n            360 => RS.HeadingScaleItem_Direction_N,\n            _ => v.ToString(\"F0\"),\n        };\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/OldAttitudeIndicator/Items/Pitch/PitchItem.cs",
    "content": "using System;\nusing Avalonia;\n\nnamespace Asv.Drones.OldAttitudeIndicator;\n\npublic partial class PitchItem : AvaloniaObject\n{\n    private readonly int _pitch;\n\n    public PitchItem(\n        int pitch,\n        double scale,\n        bool titleIsVisible = true,\n        double controlHeight = 284\n    )\n    {\n        _pitch = pitch;\n        Value = ((controlHeight / 2) - pitch) * scale;\n        if (titleIsVisible)\n        {\n            Title = pitch.ToString();\n            StartLine = new Point(0 * scale, 0 * scale);\n            StopLine = new Point(20 * scale, 0 * scale);\n        }\n        else\n        {\n            Title = string.Empty;\n            StartLine = new Point(4 * scale, 0 * scale);\n            StopLine = new Point(16 * scale, 0 * scale);\n        }\n\n        IsVisible = Math.Abs(pitch) <= 20;\n    }\n\n    public void UpdateVisibility(double pitch)\n    {\n        IsVisible = pitch >= _pitch - 20 && pitch <= _pitch + 20;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/OldAttitudeIndicator/Items/Pitch/PitchItem.properties.cs",
    "content": "using Avalonia;\n\nnamespace Asv.Drones.OldAttitudeIndicator;\n\npublic partial class PitchItem\n{\n    public static readonly DirectProperty<OldAttitudeIndicator.PitchItem, string> TitleProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.PitchItem, string>(\n            nameof(Title),\n            _ => _.Title,\n            (_, value) => _.Title = value\n        );\n\n    public string Title\n    {\n        get;\n        set => SetAndRaise(TitleProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<OldAttitudeIndicator.PitchItem, double> ValueProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.PitchItem, double>(\n            nameof(Value),\n            _ => _.Value,\n            (_, value) => _.Value = value\n        );\n\n    public double Value\n    {\n        get;\n        set => SetAndRaise(ValueProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<OldAttitudeIndicator.PitchItem, bool> IsVisibleProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.PitchItem, bool>(\n            nameof(IsVisible),\n            _ => _.IsVisible,\n            (_, value) => _.IsVisible = value\n        );\n\n    public bool IsVisible\n    {\n        get;\n        set => SetAndRaise(IsVisibleProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<OldAttitudeIndicator.PitchItem, Point> StartLineProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.PitchItem, Point>(\n            nameof(StartLine),\n            _ => _.StartLine,\n            (_, value) => _.StartLine = value\n        );\n\n    public Point StartLine\n    {\n        get;\n        set => SetAndRaise(StartLineProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<OldAttitudeIndicator.PitchItem, Point> StopLineProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.PitchItem, Point>(\n            nameof(StopLine),\n            _ => _.StopLine,\n            (_, value) => _.StopLine = value\n        );\n\n    public Point StopLine\n    {\n        get;\n        set => SetAndRaise(StopLineProperty, ref field, value);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/OldAttitudeIndicator/Items/Roll/RollItem.cs",
    "content": "using System;\nusing Avalonia;\n\nnamespace Asv.Drones.OldAttitudeIndicator;\n\npublic partial class RollItem : AvaloniaObject\n{\n    public RollItem(int angle)\n    {\n        Value = angle;\n        Title =\n            Math.Abs(angle) > 180 ? (360 - Math.Abs(angle)).ToString() : Math.Abs(angle).ToString();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/OldAttitudeIndicator/Items/Roll/RollItem.properties.cs",
    "content": "using Avalonia;\n\nnamespace Asv.Drones.OldAttitudeIndicator;\n\npublic partial class RollItem\n{\n    public static readonly DirectProperty<OldAttitudeIndicator.RollItem, string> TitleProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.RollItem, string>(\n            nameof(Title),\n            _ => _.Title,\n            (_, value) => _.Title = value\n        );\n\n    public string Title\n    {\n        get;\n        set => SetAndRaise(TitleProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<OldAttitudeIndicator.RollItem, double> ValueProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.RollItem, double>(\n            nameof(Value),\n            _ => _.Value,\n            (_, value) => _.Value = value\n        );\n\n    public double Value\n    {\n        get;\n        set => SetAndRaise(ValueProperty, ref field, value);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/OldAttitudeIndicator/Items/Scale/ScaleItem.cs",
    "content": "using System;\nusing Avalonia;\n\nnamespace Asv.Drones.OldAttitudeIndicator;\n\npublic partial class ScaleItem : AvaloniaObject\n{\n    public ScaleItem(\n        double value,\n        double valueRange,\n        int index,\n        int itemCount,\n        double fullLength,\n        double length,\n        bool isInverse = false,\n        bool showNegative = true,\n        string? fixedTitle = null\n    )\n    {\n        _valueRange = valueRange;\n        _showNegative = showNegative;\n        _isFixedTitle = fixedTitle != null;\n        var step = fullLength / (itemCount % 2 != 0 ? itemCount - 1 : itemCount);\n        _positionStep = step / valueRange;\n\n        if (!isInverse)\n        {\n            _startPosition = ((length - fullLength) / 2.0) + (step * index);\n        }\n        else\n        {\n            _startPosition = ((length - fullLength) / 2.0) + (step * (itemCount - index));\n            _positionStep *= -1;\n        }\n\n        var centerIndex = itemCount % 2 == 0 ? itemCount / 2 : (itemCount / 2) + 1;\n\n        var indexOffset = index - centerIndex;\n        _valueOffset = -1 * valueRange * indexOffset;\n\n        if (_isFixedTitle)\n        {\n            Title = fixedTitle;\n        }\n\n        UpdateValue(value);\n    }\n\n    public void UpdateValue(double value)\n    {\n        Value = GetValue(value);\n        Position = GetPosition(value);\n        if (!_isFixedTitle)\n        {\n            Title = GetTitle(Value);\n        }\n\n        IsVisible = _showNegative || Value >= 0;\n    }\n\n    protected virtual string? GetTitle(double value)\n    {\n        return Math.Round(value).ToString(\"F0\");\n    }\n\n    private double GetValue(double value)\n    {\n        return Math.Round(value) - (Math.Round(value) % _valueRange) + _valueOffset;\n    }\n\n    private double GetPosition(double value)\n    {\n        return _startPosition + (_positionStep * (Math.Round(value) % _valueRange));\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/OldAttitudeIndicator/Items/Scale/ScaleItem.properties.cs",
    "content": "using Avalonia;\n\nnamespace Asv.Drones.OldAttitudeIndicator;\n\npublic partial class ScaleItem\n{\n    private readonly double _valueRange;\n    private readonly bool _showNegative;\n    private readonly double _startPosition;\n    private readonly double _positionStep;\n    private readonly double _valueOffset;\n    private readonly bool _isFixedTitle;\n\n    public static readonly DirectProperty<OldAttitudeIndicator.ScaleItem, bool> IsVisibleProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.ScaleItem, bool>(\n            nameof(IsVisible),\n            _ => _.IsVisible,\n            (_, value) => _.IsVisible = value\n        );\n\n    public bool IsVisible\n    {\n        get;\n        set => SetAndRaise(IsVisibleProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<OldAttitudeIndicator.ScaleItem, string?> TitleProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.ScaleItem, string?>(\n            nameof(Title),\n            _ => _.Title,\n            (_, value) => _.Title = value\n        );\n\n    public string? Title\n    {\n        get;\n        set => SetAndRaise(TitleProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<OldAttitudeIndicator.ScaleItem, double> ValueProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.ScaleItem, double>(\n            nameof(Value),\n            _ => _.Value,\n            (_, value) => _.Value = value\n        );\n\n    public double Value\n    {\n        get;\n        set => SetAndRaise(ValueProperty, ref field, value);\n    }\n\n    public static readonly DirectProperty<OldAttitudeIndicator.ScaleItem, double> PositionProperty =\n        AvaloniaProperty.RegisterDirect<OldAttitudeIndicator.ScaleItem, double>(\n            nameof(Position),\n            _ => _.Position,\n            (_, value) => _.Position = value\n        );\n\n    public double Position\n    {\n        get;\n        set => SetAndRaise(PositionProperty, ref field, value);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/RouteUavIndicator/RouteUavIndicator.cs",
    "content": "﻿using System;\nusing Asv.Avalonia;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Controls.Metadata;\n\nnamespace Asv.Drones;\n\n[PseudoClasses(ProgressDisabledPseudoclass, ProgressCompletedPseudoclass)]\npublic class RouteUavIndicator : IndicatorBase\n{\n    private double _internalBorderWidth;\n\n    public static readonly DirectProperty<RouteUavIndicator, double> InternalBorderWidthProperty =\n        AvaloniaProperty.RegisterDirect<RouteUavIndicator, double>(\n            nameof(InternalBorderWidth),\n            o => o.InternalBorderWidth,\n            (o, v) => o.InternalBorderWidth = v\n        );\n\n    public double InternalBorderWidth\n    {\n        get => _internalBorderWidth;\n        private set => SetAndRaise(InternalBorderWidthProperty, ref _internalBorderWidth, value);\n    }\n\n    private double _internalBorderLeft;\n\n    public static readonly DirectProperty<RouteUavIndicator, double> InternalBorderLeftProperty =\n        AvaloniaProperty.RegisterDirect<RouteUavIndicator, double>(\n            nameof(InternalBorderLeft),\n            o => o.InternalBorderLeft,\n            (o, v) => o.InternalBorderLeft = v\n        );\n\n    public double InternalBorderLeft\n    {\n        get => _internalBorderLeft;\n        set => SetAndRaise(InternalBorderLeftProperty, ref _internalBorderLeft, value);\n    }\n\n    public static readonly StyledProperty<double> ProgressProperty = AvaloniaProperty.Register<\n        RouteUavIndicator,\n        double\n    >(nameof(Progress));\n\n    public double Progress\n    {\n        get => GetValue(ProgressProperty);\n        set => SetValue(ProgressProperty, value);\n    }\n\n    private double _internalIndicatorLeft;\n\n    public static readonly DirectProperty<RouteUavIndicator, double> InternalIndicatorLeftProperty =\n        AvaloniaProperty.RegisterDirect<RouteUavIndicator, double>(\n            nameof(InternalIndicatorLeft),\n            o => o.InternalIndicatorLeft,\n            (o, v) => o.InternalIndicatorLeft = v\n        );\n\n    public double InternalIndicatorLeft\n    {\n        get => _internalIndicatorLeft;\n        set => SetAndRaise(InternalIndicatorLeftProperty, ref _internalIndicatorLeft, value);\n    }\n\n    private string _internalProgressText;\n\n    public static readonly DirectProperty<RouteUavIndicator, string> InternalProgressTextProperty =\n        AvaloniaProperty.RegisterDirect<RouteUavIndicator, string>(\n            nameof(InternalProgressText),\n            o => o.InternalProgressText,\n            (o, v) => o.InternalProgressText = v\n        );\n\n    public string InternalProgressText\n    {\n        get => _internalProgressText;\n        set => SetAndRaise(InternalProgressTextProperty, ref _internalProgressText, value);\n    }\n\n    public static readonly StyledProperty<string> StatusTextProperty = AvaloniaProperty.Register<\n        RouteUavIndicator,\n        string\n    >(nameof(StatusText));\n\n    public string StatusText\n    {\n        get => GetValue(StatusTextProperty);\n        set => SetValue(StatusTextProperty, value);\n    }\n\n    public static readonly StyledProperty<string> SubStatusTextProperty = AvaloniaProperty.Register<\n        RouteUavIndicator,\n        string\n    >(nameof(SubStatusText));\n\n    public string SubStatusText\n    {\n        get => GetValue(SubStatusTextProperty);\n        set => SetValue(SubStatusTextProperty, value);\n    }\n\n    protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)\n    {\n        base.OnPropertyChanged(change);\n        if (change.Property == ProgressProperty)\n        {\n            var progress = (double)change.NewValue!;\n\n            if (double.IsNaN(progress))\n            {\n                InternalProgressText = string.Empty;\n                PseudoClasses.Add(ProgressDisabledPseudoclass);\n                return;\n            }\n\n            PseudoClasses.Remove(ProgressDisabledPseudoclass);\n            PseudoClasses.Set(ProgressCompletedPseudoclass, Math.Abs(Progress - 1.0) < 0.01);\n\n            InternalProgressText = $\"{progress * 100.0:F0} %\";\n\n            if (progress <= 0.0)\n            {\n                progress = 0.0000001;\n            }\n\n            if (progress >= 1.0)\n            {\n                progress = 0.9999999;\n            }\n\n            InternalIndicatorLeft = 20 + (progress * 690.0);\n            InternalBorderLeft = progress * 690.0;\n            InternalBorderWidth = 800.0 - InternalBorderLeft;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/RouteUavIndicator/RouteUavIndicatorStyles.axaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n>\n    <Design.PreviewWith>\n        <StackPanel Spacing=\"8\">\n            <drones:RouteUavIndicator\n                Width=\"400\"\n                Progress=\"0.00001\"\n                Title=\"Zero progress\"\n                StatusText=\"6 MIN\"\n                SubStatusText=\"BEFORE COMPLETE\"\n            />\n            <drones:RouteUavIndicator\n                Width=\"400\"\n                Progress=\"0.5\"\n                Title=\"Half progress\"\n                StatusText=\"6 MIN\"\n                SubStatusText=\"BEFORE COMPLETE\"\n            />\n            <drones:RouteUavIndicator\n                Width=\"400\"\n                Progress=\"1\"\n                Title=\"Complete progress\"\n                StatusText=\"6 MIN\"\n                SubStatusText=\"BEFORE COMPLETE\"\n            />\n            <drones:RouteUavIndicator\n                Width=\"400\"\n                Progress=\"NaN\"\n                Title=\"NaN progress\"\n                StatusText=\"6 MIN\"\n                SubStatusText=\"BEFORE COMPLETE\"\n            />\n        </StackPanel>\n    </Design.PreviewWith>\n    <ControlTheme x:Key=\"{x:Type drones:RouteUavIndicator}\" TargetType=\"drones:RouteUavIndicator\">\n        <!-- Set Defaults -->\n        <Setter Property=\"BorderThickness\" Value=\"1\" />\n        <Setter Property=\"MinHeight\" Value=\"140\" />\n        <Setter Property=\"MaxHeight\" Value=\"140\" />\n        <Setter Property=\"Height\" Value=\"140\" />\n        <Setter Property=\"BorderBrush\" Value=\"{DynamicResource IndicatorBorderBrush}\" />\n        <Setter Property=\"CornerRadius\" Value=\"{DynamicResource IndicatorCornerRadius}\" />\n        <Setter Property=\"Template\">\n            <ControlTemplate>\n                <Border\n                    Padding=\"10\"\n                    BorderBrush=\"{TemplateBinding BorderBrush}\"\n                    BorderThickness=\"{TemplateBinding BorderThickness}\"\n                    CornerRadius=\"{TemplateBinding CornerRadius}\"\n                >\n                    <StackPanel Spacing=\"8\">\n                        <DockPanel>\n                            <Viewbox DockPanel.Dock=\"Left\" Width=\"20\" Height=\"20\">\n                                <Path\n                                    Fill=\"{TemplateBinding Foreground}\"\n                                    Data=\"M18,15A3,3 0 0,1 21,18A3,3 0 0,1 18,21C16.69,21 15.58,20.17 15.17,19H14V17H15.17C15.58,15.83 16.69,15 18,15M18,17A1,1 0 0,0 17,18A1,1 0 0,0 18,19A1,1 0 0,0 19,18A1,1 0 0,0 18,17M18,8A1.43,1.43 0 0,0 19.43,6.57C19.43,5.78 18.79,5.14 18,5.14C17.21,5.14 16.57,5.78 16.57,6.57A1.43,1.43 0 0,0 18,8M18,2.57A4,4 0 0,1 22,6.57C22,9.56 18,14 18,14C18,14 14,9.56 14,6.57A4,4 0 0,1 18,2.57M8.83,17H10V19H8.83C8.42,20.17 7.31,21 6,21A3,3 0 0,1 3,18C3,16.69 3.83,15.58 5,15.17V14H7V15.17C7.85,15.47 8.53,16.15 8.83,17M6,17A1,1 0 0,0 5,18A1,1 0 0,0 6,19A1,1 0 0,0 7,18A1,1 0 0,0 6,17M6,3A3,3 0 0,1 9,6C9,7.31 8.17,8.42 7,8.83V10H5V8.83C3.83,8.42 3,7.31 3,6A3,3 0 0,1 6,3M6,5A1,1 0 0,0 5,6A1,1 0 0,0 6,7A1,1 0 0,0 7,6A1,1 0 0,0 6,5M11,19V17H13V19H11M7,13H5V11H7V13Z\"\n                                />\n                            </Viewbox>\n                            <TextBlock\n                                x:Name=\"ProgressTextBlock\"\n                                DockPanel.Dock=\"Right\"\n                                Foreground=\"{DynamicResource NavyMainBrush}\"\n                                FontWeight=\"Bold\"\n                                FontSize=\"{DynamicResource FontSizeNormal}\"\n                                Text=\"{TemplateBinding InternalProgressText}\"\n                            />\n                            <TextBlock\n                                Margin=\"10,0,0,0\"\n                                VerticalAlignment=\"Center\"\n                                FontSize=\"{DynamicResource FontSizeLarge}\"\n                                Text=\"{TemplateBinding Title}\"\n                            />\n                        </DockPanel>\n                        <Viewbox Height=\"50\" HorizontalAlignment=\"Stretch\">\n                            <Canvas Width=\"800\" Height=\"100\">\n                                <Ellipse\n                                    Canvas.Left=\"735\"\n                                    Canvas.Top=\"40\"\n                                    Width=\"20\"\n                                    Height=\"20\"\n                                    Fill=\"Gray\"\n                                />\n                                <Ellipse\n                                    Canvas.Left=\"40\"\n                                    Canvas.Top=\"40\"\n                                    Width=\"20\"\n                                    Height=\"20\"\n                                    Fill=\"Gray\"\n                                />\n                                <Ellipse\n                                    Canvas.Left=\"400\"\n                                    Canvas.Top=\"40\"\n                                    Width=\"20\"\n                                    Height=\"20\"\n                                    Fill=\"Gray\"\n                                />\n                                <Line\n                                    StartPoint=\"70,50\"\n                                    EndPoint=\"390,50\"\n                                    Stroke=\"{DynamicResource NavySecondaryBrush}\"\n                                    StrokeThickness=\"5\"\n                                    VerticalAlignment=\"Center\"\n                                    Height=\"10\"\n                                />\n                                <Line\n                                    StartPoint=\"430,50\"\n                                    EndPoint=\"725,50\"\n                                    Stroke=\"{DynamicResource NavyMainBrush}\"\n                                    StrokeThickness=\"5\"\n                                    VerticalAlignment=\"Center\"\n                                    Height=\"10\"\n                                />\n\n                                <Border\n                                    x:Name=\"ProgressBorder\"\n                                    Canvas.Top=\"0\"\n                                    Canvas.Left=\"{TemplateBinding InternalBorderLeft}\"\n                                    Width=\"{TemplateBinding InternalBorderWidth}\"\n                                    Height=\"100\"\n                                    CornerRadius=\"50\"\n                                    BoxShadow=\"inset 0 0 90 10 #083369\"\n                                />\n                                <Ellipse\n                                    Canvas.Left=\"710\"\n                                    Canvas.Top=\"15\"\n                                    Width=\"70\"\n                                    Height=\"70\"\n                                    Fill=\"#032246\"\n                                    StrokeThickness=\"5\"\n                                    Stroke=\"#006ffd\"\n                                />\n                                <Viewbox\n                                    x:Name=\"NavigationIcon\"\n                                    Canvas.Left=\"720\"\n                                    Canvas.Top=\"30\"\n                                    Width=\"40\"\n                                    Height=\"40\"\n                                >\n                                    <Path\n                                        Fill=\"White\"\n                                        Data=\"M21 3L3 10.53V11.5L9.84 14.16L12.5 21H13.46L21 3Z\"\n                                    />\n                                </Viewbox>\n                                <Viewbox\n                                    x:Name=\"CompletedIcon\"\n                                    IsVisible=\"False\"\n                                    Canvas.Left=\"723\"\n                                    Canvas.Top=\"28\"\n                                    Width=\"40\"\n                                    Height=\"40\"\n                                >\n                                    <Path\n                                        Fill=\"White\"\n                                        Data=\"M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M10 17L5 12L6.41 10.59L10 14.17L17.59 6.58L19 8L10 17Z\"\n                                    />\n                                </Viewbox>\n                                <Ellipse\n                                    x:Name=\"ProgressEllipse\"\n                                    Canvas.Left=\"{TemplateBinding InternalIndicatorLeft}\"\n                                    Canvas.Top=\"15\"\n                                    Width=\"70\"\n                                    Height=\"70\"\n                                    Fill=\"White\"\n                                    StrokeThickness=\"25\"\n                                    Stroke=\"#006ffd\"\n                                />\n                            </Canvas>\n                        </Viewbox>\n                        <StackPanel Orientation=\"Horizontal\" Spacing=\"8\">\n                            <TextBlock\n                                FontWeight=\"Heavy\"\n                                Text=\"{TemplateBinding StatusText}\"\n                                FontSize=\"{DynamicResource FontSizeNormal}\"\n                                Foreground=\"{DynamicResource NavyMainBrush}\"\n                            />\n                            <TextBlock\n                                FontWeight=\"Heavy\"\n                                Text=\"{TemplateBinding SubStatusText}\"\n                                FontSize=\"{DynamicResource FontSizeNormal}\"\n                                Foreground=\"#8c8b91\"\n                            />\n                        </StackPanel>\n                    </StackPanel>\n                </Border>\n            </ControlTemplate>\n        </Setter>\n\n        <Style Selector=\"^:progress-disabled /template/ TextBlock#ProgressTextBlock\">\n            <Setter Property=\"IsVisible\" Value=\"False\" />\n        </Style>\n        <Style Selector=\"^:progress-disabled /template/ Ellipse#ProgressEllipse\">\n            <Setter Property=\"IsVisible\" Value=\"False\" />\n        </Style>\n        <Style Selector=\"^:progress-disabled /template/ Border#ProgressBorder\">\n            <Setter Property=\"IsVisible\" Value=\"False\" />\n        </Style>\n        <Style Selector=\"^:progress-disabled /template/ Border#ProgressDisable\">\n            <Setter Property=\"IsVisible\" Value=\"False\" />\n        </Style>\n\n        <Style Selector=\"^:progress-completed /template/ Ellipse#ProgressEllipse\">\n            <Style.Animations>\n                <Animation\n                    Duration=\"0:0:0.5\"\n                    IterationCount=\"{DynamicResource IndicatorBlinkAnimationCount}\"\n                >\n                    <KeyFrame Cue=\"0%\">\n                        <Setter Property=\"Opacity\" Value=\"0.0\" />\n                    </KeyFrame>\n                    <KeyFrame Cue=\"100%\">\n                        <Setter Property=\"Opacity\" Value=\"1.0\" />\n                    </KeyFrame>\n                </Animation>\n            </Style.Animations>\n        </Style>\n        <Style Selector=\"^:progress-completed /template/ Viewbox#NavigationIcon\">\n            <Setter Property=\"IsVisible\" Value=\"False\" />\n        </Style>\n        <Style Selector=\"^:progress-completed /template/ Ellipse#ProgressEllipse\">\n            <Setter Property=\"IsVisible\" Value=\"False\" />\n        </Style>\n        <Style Selector=\"^:progress-completed /template/ Viewbox#CompletedIcon\">\n            <Setter Property=\"IsVisible\" Value=\"True\" />\n            <Style.Animations>\n                <Animation\n                    Duration=\"0:0:0.5\"\n                    IterationCount=\"{DynamicResource IndicatorBlinkAnimationCount}\"\n                >\n                    <KeyFrame Cue=\"0%\">\n                        <Setter Property=\"Opacity\" Value=\"0.0\" />\n                    </KeyFrame>\n                    <KeyFrame Cue=\"100%\">\n                        <Setter Property=\"Opacity\" Value=\"1.0\" />\n                    </KeyFrame>\n                </Animation>\n            </Style.Animations>\n        </Style>\n    </ControlTheme>\n</ResourceDictionary>\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/AltitudeUavIndicator/AltitudeUavIndicator.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:asv-avalonia=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    x:Class=\"Asv.Drones.AltitudeUavIndicator\"\n    x:DataType=\"drones:AltitudeUavIndicatorViewModel\"\n>\n    <Design.DataContext>\n        <drones:AltitudeUavIndicatorViewModel />\n    </Design.DataContext>\n    <asv-avalonia:TwoColumnRttBoxView />\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/AltitudeUavIndicator/AltitudeUavIndicator.axaml.cs",
    "content": "using Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class AltitudeUavIndicator : UserControl\n{\n    public AltitudeUavIndicator()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/AltitudeUavIndicator/AltitudeUavIndicatorViewModel.cs",
    "content": "using System;\nusing System.Diagnostics.CodeAnalysis;\nusing Asv.Avalonia;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\n#pragma warning disable SA1313\npublic record AltitudeRttBoxData(double AltitudeAgl, double AltitudeMsl, IUnitItem AltitudeUnit);\n#pragma warning restore SA1313\n\npublic class AltitudeUavIndicatorViewModel : TwoColumnRttBoxViewModel<AltitudeRttBoxData>\n{\n    [SetsRequiredMembers]\n    public AltitudeUavIndicatorViewModel()\n        : this(\n            nameof(AltitudeUavIndicator),\n            DesignTime.LoggerFactory,\n            new ReactiveProperty<double>(10),\n            new ReactiveProperty<double>(14),\n            DeviceTelemetryDesignPreview.Unit(AltitudeUnit.Id).CurrentUnitItem,\n            DeviceTelemetryDesignPreview.DefaultStatusColor\n        )\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    [SetsRequiredMembers]\n    public AltitudeUavIndicatorViewModel(\n        NavigationId id,\n        ILoggerFactory loggerFactory,\n        ReactiveProperty<double> altitudeAgl,\n        ReactiveProperty<double> altitudeMsl,\n        SynchronizedReactiveProperty<IUnitItem> currentUnitItem,\n        AsvColorKind defaultStatusColor,\n        TimeSpan? networkErrorTimeout = null\n    )\n        : base(\n            id,\n            loggerFactory,\n            altitudeAgl\n                .CombineLatest(\n                    altitudeMsl,\n                    currentUnitItem,\n                    (agl, msl, unit) => new AltitudeRttBoxData(agl, msl, unit)\n                )\n                .ObserveOnUIThreadDispatcher()\n                .ThrottleLast(TimeSpan.FromMilliseconds(200)),\n            networkErrorTimeout\n        )\n    {\n        Header = RS.UavRttItem_Altitude;\n        Icon = MaterialIconKind.Altimeter;\n        UpdateAction = (model, changes) =>\n        {\n            model.Left.ValueString = changes.AltitudeUnit.PrintFromSi(changes.AltitudeAgl, \"F2\");\n            model.Right.ValueString = changes.AltitudeUnit.PrintFromSi(changes.AltitudeMsl, \"F2\");\n\n            model.Left.UnitSymbol = changes.AltitudeUnit.Symbol;\n            model.Right.UnitSymbol = changes.AltitudeUnit.Symbol;\n        };\n        Status = defaultStatusColor;\n\n        Left.Header = RS.AltitudeUavIndicatorViewModel_Agl;\n        Right.Header = RS.AltitudeUavIndicatorViewModel_Msl;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/AngleUavRttIndicator/AngleUavRttIndicator.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:asv-avalonia=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    x:Class=\"Asv.Drones.AngleUavRttIndicator\"\n    x:DataType=\"drones:AngleUavRttIndicatorViewModel\"\n>\n    <Design.DataContext>\n        <drones:AngleUavRttIndicatorViewModel />\n    </Design.DataContext>\n    <asv-avalonia:TwoColumnRttBoxView />\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/AngleUavRttIndicator/AngleUavRttIndicator.axaml.cs",
    "content": "using Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class AngleUavRttIndicator : UserControl\n{\n    public AngleUavRttIndicator()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/AngleUavRttIndicator/AngleUavRttIndicatorViewModel.cs",
    "content": "using System;\nusing System.Diagnostics.CodeAnalysis;\nusing Asv.Avalonia;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\n#pragma warning disable SA1313\npublic record AngleRttBoxData(double Pitch, double Roll, IUnitItem AngleUnit);\n#pragma warning restore SA1313\n\npublic class AngleUavRttIndicatorViewModel : TwoColumnRttBoxViewModel<AngleRttBoxData>\n{\n    [SetsRequiredMembers]\n    public AngleUavRttIndicatorViewModel()\n        : this(\n            nameof(AngleUavRttIndicator),\n            DesignTime.LoggerFactory,\n            new ReactiveProperty<double>(30),\n            new ReactiveProperty<double>(10),\n            DeviceTelemetryDesignPreview.Unit(AngleUnit.Id).CurrentUnitItem,\n            DeviceTelemetryDesignPreview.DefaultStatusColor\n        )\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    [SetsRequiredMembers]\n    public AngleUavRttIndicatorViewModel(\n        NavigationId id,\n        ILoggerFactory loggerFactory,\n        ReactiveProperty<double> pitchAngle,\n        ReactiveProperty<double> rollAngle,\n        SynchronizedReactiveProperty<IUnitItem> currentAngleUnitItem,\n        AsvColorKind defaultStatusColor,\n        TimeSpan? networkErrorTimeout = null\n    )\n        : base(\n            id,\n            loggerFactory,\n            pitchAngle\n                .CombineLatest(\n                    rollAngle,\n                    currentAngleUnitItem,\n                    (agl, msl, unit) => new AngleRttBoxData(agl, msl, unit)\n                )\n                .ObserveOnUIThreadDispatcher()\n                .ThrottleLast(TimeSpan.FromMilliseconds(200)),\n            networkErrorTimeout\n        )\n    {\n        Header = RS.AngleUavRttIndicatorViewModel_Angle;\n        Icon = MaterialIconKind.Altimeter;\n        UpdateAction = (model, changes) =>\n        {\n            model.Left.ValueString = changes.AngleUnit.PrintFromSi(changes.Pitch, \"F2\");\n            model.Right.ValueString = changes.AngleUnit.PrintFromSi(changes.Roll, \"F2\");\n\n            model.Left.UnitSymbol = changes.AngleUnit.Symbol;\n            model.Right.UnitSymbol = changes.AngleUnit.Symbol;\n        };\n        Status = defaultStatusColor;\n\n        Left.Header = RS.AngleUavRttIndicatorViewModel_Pitch;\n        Right.Header = RS.AngleUavRttIndicatorViewModel_Roll;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/BatteryUavIndicator/BatteryUavIndicator.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:asv-avalonia=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"270\"\n    x:Class=\"Asv.Drones.BatteryUavIndicator\"\n    x:DataType=\"drones:BatteryUavIndicatorViewModel\"\n>\n    <Design.DataContext>\n        <drones:BatteryUavIndicatorViewModel />\n    </Design.DataContext>\n    <asv-avalonia:KeyValueRttBoxView />\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/BatteryUavIndicator/BatteryUavIndicator.axaml.cs",
    "content": "using Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class BatteryUavIndicator : UserControl\n{\n    public BatteryUavIndicator()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/BatteryUavIndicator/BatteryUavIndicatorViewModel.cs",
    "content": "using System;\nusing System.Diagnostics.CodeAnalysis;\nusing Asv.Avalonia;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\n#pragma warning disable SA1313\npublic record BatteryRttBoxData(\n    double Charge,\n    double Amperage,\n    double Voltage,\n    double Consumed,\n    IUnitItem ProgressUnit,\n    IUnitItem AmperageUnit,\n    IUnitItem CapacityUnit,\n    IUnitItem VoltageUnit\n);\n#pragma warning restore SA1313\n\npublic class BatteryUavIndicatorViewModel : KeyValueRttBoxViewModel<BatteryRttBoxData>\n{\n    private readonly AsvColorKind defaultStatusColor;\n\n    [SetsRequiredMembers]\n    public BatteryUavIndicatorViewModel()\n        : this(\n            nameof(BatteryUavIndicator),\n            DesignTime.LoggerFactory,\n            new ReactiveProperty<double>(0.76),\n            new ReactiveProperty<double>(12.4),\n            new ReactiveProperty<double>(23.8),\n            new ReactiveProperty<double>(3900),\n            DeviceTelemetryDesignPreview.Unit(ProgressUnit.Id).CurrentUnitItem,\n            DeviceTelemetryDesignPreview.Unit(AmperageUnit.Id).CurrentUnitItem,\n            DeviceTelemetryDesignPreview.Unit(CapacityUnit.Id).CurrentUnitItem,\n            DeviceTelemetryDesignPreview.Unit(VoltageUnit.Id).CurrentUnitItem,\n            DeviceTelemetryDesignPreview.DefaultStatusColor\n        )\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    [SetsRequiredMembers]\n    public BatteryUavIndicatorViewModel(\n        NavigationId id,\n        ILoggerFactory loggerFactory,\n        ReactiveProperty<double> batteryCharge,\n        ReactiveProperty<double> batteryAmperage,\n        ReactiveProperty<double> batteryVoltage,\n        ReactiveProperty<double> batteryConsumed,\n        SynchronizedReactiveProperty<IUnitItem> progressUnit,\n        SynchronizedReactiveProperty<IUnitItem> amperageUnit,\n        SynchronizedReactiveProperty<IUnitItem> capacityUnit,\n        SynchronizedReactiveProperty<IUnitItem> voltageUnit,\n        AsvColorKind defaultStatusColor,\n        TimeSpan? networkErrorTimeout = null\n    )\n        : base(\n            id,\n            loggerFactory,\n            batteryCharge\n                .CombineLatest(\n                    batteryAmperage,\n                    batteryVoltage,\n                    batteryConsumed,\n                    progressUnit,\n                    amperageUnit,\n                    capacityUnit,\n                    voltageUnit,\n                    (bC, bA, bV, bCo, prog, amp, cap, vol) =>\n                        new BatteryRttBoxData(bC, bA, bV, bCo, prog, amp, cap, vol)\n                )\n                .ObserveOnUIThreadDispatcher()\n                .ThrottleLast(TimeSpan.FromMilliseconds(200)),\n            networkErrorTimeout\n        )\n    {\n        this.defaultStatusColor = defaultStatusColor;\n\n        Header = RS.UavRttItem_Battery;\n        Icon = MaterialIconKind.Battery10;\n        UpdateAction = (model, changes) =>\n        {\n            model[\n                0,\n                RS.UavWidgetViewModel_BatteryRttBox_BatteryCharge_Header,\n                changes.ProgressUnit.Symbol\n            ].ValueString = changes.ProgressUnit.PrintFromSi(changes.Charge, \"F2\");\n            model[\n                1,\n                RS.UavWidgetViewModel_BatteryRttBox_BatteryAmperage_Header,\n                changes.AmperageUnit.Symbol\n            ].ValueString = changes.AmperageUnit.PrintFromSi(changes.Amperage, \"F2\");\n            model[\n                2,\n                RS.UavWidgetViewModel_BatteryRttBox_BatteryVoltage_Header,\n                changes.VoltageUnit.Symbol\n            ].ValueString = changes.VoltageUnit.PrintFromSi(changes.Voltage, \"F2\");\n            model[\n                3,\n                RS.UavWidgetViewModel_BatteryRttBox_BatteryConsumed_Header,\n                changes.CapacityUnit.Symbol\n            ].ValueString = changes.CapacityUnit.PrintFromSi(changes.Consumed, \"F2\");\n\n            ChangeBatteryStatus(changes.Charge);\n        };\n        Status = defaultStatusColor;\n    }\n\n    private void ChangeBatteryStatus(double percent)\n    {\n        Status = percent switch\n        {\n            > 0.7d => defaultStatusColor,\n            > 0.5d => AsvColorKind.Warning,\n            > 0.4d => AsvColorKind.Warning | AsvColorKind.Blink,\n            < 0.3d => AsvColorKind.Error | AsvColorKind.Blink,\n            _ => defaultStatusColor,\n        };\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/HeadingUavIndicator/HeadingUavIndicator.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:asv-avalonia=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    x:Class=\"Asv.Drones.HeadingUavIndicator\"\n    x:DataType=\"drones:HeadingUavIndicatorViewModel\"\n>\n    <Design.DataContext>\n        <drones:HeadingUavIndicatorViewModel />\n    </Design.DataContext>\n    <asv-avalonia:SplitDigitRttBoxView />\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/HeadingUavIndicator/HeadingUavIndicator.axaml.cs",
    "content": "using Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class HeadingUavIndicator : UserControl\n{\n    public HeadingUavIndicator()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/HeadingUavIndicator/HeadingUavIndicatorViewModel.cs",
    "content": "using System;\nusing Asv.Avalonia;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class HeadingUavIndicatorViewModel : SplitDigitRttBoxViewModel\n{\n    public HeadingUavIndicatorViewModel()\n        : this(\n            nameof(HeadingUavIndicator),\n            DesignTime.LoggerFactory,\n            DeviceTelemetryDesignPreview.UnitService,\n            new ReactiveProperty<double>(29),\n            DeviceTelemetryDesignPreview.DefaultStatusColor\n        )\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public HeadingUavIndicatorViewModel(\n        NavigationId id,\n        ILoggerFactory loggerFactory,\n        IUnitService unitService,\n        ReactiveProperty<double> heading,\n        AsvColorKind defaultStatusColor,\n        TimeSpan? networkErrorTimeout = null\n    )\n        : base(id, loggerFactory, unitService, AngleUnit.Id, heading, networkErrorTimeout)\n    {\n        Header = RS.HeadingUavIndicatorViewModel_Heading;\n        ShortHeader = RS.HeadingUavIndicatorViewModel_Heading_Short;\n        Icon = MaterialIconKind.SunAzimuth;\n        Status = defaultStatusColor;\n        FormatString = \"F0\";\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/HomeAzimuthUavIndicator/HomeAzimuthUavIndicator.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:asv-avalonia=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    x:Class=\"Asv.Drones.HomeAzimuthUavIndicator\"\n    x:DataType=\"drones:HomeAzimuthUavIndicatorViewModel\"\n>\n    <Design.DataContext>\n        <drones:HomeAzimuthUavIndicatorViewModel />\n    </Design.DataContext>\n    <asv-avalonia:SplitDigitRttBoxView />\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/HomeAzimuthUavIndicator/HomeAzimuthUavIndicator.axaml.cs",
    "content": "using Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class HomeAzimuthUavIndicator : UserControl\n{\n    public HomeAzimuthUavIndicator()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/HomeAzimuthUavIndicator/HomeAzimuthUavIndicatorViewModel.cs",
    "content": "using System;\nusing Asv.Avalonia;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class HomeAzimuthUavIndicatorViewModel : SplitDigitRttBoxViewModel\n{\n    public HomeAzimuthUavIndicatorViewModel()\n        : this(\n            nameof(HomeAzimuthUavIndicator),\n            DesignTime.LoggerFactory,\n            DeviceTelemetryDesignPreview.UnitService,\n            new ReactiveProperty<double>(30),\n            DeviceTelemetryDesignPreview.DefaultStatusColor\n        )\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public HomeAzimuthUavIndicatorViewModel(\n        NavigationId id,\n        ILoggerFactory loggerFactory,\n        IUnitService unitService,\n        ReactiveProperty<double> homeAzimuth,\n        AsvColorKind defaultStatusColor,\n        TimeSpan? networkErrorTimeout = null\n    )\n        : base(id, loggerFactory, unitService, AngleUnit.Id, homeAzimuth, networkErrorTimeout)\n    {\n        Header = RS.HomeAzimuthUavIndicatorViewModel_HomeAzimuth;\n        ShortHeader = RS.HomeAzimuthUavIndicatorViewModel_HomeAzimuth_Short;\n        Icon = MaterialIconKind.Home;\n        Status = defaultStatusColor;\n        FormatString = \"F0\";\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/VelocityUavIndicator/VelocityUavIndicator.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:asv-avalonia=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    x:Class=\"Asv.Drones.VelocityUavIndicator\"\n    x:DataType=\"drones:VelocityUavIndicatorViewModel\"\n>\n    <Design.DataContext>\n        <drones:VelocityUavIndicatorViewModel />\n    </Design.DataContext>\n    <asv-avalonia:SplitDigitRttBoxView />\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/VelocityUavIndicator/VelocityUavIndicator.axaml.cs",
    "content": "using Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class VelocityUavIndicator : UserControl\n{\n    public VelocityUavIndicator()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Controls/DeviceTelemetry/Rtt/VelocityUavIndicator/VelocityUavIndicatorViewModel.cs",
    "content": "using System;\nusing Asv.Avalonia;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class VelocityUavIndicatorViewModel : SplitDigitRttBoxViewModel\n{\n    public VelocityUavIndicatorViewModel()\n        : this(\n            nameof(VelocityUavIndicator),\n            DesignTime.LoggerFactory,\n            DeviceTelemetryDesignPreview.UnitService,\n            new ReactiveProperty<double>(19.9),\n            DeviceTelemetryDesignPreview.DefaultStatusColor\n        )\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public VelocityUavIndicatorViewModel(\n        NavigationId id,\n        ILoggerFactory loggerFactory,\n        IUnitService unitService,\n        ReactiveProperty<double> velocity,\n        AsvColorKind defaultStatusColor,\n        TimeSpan? networkErrorTimeout = null\n    )\n        : base(id, loggerFactory, unitService, VelocityUnit.Id, velocity, networkErrorTimeout)\n    {\n        Header = RS.UavRttItem_Velocity;\n        ShortHeader = RS.VelocityUavIndicatorViewModel_Velocity_Short;\n        Icon = MaterialIconKind.Speedometer;\n        Status = defaultStatusColor;\n        FormatString = \"F2\";\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Converters/Crc32StatusToColorConverter.cs",
    "content": "﻿using System;\nusing System.Globalization;\nusing Asv.Avalonia;\nusing Avalonia.Data.Converters;\n\nnamespace Asv.Drones;\n\n/// <summary>\n/// Simple converter from Crc32Status to AsvColorKind\n/// </summary>\npublic class Crc32StatusToColorConverter : IValueConverter\n{\n    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)\n    {\n        if (value is Crc32Status status)\n        {\n            return status switch\n            {\n                Crc32Status.Correct => AsvColorKind.Success,\n                Crc32Status.Incorrect => AsvColorKind.Error,\n                Crc32Status.Default => AsvColorKind.Unknown,\n                _ => AsvColorKind.None,\n            };\n        }\n\n        return AsvColorKind.None;\n    }\n\n    public object? ConvertBack(\n        object? value,\n        Type targetType,\n        object? parameter,\n        CultureInfo culture\n    )\n    {\n        if (value is AsvColorKind color)\n        {\n            return color switch\n            {\n                AsvColorKind.Success => Crc32Status.Correct,\n                AsvColorKind.Error => Crc32Status.Incorrect,\n                _ => Crc32Status.Default,\n            };\n        }\n\n        return Crc32Status.Default;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Services/ClientDeviceWidgetFactory/ClientDeviceWidgetFactory.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing Asv.Drones.Api;\nusing Asv.IO;\n\nnamespace Asv.Drones;\n\npublic class ClientDeviceWidgetFactory : IClientDeviceWidgetFactory\n{\n    private readonly IReadOnlyDictionary<Type, IClientDeviceWidgetCreationHandler> _handlers;\n\n    public ClientDeviceWidgetFactory(IEnumerable<IClientDeviceWidgetCreationHandler> handlers)\n    {\n        ArgumentNullException.ThrowIfNull(handlers);\n\n        var result = new Dictionary<Type, IClientDeviceWidgetCreationHandler>();\n        foreach (var handler in handlers)\n        {\n            if (!result.TryAdd(handler.DeviceType, handler))\n            {\n                throw new ArgumentException(\n                    $\"Duplicate widget handler for type '{handler.DeviceType.FullName}'.\"\n                );\n            }\n        }\n\n        _handlers = result;\n    }\n\n    public IFlightWidget? CreateWidget(in IClientDevice device)\n    {\n        var currentType = device.GetType();\n        while (currentType is not null)\n        {\n            if (_handlers.TryGetValue(currentType, out var handler))\n            {\n                return handler.Create(in device);\n            }\n\n            currentType = currentType.BaseType;\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Services/Devices/Gnss/GnssDeviceManagerExtentsion.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Gnss;\nusing Asv.IO;\nusing Avalonia.Media;\nusing Material.Icons;\n\nnamespace Asv.Drones;\n\npublic class GnssDeviceManagerExtension : IDeviceManagerExtension\n{\n    public void Configure(IProtocolBuilder builder)\n    {\n        builder.Features.RegisterEndpointIdTagFeature();\n\n        builder.Protocols.RegisterNmeaProtocol();\n    }\n\n    public void Configure(IDeviceExplorerBuilder builder)\n    {\n        builder.Factories.RegisterGnssDevice();\n    }\n\n    public bool TryGetIcon(DeviceId id, out MaterialIconKind? icon)\n    {\n        if (id is GnssDeviceId)\n        {\n            icon = MaterialIconKind.Satellite;\n            return true;\n        }\n\n        icon = null;\n        return false;\n    }\n\n    public bool TryGetDeviceBrush(DeviceId id, out AsvColorKind brush)\n    {\n        brush = AsvColorKind.None;\n        return false;\n    }\n\n    public void Run(IDeviceManager deviceManager)\n    {\n        // do nothing\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Services/Files/BusyFlag.cs",
    "content": "﻿using System;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic sealed class BusyFlag : IDisposable\n{\n    // guarantees correct balancing\n    // even if multiple operations overlap, and ensures that IsBusy only toggles\n    // on the very first start and the very last completion.\n    private readonly Subject<int> _delta = new();\n    public Observable<bool> IsBusy { get; }\n\n    public BusyFlag()\n    {\n        IsBusy = _delta\n            .Scan(0, static (acc, d) => acc + d)\n            .Select(static x => x > 0)\n            .Prepend(false)\n            .DistinctUntilChanged()\n            .Publish()\n            .RefCount();\n    }\n\n    public IDisposable Enter()\n    {\n        _delta.OnNext(+1);\n        return Disposable.Create(() => _delta.OnNext(-1));\n    }\n\n    public void Dispose() => _delta.OnCompleted();\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Services/Files/Local/LocalFilesService.cs",
    "content": "﻿using System.Collections.Concurrent;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.IO.Abstractions;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic class LocalFilesService(IFileSystem? fileSystem = null)\n{\n    private readonly IFileSystem _fileSystem = fileSystem ?? new FileSystem();\n\n    public IReadOnlyList<IBrowserItemViewModel> LoadBrowserItems(\n        string path,\n        string root,\n        FileBrowserBackend backend,\n        ILoggerFactory loggerFactory,\n        IDictionary<string, DirectoryItemViewModelConfig>? directoryCfgs,\n        CancellationToken ct = default,\n        ILogger? log = null\n    )\n    {\n        var result = new ConcurrentBag<IBrowserItemViewModel>();\n        ProcessBrowserDirectory(\n            path,\n            root,\n            ref result,\n            backend,\n            loggerFactory,\n            directoryCfgs,\n            ct,\n            log\n        );\n        log?.LogTrace(\"Directory processed ({Path})\", path);\n        return result.ToList();\n    }\n\n    private void ProcessBrowserDirectory(\n        string path,\n        string root,\n        ref ConcurrentBag<IBrowserItemViewModel> items,\n        FileBrowserBackend backend,\n        ILoggerFactory loggerFactory,\n        IDictionary<string, DirectoryItemViewModelConfig>? directoryCfgs,\n        CancellationToken ct = default,\n        ILogger? log = null\n    )\n    {\n        ct.ThrowIfCancellationRequested();\n\n        var rootInfo = new DirectoryInfo(root);\n        var rootId = PathHelper.EncodePathToId(root);\n\n        var rootVm = new DirectoryItemViewModel(\n            rootId,\n            null,\n            root,\n            rootInfo.Name,\n            FtpBrowserSourceType.Local,\n            loggerFactory\n        );\n\n        rootVm.AttachBackend(backend);\n        items.Add(rootVm);\n\n        foreach (var dir in _fileSystem.Directory.EnumerateDirectories(path))\n        {\n            ct.ThrowIfCancellationRequested();\n            var info = new DirectoryInfo(dir);\n\n            var id = PathHelper.EncodePathToId(dir);\n            var parent = info.Parent?.FullName ?? root;\n\n            DirectoryItemViewModelConfig? cfg = null;\n            directoryCfgs?.TryGetValue(dir, out cfg);\n            var vm = new DirectoryItemViewModel(\n                id,\n                parent,\n                dir,\n                info.Name,\n                FtpBrowserSourceType.Local,\n                loggerFactory,\n                cfg\n            );\n\n            vm.AttachBackend(backend);\n\n            items.Add(vm);\n            ProcessBrowserDirectory(\n                dir,\n                root,\n                ref items,\n                backend,\n                loggerFactory,\n                directoryCfgs,\n                ct\n            );\n        }\n\n        foreach (var file in _fileSystem.Directory.EnumerateFiles(path))\n        {\n            ct.ThrowIfCancellationRequested();\n\n            try\n            {\n                var info = new FileInfo(file);\n                var id = PathHelper.EncodePathToId(file);\n                var parent = info.Directory?.FullName ?? root;\n                var vm = new FileItemViewModel(\n                    id,\n                    parent,\n                    file,\n                    info.Name,\n                    info.Length,\n                    FtpBrowserSourceType.Local,\n                    loggerFactory\n                );\n                vm.AttachBackend(backend);\n                items.Add(vm);\n            }\n            catch (FileNotFoundException ex)\n            {\n                log?.LogWarning(ex, \"Skipped file '{File}' during scanning\", file);\n            }\n        }\n    }\n\n    public string RenameFile(string oldPath, string newPath, ILogger? log = null)\n    {\n        try\n        {\n            if (_fileSystem.File.Exists(newPath))\n            {\n                var parentDir = _fileSystem.Path.GetDirectoryName(oldPath) ?? string.Empty;\n                var baseName = _fileSystem.Path.GetFileNameWithoutExtension(newPath);\n                var ext = _fileSystem.Path.GetExtension(newPath);\n                var counter = 1;\n                while (_fileSystem.File.Exists(newPath))\n                {\n                    newPath = _fileSystem.Path.Combine(parentDir, $\"{baseName} ({counter++}){ext}\");\n                }\n            }\n            _fileSystem.File.Move(oldPath, newPath);\n            log?.LogInformation(\"File renamed to '{new}'\", newPath);\n        }\n        catch (FileNotFoundException e)\n        {\n            log?.LogError(e, \"Failed to rename file. Incorrect path: {Path}\", oldPath);\n        }\n\n        return newPath;\n    }\n\n    public string RenameDirectory(string oldPath, string newPath, ILogger? log = null)\n    {\n        try\n        {\n            if (_fileSystem.Directory.Exists(newPath))\n            {\n                var parentDir = _fileSystem.Path.GetDirectoryName(oldPath) ?? string.Empty;\n                var baseName = _fileSystem.Path.GetFileNameWithoutExtension(newPath);\n                var counter = 1;\n                while (_fileSystem.Directory.Exists(newPath))\n                {\n                    newPath = _fileSystem.Path.Combine(parentDir, $\"{baseName} ({counter++})\");\n                }\n            }\n            _fileSystem.Directory.Move(oldPath, newPath);\n            log?.LogInformation(\"Directory renamed to '{new}'\", newPath);\n        }\n        catch (DirectoryNotFoundException e)\n        {\n            log?.LogError(e, \"Failed to rename directory. Incorrect path: {Path}\", oldPath);\n        }\n\n        return newPath;\n    }\n\n    public void CreateDirectory(string path, ILogger? log = null)\n    {\n        var folderNumber = 1;\n        while (true)\n        {\n            var name = _fileSystem.Path.Combine(path, $\"Folder{folderNumber}\");\n            if (_fileSystem.Directory.Exists(name))\n            {\n                folderNumber++;\n                continue;\n            }\n\n            log?.LogInformation(\"Creating directory '{Path}'\", name);\n            _fileSystem.Directory.CreateDirectory(name);\n            return;\n        }\n    }\n\n    public void RemoveFile(string path, ILogger? log = null)\n    {\n        try\n        {\n            _fileSystem.File.Delete(path);\n            log?.LogInformation(\"File removed: '{Path}'\", path);\n        }\n        catch (FileNotFoundException e)\n        {\n            log?.LogError(e, \"File '{Path}' not found\", path);\n        }\n    }\n\n    public void RemoveDirectory(string path, bool recursive, ILogger? log = null)\n    {\n        try\n        {\n            _fileSystem.Directory.Delete(path, recursive);\n            log?.LogInformation(\"Directory removed: '{Path}'\", path);\n        }\n        catch (FileNotFoundException e)\n        {\n            log?.LogError(e, \"Directory '{Path}' not found\", path);\n        }\n    }\n\n    public async Task<uint> CalculateCrc32Async(\n        string path,\n        CancellationToken ct = default,\n        ILogger? log = null\n    )\n    {\n        try\n        {\n            var crc32 = Crc32Mavlink.Accumulate(await _fileSystem.File.ReadAllBytesAsync(path, ct));\n            log?.LogInformation(\"File crc32: {crc32}\", crc32);\n            return crc32;\n        }\n        catch (FileNotFoundException e)\n        {\n            log?.LogError(e, \"File '{Path}' not found\", path);\n        }\n\n        return 0U;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Services/Files/PathHelper.cs",
    "content": "﻿using System;\nusing Asv.Avalonia;\n\nnamespace Asv.Drones;\n\npublic static class PathHelper\n{\n    public static NavigationId EncodePathToId(string path)\n    {\n        var utf8 = System.Text.Encoding.UTF8.GetBytes(path);\n        return Convert\n            .ToBase64String(utf8)\n            .Replace('+', '.')\n            .Replace('/', '_')\n            .Replace(\"=\", string.Empty);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Services/Files/ProgressWithLock.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic sealed class ProgressWithLock(ILoggerFactory? loggerFactory = null) : IDisposable\n{\n    private readonly ILogger _log = (\n        loggerFactory ?? NullLoggerFactory.Instance\n    ).CreateLogger<ProgressWithLock>();\n\n    private CancellationTokenSource? _cts;\n    private readonly Lock _sync = new();\n\n    /// <summary>Gets true while a transfer is in progress.</summary>\n    public ReactiveProperty<bool> IsTransferInProgress { get; } = new(false);\n\n    /// <summary>Gets controls progress overlay visibility.</summary>\n    public ReactiveProperty<bool> IsProgressVisible { get; } = new(false);\n\n    /// <summary>Gets progress value in range [0..1].</summary>\n    public ReactiveProperty<double> Progress { get; } = new(0);\n\n    /// <summary>\n    /// Disposable scope that represents a single transfer session.\n    /// When disposed, it finalizes the transfer by calling Complete().\n    /// </summary>\n    public readonly struct TransferScope : IDisposable\n    {\n        private readonly ProgressWithLock _owner;\n\n        /// <summary>Gets a linked cancellation token for the current transfer.</summary>\n        public CancellationToken Token { get; }\n\n        /// <summary>\n        /// Gets a progress reporter bound to the owner's Report method.\n        /// Prefer to reuse if you report often.\n        /// </summary>\n        public IProgress<double> Reporter { get; }\n\n        internal TransferScope(ProgressWithLock owner, CancellationToken token)\n        {\n            _owner = owner;\n            Token = token;\n            Reporter = new Progress<double>(owner.Report);\n        }\n\n        public void Dispose()\n        {\n            _owner.Complete();\n        }\n    }\n\n    /// <summary>\n    /// Starts a transfer session.\n    /// Use <c>using var t = _transfer.BeginScope(ct);</c> and pass <c>t.Token</c> to async operations.\n    /// </summary>\n    /// <returns>A disposable scope.</returns>\n    public TransferScope BeginScope(CancellationToken ct = default)\n    {\n        using (_sync.EnterScope())\n        {\n            _cts?.Dispose();\n            _cts = ct.CanBeCanceled\n                ? CancellationTokenSource.CreateLinkedTokenSource(ct)\n                : new CancellationTokenSource();\n\n            IsTransferInProgress.OnNext(true);\n            IsProgressVisible.OnNext(true);\n            Progress.OnNext(0);\n\n            _log.LogDebug(\"Transfer started\");\n            return new TransferScope(this, _cts.Token);\n        }\n    }\n\n    /// <summary>\n    /// Finish current transfer (hides progress, disposes CTS).\n    /// Safe to call multiple times.\n    /// </summary>\n    public void Complete()\n    {\n        using (_sync.EnterScope())\n        {\n            if (IsTransferInProgress.Value || IsProgressVisible.Value)\n            {\n                IsTransferInProgress.OnNext(false);\n                IsProgressVisible.OnNext(false);\n                Progress.OnNext(0);\n                _log.LogDebug(\"Transfer completed\");\n            }\n\n            _cts?.Dispose();\n            _cts = null;\n        }\n    }\n\n    /// <summary>\n    /// Update progress safely (0..1). Values outside the range are clamped.\n    /// </summary>\n    public void Report(double value)\n    {\n        if (double.IsNaN(value) || double.IsInfinity(value))\n        {\n            value = 0;\n        }\n\n        if (value < 0)\n        {\n            value = 0;\n        }\n\n        if (value > 1)\n        {\n            value = 1;\n        }\n\n        Progress.OnNext(value);\n    }\n\n    /// <summary>\n    /// Tries to cancel the current transfer if any.\n    /// </summary>\n    public bool TryCancel()\n    {\n        using (_sync.EnterScope())\n        {\n            if (_cts == null)\n            {\n                return false;\n            }\n\n            try\n            {\n                _cts.Cancel();\n                _log.LogInformation(\"Transfer cancellation requested\");\n                return true;\n            }\n            catch (ObjectDisposedException)\n            {\n                return false;\n            }\n        }\n    }\n\n    public void Dispose()\n    {\n        Complete();\n        IsTransferInProgress.Dispose();\n        IsProgressVisible.Dispose();\n        Progress.Dispose();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Services/Files/Remote/FtpClientService.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.IO.Abstractions;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\nusing ObservableCollections;\nusing R3;\n\nnamespace Asv.Drones;\n\n/// <summary>\n/// Unified API for manipulating with files via FTP\n/// </summary>\npublic sealed class FtpClientService(\n    IFtpClientEx ftp,\n    ILoggerFactory logFactory,\n    IFileSystem? fileSystem = null\n) : IDisposable\n{\n    private readonly IFileSystem _fileSystem = fileSystem ?? new FileSystem();\n    private readonly ILogger _log = logFactory.CreateLogger<FtpClientService>();\n    private readonly Subject<Unit> _remoteChanged = new();\n    private readonly BusyFlag _busy = new();\n\n    /// <summary>\n    /// Gets <c>true</c> when FTP-operation executes\n    /// and <c>false</c> when the client is not busy anymore.\n    /// </summary>\n    public Observable<bool> RemoteChanging => _busy.IsBusy;\n\n    /// <summary>Gets an observable that emits whenever this service changes the remote file system.</summary>\n    public Observable<Unit> RemoteChanged => _remoteChanged;\n\n    public async Task DownloadAsync(\n        string from,\n        string to,\n        FtpEntryType type,\n        CancellationToken ct,\n        byte partSize = MavlinkFtpHelper.MaxDataSize,\n        IProgress<double>? progress = null\n    )\n    {\n        ArgumentException.ThrowIfNullOrEmpty(from);\n        ArgumentException.ThrowIfNullOrEmpty(to);\n\n        if (!_fileSystem.Directory.Exists(to))\n        {\n            _fileSystem.Directory.CreateDirectory(to);\n        }\n\n        switch (type)\n        {\n            case FtpEntryType.File:\n                await DownloadFileAsync(from, to, ct, partSize, progress);\n                break;\n            case FtpEntryType.Directory:\n                await DownloadDirectoryAsync(from, to, ct, partSize, progress);\n                break;\n            default:\n                throw new ArgumentOutOfRangeException($\"Unsupported FTP entry type: {type}\");\n        }\n    }\n\n    public async Task BurstDownloadAsync(\n        string from,\n        string to,\n        FtpEntryType type,\n        CancellationToken ct,\n        byte partSize = MavlinkFtpHelper.MaxDataSize,\n        IProgress<double>? progress = null\n    )\n    {\n        ArgumentException.ThrowIfNullOrEmpty(from);\n        ArgumentException.ThrowIfNullOrEmpty(to);\n\n        if (!_fileSystem.Directory.Exists(to))\n        {\n            _fileSystem.Directory.CreateDirectory(to);\n        }\n\n        switch (type)\n        {\n            case FtpEntryType.File:\n                await BurstDownloadFileAsync(from, to, ct, partSize, progress);\n                break;\n            case FtpEntryType.Directory:\n                await BurstDownloadDirectoryAsync(from, to, ct, partSize, progress);\n                break;\n            default:\n                throw new ArgumentOutOfRangeException($\"Unsupported FTP entry type: {type}\");\n        }\n    }\n\n    public async Task UploadAsync(\n        string from,\n        string to,\n        FtpEntryType type,\n        CancellationToken ct,\n        IProgress<double>? progress = null\n    )\n    {\n        ArgumentException.ThrowIfNullOrEmpty(from);\n        ArgumentException.ThrowIfNullOrEmpty(to);\n\n        switch (type)\n        {\n            case FtpEntryType.File:\n                await UploadFileAsync(from, to, ct, progress);\n                break;\n            case FtpEntryType.Directory:\n                await UploadDirectoryAsync(from, to, ct, progress);\n                break;\n            default:\n                throw new ArgumentOutOfRangeException(nameof(type), type, null);\n        }\n    }\n\n    private async Task DownloadFileAsync(\n        string remoteFilePath,\n        string localDirectory,\n        CancellationToken ct,\n        byte partSize = MavlinkFtpHelper.MaxDataSize,\n        IProgress<double>? progress = null\n    )\n    {\n        using var busyScope = _busy.Enter();\n        ct.ThrowIfCancellationRequested();\n\n        await using var stream = new FileStream(\n            _fileSystem.Path.Combine(localDirectory, GetRemoteFileName(remoteFilePath)),\n            FileMode.Create,\n            FileAccess.Write,\n            FileShare.None\n        );\n\n        await ftp.DownloadFile(remoteFilePath, stream, progress, partSize, ct);\n        _log.LogInformation(\"Downloaded {path} -> {dir}\", remoteFilePath, localDirectory);\n    }\n\n    private async Task BurstDownloadFileAsync(\n        string remoteFilePath,\n        string localDirectory,\n        CancellationToken ct,\n        byte partSize = MavlinkFtpHelper.MaxDataSize,\n        IProgress<double>? progress = null\n    )\n    {\n        using var busyScope = _busy.Enter();\n        ct.ThrowIfCancellationRequested();\n\n        await using var stream = new FileStream(\n            _fileSystem.Path.Combine(localDirectory, GetRemoteFileName(remoteFilePath)),\n            FileMode.Create,\n            FileAccess.Write,\n            FileShare.None\n        );\n\n        await ftp.BurstDownloadFile(remoteFilePath, stream, progress, partSize, ct);\n        _log.LogInformation(\"Burst-Downloaded {path} -> {dir}\", remoteFilePath, localDirectory);\n    }\n\n    private async Task UploadFileAsync(\n        string localFilePath,\n        string remoteDirectory,\n        CancellationToken ct,\n        IProgress<double>? progress = null\n    )\n    {\n        using var busyScope = _busy.Enter();\n        ct.ThrowIfCancellationRequested();\n\n        await using var stream = new FileStream(\n            localFilePath,\n            FileMode.Open,\n            FileAccess.Read,\n            FileShare.Read\n        );\n\n        // TODO: sends \"(Error to CreateFile: Fail)\" after cancellation\n        await ftp.UploadFile(remoteDirectory, stream, progress, ct);\n        _log.LogInformation(\"Uploaded {file} -> {target}\", localFilePath, remoteDirectory);\n\n        _remoteChanged.OnNext(Unit.Default);\n    }\n\n    private async Task DownloadDirectoryAsync(\n        string remoteDirectoryPath,\n        string localDirectory,\n        CancellationToken ct,\n        byte partSize = MavlinkFtpHelper.MaxDataSize,\n        IProgress<double>? progress = null\n    )\n    {\n        using var busyScope = _busy.Enter();\n        ct.ThrowIfCancellationRequested();\n\n        const char dirSep = MavlinkFtpHelper.DirectorySeparator;\n        var remoteRoot = remoteDirectoryPath.TrimEnd(dirSep);\n        ArgumentException.ThrowIfNullOrEmpty(remoteRoot);\n\n        var remoteRootName = remoteRoot[(remoteRoot.LastIndexOf(dirSep) + 1)..];\n\n        var destRoot = _fileSystem.Path.Combine(localDirectory, remoteRootName);\n        _fileSystem.Directory.CreateDirectory(destRoot);\n\n        var remotePrefix = $\"{remoteRoot}{dirSep}\";\n\n        var dirs = ftp\n            .Entries.Where(e =>\n                e.Value.Type is FtpEntryType.Directory\n                && e.Key.StartsWith(remotePrefix, StringComparison.Ordinal)\n            )\n            .Select(e => e.Key)\n            .Order()\n            .ToList();\n\n        var files = ftp\n            .Entries.Where(e =>\n                e.Value.Type is FtpEntryType.File\n                && e.Key.StartsWith(remotePrefix, StringComparison.Ordinal)\n            )\n            .Select(e => e.Key)\n            .Order()\n            .ToList();\n\n        foreach (var dir in dirs)\n        {\n            ct.ThrowIfCancellationRequested();\n\n            var relative = dir[remotePrefix.Length..].TrimEnd(dirSep);\n            if (relative.Length == 0)\n            {\n                continue;\n            }\n\n            var localDir = _fileSystem.Path.Combine(\n                destRoot,\n                relative.Replace(dirSep, _fileSystem.Path.DirectorySeparatorChar)\n            );\n\n            _fileSystem.Directory.CreateDirectory(localDir);\n        }\n\n        var total = files.Count;\n        if (total == 0)\n        {\n            progress?.Report(1.0);\n            _log.LogInformation(\n                \"Remote directory '{remote}' contains no files\",\n                remoteDirectoryPath\n            );\n            return;\n        }\n\n        var completed = 0;\n        foreach (var file in files)\n        {\n            ct.ThrowIfCancellationRequested();\n\n            var relative = file[remotePrefix.Length..];\n            var localFileDir = _fileSystem.Path.Combine(\n                destRoot,\n                _fileSystem\n                    .Path.GetDirectoryName(relative)!\n                    .Replace(dirSep, _fileSystem.Path.DirectorySeparatorChar)\n            );\n\n            _fileSystem.Directory.CreateDirectory(localFileDir);\n\n            var completedSafe = completed;\n            var nested = progress is null\n                ? null\n                : new Progress<double>(p => progress.Report((completedSafe + p) / total));\n\n            await DownloadFileAsync(file, localFileDir, ct, partSize, nested);\n\n            completed++;\n            progress?.Report((double)completed / total);\n        }\n\n        _log.LogInformation(\n            \"Downloaded directory '{remote}' -> '{dest}' (files: {count})\",\n            remoteDirectoryPath,\n            destRoot,\n            total\n        );\n    }\n\n    private async Task BurstDownloadDirectoryAsync(\n        string remoteDirectoryPath,\n        string localDirectory,\n        CancellationToken ct,\n        byte partSize = MavlinkFtpHelper.MaxDataSize,\n        IProgress<double>? progress = null\n    )\n    {\n        using var busyScope = _busy.Enter();\n        ct.ThrowIfCancellationRequested();\n\n        const char dirSep = MavlinkFtpHelper.DirectorySeparator;\n        var remoteRoot = remoteDirectoryPath.TrimEnd(dirSep);\n        ArgumentException.ThrowIfNullOrEmpty(remoteRoot);\n\n        var remoteRootName = remoteRoot[(remoteRoot.LastIndexOf(dirSep) + 1)..];\n\n        var destRoot = _fileSystem.Path.Combine(localDirectory, remoteRootName);\n        _fileSystem.Directory.CreateDirectory(destRoot);\n\n        var remotePrefix = $\"{remoteRoot}{dirSep}\";\n\n        var dirs = ftp\n            .Entries.Where(e =>\n                e.Value.Type == FtpEntryType.Directory\n                && e.Key.StartsWith(remotePrefix, StringComparison.Ordinal)\n            )\n            .Select(e => e.Key)\n            .Order()\n            .ToList();\n\n        var files = ftp\n            .Entries.Where(e =>\n                e.Value.Type == FtpEntryType.File\n                && e.Key.StartsWith(remotePrefix, StringComparison.Ordinal)\n            )\n            .Select(e => e.Key)\n            .Order()\n            .ToList();\n\n        foreach (var dir in dirs)\n        {\n            ct.ThrowIfCancellationRequested();\n\n            var relative = dir[remotePrefix.Length..].TrimEnd(dirSep);\n            if (relative.Length == 0)\n            {\n                continue;\n            }\n\n            var localDir = _fileSystem.Path.Combine(\n                destRoot,\n                relative.Replace(dirSep, _fileSystem.Path.DirectorySeparatorChar)\n            );\n\n            _fileSystem.Directory.CreateDirectory(localDir);\n        }\n\n        var total = files.Count;\n        if (total == 0)\n        {\n            progress?.Report(1.0);\n            _log.LogInformation(\n                \"Remote directory '{remote}' contains no files\",\n                remoteDirectoryPath\n            );\n            return;\n        }\n\n        var completed = 0;\n        foreach (var file in files)\n        {\n            ct.ThrowIfCancellationRequested();\n\n            var relative = file[remotePrefix.Length..];\n            var localFileDir = _fileSystem.Path.Combine(\n                destRoot,\n                _fileSystem\n                    .Path.GetDirectoryName(relative)!\n                    .Replace(dirSep, _fileSystem.Path.DirectorySeparatorChar)\n            );\n\n            _fileSystem.Directory.CreateDirectory(localFileDir);\n\n            var completedSafe = completed;\n            var nested = progress is null\n                ? null\n                : new Progress<double>(p => progress.Report((completedSafe + p) / total));\n\n            // TODO: sends \"(Timeout to execute 'FILE_TRANSFER_PROTOCOL')\" when tries to TerminateSession(0)\n            await BurstDownloadFileAsync(file, localFileDir, ct, partSize, nested);\n\n            completed++;\n            progress?.Report((double)completed / total);\n        }\n\n        _log.LogInformation(\n            \"Burst-Downloaded directory '{remote}' -> '{dest}' (files: {count})\",\n            remoteDirectoryPath,\n            destRoot,\n            total\n        );\n    }\n\n    private async Task UploadDirectoryAsync(\n        string localDirectoryPath,\n        string remoteDirectory,\n        CancellationToken ct,\n        IProgress<double>? progress = null\n    )\n    {\n        using var busyScope = _busy.Enter();\n        ct.ThrowIfCancellationRequested();\n\n        var localRoot = new DirectoryInfo(localDirectoryPath);\n        if (!localRoot.Exists)\n        {\n            throw new DirectoryNotFoundException(localDirectoryPath);\n        }\n\n        const char rSep = MavlinkFtpHelper.DirectorySeparator;\n        var rootName = localRoot.Name;\n\n        var remoteDirNorm = remoteDirectory;\n        if (!remoteDirNorm.EndsWith(rSep))\n        {\n            remoteDirNorm += rSep;\n        }\n\n        var lastSegment = remoteDirNorm\n            .TrimEnd(rSep)\n            .Split(rSep, StringSplitOptions.RemoveEmptyEntries)\n            .LastOrDefault();\n\n        var remoteRoot = string.Equals(lastSegment, rootName, StringComparison.Ordinal)\n            ? remoteDirNorm\n            : $\"{remoteDirNorm}{rootName}{rSep}\";\n\n        await ftp.Base.CreateDirectory(remoteRoot, ct);\n\n        var localDirs = _fileSystem.Directory.GetDirectories(\n            localDirectoryPath,\n            \"*\",\n            SearchOption.AllDirectories\n        );\n\n        foreach (var dir in localDirs)\n        {\n            var rel = _fileSystem\n                .Path.GetRelativePath(localDirectoryPath, dir)\n                .Replace(_fileSystem.Path.DirectorySeparatorChar, rSep);\n\n            var remoteDir = MavlinkFtpHelper.Combine(remoteRoot, $\"{rel}{rSep}\");\n\n            await ftp.Base.CreateDirectory(remoteDir, ct);\n        }\n\n        var localFiles = _fileSystem.Directory.GetFiles(\n            localDirectoryPath,\n            \"*\",\n            SearchOption.AllDirectories\n        );\n        var totalBytes = localFiles.Sum(f => new FileInfo(f).Length);\n        long uploaded = 0;\n\n        foreach (var file in localFiles)\n        {\n            ct.ThrowIfCancellationRequested();\n\n            var rel = _fileSystem\n                .Path.GetRelativePath(localDirectoryPath, file)\n                .Replace(_fileSystem.Path.DirectorySeparatorChar, rSep);\n\n            var remoteFilePath = MavlinkFtpHelper.Combine(remoteRoot, rel);\n\n            await using var stream = new FileStream(\n                file,\n                FileMode.Open,\n                FileAccess.Read,\n                FileShare.Read\n            );\n\n            var fileLength = stream.Length;\n            var uploadedSafe = uploaded;\n            var fileProgress =\n                progress == null\n                    ? null\n                    : new Progress<double>(p =>\n                    {\n                        var current = uploadedSafe + (long)(fileLength * p);\n                        progress.Report(current / (double)totalBytes);\n                    });\n\n            await ftp.UploadFile(remoteFilePath, stream, fileProgress, ct);\n\n            uploaded += fileLength;\n            progress?.Report(uploaded / (double)totalBytes);\n            _log.LogInformation(\"Uploaded {File} -> {Target}\", file, remoteFilePath);\n        }\n\n        _log.LogInformation(\n            \"Uploaded folder {local} -> {remote} (files: {count})\",\n            localDirectoryPath,\n            remoteDirectory,\n            localFiles.Length\n        );\n\n        _remoteChanged.OnNext(Unit.Default);\n    }\n\n    public async Task RemoveDirectoryAsync(\n        string path,\n        bool recursive = true,\n        CancellationToken ct = default\n    )\n    {\n        try\n        {\n            if (recursive)\n            {\n                await RemoveDirectoryRecursive(path);\n            }\n            else\n            {\n                await ftp.Base.RemoveDirectory(path, ct);\n            }\n\n            _log.LogInformation(\"Directory removed: {path}\", path);\n        }\n        catch (Exception e)\n        {\n            _log.LogError(e, \"Failed to remove directory\");\n        }\n        finally\n        {\n            _remoteChanged.OnNext(Unit.Default);\n        }\n\n        return;\n\n        async Task RemoveDirectoryRecursive(string directoryPath)\n        {\n            var itemsInDir = ftp.Entries.Where(x => x.Value.ParentPath == directoryPath).ToList();\n\n            foreach (var e in itemsInDir)\n            {\n                switch (e.Value.Type)\n                {\n                    case FtpEntryType.Directory:\n                        await RemoveDirectoryRecursive(e.Key);\n                        break;\n                    case FtpEntryType.File:\n                        await ftp.Base.RemoveFile(e.Key, ct);\n                        break;\n                    default:\n                        _log.LogError(\"Unknown FTP entry type: ({type})\", e.Value.Type);\n                        break;\n                }\n            }\n\n            await ftp.Base.RemoveDirectory(directoryPath, ct);\n        }\n    }\n\n    public async Task RemoveFileAsync(string path, CancellationToken ct = default)\n    {\n        await ftp.Base.RemoveFile(path, ct);\n        _log.LogInformation(\"File removed: {path}\", path);\n        _remoteChanged.OnNext(Unit.Default);\n    }\n\n    public async Task CreateDirectoryAsync(string path, CancellationToken ct = default)\n    {\n        var folderNumber = 1;\n        while (true)\n        {\n            var name = $\"Folder{folderNumber}{MavlinkFtpHelper.DirectorySeparator}\";\n            ftp.Entries.FirstOrDefault(x => x.Key == $\"{path}{name}\").Deconstruct(out var k, out _);\n            if (!string.IsNullOrEmpty(k))\n            {\n                folderNumber++;\n                continue;\n            }\n\n            name =\n                path == $\"{MavlinkFtpHelper.DirectorySeparator}\"\n                    ? $\"{MavlinkFtpHelper.DirectorySeparator}{name}\"\n                    : $\"{path}{MavlinkFtpHelper.DirectorySeparator}{name}\";\n\n            _log.LogInformation(\"Creating directory '{Path}'\", name);\n            await ftp.Base.CreateDirectory(name, ct);\n            break;\n        }\n        _remoteChanged.OnNext(Unit.Default);\n    }\n\n    public async Task<uint> CalculateCrc32Async(string filePath, CancellationToken ct = default)\n    {\n        try\n        {\n            var crc32 = await ftp.Base.CalcFileCrc32(filePath, ct);\n            _log.LogInformation(\"File crc32: {crc32}\", crc32);\n            return crc32;\n        }\n        catch (FileNotFoundException e)\n        {\n            _log.LogError(e, \"File '{Path}' not found\", filePath);\n        }\n\n        return 0U;\n    }\n\n    public async Task<string> RenameAsync(\n        string oldPath,\n        string newPath,\n        CancellationToken ct = default\n    )\n    {\n        var parentDir = ftp.Entries[oldPath].ParentPath;\n\n        try\n        {\n            if (ftp.Entries.ContainsKey(newPath))\n            {\n                var fullName = MavlinkFtpHelper.GetFileName(newPath);\n                var baseName = _fileSystem.Path.GetFileNameWithoutExtension(fullName);\n                var ext = _fileSystem.Path.GetExtension(fullName);\n                var counter = 1;\n\n                while (ftp.Entries.ContainsKey(newPath))\n                {\n                    newPath = MavlinkFtpHelper.Combine(parentDir, $\"{baseName} ({counter++}){ext}\");\n                }\n            }\n            else\n            {\n                await ftp.Base.Rename(oldPath, newPath, ct);\n                _log.LogInformation(\"File renamed to '{new}'\", newPath);\n            }\n        }\n        catch (FileNotFoundException e)\n        {\n            _log.LogError(e, \"Failed to rename file. Incorrect path: {Path}\", oldPath);\n        }\n\n        _remoteChanged.OnNext(Unit.Default);\n\n        return newPath;\n    }\n\n    public async Task<IReadOnlyObservableDictionary<string, IFtpEntry>> Refresh(\n        CancellationToken ct = default\n    )\n    {\n        await ftp.Refresh(MavlinkFtpHelper.DirectorySeparator.ToString(), true, ct);\n        return ftp.Entries;\n    }\n\n    private static string GetRemoteFileName(string path)\n    {\n        if (string.IsNullOrEmpty(path))\n        {\n            return path;\n        }\n\n        var slash = path.LastIndexOf(MavlinkFtpHelper.DirectorySeparator);\n        return slash >= 0 ? path[(slash + 1)..] : path;\n    }\n\n    public void Dispose()\n    {\n        _busy.Dispose();\n        _remoteChanged.OnCompleted();\n        ftp.Base.ResetSessions();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Core/Services/Files/Remote/RemoteEntriesSync.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing Asv.Avalonia;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Asv.Modeling;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\nusing ObservableCollections;\nusing R3;\n\nnamespace Asv.Drones;\n\n/// <summary>\n/// Synchronizes source dictionary of IFtpEntry into a flat VM list.\n/// Applies canonicalization and centralizes add/replace/remove logic.\n/// </summary>\npublic sealed class RemoteEntriesSync : IDisposable\n{\n    private readonly ObservableDictionary<string, IFtpEntry> _source;\n    private readonly ObservableList<IBrowserItemViewModel> _target;\n    private readonly Func<string, IFtpEntry, IBrowserItemViewModel> _factory;\n    private readonly ILogger _log;\n\n    private readonly IDisposable _subscriptions;\n    private readonly char _separator;\n\n    public RemoteEntriesSync(\n        ObservableDictionary<string, IFtpEntry> source,\n        ObservableList<IBrowserItemViewModel> target,\n        Func<string, IFtpEntry, IBrowserItemViewModel> factory,\n        ILoggerFactory? loggerFactory = null,\n        char separator = MavlinkFtpHelper.DirectorySeparator\n    )\n    {\n        ArgumentNullException.ThrowIfNull(source);\n        ArgumentNullException.ThrowIfNull(target);\n        ArgumentNullException.ThrowIfNull(factory);\n        _separator = separator;\n        _source = source;\n        _target = target;\n        _factory = factory;\n        _log = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<RemoteEntriesSync>();\n\n        var sub1 = _source\n            .ObserveAdd()\n            .Subscribe(kv =>\n            {\n                var pair = kv.Value;\n                var isDir = pair.Value.Type is FtpEntryType.Directory;\n                var key = FtpBrowserPath.Normalize(pair.Key, isDir, _separator);\n\n                var existing = _target.FirstOrDefault(i => i.Path == key);\n                if (existing != null)\n                {\n                    TrySyncMetadata(existing, pair.Value);\n                    return;\n                }\n\n                _target.Add(_factory(key, pair.Value));\n            });\n\n        var sub2 = _source\n            .ObserveRemove()\n            .Subscribe(kv =>\n            {\n                var pair = kv.Value;\n                var isDir = pair.Value.Type is FtpEntryType.Directory;\n                var key = FtpBrowserPath.Normalize(pair.Key, isDir, _separator);\n\n                var victim = _target.FirstOrDefault(i => i.Path == key);\n                if (victim != null)\n                {\n                    _target.Remove(victim);\n                }\n            });\n\n        var sub3 = _source\n            .ObserveReplace()\n            .Subscribe(kv =>\n            {\n                var oldPair = kv.OldValue;\n                var newPair = kv.NewValue;\n\n                var isOldDir = oldPair.Value.Type is FtpEntryType.Directory;\n                var oldKey = FtpBrowserPath.Normalize(oldPair.Key, isOldDir, _separator);\n\n                var isNewDir = newPair.Value.Type is FtpEntryType.Directory;\n                var newKey = FtpBrowserPath.Normalize(newPair.Key, isNewDir, _separator);\n\n                var victim =\n                    _target.FirstOrDefault(i => i.Path == oldKey)\n                    ?? _target.FirstOrDefault(i => i.Path == newKey);\n\n                if (victim != null)\n                {\n                    _target.Remove(victim);\n                }\n\n                var existing = _target.FirstOrDefault(i => i.Path == newKey);\n                if (existing != null)\n                {\n                    TrySyncMetadata(existing, newPair.Value);\n                    return;\n                }\n\n                _target.Add(_factory(newKey, newPair.Value));\n            });\n\n        var sub4 = _source.ObserveReset().Subscribe(_ => _target.RemoveAll());\n\n        _subscriptions = Disposable.Combine(sub1, sub2, sub3, sub4);\n\n        _log.LogDebug(\"RemoteEntriesSync started\");\n    }\n\n    private static void TrySyncMetadata(IBrowserItemViewModel vm, IFtpEntry entry)\n    {\n        if (vm is BrowserItemViewModel b && entry.Name != b.Name)\n        {\n            b.Name = entry.Name;\n        }\n    }\n\n    public void Dispose()\n    {\n        _subscriptions.Dispose();\n        _log.LogDebug(\"RemoteEntriesSync stopped\");\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/RS.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code was generated by a tool.\n//\n//     Changes to this file may cause incorrect behavior and will be lost if\n//     the code is regenerated.\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace Asv.Drones {\n    using System;\n    \n    \n    /// <summary>\n    ///   A strongly-typed resource class, for looking up localized strings, etc.\n    /// </summary>\n    // This class was auto-generated by the StronglyTypedResourceBuilder\n    // class via a tool like ResGen or Visual Studio.\n    // To add or remove a member, edit your .ResX file then rerun ResGen\n    // with the /str option, or rebuild your VS project.\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"4.0.0.0\")]\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    public class RS {\n        \n        private static global::System.Resources.ResourceManager resourceMan;\n        \n        private static global::System.Globalization.CultureInfo resourceCulture;\n        \n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\n        internal RS() {\n        }\n        \n        /// <summary>\n        ///   Returns the cached ResourceManager instance used by this class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        public static global::System.Resources.ResourceManager ResourceManager {\n            get {\n                if (object.ReferenceEquals(resourceMan, null)) {\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"Asv.Drones.RS\", typeof(RS).Assembly);\n                    resourceMan = temp;\n                }\n                return resourceMan;\n            }\n        }\n        \n        /// <summary>\n        ///   Overrides the current thread's CurrentUICulture property for all\n        ///   resource lookups using this strongly typed resource class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        public static global::System.Globalization.CultureInfo Culture {\n            get {\n                return resourceCulture;\n            }\n            set {\n                resourceCulture = value;\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Altitude.\n        /// </summary>\n        public static string AltitudeIndicatorStyles_ToolTip_Altitude {\n            get {\n                return ResourceManager.GetString(\"AltitudeIndicatorStyles_ToolTip_Altitude\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Compass.\n        /// </summary>\n        public static string AltitudeIndicatorStyles_ToolTip_Compass {\n            get {\n                return ResourceManager.GetString(\"AltitudeIndicatorStyles_ToolTip_Compass\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Velocity.\n        /// </summary>\n        public static string AltitudeIndicatorStyles_ToolTip_Velocity {\n            get {\n                return ResourceManager.GetString(\"AltitudeIndicatorStyles_ToolTip_Velocity\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Vibration.\n        /// </summary>\n        public static string AltitudeIndicatorStyles_ToolTip_Vibration {\n            get {\n                return ResourceManager.GetString(\"AltitudeIndicatorStyles_ToolTip_Vibration\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Enter the size of a download block.\n        /// </summary>\n        public static string BurstDownloadDialogView_Description_Text {\n            get {\n                return ResourceManager.GetString(\"BurstDownloadDialogView_Description_Text\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that download an element by burst.\n        /// </summary>\n        public static string BurstDownloadItemCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"BurstDownloadItemCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Burst-Download.\n        /// </summary>\n        public static string BurstDownloadItemCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"BurstDownloadItemCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that checks a cyclic redundancy of a file (crc32).\n        /// </summary>\n        public static string CalculateCrc32Command_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"CalculateCrc32Command_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Calculate CRC32.\n        /// </summary>\n        public static string CalculateCrc32Command_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"CalculateCrc32Command_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that changes frame type.\n        /// </summary>\n        public static string ChangeFrameTypeCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"ChangeFrameTypeCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Change frame type.\n        /// </summary>\n        public static string ChangeFrameTypeCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"ChangeFrameTypeCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that clears all packets.\n        /// </summary>\n        public static string ClearAllPacketsCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"ClearAllPacketsCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Clear all packets.\n        /// </summary>\n        public static string ClearAllPacketsCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"ClearAllPacketsCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that creates a folder.\n        /// </summary>\n        public static string CreateDirectoryCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"CreateDirectoryCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Create a folder.\n        /// </summary>\n        public static string CreateDirectoryCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"CreateDirectoryCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that downloads remote entries.\n        /// </summary>\n        public static string DownloadItemCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"DownloadItemCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Download.\n        /// </summary>\n        public static string DownloadItemCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"DownloadItemCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that exports packets to a CSV file.\n        /// </summary>\n        public static string ExportPacketsToCsvCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"ExportPacketsToCsvCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Export packets to CSV.\n        /// </summary>\n        public static string ExportPacketsToCsvCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"ExportPacketsToCsvCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Connect to the device&apos;s FTP service....\n        /// </summary>\n        public static string FileBrowserView_AwaitingScreen_Description {\n            get {\n                return ResourceManager.GetString(\"FileBrowserView_AwaitingScreen_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Connecting.\n        /// </summary>\n        public static string FileBrowserView_AwaitingScreen_Header {\n            get {\n                return ResourceManager.GetString(\"FileBrowserView_AwaitingScreen_Header\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Burst download.\n        /// </summary>\n        public static string FileBrowserView_Button_BurstDownload_Content {\n            get {\n                return ResourceManager.GetString(\"FileBrowserView_Button_BurstDownload_Content\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Download.\n        /// </summary>\n        public static string FileBrowserView_Button_Download_Content {\n            get {\n                return ResourceManager.GetString(\"FileBrowserView_Button_Download_Content\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Local Search.\n        /// </summary>\n        public static string FileBrowserView_Watermark_Local {\n            get {\n                return ResourceManager.GetString(\"FileBrowserView_Watermark_Local\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Remote Search.\n        /// </summary>\n        public static string FileBrowserView_Watermark_Remote {\n            get {\n                return ResourceManager.GetString(\"FileBrowserView_Watermark_Remote\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Download.\n        /// </summary>\n        public static string FileBrowserViewModel_BurstDownloadDialog_PrimaryButtonText {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_BurstDownloadDialog_PrimaryButtonText\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Cancel.\n        /// </summary>\n        public static string FileBrowserViewModel_BurstDownloadDialog_SecondaryButtonText {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_BurstDownloadDialog_SecondaryButtonText\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Burst download.\n        /// </summary>\n        public static string FileBrowserViewModel_BurstDownloadDialog_Title {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_BurstDownloadDialog_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Do you want to download file &apos;{0}&apos;?.\n        /// </summary>\n        public static string FileBrowserViewModel_DownloadDialog_Message {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_DownloadDialog_Message\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Download.\n        /// </summary>\n        public static string FileBrowserViewModel_DownloadDialog_Title {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_DownloadDialog_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to File downloaded successfully: {0}.\n        /// </summary>\n        public static string FileBrowserViewModel_FileDownloadedSuccessfully {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_FileDownloadedSuccessfully\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Do you want to remove the item?.\n        /// </summary>\n        public static string FileBrowserViewModel_RemoveDialog_Message {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_RemoveDialog_Message\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Remove.\n        /// </summary>\n        public static string FileBrowserViewModel_RemoveDialog_Title {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_RemoveDialog_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Input new name.\n        /// </summary>\n        public static string FileBrowserViewModel_RenameDialog_Message {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_RenameDialog_Message\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Accept.\n        /// </summary>\n        public static string FileBrowserViewModel_RenameDialog_PrimaryButtonText {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_RenameDialog_PrimaryButtonText\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Cancel.\n        /// </summary>\n        public static string FileBrowserViewModel_RenameDialog_SecondaryButtonText {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_RenameDialog_SecondaryButtonText\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Rename.\n        /// </summary>\n        public static string FileBrowserViewModel_RenameDialog_Title {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_RenameDialog_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Browser.\n        /// </summary>\n        public static string FileBrowserViewModel_Title {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Do you want to upload file &apos;{0}&apos; to device?.\n        /// </summary>\n        public static string FileBrowserViewModel_UploadingDialog_Message {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_UploadingDialog_Message\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Uploading.\n        /// </summary>\n        public static string FileBrowserViewModel_UploadingDialog_Title {\n            get {\n                return ResourceManager.GetString(\"FileBrowserViewModel_UploadingDialog_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that finds a file selected in a remote tree in a local tree.\n        /// </summary>\n        public static string FindFileOnLocalCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"FindFileOnLocalCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Find file.\n        /// </summary>\n        public static string FindFileOnLocalCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"FindFileOnLocalCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Flight.\n        /// </summary>\n        public static string FlightPageViewModel_Title {\n            get {\n                return ResourceManager.GetString(\"FlightPageViewModel_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to 2D position Fix.\n        /// </summary>\n        public static string GpsFixType_GpsFixType2dFix {\n            get {\n                return ResourceManager.GetString(\"GpsFixType_GpsFixType2dFix\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to 3D position Fix.\n        /// </summary>\n        public static string GpsFixType_GpsFixType3dFix {\n            get {\n                return ResourceManager.GetString(\"GpsFixType_GpsFixType3dFix\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to DGPS/SBAS.\n        /// </summary>\n        public static string GpsFixType_GpsFixTypeDgps {\n            get {\n                return ResourceManager.GetString(\"GpsFixType_GpsFixTypeDgps\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to No GPS connected.\n        /// </summary>\n        public static string GpsFixType_GpsFixTypeNoGps {\n            get {\n                return ResourceManager.GetString(\"GpsFixType_GpsFixTypeNoGps\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to PPP 3D position.\n        /// </summary>\n        public static string GpsFixType_GpsFixTypePpp {\n            get {\n                return ResourceManager.GetString(\"GpsFixType_GpsFixTypePpp\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Rtk Fixed.\n        /// </summary>\n        public static string GpsFixType_GpsFixTypeRtkFixed {\n            get {\n                return ResourceManager.GetString(\"GpsFixType_GpsFixTypeRtkFixed\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to RTK Float.\n        /// </summary>\n        public static string GpsFixType_GpsFixTypeRtkFloat {\n            get {\n                return ResourceManager.GetString(\"GpsFixType_GpsFixTypeRtkFloat\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Static Fix.\n        /// </summary>\n        public static string GpsFixType_GpsFixTypeStatic {\n            get {\n                return ResourceManager.GetString(\"GpsFixType_GpsFixTypeStatic\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to E.\n        /// </summary>\n        public static string HeadingScaleItem_Direction_E {\n            get {\n                return ResourceManager.GetString(\"HeadingScaleItem_Direction_E\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to N.\n        /// </summary>\n        public static string HeadingScaleItem_Direction_N {\n            get {\n                return ResourceManager.GetString(\"HeadingScaleItem_Direction_N\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to NE.\n        /// </summary>\n        public static string HeadingScaleItem_Direction_NE {\n            get {\n                return ResourceManager.GetString(\"HeadingScaleItem_Direction_NE\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to NW.\n        /// </summary>\n        public static string HeadingScaleItem_Direction_NW {\n            get {\n                return ResourceManager.GetString(\"HeadingScaleItem_Direction_NW\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to S.\n        /// </summary>\n        public static string HeadingScaleItem_Direction_S {\n            get {\n                return ResourceManager.GetString(\"HeadingScaleItem_Direction_S\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to SE.\n        /// </summary>\n        public static string HeadingScaleItem_Direction_SE {\n            get {\n                return ResourceManager.GetString(\"HeadingScaleItem_Direction_SE\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to SW.\n        /// </summary>\n        public static string HeadingScaleItem_Direction_SW {\n            get {\n                return ResourceManager.GetString(\"HeadingScaleItem_Direction_SW\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to W.\n        /// </summary>\n        public static string HeadingScaleItem_Direction_W {\n            get {\n                return ResourceManager.GetString(\"HeadingScaleItem_Direction_W\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Edit mavlink device parameters.\n        /// </summary>\n        public static string HomePageParamsDeviceItemAction_ActionViewModel_Description {\n            get {\n                return ResourceManager.GetString(\"HomePageParamsDeviceItemAction_ActionViewModel_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Params editor.\n        /// </summary>\n        public static string HomePageParamsDeviceItemAction_ActionViewModel_Header {\n            get {\n                return ResourceManager.GetString(\"HomePageParamsDeviceItemAction_ActionViewModel_Header\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Connecting to the device....\n        /// </summary>\n        public static string MavParamsPageView_AwaitingScreen_Description {\n            get {\n                return ResourceManager.GetString(\"MavParamsPageView_AwaitingScreen_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Connecting.\n        /// </summary>\n        public static string MavParamsPageView_AwaitingScreen_Header {\n            get {\n                return ResourceManager.GetString(\"MavParamsPageView_AwaitingScreen_Header\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Cancel.\n        /// </summary>\n        public static string MavParamsPageView_CancelButton_Content {\n            get {\n                return ResourceManager.GetString(\"MavParamsPageView_CancelButton_Content\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Loading.\n        /// </summary>\n        public static string MavParamsPageView_Loading_Text {\n            get {\n                return ResourceManager.GetString(\"MavParamsPageView_Loading_Text\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Unknown.\n        /// </summary>\n        public static string MavParamsPageViewModel_DeviceName_Unknown {\n            get {\n                return ResourceManager.GetString(\"MavParamsPageViewModel_DeviceName_Unknown\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Params.\n        /// </summary>\n        public static string MavParamsPageViewModel_Title {\n            get {\n                return ResourceManager.GetString(\"MavParamsPageViewModel_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Download Mission.\n        /// </summary>\n        public static string MissionProgressView_DownloadButton_Text {\n            get {\n                return ResourceManager.GetString(\"MissionProgressView_DownloadButton_Text\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Downloading mission.\n        /// </summary>\n        public static string MissionProgressView_DownLoadingMission {\n            get {\n                return ResourceManager.GetString(\"MissionProgressView_DownLoadingMission\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Home.\n        /// </summary>\n        public static string MissionProgressView_HomeDistance {\n            get {\n                return ResourceManager.GetString(\"MissionProgressView_HomeDistance\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Mission.\n        /// </summary>\n        public static string MissionProgressView_MissionDistanceRTT {\n            get {\n                return ResourceManager.GetString(\"MissionProgressView_MissionDistanceRTT\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Refresh mission.\n        /// </summary>\n        public static string MissionProgressView_RefreshButton_Text {\n            get {\n                return ResourceManager.GetString(\"MissionProgressView_RefreshButton_Text\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to before complete.\n        /// </summary>\n        public static string MissionProgressView_SubStatusText {\n            get {\n                return ResourceManager.GetString(\"MissionProgressView_SubStatusText\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Target.\n        /// </summary>\n        public static string MissionProgressView_TargetDistance {\n            get {\n                return ResourceManager.GetString(\"MissionProgressView_TargetDistance\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Mission progress.\n        /// </summary>\n        public static string MissionProgressView_Title {\n            get {\n                return ResourceManager.GetString(\"MissionProgressView_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Total.\n        /// </summary>\n        public static string MissionProgressView_TotalDistanceRTT {\n            get {\n                return ResourceManager.GetString(\"MissionProgressView_TotalDistanceRTT\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to min.\n        /// </summary>\n        public static string MissionProgressViewModel_MissionFlightTime_Symbol {\n            get {\n                return ResourceManager.GetString(\"MissionProgressViewModel_MissionFlightTime_Symbol\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Motor.\n        /// </summary>\n        public static string MotorItemView_Label_MotorId {\n            get {\n                return ResourceManager.GetString(\"MotorItemView_Label_MotorId\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to PWM.\n        /// </summary>\n        public static string MotorItemView_MonitoringLabel_Pwm {\n            get {\n                return ResourceManager.GetString(\"MotorItemView_MonitoringLabel_Pwm\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Servo.\n        /// </summary>\n        public static string MotorItemView_MonitoringLabel_Servo {\n            get {\n                return ResourceManager.GetString(\"MotorItemView_MonitoringLabel_Servo\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to START/STOP.\n        /// </summary>\n        public static string MotorItemView_ToolTip_StartStop {\n            get {\n                return ResourceManager.GetString(\"MotorItemView_ToolTip_StartStop\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to N/A.\n        /// </summary>\n        public static string NotANumber {\n            get {\n                return ResourceManager.GetString(\"NotANumber\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that opens file browser.\n        /// </summary>\n        public static string OpenFileBrowserCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"OpenFileBrowserCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open file browser.\n        /// </summary>\n        public static string OpenFileBrowserCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"OpenFileBrowserCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that opens flight mode page.\n        /// </summary>\n        public static string OpenFlightModeCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"OpenFlightModeCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open flight mode.\n        /// </summary>\n        public static string OpenFlightModeCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"OpenFlightModeCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command opens mavlink params editor.\n        /// </summary>\n        public static string OpenMavParamsCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"OpenMavParamsCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open mavlink params.\n        /// </summary>\n        public static string OpenMavParamsCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"OpenMavParamsCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that opens packet viewer.\n        /// </summary>\n        public static string OpenPacketViewerCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"OpenPacketViewerCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open packet viewer.\n        /// </summary>\n        public static string OpenPacketViewerCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"OpenPacketViewerCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that opens setup page for the drone.\n        /// </summary>\n        public static string OpenSetupCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"OpenSetupCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Open Setup page.\n        /// </summary>\n        public static string OpenSetupCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"OpenSetupCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Date.\n        /// </summary>\n        public static string PacketMessageViewModel_CsvColumn_Date {\n            get {\n                return ResourceManager.GetString(\"PacketMessageViewModel_CsvColumn_Date\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Message.\n        /// </summary>\n        public static string PacketMessageViewModel_CsvColumn_Message {\n            get {\n                return ResourceManager.GetString(\"PacketMessageViewModel_CsvColumn_Message\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Source.\n        /// </summary>\n        public static string PacketMessageViewModel_CsvColumn_Source {\n            get {\n                return ResourceManager.GetString(\"PacketMessageViewModel_CsvColumn_Source\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Type.\n        /// </summary>\n        public static string PacketMessageViewModel_CsvColumn_Type {\n            get {\n                return ResourceManager.GetString(\"PacketMessageViewModel_CsvColumn_Type\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Sources.\n        /// </summary>\n        public static string PacketViewerView_ExpanderFilterBySources_Header {\n            get {\n                return ResourceManager.GetString(\"PacketViewerView_ExpanderFilterBySources_Header\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Types.\n        /// </summary>\n        public static string PacketViewerView_ExpanderFilterByTypes_Header {\n            get {\n                return ResourceManager.GetString(\"PacketViewerView_ExpanderFilterByTypes_Header\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Check All.\n        /// </summary>\n        public static string PacketViewerView_Filters_CheckAll {\n            get {\n                return ResourceManager.GetString(\"PacketViewerView_Filters_CheckAll\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Clear All.\n        /// </summary>\n        public static string PacketViewerView_ToolTip_ClearAll {\n            get {\n                return ResourceManager.GetString(\"PacketViewerView_ToolTip_ClearAll\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to PLAY/PAUSE.\n        /// </summary>\n        public static string PacketViewerView_ToolTip_PlayPause {\n            get {\n                return ResourceManager.GetString(\"PacketViewerView_ToolTip_PlayPause\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Save.\n        /// </summary>\n        public static string PacketViewerView_ToolTip_Save {\n            get {\n                return ResourceManager.GetString(\"PacketViewerView_ToolTip_Save\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Cancel.\n        /// </summary>\n        public static string PacketViewerViewModel_SavePacketMessagesDialog_CloseButtonText {\n            get {\n                return ResourceManager.GetString(\"PacketViewerViewModel_SavePacketMessagesDialog_CloseButtonText\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Accept.\n        /// </summary>\n        public static string PacketViewerViewModel_SavePacketMessagesDialog_PrimaryButtonText {\n            get {\n                return ResourceManager.GetString(\"PacketViewerViewModel_SavePacketMessagesDialog_PrimaryButtonText\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Select Separator:.\n        /// </summary>\n        public static string PacketViewerViewModel_SavePacketMessagesDialog_Title {\n            get {\n                return ResourceManager.GetString(\"PacketViewerViewModel_SavePacketMessagesDialog_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Packet Viewer.\n        /// </summary>\n        public static string PacketViewerViewModel_Title {\n            get {\n                return ResourceManager.GetString(\"PacketViewerViewModel_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Remove all pinned parameters.\n        /// </summary>\n        public static string ParametersEditorPageView_PinsOffButton_ToolTip {\n            get {\n                return ResourceManager.GetString(\"ParametersEditorPageView_PinsOffButton_ToolTip\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Star this parameter.\n        /// </summary>\n        public static string ParametersEditorPageView_StarButton_ToolTip {\n            get {\n                return ResourceManager.GetString(\"ParametersEditorPageView_StarButton_ToolTip\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Show only starred parameters.\n        /// </summary>\n        public static string ParametersEditorPageView_StarsToggleButton_ToolTip {\n            get {\n                return ResourceManager.GetString(\"ParametersEditorPageView_StarsToggleButton_ToolTip\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Update all parameters.\n        /// </summary>\n        public static string ParametersEditorPageView_UpdateButton_ToolTip {\n            get {\n                return ResourceManager.GetString(\"ParametersEditorPageView_UpdateButton_ToolTip\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Search.\n        /// </summary>\n        public static string ParametersEditorPageViewModel_Search {\n            get {\n                return ResourceManager.GetString(\"ParametersEditorPageViewModel_Search\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Total: {0}.\n        /// </summary>\n        public static string ParametersEditorPageViewModel_Total {\n            get {\n                return ResourceManager.GetString(\"ParametersEditorPageViewModel_Total\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Reboot required.\n        /// </summary>\n        public static string ParamItemView_InlineNotification_RebootRequired {\n            get {\n                return ResourceManager.GetString(\"ParamItemView_InlineNotification_RebootRequired\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to On/ off pin for this parameter.\n        /// </summary>\n        public static string ParamItemView_PinToggleButton_ToolTip {\n            get {\n                return ResourceManager.GetString(\"ParamItemView_PinToggleButton_ToolTip\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Refresh.\n        /// </summary>\n        public static string ParamItemView_UpdateButton_Text {\n            get {\n                return ResourceManager.GetString(\"ParamItemView_UpdateButton_Text\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Update this parameter from UAV.\n        /// </summary>\n        public static string ParamItemView_UpdateButton_ToolTip {\n            get {\n                return ResourceManager.GetString(\"ParamItemView_UpdateButton_ToolTip\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Set.\n        /// </summary>\n        public static string ParamItemView_WriteButton_Text {\n            get {\n                return ResourceManager.GetString(\"ParamItemView_WriteButton_Text\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Write this parameter to UAV.\n        /// </summary>\n        public static string ParamItemView_WriteButton_ToolTip {\n            get {\n                return ResourceManager.GetString(\"ParamItemView_WriteButton_ToolTip\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Cancel.\n        /// </summary>\n        public static string ParamPageViewModel_DataLossDialog_CloseButtonText {\n            get {\n                return ResourceManager.GetString(\"ParamPageViewModel_DataLossDialog_CloseButtonText\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to You have unsaved changes. Do you want to save them?.\n        /// </summary>\n        public static string ParamPageViewModel_DataLossDialog_Content {\n            get {\n                return ResourceManager.GetString(\"ParamPageViewModel_DataLossDialog_Content\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Save.\n        /// </summary>\n        public static string ParamPageViewModel_DataLossDialog_PrimaryButtonText {\n            get {\n                return ResourceManager.GetString(\"ParamPageViewModel_DataLossDialog_PrimaryButtonText\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Don&apos;t save.\n        /// </summary>\n        public static string ParamPageViewModel_DataLossDialog_SecondaryButtonText {\n            get {\n                return ResourceManager.GetString(\"ParamPageViewModel_DataLossDialog_SecondaryButtonText\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Potential data loss warning.\n        /// </summary>\n        public static string ParamPageViewModel_DataLossDialog_Title {\n            get {\n                return ResourceManager.GetString(\"ParamPageViewModel_DataLossDialog_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that updates param from the mavlink device.\n        /// </summary>\n        public static string ReadParamCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"ReadParamCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Update param.\n        /// </summary>\n        public static string ReadParamCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"ReadParamCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that removes item.\n        /// </summary>\n        public static string RemoveItemCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"RemoveItemCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Remove item.\n        /// </summary>\n        public static string RemoveItemCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"RemoveItemCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that commits an item renaming.\n        /// </summary>\n        public static string RenameItemCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"RenameItemCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Commit item rename.\n        /// </summary>\n        public static string RenameItemCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"RenameItemCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to TAB.\n        /// </summary>\n        public static string SavePacketMessagesDialogView_Separator_Tab {\n            get {\n                return ResourceManager.GetString(\"SavePacketMessagesDialogView_Separator_Tab\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Take off.\n        /// </summary>\n        public static string SetAltitudeDialogViewModel_ApplyDialog_PrimaryButton_TakeOff {\n            get {\n                return ResourceManager.GetString(\"SetAltitudeDialogViewModel_ApplyDialog_PrimaryButton_TakeOff\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Cancel.\n        /// </summary>\n        public static string SetAltitudeDialogViewModel_ApplyDialog_SecondaryButton_Cancel {\n            get {\n                return ResourceManager.GetString(\"SetAltitudeDialogViewModel_ApplyDialog_SecondaryButton_Cancel\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Set.\n        /// </summary>\n        public static string SetupFrameTypeView_ApplyFrame {\n            get {\n                return ResourceManager.GetString(\"SetupFrameTypeView_ApplyFrame\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Current frame.\n        /// </summary>\n        public static string SetupFrameTypeView_CurrentFrame {\n            get {\n                return ResourceManager.GetString(\"SetupFrameTypeView_CurrentFrame\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Description.\n        /// </summary>\n        public static string SetupFrameTypeView_CurrentFrame_Meta {\n            get {\n                return ResourceManager.GetString(\"SetupFrameTypeView_CurrentFrame_Meta\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Refresh current frame.\n        /// </summary>\n        public static string SetupFrameTypeView_CurrentFrame_Refresh {\n            get {\n                return ResourceManager.GetString(\"SetupFrameTypeView_CurrentFrame_Refresh\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Metadata.\n        /// </summary>\n        public static string SetupFrameTypeView_FrameMetadata {\n            get {\n                return ResourceManager.GetString(\"SetupFrameTypeView_FrameMetadata\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Please wait.\n        /// </summary>\n        public static string SetupFrameTypeView_Loader_Description {\n            get {\n                return ResourceManager.GetString(\"SetupFrameTypeView_Loader_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Loading....\n        /// </summary>\n        public static string SetupFrameTypeView_Loader_Header {\n            get {\n                return ResourceManager.GetString(\"SetupFrameTypeView_Loader_Header\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Do you want to change frame type?.\n        /// </summary>\n        public static string SetupFrameTypeViewModel_ApplyConfirmation_Message {\n            get {\n                return ResourceManager.GetString(\"SetupFrameTypeViewModel_ApplyConfirmation_Message\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Change frame type.\n        /// </summary>\n        public static string SetupFrameTypeViewModel_ApplyConfirmation_Title {\n            get {\n                return ResourceManager.GetString(\"SetupFrameTypeViewModel_ApplyConfirmation_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Unknown.\n        /// </summary>\n        public static string SetupFrameTypeViewModel_CurrentFrame_Unknown {\n            get {\n                return ResourceManager.GetString(\"SetupFrameTypeViewModel_CurrentFrame_Unknown\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Frame type.\n        /// </summary>\n        public static string SetupFrameTypeViewModel_Name {\n            get {\n                return ResourceManager.GetString(\"SetupFrameTypeViewModel_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to s.\n        /// </summary>\n        public static string SetupMotorsView_DurationLabel_TimeUnit {\n            get {\n                return ResourceManager.GetString(\"SetupMotorsView_DurationLabel_TimeUnit\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Duration.\n        /// </summary>\n        public static string SetupMotorsView_TestDuration {\n            get {\n                return ResourceManager.GetString(\"SetupMotorsView_TestDuration\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Motor Test.\n        /// </summary>\n        public static string SetupMotorsViewModel_Name {\n            get {\n                return ResourceManager.GetString(\"SetupMotorsViewModel_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Setup.\n        /// </summary>\n        public static string SetupPageViewModel_Title {\n            get {\n                return ResourceManager.GetString(\"SetupPageViewModel_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command to stop receiving parameters from the mavlink device.\n        /// </summary>\n        public static string StopUpdateParamsCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"StopUpdateParamsCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Stop refreshing params.\n        /// </summary>\n        public static string StopUpdateParamsCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"StopUpdateParamsCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Set an Auto mode. Use it to continue mission after interrupt.\n        /// </summary>\n        public static string UavAction_AutoMode_Description {\n            get {\n                return ResourceManager.GetString(\"UavAction_AutoMode_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Auto mode.\n        /// </summary>\n        public static string UavAction_AutoMode_Name {\n            get {\n                return ResourceManager.GetString(\"UavAction_AutoMode_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Guided mode.\n        /// </summary>\n        public static string UavAction_GuidedMode {\n            get {\n                return ResourceManager.GetString(\"UavAction_GuidedMode\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Set a Guided mode.\n        /// </summary>\n        public static string UavAction_GuidedMode_Description {\n            get {\n                return ResourceManager.GetString(\"UavAction_GuidedMode_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Land.\n        /// </summary>\n        public static string UavAction_Land {\n            get {\n                return ResourceManager.GetString(\"UavAction_Land\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Immediately Land.\n        /// </summary>\n        public static string UavAction_Land_Description {\n            get {\n                return ResourceManager.GetString(\"UavAction_Land_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Return to launch.\n        /// </summary>\n        public static string UavAction_Rtl_Description {\n            get {\n                return ResourceManager.GetString(\"UavAction_Rtl_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to RTL.\n        /// </summary>\n        public static string UavAction_Rtl_Name {\n            get {\n                return ResourceManager.GetString(\"UavAction_Rtl_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Start Mission.\n        /// </summary>\n        public static string UavAction_StartMission {\n            get {\n                return ResourceManager.GetString(\"UavAction_StartMission\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Begins uploaded mission.\n        /// </summary>\n        public static string UavAction_StartMission_Description {\n            get {\n                return ResourceManager.GetString(\"UavAction_StartMission_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to TakeOff.\n        /// </summary>\n        public static string UavAction_TakeOff {\n            get {\n                return ResourceManager.GetString(\"UavAction_TakeOff\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Start takeoff.\n        /// </summary>\n        public static string UavAction_TakeOff_Description {\n            get {\n                return ResourceManager.GetString(\"UavAction_TakeOff_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Altitude.\n        /// </summary>\n        public static string UavRttItem_Altitude {\n            get {\n                return ResourceManager.GetString(\"UavRttItem_Altitude\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Azimuth.\n        /// </summary>\n        public static string UavRttItem_Azimuth {\n            get {\n                return ResourceManager.GetString(\"UavRttItem_Azimuth\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Battery.\n        /// </summary>\n        public static string UavRttItem_Battery {\n            get {\n                return ResourceManager.GetString(\"UavRttItem_Battery\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to GNSS.\n        /// </summary>\n        public static string UavRttItem_GNSS {\n            get {\n                return ResourceManager.GetString(\"UavRttItem_GNSS\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Link.\n        /// </summary>\n        public static string UavRttItem_Link {\n            get {\n                return ResourceManager.GetString(\"UavRttItem_Link\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Mode.\n        /// </summary>\n        public static string UavRttItem_Mode {\n            get {\n                return ResourceManager.GetString(\"UavRttItem_Mode\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Velocity.\n        /// </summary>\n        public static string UavRttItem_Velocity {\n            get {\n                return ResourceManager.GetString(\"UavRttItem_Velocity\", resourceCulture);\n            }\n        }\n\n        /// <summary>\n        ///   Looks up a localized string similar to GS.\n        /// </summary>\n        public static string VelocityUavIndicatorViewModel_Velocity_Short {\n            get {\n                return ResourceManager.GetString(\"VelocityUavIndicatorViewModel_Velocity_Short\", resourceCulture);\n            }\n        }\n\n        /// <summary>\n        ///   Looks up a localized string similar to Heading.\n        /// </summary>\n        public static string HeadingUavIndicatorViewModel_Heading {\n            get {\n                return ResourceManager.GetString(\"HeadingUavIndicatorViewModel_Heading\", resourceCulture);\n            }\n        }\n\n        /// <summary>\n        ///   Looks up a localized string similar to HDG.\n        /// </summary>\n        public static string HeadingUavIndicatorViewModel_Heading_Short {\n            get {\n                return ResourceManager.GetString(\"HeadingUavIndicatorViewModel_Heading_Short\", resourceCulture);\n            }\n        }\n\n        /// <summary>\n        ///   Looks up a localized string similar to Home azimuth.\n        /// </summary>\n        public static string HomeAzimuthUavIndicatorViewModel_HomeAzimuth {\n            get {\n                return ResourceManager.GetString(\"HomeAzimuthUavIndicatorViewModel_HomeAzimuth\", resourceCulture);\n            }\n        }\n\n        /// <summary>\n        ///   Looks up a localized string similar to HOME.\n        /// </summary>\n        public static string HomeAzimuthUavIndicatorViewModel_HomeAzimuth_Short {\n            get {\n                return ResourceManager.GetString(\"HomeAzimuthUavIndicatorViewModel_HomeAzimuth_Short\", resourceCulture);\n            }\n        }\n\n        /// <summary>\n        ///   Looks up a localized string similar to Angle.\n        /// </summary>\n        public static string AngleUavRttIndicatorViewModel_Angle {\n            get {\n                return ResourceManager.GetString(\"AngleUavRttIndicatorViewModel_Angle\", resourceCulture);\n            }\n        }\n\n        /// <summary>\n        ///   Looks up a localized string similar to AGL.\n        /// </summary>\n        public static string AltitudeUavIndicatorViewModel_Agl {\n            get {\n                return ResourceManager.GetString(\"AltitudeUavIndicatorViewModel_Agl\", resourceCulture);\n            }\n        }\n\n        /// <summary>\n        ///   Looks up a localized string similar to MSL.\n        /// </summary>\n        public static string AltitudeUavIndicatorViewModel_Msl {\n            get {\n                return ResourceManager.GetString(\"AltitudeUavIndicatorViewModel_Msl\", resourceCulture);\n            }\n        }\n\n        /// <summary>\n        ///   Looks up a localized string similar to Pitch.\n        /// </summary>\n        public static string AngleUavRttIndicatorViewModel_Pitch {\n            get {\n                return ResourceManager.GetString(\"AngleUavRttIndicatorViewModel_Pitch\", resourceCulture);\n            }\n        }\n\n        /// <summary>\n        ///   Looks up a localized string similar to Roll.\n        /// </summary>\n        public static string AngleUavRttIndicatorViewModel_Roll {\n            get {\n                return ResourceManager.GetString(\"AngleUavRttIndicatorViewModel_Roll\", resourceCulture);\n            }\n        }\n\n        /// <summary>\n        ///   Looks up a localized string similar to Amperage.\n        /// </summary>\n        public static string UavWidgetViewModel_BatteryRttBox_BatteryAmperage_Header {\n            get {\n                return ResourceManager.GetString(\"UavWidgetViewModel_BatteryRttBox_BatteryAmperage_Header\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Charge.\n        /// </summary>\n        public static string UavWidgetViewModel_BatteryRttBox_BatteryCharge_Header {\n            get {\n                return ResourceManager.GetString(\"UavWidgetViewModel_BatteryRttBox_BatteryCharge_Header\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Capacity.\n        /// </summary>\n        public static string UavWidgetViewModel_BatteryRttBox_BatteryConsumed_Header {\n            get {\n                return ResourceManager.GetString(\"UavWidgetViewModel_BatteryRttBox_BatteryConsumed_Header\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Voltage.\n        /// </summary>\n        public static string UavWidgetViewModel_BatteryRttBox_BatteryVoltage_Header {\n            get {\n                return ResourceManager.GetString(\"UavWidgetViewModel_BatteryRttBox_BatteryVoltage_Header\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Hdop.\n        /// </summary>\n        public static string UavWidgetViewModel_GnssRttBox_Hdop_Header {\n            get {\n                return ResourceManager.GetString(\"UavWidgetViewModel_GnssRttBox_Hdop_Header\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Mode.\n        /// </summary>\n        public static string UavWidgetViewModel_GnssRttBox_Mode_Header {\n            get {\n                return ResourceManager.GetString(\"UavWidgetViewModel_GnssRttBox_Mode_Header\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Satellites.\n        /// </summary>\n        public static string UavWidgetViewModel_GnssRttBox_SatellitesCount_Header {\n            get {\n                return ResourceManager.GetString(\"UavWidgetViewModel_GnssRttBox_SatellitesCount_Header\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Vdop.\n        /// </summary>\n        public static string UavWidgetViewModel_GnssRttBox_Vdop_Header {\n            get {\n                return ResourceManager.GetString(\"UavWidgetViewModel_GnssRttBox_Vdop_Header\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Input Altitude.\n        /// </summary>\n        public static string UavWidgetViewModel_SetAltitudeDialog_Title {\n            get {\n                return ResourceManager.GetString(\"UavWidgetViewModel_SetAltitudeDialog_Title\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Armed.\n        /// </summary>\n        public static string UavWidgetViewModel_StatusText_Armed {\n            get {\n                return ResourceManager.GetString(\"UavWidgetViewModel_StatusText_Armed\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Disarmed.\n        /// </summary>\n        public static string UavWidgetViewModel_StatusText_DisArmed {\n            get {\n                return ResourceManager.GetString(\"UavWidgetViewModel_StatusText_DisArmed\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Pull Up.\n        /// </summary>\n        public static string UavWidgetViewModel_StatusText_PullUp {\n            get {\n                return ResourceManager.GetString(\"UavWidgetViewModel_StatusText_PullUp\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to byte.\n        /// </summary>\n        public static string Unit_Byte_Abbreviation {\n            get {\n                return ResourceManager.GetString(\"Unit_Byte_Abbreviation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to GB.\n        /// </summary>\n        public static string Unit_Gigabyte_Abbreviation {\n            get {\n                return ResourceManager.GetString(\"Unit_Gigabyte_Abbreviation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to KB.\n        /// </summary>\n        public static string Unit_Kilobyte_Abbreviation {\n            get {\n                return ResourceManager.GetString(\"Unit_Kilobyte_Abbreviation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to MB.\n        /// </summary>\n        public static string Unit_Megabyte_Abbreviation {\n            get {\n                return ResourceManager.GetString(\"Unit_Megabyte_Abbreviation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to TB.\n        /// </summary>\n        public static string Unit_Terabyte_Abbreviation {\n            get {\n                return ResourceManager.GetString(\"Unit_Terabyte_Abbreviation\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command removes all pinned items from the params page.\n        /// </summary>\n        public static string UnpinAllParamsCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"UnpinAllParamsCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Unpin all params.\n        /// </summary>\n        public static string UnpinAllParamsCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"UnpinAllParamsCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that refreshes params from the mavlink device.\n        /// </summary>\n        public static string UpdateParamsCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"UpdateParamsCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Refresh params.\n        /// </summary>\n        public static string UpdateParamsCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"UpdateParamsCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that uploads files to the remote device.\n        /// </summary>\n        public static string UploadItemCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"UploadItemCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Upload.\n        /// </summary>\n        public static string UploadItemCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"UploadItemCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Command that writes the param to the mavlink device.\n        /// </summary>\n        public static string WriteParamCommand_CommandInfo_Description {\n            get {\n                return ResourceManager.GetString(\"WriteParamCommand_CommandInfo_Description\", resourceCulture);\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Write param.\n        /// </summary>\n        public static string WritePatamCommand_CommandInfo_Name {\n            get {\n                return ResourceManager.GetString(\"WritePatamCommand_CommandInfo_Name\", resourceCulture);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/RS.resx",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<root>\n    <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n                xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n        <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n\n        </xsd:element>\n    </xsd:schema>\n    <resheader name=\"resmimetype\">\n        <value>text/microsoft-resx</value>\n    </resheader>\n    <resheader name=\"version\">\n        <value>1.3</value>\n    </resheader>\n    <resheader name=\"reader\">\n        <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,\n            PublicKeyToken=b77a5c561934e089\n        </value>\n    </resheader>\n    <resheader name=\"writer\">\n        <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,\n            PublicKeyToken=b77a5c561934e089\n        </value>\n    </resheader>\n    <data name=\"ParamItemView_PinToggleButton_ToolTip\" xml:space=\"preserve\">\n        <value>On/ off pin for this parameter</value>\n    </data>\n    <data name=\"ParamItemView_UpdateButton_ToolTip\" xml:space=\"preserve\">\n        <value>Update this parameter from UAV</value>\n    </data>\n    <data name=\"ParamItemView_UpdateButton_Text\" xml:space=\"preserve\">\n        <value>Refresh</value>\n    </data>\n    <data name=\"ParamItemView_WriteButton_ToolTip\" xml:space=\"preserve\">\n        <value>Write this parameter to UAV</value>\n    </data>\n    <data name=\"ParamItemView_WriteButton_Text\" xml:space=\"preserve\">\n        <value>Set</value>\n    </data>\n    <data name=\"ParamItemView_InlineNotification_RebootRequired\" xml:space=\"preserve\">\n        <value>Reboot required</value>\n    </data>\n    <data name=\"ParametersEditorPageView_StarsToggleButton_ToolTip\" xml:space=\"preserve\">\n        <value>Show only starred parameters</value>\n    </data>\n    <data name=\"ParametersEditorPageView_UpdateButton_ToolTip\" xml:space=\"preserve\">\n        <value>Update all parameters</value>\n    </data>\n    <data name=\"ParametersEditorPageView_PinsOffButton_ToolTip\" xml:space=\"preserve\">\n        <value>Remove all pinned parameters</value>\n    </data>\n    <data name=\"ParametersEditorPageViewModel_Search\" xml:space=\"preserve\">\n        <value>Search</value>\n    </data>\n    <data name=\"ParametersEditorPageViewModel_Total\" xml:space=\"preserve\">\n        <value>Total: {0}</value>\n    </data>\n    <data name=\"ParametersEditorPageView_StarButton_ToolTip\" xml:space=\"preserve\">\n        <value>Star this parameter</value>\n    </data>\n    <data name=\"ParamPageViewModel_DataLossDialog_Content\" xml:space=\"preserve\">\n        <value>You have unsaved changes. Do you want to save them?</value>\n    </data>\n    <data name=\"ParamPageViewModel_DataLossDialog_Title\" xml:space=\"preserve\">\n        <value>Potential data loss warning</value>\n    </data>\n    <data name=\"ParamPageViewModel_DataLossDialog_PrimaryButtonText\" xml:space=\"preserve\">\n        <value>Save</value>\n    </data>\n    <data name=\"ParamPageViewModel_DataLossDialog_SecondaryButtonText\" xml:space=\"preserve\">\n        <value>Don't save</value>\n    </data>\n    <data name=\"ParamPageViewModel_DataLossDialog_CloseButtonText\" xml:space=\"preserve\">\n        <value>Cancel</value>\n    </data>\n    <data name=\"Unit_Byte_Abbreviation\" xml:space=\"preserve\">\n        <value>byte</value>\n    </data>\n    <data name=\"Unit_Kilobyte_Abbreviation\" xml:space=\"preserve\">\n        <value>KB</value>\n    </data>\n    <data name=\"Unit_Megabyte_Abbreviation\" xml:space=\"preserve\">\n        <value>MB</value>\n    </data>\n    <data name=\"Unit_Gigabyte_Abbreviation\" xml:space=\"preserve\">\n        <value>GB</value>\n    </data>\n    <data name=\"Unit_Terabyte_Abbreviation\" xml:space=\"preserve\">\n        <value>TB</value>\n    </data>\n    <data name=\"FileBrowserViewModel_UploadingDialog_Title\" xml:space=\"preserve\">\n        <value>Uploading</value>\n    </data>\n    <data name=\"FileBrowserViewModel_UploadingDialog_Message\" xml:space=\"preserve\">\n        <value>Do you want to upload file '{0}' to device?</value>\n    </data>\n    <data name=\"FileBrowserViewModel_DownloadDialog_Title\" xml:space=\"preserve\">\n        <value>Download</value>\n    </data>\n    <data name=\"FileBrowserViewModel_DownloadDialog_Message\" xml:space=\"preserve\">\n        <value>Do you want to download file '{0}'?</value>\n    </data>\n    <data name=\"FileBrowserViewModel_FileDownloadedSuccessfully\" xml:space=\"preserve\">\n        <value>File downloaded successfully: {0}</value>\n    </data>\n    <data name=\"FileBrowserViewModel_RemoveDialog_Title\" xml:space=\"preserve\">\n        <value>Remove</value>\n    </data>\n    <data name=\"FileBrowserViewModel_RemoveDialog_Message\" xml:space=\"preserve\">\n        <value>Do you want to remove the item?</value>\n    </data>\n    <data name=\"FileBrowserViewModel_BurstDownloadDialog_Title\" xml:space=\"preserve\">\n        <value>Burst download</value>\n    </data>\n    <data name=\"FileBrowserViewModel_BurstDownloadDialog_PrimaryButtonText\" xml:space=\"preserve\">\n        <value>Download</value>\n    </data>\n    <data name=\"FileBrowserViewModel_BurstDownloadDialog_SecondaryButtonText\" xml:space=\"preserve\">\n        <value>Cancel</value>\n    </data>\n    <data name=\"MavParamsPageViewModel_DeviceName_Unknown\" xml:space=\"preserve\">\n        <value>Unknown</value>\n    </data>\n    <data name=\"NotANumber\" xml:space=\"preserve\">\n        <value>N/A</value>\n    </data>\n    <data name=\"MissionProgressView_Title\" xml:space=\"preserve\">\n        <value>Mission progress</value>\n    </data>\n    <data name=\"MissionProgressView_SubStatusText\" xml:space=\"preserve\">\n        <value>before complete</value>\n    </data>\n    <data name=\"MissionProgressView_MissionDistanceRTT\" xml:space=\"preserve\">\n        <value>Mission</value>\n    </data>\n    <data name=\"MissionProgressView_TotalDistanceRTT\" xml:space=\"preserve\">\n        <value>Total</value>\n    </data>\n    <data name=\"MissionProgressView_HomeDistance\" xml:space=\"preserve\">\n        <value>Home</value>\n    </data>\n    <data name=\"MissionProgressView_TargetDistance\" xml:space=\"preserve\">\n        <value>Target</value>\n    </data>\n    <data name=\"MissionProgressView_DownLoadingMission\" xml:space=\"preserve\">\n        <value>Downloading mission</value>\n    </data>\n    <data name=\"UavAction_AutoMode_Name\" xml:space=\"preserve\">\n        <value>Auto mode</value>\n    </data>\n    <data name=\"UavAction_GuidedMode\" xml:space=\"preserve\">\n        <value>Guided mode</value>\n    </data>\n    <data name=\"UavAction_TakeOff\" xml:space=\"preserve\">\n        <value>TakeOff</value>\n    </data>\n    <data name=\"UavAction_Rtl_Name\" xml:space=\"preserve\">\n        <value>RTL</value>\n    </data>\n    <data name=\"UavAction_Land\" xml:space=\"preserve\">\n        <value>Land</value>\n    </data>\n    <data name=\"UavAction_StartMission\" xml:space=\"preserve\">\n        <value>Start Mission</value>\n    </data>\n    <data name=\"UavAction_StartMission_Description\" xml:space=\"preserve\">\n        <value>Begins uploaded mission</value>\n    </data>\n    <data name=\"UavRttItem_Mode\" xml:space=\"preserve\">\n        <value>Mode</value>\n    </data>\n    <data name=\"UavRttItem_Altitude\" xml:space=\"preserve\">\n        <value>Altitude</value>\n    </data>\n    <data name=\"UavRttItem_Velocity\" xml:space=\"preserve\">\n        <value>Velocity</value>\n    </data>\n    <data name=\"UavRttItem_Azimuth\" xml:space=\"preserve\">\n        <value>Azimuth</value>\n    </data>\n    <data name=\"UavRttItem_Battery\" xml:space=\"preserve\">\n        <value>Battery</value>\n    </data>\n    <data name=\"UavRttItem_GNSS\" xml:space=\"preserve\">\n        <value>GNSS</value>\n    </data>\n    <data name=\"UavRttItem_Link\" xml:space=\"preserve\">\n        <value>Link</value>\n    </data>\n    <data name=\"UavWidgetViewModel_StatusText_Armed\" xml:space=\"preserve\">\n        <value>Armed</value>\n    </data>\n    <data name=\"UavWidgetViewModel_StatusText_PullUp\" xml:space=\"preserve\">\n        <value>Pull Up</value>\n    </data>\n    <data name=\"UavWidgetViewModel_StatusText_DisArmed\" xml:space=\"preserve\">\n        <value>Disarmed</value>\n    </data>\n    <data name=\"FlightPageViewModel_Title\" xml:space=\"preserve\">\n        <value>Flight</value>\n    </data>\n    <data name=\"UavAction_TakeOff_Description\" xml:space=\"preserve\">\n        <value>Start takeoff</value>\n    </data>\n    <data name=\"UavAction_Rtl_Description\" xml:space=\"preserve\">\n        <value>Return to launch</value>\n    </data>\n    <data name=\"UavAction_Land_Description\" xml:space=\"preserve\">\n        <value>Immediately Land</value>\n    </data>\n    <data name=\"UavAction_AutoMode_Description\" xml:space=\"preserve\">\n        <value>Set an Auto mode. Use it to continue mission after interrupt</value>\n    </data>\n    <data name=\"UavAction_GuidedMode_Description\" xml:space=\"preserve\">\n        <value>Set a Guided mode</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixType2dFix\" xml:space=\"preserve\">\n        <value>2D position Fix</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixTypeRtkFloat\" xml:space=\"preserve\">\n        <value>RTK Float</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixTypeRtkFixed\" xml:space=\"preserve\">\n        <value>Rtk Fixed</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixTypeDgps\" xml:space=\"preserve\">\n        <value>DGPS/SBAS</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixTypePpp\" xml:space=\"preserve\">\n        <value>PPP 3D position</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixType3dFix\" xml:space=\"preserve\">\n        <value>3D position Fix</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixTypeStatic\" xml:space=\"preserve\">\n        <value>Static Fix</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixTypeNoGps\" xml:space=\"preserve\">\n        <value>No GPS connected</value>\n    </data>\n    <data name=\"SetAltitudeDialogViewModel_ApplyDialog_PrimaryButton_TakeOff\" xml:space=\"preserve\">\n        <value>Take off</value>\n    </data>\n    <data name=\"SetAltitudeDialogViewModel_ApplyDialog_SecondaryButton_Cancel\" xml:space=\"preserve\">\n        <value>Cancel</value>\n    </data>\n    <data name=\"MissionProgressViewModel_MissionFlightTime_Symbol\" xml:space=\"preserve\">\n        <value>min</value>\n    </data>\n    <data name=\"FileBrowserView_Watermark_Local\" xml:space=\"preserve\">\n        <value>Local Search</value>\n    </data>\n    <data name=\"FileBrowserView_Watermark_Remote\" xml:space=\"preserve\">\n        <value>Remote Search</value>\n    </data>\n    <data name=\"FileBrowserView_Button_Download_Content\" xml:space=\"preserve\">\n        <value>Download</value>\n    </data>\n    <data name=\"FileBrowserView_Button_BurstDownload_Content\" xml:space=\"preserve\">\n        <value>Burst download</value>\n    </data>\n    <data name=\"UnpinAllParamsCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Unpin all params</value>\n    </data>\n    <data name=\"WriteParamCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that writes the param to the mavlink device</value>\n    </data>\n    <data name=\"WritePatamCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Write param</value>\n    </data>\n    <data name=\"UnpinAllParamsCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command removes all pinned items from the params page</value>\n    </data>\n    <data name=\"StopUpdateParamsCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Stop refreshing params</value>\n    </data>\n    <data name=\"StopUpdateParamsCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command to stop receiving parameters from the mavlink device</value>\n    </data>\n    <data name=\"UpdateParamsCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Refresh params</value>\n    </data>\n    <data name=\"UpdateParamsCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that refreshes params from the mavlink device</value>\n    </data>\n    <data name=\"ReadParamCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Update param</value>\n    </data>\n    <data name=\"ReadParamCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that updates param from the mavlink device</value>\n    </data>\n    <data name=\"OpenMavParamsCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Open mavlink params</value>\n    </data>\n    <data name=\"OpenMavParamsCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command opens mavlink params editor</value>\n    </data>\n    <data name=\"HomePageParamsDeviceItemAction_ActionViewModel_Header\" xml:space=\"preserve\">\n        <value>Params editor</value>\n    </data>\n    <data name=\"HomePageParamsDeviceItemAction_ActionViewModel_Description\" xml:space=\"preserve\">\n        <value>Edit mavlink device parameters</value>\n    </data>\n    <data name=\"PacketViewerViewModel_SavePacketMessagesDialog_Title\" xml:space=\"preserve\">\n        <value>Select Separator:</value>\n    </data>\n    <data name=\"SavePacketMessagesDialogView_Separator_Tab\" xml:space=\"preserve\">\n        <value>TAB</value>\n    </data>\n    <data name=\"PacketViewerView_ToolTip_Save\" xml:space=\"preserve\">\n        <value>Save</value>\n    </data>\n    <data name=\"PacketViewerView_ToolTip_ClearAll\" xml:space=\"preserve\">\n        <value>Clear All</value>\n    </data>\n    <data name=\"PacketViewerView_ToolTip_PlayPause\" xml:space=\"preserve\">\n        <value>PLAY/PAUSE</value>\n    </data>\n    <data name=\"PacketMessageViewModel_CsvColumn_Date\" xml:space=\"preserve\">\n        <value>Date</value>\n    </data>\n    <data name=\"PacketMessageViewModel_CsvColumn_Source\" xml:space=\"preserve\">\n        <value>Source</value>\n    </data>\n    <data name=\"PacketMessageViewModel_CsvColumn_Type\" xml:space=\"preserve\">\n        <value>Type</value>\n    </data>\n    <data name=\"PacketMessageViewModel_CsvColumn_Message\" xml:space=\"preserve\">\n        <value>Message</value>\n    </data>\n    <data name=\"PacketViewerView_ExpanderFilterBySources_Header\" xml:space=\"preserve\">\n        <value>Sources</value>\n    </data>\n    <data name=\"PacketViewerView_ExpanderFilterByTypes_Header\" xml:space=\"preserve\">\n        <value>Types</value>\n    </data>\n    <data name=\"PacketViewerView_Filters_CheckAll\" xml:space=\"preserve\">\n        <value>Check All</value>\n    </data>\n    <data name=\"PacketViewerViewModel_SavePacketMessagesDialog_CloseButtonText\" xml:space=\"preserve\">\n        <value>Cancel</value>\n    </data>\n    <data name=\"PacketViewerViewModel_SavePacketMessagesDialog_PrimaryButtonText\" xml:space=\"preserve\">\n        <value>Accept</value>\n    </data>\n    <data name=\"FileBrowserViewModel_RenameDialog_Title\" xml:space=\"preserve\">\n        <value>Rename</value>\n    </data>\n    <data name=\"FileBrowserViewModel_RenameDialog_PrimaryButtonText\" xml:space=\"preserve\">\n        <value>Accept</value>\n    </data>\n    <data name=\"FileBrowserViewModel_RenameDialog_SecondaryButtonText\" xml:space=\"preserve\">\n        <value>Cancel</value>\n    </data>\n    <data name=\"FileBrowserViewModel_RenameDialog_Message\" xml:space=\"preserve\">\n        <value>Input new name</value>\n    </data>\n    <data name=\"OpenFlightModeCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Open flight mode</value>\n    </data>\n    <data name=\"OpenFlightModeCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that opens flight mode page</value>\n    </data>\n    <data name=\"OpenFileBrowserCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Open file browser</value>\n    </data>\n    <data name=\"OpenFileBrowserCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that opens file browser</value>\n    </data>\n    <data name=\"OpenPacketViewerCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Open packet viewer</value>\n    </data>\n    <data name=\"OpenPacketViewerCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that opens packet viewer</value>\n    </data>\n    <data name=\"AltitudeIndicatorStyles_ToolTip_Velocity\" xml:space=\"preserve\">\n        <value>Velocity</value>\n    </data>\n    <data name=\"AltitudeIndicatorStyles_ToolTip_Altitude\" xml:space=\"preserve\">\n        <value>Altitude</value>\n    </data>\n    <data name=\"AltitudeIndicatorStyles_ToolTip_Compass\" xml:space=\"preserve\">\n        <value>Compass</value>\n    </data>\n    <data name=\"AltitudeIndicatorStyles_ToolTip_Vibration\" xml:space=\"preserve\">\n        <value>Vibration</value>\n    </data>\n    <data name=\"FileBrowserViewModel_Title\" xml:space=\"preserve\">\n        <value>Browser</value>\n    </data>\n    <data name=\"UavWidgetViewModel_SetAltitudeDialog_Title\" xml:space=\"preserve\">\n        <value>Input Altitude</value>\n    </data>\n    <data name=\"MavParamsPageView_AwaitingScreen_Header\" xml:space=\"preserve\">\n        <value>Connecting</value>\n    </data>\n    <data name=\"MavParamsPageView_AwaitingScreen_Description\" xml:space=\"preserve\">\n        <value>Connecting to the device...</value>\n    </data>\n    <data name=\"MavParamsPageView_CancelButton_Content\" xml:space=\"preserve\">\n        <value>Cancel</value>\n    </data>\n    <data name=\"MavParamsPageView_Loading_Text\" xml:space=\"preserve\">\n        <value>Loading</value>\n    </data>\n    <data name=\"PacketViewerViewModel_Title\" xml:space=\"preserve\">\n        <value>Packet Viewer</value>\n    </data>\n    <data name=\"MavParamsPageViewModel_Title\" xml:space=\"preserve\">\n        <value>Params</value>\n    </data>\n    <data name=\"ClearAllPacketsCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Clear all packets</value>\n    </data>\n    <data name=\"ClearAllPacketsCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that clears all packets</value>\n    </data>\n    <data name=\"ExportPacketsToCsvCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Export packets to CSV</value>\n    </data>\n    <data name=\"ExportPacketsToCsvCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that exports packets to a CSV file</value>\n    </data>\n    <data name=\"RenameItemCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Commit item rename</value>\n    </data>\n    <data name=\"RenameItemCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that commits an item renaming</value>\n    </data>\n    <data name=\"FileBrowserView_AwaitingScreen_Header\" xml:space=\"preserve\">\n        <value>Connecting</value>\n    </data>\n    <data name=\"FileBrowserView_AwaitingScreen_Description\" xml:space=\"preserve\">\n        <value>Connect to the device's FTP service...</value>\n    </data>\n    <data name=\"RemoveItemCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Remove item</value>\n    </data>\n    <data name=\"RemoveItemCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that removes item</value>\n    </data>\n    <data name=\"CalculateCrc32Command_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that checks a cyclic redundancy of a file (crc32)</value>\n    </data>\n    <data name=\"CalculateCrc32Command_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Calculate CRC32</value>\n    </data>\n    <data name=\"CreateDirectoryCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Create a folder</value>\n    </data>\n    <data name=\"CreateDirectoryCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that creates a folder</value>\n    </data>\n    <data name=\"BurstDownloadItemCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Burst-Download</value>\n    </data>\n    <data name=\"BurstDownloadItemCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that download an element by burst</value>\n    </data>\n    <data name=\"DownloadItemCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Download</value>\n    </data>\n    <data name=\"DownloadItemCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that downloads remote entries</value>\n    </data>\n    <data name=\"UploadItemCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Upload</value>\n    </data>\n    <data name=\"UploadItemCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that uploads files to the remote device</value>\n    </data>\n    <data name=\"FindFileOnLocalCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Find file</value>\n    </data>\n    <data name=\"FindFileOnLocalCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that finds a file selected in a remote tree in a local tree</value>\n    </data>\n    <data name=\"SetupPageViewModel_Title\" xml:space=\"preserve\">\n        <value>Setup</value>\n    </data>\n    <data name=\"SetupFrameTypeViewModel_Name\" xml:space=\"preserve\">\n        <value>Frame type</value>\n    </data>\n    <data name=\"ChangeFrameTypeCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that changes frame type</value>\n    </data>\n    <data name=\"ChangeFrameTypeCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Change frame type</value>\n    </data>\n    <data name=\"SetupFrameTypeView_Loader_Header\" xml:space=\"preserve\">\n        <value>Loading...</value>\n    </data>\n    <data name=\"SetupFrameTypeView_Loader_Description\" xml:space=\"preserve\">\n        <value>Please wait</value>\n    </data>\n    <data name=\"SetupFrameTypeView_CurrentFrame\" xml:space=\"preserve\">\n        <value>Current frame</value>\n    </data>\n    <data name=\"SetupFrameTypeView_FrameMetadata\" xml:space=\"preserve\">\n        <value>Metadata</value>\n    </data>\n    <data name=\"SetupFrameTypeView_ApplyFrame\" xml:space=\"preserve\">\n        <value>Set</value>\n    </data>\n    <data name=\"SetupFrameTypeViewModel_ApplyConfirmation_Title\" xml:space=\"preserve\">\n        <value>Change frame type</value>\n    </data>\n    <data name=\"SetupFrameTypeViewModel_ApplyConfirmation_Message\" xml:space=\"preserve\">\n        <value>Do you want to change frame type?</value>\n    </data>\n    <data name=\"SetupFrameTypeViewModel_CurrentFrame_Unknown\" xml:space=\"preserve\">\n        <value>Unknown</value>\n    </data>\n    <data name=\"OpenSetupCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Open Setup page</value>\n    </data>\n    <data name=\"OpenSetupCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Command that opens setup page for the drone</value>\n    </data>\n    <data name=\"SetupFrameTypeView_CurrentFrame_Refresh\" xml:space=\"preserve\">\n        <value>Refresh current frame</value>\n    </data>\n    <data name=\"SetupFrameTypeView_CurrentFrame_Meta\" xml:space=\"preserve\">\n        <value>Description</value>\n    </data>\n    <data name=\"MissionProgressView_RefreshButton_Text\" xml:space=\"preserve\">\n        <value>Refresh mission</value>\n    </data>\n    <data name=\"MissionProgressView_DownloadButton_Text\" xml:space=\"preserve\">\n        <value>Download Mission</value>\n    </data>\n    <data name=\"SetupMotorsView_TestDuration\" xml:space=\"preserve\">\n        <value>Duration</value>\n    </data>\n    <data name=\"SetupMotorsView_DurationLabel_TimeUnit\" xml:space=\"preserve\">\n        <value>s</value>\n    </data>\n    <data name=\"MotorItemView_Label_MotorId\" xml:space=\"preserve\">\n        <value>Motor</value>\n    </data>\n    <data name=\"MotorItemView_MonitoringLabel_Servo\" xml:space=\"preserve\">\n        <value>Servo</value>\n    </data>\n    <data name=\"MotorItemView_MonitoringLabel_Pwm\" xml:space=\"preserve\">\n        <value>PWM</value>\n    </data>\n    <data name=\"MotorItemView_ToolTip_StartStop\" xml:space=\"preserve\">\n        <value>START/STOP</value>\n    </data>\n    <data name=\"SetupMotorsViewModel_Name\" xml:space=\"preserve\">\n        <value>Motor Test</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_N\" xml:space=\"preserve\">\n        <value>N</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_NE\" xml:space=\"preserve\">\n        <value>NE</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_E\" xml:space=\"preserve\">\n        <value>E</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_SE\" xml:space=\"preserve\">\n        <value>SE</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_S\" xml:space=\"preserve\">\n        <value>S</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_SW\" xml:space=\"preserve\">\n        <value>SW</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_W\" xml:space=\"preserve\">\n        <value>W</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_NW\" xml:space=\"preserve\">\n        <value>NW</value>\n    </data>\n    <data name=\"BurstDownloadDialogView_Description_Text\" xml:space=\"preserve\">\n        <value>Enter the size of a download block</value>\n    </data>\n    <data name=\"UavWidgetViewModel_BatteryRttBox_BatteryCharge_Header\" xml:space=\"preserve\">\n        <value>Charge</value>\n    </data>\n    <data name=\"UavWidgetViewModel_BatteryRttBox_BatteryAmperage_Header\" xml:space=\"preserve\">\n        <value>Amperage</value>\n    </data>\n    <data name=\"UavWidgetViewModel_BatteryRttBox_BatteryVoltage_Header\" xml:space=\"preserve\">\n        <value>Voltage</value>\n    </data>\n    <data name=\"UavWidgetViewModel_BatteryRttBox_BatteryConsumed_Header\" xml:space=\"preserve\">\n        <value>Capacity</value>\n    </data>\n    <data name=\"UavWidgetViewModel_GnssRttBox_SatellitesCount_Header\" xml:space=\"preserve\">\n        <value>Satellites</value>\n    </data>\n    <data name=\"UavWidgetViewModel_GnssRttBox_Hdop_Header\" xml:space=\"preserve\">\n        <value>Hdop</value>\n    </data>\n    <data name=\"UavWidgetViewModel_GnssRttBox_Vdop_Header\" xml:space=\"preserve\">\n        <value>Vdop</value>\n    </data>\n    <data name=\"UavWidgetViewModel_GnssRttBox_Mode_Header\" xml:space=\"preserve\">\n        <value>Mode</value>\n    </data>\n    <data name=\"HeadingUavIndicatorViewModel_Heading\" xml:space=\"preserve\">\n        <value>Heading</value>\n    </data>\n    <data name=\"HeadingUavIndicatorViewModel_Heading_Short\" xml:space=\"preserve\">\n        <value>HDG</value>\n    </data>\n    <data name=\"HomeAzimuthUavIndicatorViewModel_HomeAzimuth\" xml:space=\"preserve\">\n        <value>Home azimuth</value>\n    </data>\n    <data name=\"HomeAzimuthUavIndicatorViewModel_HomeAzimuth_Short\" xml:space=\"preserve\">\n        <value>HOME</value>\n    </data>\n    <data name=\"VelocityUavIndicatorViewModel_Velocity_Short\" xml:space=\"preserve\">\n        <value>GS</value>\n    </data>\n    <data name=\"AngleUavRttIndicatorViewModel_Angle\" xml:space=\"preserve\">\n        <value>Angle</value>\n    </data>\n    <data name=\"AltitudeUavIndicatorViewModel_Agl\" xml:space=\"preserve\">\n        <value>AGL</value>\n    </data>\n    <data name=\"AltitudeUavIndicatorViewModel_Msl\" xml:space=\"preserve\">\n        <value>MSL</value>\n    </data>\n    <data name=\"AngleUavRttIndicatorViewModel_Pitch\" xml:space=\"preserve\">\n        <value>Pitch</value>\n    </data>\n    <data name=\"AngleUavRttIndicatorViewModel_Roll\" xml:space=\"preserve\">\n        <value>Roll</value>\n    </data>\n</root>"
  },
  {
    "path": "src/Asv.Drones/RS.ru.resx",
    "content": "<root>\n    <resheader name=\"resmimetype\">\n        <value>text/microsoft-resx</value>\n    </resheader>\n    <resheader name=\"version\">\n        <value>1.3</value>\n    </resheader>\n    <resheader name=\"reader\">\n        <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n    </resheader>\n    <resheader name=\"writer\">\n        <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n    </resheader>\n    <data name=\"ParamItemView_PinToggleButton_ToolTip\" xml:space=\"preserve\">\n        <value>Включить/ выключить закрепление этого параметра</value>\n    </data>\n    <data name=\"ParamItemView_UpdateButton_ToolTip\" xml:space=\"preserve\">\n        <value>Обновить этот парметр из БПЛА</value>\n    </data>\n    <data name=\"ParamItemView_UpdateButton_Text\" xml:space=\"preserve\">\n        <value>Обновить</value>\n    </data>\n    <data name=\"ParamItemView_WriteButton_ToolTip\" xml:space=\"preserve\">\n        <value>Записать этот парметр в БПЛА</value>\n    </data>\n    <data name=\"ParamItemView_WriteButton_Text\" xml:space=\"preserve\">\n        <value>Установить</value>\n    </data>\n    <data name=\"ParamItemView_InlineNotification_RebootRequired\" xml:space=\"preserve\">\n        <value>Требуется перезагрузка</value>\n    </data>\n    <data name=\"ParametersEditorPageView_StarsToggleButton_ToolTip\" xml:space=\"preserve\">\n        <value>Показать только избранные параметры</value>\n    </data>\n    <data name=\"ParametersEditorPageView_UpdateButton_ToolTip\" xml:space=\"preserve\">\n        <value>Обновить все параметры</value>\n    </data>\n    <data name=\"ParametersEditorPageView_PinsOffButton_ToolTip\" xml:space=\"preserve\">\n        <value>Убрать все прикреплённые параметры</value>\n    </data>\n    <data name=\"ParametersEditorPageViewModel_Search\" xml:space=\"preserve\">\n        <value>Найти</value>\n    </data>\n    <data name=\"ParametersEditorPageViewModel_Total\" xml:space=\"preserve\">\n        <value>Всего: {0}</value>\n    </data>\n    <data name=\"ParametersEditorPageView_StarButton_ToolTip\" xml:space=\"preserve\">\n        <value>Добавить параметр в избранные</value>\n    </data>\n    <data name=\"ParamPageViewModel_DataLossDialog_Content\" xml:space=\"preserve\">\n        <value>У вас есть несохраненные данные. Хотите сохранить их?</value>\n    </data>\n    <data name=\"ParamPageViewModel_DataLossDialog_Title\" xml:space=\"preserve\">\n        <value>Предупреждение о возможной потере данных</value>\n    </data>\n    <data name=\"ParamPageViewModel_DataLossDialog_PrimaryButtonText\" xml:space=\"preserve\">\n        <value>Сохранить</value>\n    </data>\n    <data name=\"ParamPageViewModel_DataLossDialog_SecondaryButtonText\" xml:space=\"preserve\">\n        <value>Не сохранять</value>\n    </data>\n    <data name=\"ParamPageViewModel_DataLossDialog_CloseButtonText\" xml:space=\"preserve\">\n        <value>Отмена</value>\n    </data>\n    <data name=\"Unit_Byte_Abbreviation\" xml:space=\"preserve\">\n        <value>байт</value>\n    </data>\n    <data name=\"Unit_Gigabyte_Abbreviation\" xml:space=\"preserve\">\n        <value>ГБ</value>\n    </data>\n    <data name=\"Unit_Kilobyte_Abbreviation\" xml:space=\"preserve\">\n        <value>КБ</value>\n    </data>\n    <data name=\"Unit_Megabyte_Abbreviation\" xml:space=\"preserve\">\n        <value>МБ</value>\n    </data>\n    <data name=\"Unit_Terabyte_Abbreviation\" xml:space=\"preserve\">\n        <value>ТБ</value>\n    </data>\n    <data name=\"FileBrowserViewModel_UploadingDialog_Title\" xml:space=\"preserve\">\n        <value>Выгрузка</value>\n    </data>\n    <data name=\"FileBrowserViewModel_UploadingDialog_Message\" xml:space=\"preserve\">\n        <value>Вы хотите загрузить файл '{0}' на устройство?</value>\n    </data>\n    <data name=\"FileBrowserViewModel_DownloadDialog_Title\" xml:space=\"preserve\">\n        <value>Загрузка</value>\n    </data>\n    <data name=\"FileBrowserViewModel_DownloadDialog_Message\" xml:space=\"preserve\">\n        <value>Вы хотите скачать файл '{0}'?</value>\n    </data>\n    <data name=\"FileBrowserViewModel_FileDownloadedSuccessfully\" xml:space=\"preserve\">\n        <value>Файл успешно загружен: {0}</value>\n    </data>\n    <data name=\"FileBrowserViewModel_RemoveDialog_Title\" xml:space=\"preserve\">\n        <value>Удаление</value>\n    </data>\n    <data name=\"FileBrowserViewModel_RemoveDialog_Message\" xml:space=\"preserve\">\n        <value>Вы хотите удалить элемент?</value>\n    </data>\n    <data name=\"FileBrowserViewModel_BurstDownloadDialog_Title\" xml:space=\"preserve\">\n        <value>Загрузка (пакетная)</value>\n    </data>\n    <data name=\"FileBrowserViewModel_BurstDownloadDialog_PrimaryButtonText\" xml:space=\"preserve\">\n        <value>Загрузить</value>\n    </data>\n    <data name=\"FileBrowserViewModel_BurstDownloadDialog_SecondaryButtonText\" xml:space=\"preserve\">\n        <value>Отмена</value>\n    </data>\n    <data name=\"MavParamsPageViewModel_DeviceName_Unknown\" xml:space=\"preserve\">\n        <value>Неизвестно</value>\n    </data>\n    <data name=\"FileBrowserView_Watermark_Local\" xml:space=\"preserve\">\n        <value>Локальный поиск</value>\n    </data>\n    <data name=\"FileBrowserView_Watermark_Remote\" xml:space=\"preserve\">\n        <value>Удалённый поиск</value>\n    </data>\n    <data name=\"FileBrowserView_Button_Download_Content\" xml:space=\"preserve\">\n        <value>Загрузить</value>\n    </data>\n    <data name=\"FileBrowserView_Button_BurstDownload_Content\" xml:space=\"preserve\">\n        <value>Загрузить (пакетами)</value>\n    </data>\n    <data name=\"NotANumber\" xml:space=\"preserve\">\n        <value>Н/Д</value>\n    </data>\n    <data name=\"MissionProgressView_HomeDistance\" xml:space=\"preserve\">\n        <value>Стартовая точка</value>\n    </data>\n    <data name=\"MissionProgressView_MissionDistanceRTT\" xml:space=\"preserve\">\n        <value>Миссия</value>\n    </data>\n    <data name=\"MissionProgressView_SubStatusText\" xml:space=\"preserve\">\n        <value>до завершения</value>\n    </data>\n    <data name=\"MissionProgressView_TargetDistance\" xml:space=\"preserve\">\n        <value>Цель</value>\n    </data>\n    <data name=\"MissionProgressView_Title\" xml:space=\"preserve\">\n        <value>Прогресс миссии</value>\n    </data>\n    <data name=\"MissionProgressView_TotalDistanceRTT\" xml:space=\"preserve\">\n        <value>Общая дистанция</value>\n    </data>\n    <data name=\"MissionProgressView_DownLoadingMission\" xml:space=\"preserve\">\n        <value>Загрузка миссии</value>\n    </data>\n    <data name=\"UavAction_AutoMode_Name\" xml:space=\"preserve\">\n        <value>Авто режим</value>\n    </data>\n    <data name=\"UavAction_GuidedMode\" xml:space=\"preserve\">\n        <value>Ручной режим</value>\n    </data>\n    <data name=\"UavAction_Land\" xml:space=\"preserve\">\n        <value>Посадка</value>\n    </data>\n    <data name=\"UavAction_Rtl_Name\" xml:space=\"preserve\">\n        <value>Домой</value>\n    </data>\n    <data name=\"UavAction_StartMission\" xml:space=\"preserve\">\n        <value>Начать миссию</value>\n    </data>\n    <data name=\"UavAction_TakeOff\" xml:space=\"preserve\">\n        <value>Взлететь</value>\n    </data>\n    <data name=\"UavRttItem_Altitude\" xml:space=\"preserve\">\n        <value>Высота</value>\n    </data>\n    <data name=\"UavRttItem_Azimuth\" xml:space=\"preserve\">\n        <value>Азимут</value>\n    </data>\n    <data name=\"UavRttItem_Battery\" xml:space=\"preserve\">\n        <value>Батарея</value>\n    </data>\n    <data name=\"UavRttItem_GNSS\" xml:space=\"preserve\">\n        <value>ГНСС</value>\n    </data>\n    <data name=\"UavRttItem_Link\" xml:space=\"preserve\">\n        <value>Связь с НСУ</value>\n    </data>\n    <data name=\"UavRttItem_Mode\" xml:space=\"preserve\">\n        <value>Режим</value>\n    </data>\n    <data name=\"UavRttItem_Velocity\" xml:space=\"preserve\">\n        <value>Скорость</value>\n    </data>\n    <data name=\"UavWidgetViewModel_StatusText_PullUp\" xml:space=\"preserve\">\n        <value>Наберите высоту</value>\n    </data>\n    <data name=\"UavWidgetViewModel_StatusText_Armed\" xml:space=\"preserve\">\n        <value>Разблокировано</value>\n    </data>\n    <data name=\"UavWidgetViewModel_StatusText_DisArmed\" xml:space=\"preserve\">\n        <value>Заблокировано</value>\n    </data>\n    <data name=\"FlightPageViewModel_Title\" xml:space=\"preserve\">\n        <value>Полет</value>\n    </data>\n    <data name=\"UavAction_TakeOff_Description\" xml:space=\"preserve\">\n        <value>Начать взлёт</value>\n    </data>\n    <data name=\"UavAction_Rtl_Description\" xml:space=\"preserve\">\n        <value>Возврат на точку запуска</value>\n    </data>\n    <data name=\"UavAction_AutoMode_Description\" xml:space=\"preserve\">\n        <value>Автоматический режим. Используйте для продолжения миссии после прерывания</value>\n    </data>\n    <data name=\"UavAction_Land_Description\" xml:space=\"preserve\">\n        <value>Незамедлительная посадка</value>\n    </data>\n    <data name=\"UavAction_GuidedMode_Description\" xml:space=\"preserve\">\n        <value>Устанавливает ручной режим</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixType2dFix\" xml:space=\"preserve\">\n        <value>2Д позиция Фикс</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixType3dFix\" xml:space=\"preserve\">\n        <value>3Д позиция Фикс</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixTypeDgps\" xml:space=\"preserve\">\n        <value>DGRS/SBAS</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixTypeNoGps\" xml:space=\"preserve\">\n        <value>Нет подключения</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixTypePpp\" xml:space=\"preserve\">\n        <value>PPP 3Д позиция</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixTypeRtkFixed\" xml:space=\"preserve\">\n        <value>RTK фиксированный</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixTypeRtkFloat\" xml:space=\"preserve\">\n        <value>RTK свободный</value>\n    </data>\n    <data name=\"GpsFixType_GpsFixTypeStatic\" xml:space=\"preserve\">\n        <value>Статичный</value>\n    </data>\n    <data name=\"UavAction_StartMission_Description\" xml:space=\"preserve\">\n        <value>Запуск ранее загруженной миссии</value>\n    </data>\n    <data name=\"SetAltitudeDialogViewModel_ApplyDialog_SecondaryButton_Cancel\" xml:space=\"preserve\">\n        <value>Отмена</value>\n    </data>\n    <data name=\"SetAltitudeDialogViewModel_ApplyDialog_PrimaryButton_TakeOff\" xml:space=\"preserve\">\n        <value>Взлететь</value>\n    </data>\n    <data name=\"MissionProgressViewModel_MissionFlightTime_Symbol\" xml:space=\"preserve\">\n        <value>мин</value>\n    </data>\n    <data name=\"UnpinAllParamsCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Снять пины со всех параметров</value>\n    </data>\n    <data name=\"WriteParamCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда записывает параметр в мавлинк устройство</value>\n    </data>\n    <data name=\"WritePatamCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Записать параметр</value>\n    </data>\n    <data name=\"UnpinAllParamsCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда снимает пины со всех параметров на странице параметров</value>\n    </data>\n    <data name=\"StopUpdateParamsCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда останавливает получение параметров с устройства мавлинк</value>\n    </data>\n    <data name=\"StopUpdateParamsCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Остановить получение параметров</value>\n    </data>\n    <data name=\"UpdateParamsCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Обновить параметры</value>\n    </data>\n    <data name=\"UpdateParamsCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда обновляет параметры с устройства мавлинк</value>\n    </data>\n    <data name=\"ReadParamCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Обновить параметр</value>\n    </data>\n    <data name=\"ReadParamCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда обновляет один параметр с устройства мавлинк</value>\n    </data>\n    <data name=\"OpenMavParamsCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Открыть параметры устройства мавлинк</value>\n    </data>\n    <data name=\"OpenMavParamsCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда открывает реадактор мавлинк параметров</value>\n    </data>\n    <data name=\"HomePageParamsDeviceItemAction_ActionViewModel_Header\" xml:space=\"preserve\">\n        <value>Редактор параметров</value>\n    </data>\n    <data name=\"HomePageParamsDeviceItemAction_ActionViewModel_Description\" xml:space=\"preserve\">\n        <value>Изменить параметры мавлинк устройства</value>\n    </data>\n    <data name=\"PacketViewerViewModel_SavePacketMessagesDialog_Title\" xml:space=\"preserve\">\n        <value>Выберите разделитель:</value>\n    </data>\n    <data name=\"SavePacketMessagesDialogView_Separator_Tab\" xml:space=\"preserve\">\n        <value>Табуляция</value>\n    </data>\n    <data name=\"PacketViewerView_ToolTip_Save\" xml:space=\"preserve\">\n        <value>Сохранить</value>\n    </data>\n    <data name=\"PacketViewerView_ToolTip_ClearAll\" xml:space=\"preserve\">\n        <value>Очистить все</value>\n    </data>\n    <data name=\"PacketViewerView_ToolTip_PlayPause\" xml:space=\"preserve\">\n        <value>ЗАПУСТИТЬ/ОСТАНОВИТЬ</value>\n    </data>\n    <data name=\"PacketMessageViewModel_CsvColumn_Date\" xml:space=\"preserve\">\n        <value>Дата</value>\n    </data>\n    <data name=\"PacketMessageViewModel_CsvColumn_Message\" xml:space=\"preserve\">\n        <value>Сообщение</value>\n    </data>\n    <data name=\"PacketMessageViewModel_CsvColumn_Source\" xml:space=\"preserve\">\n        <value>Источник</value>\n    </data>\n    <data name=\"PacketMessageViewModel_CsvColumn_Type\" xml:space=\"preserve\">\n        <value>Тип</value>\n    </data>\n    <data name=\"PacketViewerView_ExpanderFilterBySources_Header\" xml:space=\"preserve\">\n        <value>Источники</value>\n    </data>\n    <data name=\"PacketViewerView_ExpanderFilterByTypes_Header\" xml:space=\"preserve\">\n        <value>Типы</value>\n    </data>\n    <data name=\"PacketViewerView_Filters_CheckAll\" xml:space=\"preserve\">\n        <value>Выбрать все</value>\n    </data>\n    <data name=\"PacketViewerViewModel_SavePacketMessagesDialog_CloseButtonText\" xml:space=\"preserve\">\n        <value>Отменить</value>\n    </data>\n    <data name=\"PacketViewerViewModel_SavePacketMessagesDialog_PrimaryButtonText\" xml:space=\"preserve\">\n        <value>Подтвердить</value>\n    </data>\n    <data name=\"FileBrowserViewModel_RenameDialog_Title\" xml:space=\"preserve\">\n        <value>Переименовать</value>\n    </data>\n    <data name=\"FileBrowserViewModel_RenameDialog_PrimaryButtonText\" xml:space=\"preserve\">\n        <value>Подтвердить</value>\n    </data>\n    <data name=\"FileBrowserViewModel_RenameDialog_SecondaryButtonText\" xml:space=\"preserve\">\n        <value>Отменить</value>\n    </data>\n    <data name=\"FileBrowserViewModel_RenameDialog_Message\" xml:space=\"preserve\">\n        <value>Введите новое имя</value>\n    </data>\n    <data name=\"OpenFlightModeCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Открыть режим полета</value>\n    </data>\n    <data name=\"OpenFlightModeCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда, которая открывает страницу режима полета</value>\n    </data>\n    <data name=\"OpenFileBrowserCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Открыть браузер файлов</value>\n    </data>\n    <data name=\"OpenFileBrowserCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда открывает файл браузер</value>\n    </data>\n    <data name=\"OpenPacketViewerCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Открыть просмотр пакетов</value>\n    </data>\n    <data name=\"OpenPacketViewerCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда открывает просмотр пакетов</value>\n    </data>\n    <data name=\"AltitudeIndicatorStyles_ToolTip_Velocity\" xml:space=\"preserve\">\n        <value>Скорость</value>\n    </data>\n    <data name=\"AltitudeIndicatorStyles_ToolTip_Altitude\" xml:space=\"preserve\">\n        <value>Высота</value>\n    </data>\n    <data name=\"AltitudeIndicatorStyles_ToolTip_Compass\" xml:space=\"preserve\">\n        <value>Компас</value>\n    </data>\n    <data name=\"AltitudeIndicatorStyles_ToolTip_Vibration\" xml:space=\"preserve\">\n        <value>Вибрация</value>\n    </data>\n    <data name=\"FileBrowserViewModel_Title\" xml:space=\"preserve\">\n        <value>Браузер</value>\n    </data>\n    <data name=\"UavWidgetViewModel_SetAltitudeDialog_Title\" xml:space=\"preserve\">\n        <value>Введите высоту</value>\n    </data>\n    <data name=\"MavParamsPageView_AwaitingScreen_Header\" xml:space=\"preserve\">\n        <value>Подключение</value>\n    </data>\n    <data name=\"MavParamsPageView_AwaitingScreen_Description\" xml:space=\"preserve\">\n        <value>Подключение к устройству...</value>\n    </data>\n    <data name=\"MavParamsPageView_CancelButton_Content\" xml:space=\"preserve\">\n        <value>Отмена</value>\n    </data>\n    <data name=\"MavParamsPageView_Loading_Text\" xml:space=\"preserve\">\n        <value>Загрузка...</value>\n    </data>\n    <data name=\"PacketViewerViewModel_Title\" xml:space=\"preserve\">\n        <value>Просмотр пакетов</value>\n    </data>\n    <data name=\"MavParamsPageViewModel_Title\" xml:space=\"preserve\">\n        <value>Параметры</value>\n    </data>\n    <data name=\"RenameItemCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Подтвердить переименование элемента</value>\n    </data>\n    <data name=\"RenameItemCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда, которая подтверждает переименование элемента</value>\n    </data>\n    <data name=\"ClearAllPacketsCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Очистить все пакеты</value>\n    </data>\n    <data name=\"ClearAllPacketsCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда, которая очищает все пакеты</value>\n    </data>\n    <data name=\"ExportPacketsToCsvCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Экспортировать пакеты в CSV файл</value>\n    </data>\n    <data name=\"ExportPacketsToCsvCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда экспортирует пакеты в файл с CSV расширением</value>\n    </data>\n    <data name=\"FileBrowserView_AwaitingScreen_Description\" xml:space=\"preserve\">\n        <value>Подключение к FTP сервису устройства...</value>\n    </data>\n    <data name=\"FileBrowserView_AwaitingScreen_Header\" xml:space=\"preserve\">\n        <value>Подключение</value>\n    </data>\n    <data name=\"RemoveItemCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Удалить элемент</value>\n    </data>\n    <data name=\"RemoveItemCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда, которая удаляет элемент</value>\n    </data>\n    <data name=\"CalculateCrc32Command_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда, которая проверяет циклическую избыточность файла (crc32)</value>\n    </data>\n    <data name=\"CalculateCrc32Command_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Рассчитать CRC32</value>\n    </data>\n    <data name=\"CreateDirectoryCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Создать папку</value>\n    </data>\n    <data name=\"CreateDirectoryCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда, которая создаёт папку</value>\n    </data>\n    <data name=\"BurstDownloadItemCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Загрузить (пакетами)</value>\n    </data>\n    <data name=\"BurstDownloadItemCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда, которая загружает элемент пакетами</value>\n    </data>\n    <data name=\"DownloadItemCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Загрузка</value>\n    </data>\n    <data name=\"DownloadItemCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда, которая загружает удалённые элементы</value>\n    </data>\n    <data name=\"UploadItemCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Отгрузка</value>\n    </data>\n    <data name=\"UploadItemCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда, которая отгружает файлы на удалённое устройство</value>\n    </data>\n    <data name=\"FindFileOnLocalCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Найти файл</value>\n    </data>\n    <data name=\"FindFileOnLocalCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда, которая находит файл, выбранный в удаленном дереве, в локальном дереве</value>\n    </data>\n    <data name=\"SetupPageViewModel_Title\" xml:space=\"preserve\">\n        <value>Настройка</value>\n    </data>\n    <data name=\"SetupFrameTypeViewModel_Name\" xml:space=\"preserve\">\n        <value>Тип рамы</value>\n    </data>\n    <data name=\"ChangeFrameTypeCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда, которая изменяет тип рамы</value>\n    </data>\n    <data name=\"ChangeFrameTypeCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Изменить тип рамы</value>\n    </data>\n    <data name=\"SetupFrameTypeView_Loader_Header\" xml:space=\"preserve\">\n        <value>Загрузка...</value>\n    </data>\n    <data name=\"SetupFrameTypeView_Loader_Description\" xml:space=\"preserve\">\n        <value>Пожалуйста, подождите</value>\n    </data>\n    <data name=\"SetupFrameTypeView_ApplyFrame\" xml:space=\"preserve\">\n        <value>Установить</value>\n    </data>\n    <data name=\"SetupFrameTypeView_FrameMetadata\" xml:space=\"preserve\">\n        <value>Мета-данные</value>\n    </data>\n    <data name=\"SetupFrameTypeView_CurrentFrame\" xml:space=\"preserve\">\n        <value>Текущая рама</value>\n    </data>\n    <data name=\"SetupFrameTypeViewModel_ApplyConfirmation_Title\" xml:space=\"preserve\">\n        <value>Смена типа рамы</value>\n    </data>\n    <data name=\"SetupFrameTypeViewModel_ApplyConfirmation_Message\" xml:space=\"preserve\">\n        <value>Вы хотите сменить тип рамы?</value>\n    </data>\n    <data name=\"SetupFrameTypeViewModel_CurrentFrame_Unknown\" xml:space=\"preserve\">\n        <value>Неизвестно</value>\n    </data>\n    <data name=\"OpenSetupCommand_CommandInfo_Name\" xml:space=\"preserve\">\n        <value>Открыть страницу настройки</value>\n    </data>\n    <data name=\"OpenSetupCommand_CommandInfo_Description\" xml:space=\"preserve\">\n        <value>Команда открывает страницу для настройки дрона</value>\n    </data>\n    <data name=\"SetupFrameTypeView_CurrentFrame_Refresh\" xml:space=\"preserve\">\n        <value>Обновить текущую раму</value>\n    </data>\n    <data name=\"SetupFrameTypeView_CurrentFrame_Meta\" xml:space=\"preserve\">\n        <value>Описание</value>\n    </data>\n    <data name=\"MissionProgressView_RefreshButton_Text\" xml:space=\"preserve\">\n        <value>Обновить миссию</value>\n    </data>\n    <data name=\"MissionProgressView_DownloadButton_Text\" xml:space=\"preserve\">\n        <value>Скачать миссию</value>\n    </data>\n    <data name=\"SetupMotorsView_TestDuration\" xml:space=\"preserve\">\n        <value>Время теста</value>\n    </data>\n    <data name=\"SetupMotorsView_DurationLabel_TimeUnit\" xml:space=\"preserve\">\n        <value>сек</value>\n    </data>\n    <data name=\"MotorItemView_Label_MotorId\" xml:space=\"preserve\">\n        <value>Мотор</value>\n    </data>\n    <data name=\"MotorItemView_MonitoringLabel_Servo\" xml:space=\"preserve\">\n        <value>Сервопривод </value>\n    </data>\n    <data name=\"MotorItemView_MonitoringLabel_Pwm\" xml:space=\"preserve\">\n        <value>ШИМ</value>\n    </data>\n    <data name=\"MotorItemView_ToolTip_StartStop\" xml:space=\"preserve\">\n        <value>ЗАПУСТИТЬ/ОСТАНОВИТЬ</value>\n    </data>\n    <data name=\"SetupMotorsViewModel_Name\" xml:space=\"preserve\">\n        <value>Тест моторов</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_E\" xml:space=\"preserve\">\n        <value>В</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_N\" xml:space=\"preserve\">\n        <value>С</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_NE\" xml:space=\"preserve\">\n        <value>СВ</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_NW\" xml:space=\"preserve\">\n        <value>СЗ</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_S\" xml:space=\"preserve\">\n        <value>Ю</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_SE\" xml:space=\"preserve\">\n        <value>ЮВ</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_SW\" xml:space=\"preserve\">\n        <value>ЮЗ</value>\n    </data>\n    <data name=\"HeadingScaleItem_Direction_W\" xml:space=\"preserve\">\n        <value>З</value>\n    </data>\n    <data name=\"BurstDownloadDialogView_Description_Text\" xml:space=\"preserve\">\n        <value>Введите размер блока для загрузки</value>\n    </data>\n    <data name=\"UavWidgetViewModel_BatteryRttBox_BatteryCharge_Header\" xml:space=\"preserve\">\n        <value>Заряд</value>\n    </data>\n    <data name=\"UavWidgetViewModel_BatteryRttBox_BatteryConsumed_Header\" xml:space=\"preserve\">\n        <value>Емкость</value>\n    </data>\n    <data name=\"UavWidgetViewModel_BatteryRttBox_BatteryAmperage_Header\" xml:space=\"preserve\">\n        <value>Сила тока</value>\n    </data>\n    <data name=\"UavWidgetViewModel_BatteryRttBox_BatteryVoltage_Header\" xml:space=\"preserve\">\n        <value>Напряжение</value>\n    </data>\n    <data name=\"UavWidgetViewModel_GnssRttBox_SatellitesCount_Header\" xml:space=\"preserve\">\n        <value>Спутники</value>\n    </data>\n    <data name=\"UavWidgetViewModel_GnssRttBox_Hdop_Header\" xml:space=\"preserve\">\n        <value>Hdop</value>\n    </data>\n    <data name=\"UavWidgetViewModel_GnssRttBox_Vdop_Header\" xml:space=\"preserve\">\n        <value>Vdop</value>\n    </data>\n    <data name=\"UavWidgetViewModel_GnssRttBox_Mode_Header\" xml:space=\"preserve\">\n        <value>Режим</value>\n    </data>\n    <data name=\"HeadingUavIndicatorViewModel_Heading\" xml:space=\"preserve\">\n        <value>Курс</value>\n    </data>\n    <data name=\"HeadingUavIndicatorViewModel_Heading_Short\" xml:space=\"preserve\">\n        <value>КУРС</value>\n    </data>\n    <data name=\"HomeAzimuthUavIndicatorViewModel_HomeAzimuth\" xml:space=\"preserve\">\n        <value>Азимут дома</value>\n    </data>\n    <data name=\"HomeAzimuthUavIndicatorViewModel_HomeAzimuth_Short\" xml:space=\"preserve\">\n        <value>ДОМ</value>\n    </data>\n    <data name=\"VelocityUavIndicatorViewModel_Velocity_Short\" xml:space=\"preserve\">\n        <value>СК</value>\n    </data>\n    <data name=\"AngleUavRttIndicatorViewModel_Angle\" xml:space=\"preserve\">\n        <value>Углы</value>\n    </data>\n    <data name=\"AltitudeUavIndicatorViewModel_Agl\" xml:space=\"preserve\">\n        <value>AGL</value>\n    </data>\n    <data name=\"AltitudeUavIndicatorViewModel_Msl\" xml:space=\"preserve\">\n        <value>MSL</value>\n    </data>\n    <data name=\"AngleUavRttIndicatorViewModel_Pitch\" xml:space=\"preserve\">\n        <value>Тангаж</value>\n    </data>\n    <data name=\"AngleUavRttIndicatorViewModel_Roll\" xml:space=\"preserve\">\n        <value>Крен</value>\n    </data>\n</root>"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/Contracts/FileBrowserBackend.cs",
    "content": "﻿using System;\n\nnamespace Asv.Drones;\n\npublic sealed class FileBrowserBackend(LocalFilesService local, FtpClientService ftp)\n{\n    private LocalBrowserItemsOperations LocalItemsOperations { get; } = new(local);\n    private RemoteBrowserItemsOperations RemoteItemsOperations { get; } = new(ftp);\n\n    public IBrowserItemsOperations ResolveOps(FtpBrowserSourceType type)\n    {\n        return type switch\n        {\n            FtpBrowserSourceType.Local => LocalItemsOperations,\n            FtpBrowserSourceType.Remote => RemoteItemsOperations,\n            _ => throw new ArgumentOutOfRangeException(nameof(type), type, null),\n        };\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/Contracts/IBrowserItemsOperations.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic interface IBrowserItemsOperations\n{\n    char Separator { get; }\n\n    ValueTask<string> RenameFileAsync(\n        string oldPath,\n        string newPath,\n        ILogger logger,\n        CancellationToken ct\n    );\n    ValueTask<string> RenameDirectoryAsync(\n        string oldPath,\n        string newPath,\n        ILogger logger,\n        CancellationToken ct\n    );\n    ValueTask RemoveDirectoryAsync(string path, ILogger logger, CancellationToken ct);\n    ValueTask RemoveFileAsync(string path, ILogger logger, CancellationToken ct);\n    ValueTask CreateDirectoryAsync(string path, ILogger logger, CancellationToken ct);\n    ValueTask<uint> CalculateCrc32Async(string path, ILogger logger, CancellationToken ct);\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/Contracts/LocalBrowserItemsOperations.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic sealed class LocalBrowserItemsOperations(LocalFilesService local) : IBrowserItemsOperations\n{\n    private readonly LocalFilesService _local =\n        local ?? throw new ArgumentNullException(nameof(local));\n\n    public char Separator => Path.DirectorySeparatorChar;\n\n    public ValueTask<string> RenameDirectoryAsync(\n        string oldPath,\n        string newPath,\n        ILogger logger,\n        CancellationToken ct\n    )\n    {\n        var result = _local.RenameDirectory(oldPath, newPath, logger);\n        return ValueTask.FromResult(result);\n    }\n\n    public ValueTask<string> RenameFileAsync(\n        string oldPath,\n        string newPath,\n        ILogger logger,\n        CancellationToken ct\n    )\n    {\n        var result = _local.RenameFile(oldPath, newPath, logger);\n        return ValueTask.FromResult(result);\n    }\n\n    public ValueTask RemoveDirectoryAsync(string path, ILogger logger, CancellationToken ct)\n    {\n        ct.ThrowIfCancellationRequested();\n        _local.RemoveDirectory(path, true, logger);\n        return ValueTask.CompletedTask;\n    }\n\n    public ValueTask RemoveFileAsync(string path, ILogger logger, CancellationToken ct)\n    {\n        ct.ThrowIfCancellationRequested();\n        _local.RemoveFile(path, logger);\n        return ValueTask.CompletedTask;\n    }\n\n    public ValueTask CreateDirectoryAsync(string path, ILogger logger, CancellationToken ct)\n    {\n        _local.CreateDirectory(path, logger);\n        return ValueTask.CompletedTask;\n    }\n\n    public async ValueTask<uint> CalculateCrc32Async(\n        string path,\n        ILogger logger,\n        CancellationToken ct\n    )\n    {\n        return await _local.CalculateCrc32Async(path, ct, logger);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/Contracts/RemoteBrowserItemsOperations.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic sealed class RemoteBrowserItemsOperations(FtpClientService ftp) : IBrowserItemsOperations\n{\n    private readonly FtpClientService _ftp = ftp ?? throw new ArgumentNullException(nameof(ftp));\n\n    public char Separator => MavlinkFtpHelper.DirectorySeparator;\n\n    public async ValueTask<string> RenameDirectoryAsync(\n        string oldPath,\n        string newPath,\n        ILogger logger,\n        CancellationToken ct\n    )\n    {\n        return await _ftp.RenameAsync(oldPath, newPath, ct);\n    }\n\n    public async ValueTask<string> RenameFileAsync(\n        string oldPath,\n        string newPath,\n        ILogger logger,\n        CancellationToken ct\n    )\n    {\n        return await _ftp.RenameAsync(oldPath, newPath, ct);\n    }\n\n    public async ValueTask RemoveDirectoryAsync(string path, ILogger logger, CancellationToken ct)\n    {\n        await _ftp.RemoveDirectoryAsync(path, true, ct);\n    }\n\n    public async ValueTask RemoveFileAsync(string path, ILogger logger, CancellationToken ct)\n    {\n        await _ftp.RemoveFileAsync(path, ct);\n    }\n\n    public async ValueTask CreateDirectoryAsync(string path, ILogger logger, CancellationToken ct)\n    {\n        await _ftp.CreateDirectoryAsync(path, ct);\n    }\n\n    public async ValueTask<uint> CalculateCrc32Async(\n        string path,\n        ILogger logger,\n        CancellationToken ct\n    )\n    {\n        return await _ftp.CalculateCrc32Async(path, ct);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/Dialogs/BurstDownloadDialogView.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    x:Class=\"Asv.Drones.BurstDownloadDialogView\"\n    x:DataType=\"drones:BurstDownloadDialogViewModel\"\n>\n    <StackPanel Spacing=\"10\" Margin=\"10\">\n        <TextBlock Text=\"{x:Static drones:RS.BurstDownloadDialogView_Description_Text}\" />\n        <NumericUpDown\n            Increment=\"1\"\n            Minimum=\"0\"\n            Maximum=\"239\"\n            AllowSpin=\"True\"\n            Value=\"{Binding PacketSize.Value}\"\n        />\n    </StackPanel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/Dialogs/BurstDownloadDialogView.axaml.cs",
    "content": "using Asv.Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class BurstDownloadDialogView : UserControl\n{\n    public BurstDownloadDialogView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/Dialogs/BurstDownloadDialogViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class BurstDownloadDialogViewModel : DialogViewModelBase\n{\n    private const string DialogId = $\"{BaseId}.burst\";\n\n    public BurstDownloadDialogViewModel()\n        : this(DesignTime.LoggerFactory)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public BurstDownloadDialogViewModel(ILoggerFactory loggerFactory)\n        : base(DialogId, loggerFactory)\n    {\n        PacketSize = new BindableReactiveProperty<byte?>(\n            MavlinkFtpHelper.MaxDataSize\n        ).DisposeItWith(Disposable);\n        PacketSize\n            .EnableValidationRoutable(\n                arg =>\n                {\n                    if (arg is < 1 or > MavlinkFtpHelper.MaxDataSize)\n                    {\n                        return ValidationResult.FailAsOutOfRange(\n                            \"1\",\n                            MavlinkFtpHelper.MaxDataSize.ToString()\n                        );\n                    }\n\n                    return ValidationResult.Success;\n                },\n                this,\n                isForceValidation: true\n            )\n            .DisposeItWith(Disposable);\n    }\n\n    public BindableReactiveProperty<byte?> PacketSize { get; }\n\n    public override void ApplyDialog(ContentDialog dialog)\n    {\n        dialog.DefaultButton = ContentDialogButton.Primary;\n        _sub.Disposable = IsValid.Subscribe(b => dialog.IsPrimaryButtonEnabled = b);\n    }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        return [];\n    }\n\n    private readonly SerialDisposable _sub = new();\n\n    protected override void Dispose(bool disposing)\n    {\n        if (disposing)\n        {\n            _sub.Dispose();\n        }\n\n        base.Dispose(disposing);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/FileBrowserView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:avalonia=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    xmlns:avalonia1=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"800\"\n    d:DesignHeight=\"450\"\n    x:CompileBindings=\"True\"\n    x:Class=\"Asv.Drones.FileBrowserView\"\n    x:DataType=\"drones:FileBrowserViewModel\"\n>\n    <Design.DataContext>\n        <drones:FileBrowserViewModel />\n    </Design.DataContext>\n    <UserControl.Resources>\n        <drones:Crc32StatusToColorConverter x:Key=\"Crc32StatusToColorConverter\" />\n        <avalonia1:AsvColorKind x:Key=\"IconColor\">Info5</avalonia1:AsvColorKind>\n        <avalonia1:AsvColorKind x:Key=\"FolderColor\">Info10</avalonia1:AsvColorKind>\n        <avalonia1:AsvColorKind x:Key=\"FileColor\">Info5</avalonia1:AsvColorKind>\n    </UserControl.Resources>\n    <UserControl.Styles>\n        <Style Selector=\"TreeViewItem\" x:DataType=\"drones:BrowserNode\">\n            <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n            <Setter Property=\"IsExpanded\" Value=\"{Binding Base.IsExpanded, Mode=TwoWay}\" />\n            <Setter Property=\"IsSelected\" Value=\"{Binding Base.IsSelected, Mode=TwoWay}\" />\n        </Style>\n        <Style Selector=\"avalonia|MaterialIcon.folder\">\n            <Setter Property=\"Kind\" Value=\"FolderOutline\" />\n            <Setter Property=\"avalonia1:AsvPallete.Color\" Value=\"{StaticResource FolderColor}\" />\n        </Style>\n        <Style Selector=\"avalonia|MaterialIcon.folder.selected\">\n            <Setter Property=\"Kind\" Value=\"Folder\" />\n            <Setter Property=\"avalonia1:AsvPallete.Color\" Value=\"{StaticResource FolderColor}\" />\n        </Style>\n        <Style Selector=\"avalonia|MaterialIcon.folder.expanded\">\n            <Setter Property=\"Kind\" Value=\"FolderOpenOutline\" />\n            <Setter Property=\"avalonia1:AsvPallete.Color\" Value=\"{StaticResource FolderColor}\" />\n        </Style>\n        <Style Selector=\"avalonia|MaterialIcon.file\">\n            <Setter Property=\"Kind\" Value=\"FileMarkerOutline\" />\n            <Setter Property=\"avalonia1:AsvPallete.Color\" Value=\"{StaticResource FileColor}\" />\n        </Style>\n        <Style Selector=\"avalonia|MaterialIcon.file.selected\" x:DataType=\"drones:BrowserNode\">\n            <Setter Property=\"Kind\" Value=\"FileMarker\" />\n        </Style>\n        <Style Selector=\"Button avalonia|MaterialIcon, Button avalonia|MaterialIconExt\">\n            <Setter Property=\"avalonia1:AsvPallete.Color\" Value=\"{StaticResource IconColor}\" />\n        </Style>\n        <Style Selector=\"Button.delete avalonia|MaterialIcon, Button.delete avalonia|MaterialIconExt\">\n            <Setter Property=\"avalonia1:AsvPallete.Color\" Value=\"Error\" />\n        </Style>\n        <Style Selector=\"Button:disabled avalonia|MaterialIcon, Button:disabled avalonia|MaterialIconExt\">\n            <Setter Property=\"avalonia1:AsvPallete.Color\" Value=\"Unknown\" />\n            <Setter Property=\"Button.Opacity\" Value=\"0.4\" />\n        </Style>\n        <Style Selector=\"Button\">\n            <Setter Property=\"Theme\" Value=\"{DynamicResource TransparentButton}\" />\n        </Style>\n    </UserControl.Styles>\n    <Panel>\n        <avalonia1:AwaitingScreen\n            Header=\"{x:Static drones:RS.FileBrowserView_AwaitingScreen_Header}\"\n            Description=\"{x:Static drones:RS.FileBrowserView_AwaitingScreen_Description}\"\n            IsVisible=\"{Binding !IsDeviceInitialized.Value}\"\n        />\n        <Grid RowDefinitions=\"*, Auto\" IsVisible=\"{Binding IsDeviceInitialized.Value} \">\n            <Grid Grid.Row=\"0\" x:Name=\"MainGrid\">\n                <Grid.ColumnDefinitions>\n                    <ColumnDefinition Width=\"*\" MinWidth=\"300\" />\n                    <ColumnDefinition Width=\"30\" />\n                    <ColumnDefinition Width=\"*\" MinWidth=\"300\" />\n                </Grid.ColumnDefinitions>\n\n                <Border Grid.Row=\"0\" Grid.Column=\"0\" Padding=\"8\" x:Name=\"LocalFilesColumn\">\n                    <DockPanel>\n                        <StackPanel\n                            KeyboardNavigation.TabNavigation=\"None\"\n                            DockPanel.Dock=\"Top\"\n                            Spacing=\"4\"\n                        >\n                            <StackPanel Spacing=\"4\" Orientation=\"Horizontal\">\n                                <Button\n                                    Command=\"{Binding CreateLocalFolderCommand}\"\n                                    CommandParameter=\"{Binding LocalSelectedItem.Value}\"\n                                >\n                                    <avalonia:MaterialIcon\n                                        Kind=\"FolderPlusOutline\"\n                                        avalonia1:AsvPallete.Color=\"{StaticResource FolderColor}\"\n                                    />\n                                </Button>\n                                <Button\n                                    Command=\"{Binding LocalRenameCommand}\"\n                                    CommandParameter=\"{Binding LocalSelectedItem.Value}\"\n                                >\n                                    <avalonia:MaterialIcon Kind=\"Pencil\" />\n                                </Button>\n                                <Button\n                                    Command=\"{Binding CalculateLocalCrc32Command}\"\n                                    CommandParameter=\"{Binding LocalSelectedItem.Value}\"\n                                >\n                                    <avalonia:MaterialIcon Kind=\"KeyOutline\" />\n                                </Button>\n                                <Button\n                                    Classes=\"delete\"\n                                    Command=\"{Binding RemoveLocalItemCommand}\"\n                                    CommandParameter=\"{Binding LocalSelectedItem.Value}\"\n                                >\n                                    <avalonia:MaterialIcon Kind=\"Trash\" />\n                                </Button>\n                            </StackPanel>\n                            <Grid ColumnDefinitions=\"*, Auto\" Margin=\"0 0 0 4\">\n                                <avalonia1:SearchBoxView\n                                    Grid.Column=\"0\"\n                                    DataContext=\"{Binding LocalSearch}\"\n                                    Margin=\"0 0 8 0\"\n                                />\n                                <Button\n                                    Grid.Column=\"1\"\n                                    VerticalAlignment=\"Stretch\"\n                                    HorizontalAlignment=\"Center\"\n                                    Command=\"{Binding RefreshLocalCommand}\"\n                                >\n                                    <avalonia:MaterialIcon\n                                        Kind=\"Refresh\"\n                                        avalonia1:AsvPallete.Color=\"{StaticResource IconColor}\"\n                                    />\n                                </Button>\n                            </Grid>\n                        </StackPanel>\n\n                        <!--Local tree-->\n                        <TreeView\n                            ItemsSource=\"{Binding LocalItemsTree.Items}\"\n                            SelectedItem=\"{Binding LocalSelectedItem.Value}\"\n                            drones:TreeViewBehaviors.IgnoreEnter=\"True\"\n                            PointerPressed=\"TreeView_OnPointerPressed\"\n                        >\n                            <TreeView.ItemTemplate>\n                                <TreeDataTemplate\n                                    ItemsSource=\"{Binding Items}\"\n                                    DataType=\"{x:Type drones:BrowserNode}\"\n                                >\n                                    <Grid ColumnDefinitions=\"Auto, Auto, *, Auto\">\n                                        <avalonia:MaterialIcon\n                                            Grid.Column=\"0\"\n                                            Margin=\"0,0,8,0\"\n                                            Classes.folder=\"{Binding Base.HasChildren}\"\n                                            Classes.file=\"{Binding !Base.HasChildren}\"\n                                            Classes.expanded=\"{Binding IsExpanded,\n                                                                RelativeSource={RelativeSource AncestorType=TreeViewItem}}\"\n                                            Classes.selected=\"{Binding IsSelected,\n                                                                RelativeSource={RelativeSource AncestorType=TreeViewItem}}\"\n                                            Classes=\"small\"\n                                        />\n                                        <TextBlock\n                                            Grid.Column=\"1\"\n                                            VerticalAlignment=\"Center\"\n                                            IsVisible=\"{Binding !Base.EditMode}\"\n                                            Text=\"{Binding Base.Name}\"\n                                        />\n                                        <TextBox\n                                            Grid.Column=\"1\"\n                                            VerticalAlignment=\"Center\"\n                                            IsVisible=\"{Binding Base.EditMode}\"\n                                            Text=\"{Binding Base.EditedName.Value}\"\n                                        >\n                                            <TextBox.InnerRightContent>\n                                                <Button\n                                                    Content=\"{avalonia:MaterialIconExt Kind=Check}\"\n                                                    Command=\"{Binding Base.CommitRename}\"\n                                                    CommandParameter=\"{Binding Base}\"\n                                                    VerticalAlignment=\"Stretch\"\n                                                    HorizontalAlignment=\"Stretch\"\n                                                    Margin=\"1\"\n                                                />\n                                            </TextBox.InnerRightContent>\n                                        </TextBox>\n                                        <TextBlock\n                                            Grid.Column=\"2\"\n                                            Margin=\"0,0,8,0\"\n                                            VerticalAlignment=\"Center\"\n                                            HorizontalAlignment=\"Right\"\n                                            Text=\"{Binding Base.Crc32Hex}\"\n                                            avalonia1:AsvPallete.Color=\"{CompiledBinding Base.Crc32Status, Converter={StaticResource Crc32StatusToColorConverter}}\"\n                                        ></TextBlock>\n                                        <TextBlock\n                                            Grid.Column=\"3\"\n                                            Margin=\"0,0,8,0\"\n                                            VerticalAlignment=\"Center\"\n                                            HorizontalAlignment=\"Right\"\n                                            Text=\"{Binding Base.Size}\"\n                                        />\n                                    </Grid>\n                                </TreeDataTemplate>\n                            </TreeView.ItemTemplate>\n                        </TreeView>\n                    </DockPanel>\n                </Border>\n\n                <!--Middle space-->\n                <GridSplitter\n                    Grid.Row=\"0\"\n                    Grid.Column=\"1\"\n                    x:Name=\"GridSplitterColumn\"\n                    Width=\"30\"\n                    KeyboardNavigation.TabNavigation=\"None\"\n                    DragCompleted=\"GridSplitter_Dragged\"\n                    IsTabStop=\"False\"\n                />\n\n                <StackPanel\n                    Grid.Row=\"0\"\n                    Grid.Column=\"1\"\n                    x:Name=\"RemoteFilesColumn\"\n                    KeyboardNavigation.TabNavigation=\"None\"\n                    IsEnabled=\"{Binding !IsUiBlocked.Value}\"\n                    IsTabStop=\"False\"\n                    HorizontalAlignment=\"Stretch\"\n                    VerticalAlignment=\"Center\"\n                    Margin=\"2\"\n                    Spacing=\"8\"\n                >\n                    <Button\n                        Height=\"50\"\n                        Content=\"{avalonia:MaterialIconExt Kind=TransferRight}\"\n                        Command=\"{Binding UploadCommand}\"\n                        IsEnabled=\"{Binding !IsUiBlocked.Value}\"\n                        CommandParameter=\"{Binding LocalSelectedItem.Value}\"\n                    />\n                    <Button\n                        Height=\"50\"\n                        Content=\"{avalonia:MaterialIconExt Kind=TransferLeft}\"\n                        IsEnabled=\"{Binding !IsUiBlocked.Value}\"\n                        Command=\"{Binding ShowDownloadPopupCommand}\"\n                    />\n\n                    <Popup\n                        IsOpen=\"{Binding IsDownloadPopupOpen.Value}\"\n                        ShouldUseOverlayLayer=\"True\"\n                        OverlayDismissEventPassThrough=\"False\"\n                        Placement=\"RightEdgeAlignedBottom\"\n                        HorizontalOffset=\"12\"\n                        Topmost=\"True\"\n                    >\n                        <Border Padding=\"10\" CornerRadius=\"6\" avalonia1:AsvPallete.Color=\"None\">\n                            <StackPanel>\n                                <Button\n                                    Content=\"{x:Static drones:RS.FileBrowserView_Button_Download_Content}\"\n                                    Command=\"{Binding DownloadCommand}\"\n                                    IsEnabled=\"{Binding !IsUiBlocked.Value}\"\n                                    CommandParameter=\"{Binding RemoteSelectedItem.Value}\"\n                                    Margin=\"0,0,0,5\"\n                                />\n                                <Button\n                                    Content=\"{x:Static drones:RS.FileBrowserView_Button_BurstDownload_Content}\"\n                                    IsEnabled=\"{Binding !IsUiBlocked.Value}\"\n                                    Command=\"{Binding BurstDownloadCommand}\"\n                                    CommandParameter=\"{Binding RemoteSelectedItem.Value}\"\n                                />\n                            </StackPanel>\n                        </Border>\n                    </Popup>\n                </StackPanel>\n\n                <Border Grid.Row=\"0\" Grid.Column=\"2\" Padding=\"8\">\n                    <DockPanel>\n                        <StackPanel\n                            KeyboardNavigation.TabNavigation=\"None\"\n                            DockPanel.Dock=\"Top\"\n                            Spacing=\"4\"\n                        >\n                            <StackPanel Spacing=\"4\" Orientation=\"Horizontal\">\n                                <Button\n                                    Command=\"{Binding CreateRemoteFolderCommand}\"\n                                    CommandParameter=\"{Binding RemoteSelectedItem.Value}\"\n                                >\n                                    <avalonia:MaterialIcon\n                                        Kind=\"FolderPlusOutline\"\n                                        avalonia1:AsvPallete.Color=\"{StaticResource FolderColor}\"\n                                    />\n                                </Button>\n                                <Button\n                                    Command=\"{Binding RemoteRenameCommand}\"\n                                    CommandParameter=\"{Binding RemoteSelectedItem.Value}\"\n                                >\n                                    <avalonia:MaterialIcon Kind=\"Pencil\" />\n                                </Button>\n                                <Button\n                                    Command=\"{Binding CompareSelectedItemsCommand}\"\n                                    CommandParameter=\"{Binding LocalSelectedItem.Value}\"\n                                >\n                                    <avalonia:MaterialIcon Kind=\"SwapHorizontalCircleOutline\" />\n                                </Button>\n                                <Button Command=\"{Binding FindFileOnLocalCommand}\">\n                                    <avalonia:MaterialIcon Kind=\"FileFindOutline\" />\n                                </Button>\n                                <Button\n                                    Command=\"{Binding CalculateRemoteCrc32Command}\"\n                                    CommandParameter=\"{Binding RemoteSelectedItem.Value}\"\n                                >\n                                    <avalonia:MaterialIcon Kind=\"KeyOutline\" />\n                                </Button>\n                                <Button\n                                    Classes=\"delete\"\n                                    Command=\"{Binding RemoveRemoteItemCommand}\"\n                                    CommandParameter=\"{Binding RemoteSelectedItem.Value}\"\n                                >\n                                    <avalonia:MaterialIcon Kind=\"Trash\" />\n                                </Button>\n                            </StackPanel>\n                            <Grid ColumnDefinitions=\"*, Auto\" Margin=\"0 0 0 4\">\n                                <avalonia1:SearchBoxView\n                                    Grid.Column=\"0\"\n                                    DataContext=\"{Binding RemoteSearch}\"\n                                    Margin=\"0 0 8 0\"\n                                />\n                                <Button\n                                    Grid.Column=\"1\"\n                                    VerticalAlignment=\"Stretch\"\n                                    HorizontalAlignment=\"Center\"\n                                    Command=\"{Binding RefreshRemoteCommand}\"\n                                >\n                                    <avalonia:MaterialIcon\n                                        Kind=\"Refresh\"\n                                        avalonia1:AsvPallete.Color=\"{StaticResource IconColor}\"\n                                    />\n                                </Button>\n                            </Grid>\n                        </StackPanel>\n\n                        <!--Remote tree-->\n                        <TreeView\n                            ItemsSource=\"{Binding RemoteItemsTree.Items}\"\n                            SelectedItem=\"{Binding RemoteSelectedItem.Value}\"\n                            drones:TreeViewBehaviors.IgnoreEnter=\"True\"\n                            PointerPressed=\"TreeView_OnPointerPressed\"\n                        >\n                            <TreeView.ItemTemplate>\n                                <TreeDataTemplate\n                                    ItemsSource=\"{Binding Items}\"\n                                    DataType=\"{x:Type drones:BrowserNode}\"\n                                >\n                                    <Grid ColumnDefinitions=\"Auto, Auto, *, Auto\">\n                                        <avalonia:MaterialIcon\n                                            Grid.Column=\"0\"\n                                            Margin=\"0,0,8,0\"\n                                            Classes.folder=\"{Binding Base.HasChildren}\"\n                                            Classes.file=\"{Binding !Base.HasChildren}\"\n                                            Classes.expanded=\"{Binding IsExpanded,\n                                                                RelativeSource={RelativeSource AncestorType=TreeViewItem}}\"\n                                            Classes.selected=\"{Binding IsSelected,\n                                                                RelativeSource={RelativeSource AncestorType=TreeViewItem}}\"\n                                            Classes=\"small\"\n                                        />\n                                        <TextBlock\n                                            Grid.Column=\"1\"\n                                            VerticalAlignment=\"Center\"\n                                            IsVisible=\"{Binding !Base.EditMode}\"\n                                            Text=\"{Binding Base.Name}\"\n                                        />\n                                        <TextBox\n                                            Grid.Column=\"1\"\n                                            VerticalAlignment=\"Center\"\n                                            IsVisible=\"{Binding Base.EditMode}\"\n                                            Text=\"{Binding Base.EditedName.Value}\"\n                                        >\n                                            <TextBox.InnerRightContent>\n                                                <Button\n                                                    Content=\"{avalonia:MaterialIconExt Kind=Check}\"\n                                                    Command=\"{Binding Base.CommitRename}\"\n                                                    CommandParameter=\"{Binding Base}\"\n                                                    VerticalAlignment=\"Stretch\"\n                                                    HorizontalAlignment=\"Stretch\"\n                                                    Margin=\"1\"\n                                                />\n                                            </TextBox.InnerRightContent>\n                                        </TextBox>\n                                        <TextBlock\n                                            Grid.Column=\"2\"\n                                            Margin=\"0,0,8,0\"\n                                            VerticalAlignment=\"Center\"\n                                            HorizontalAlignment=\"Right\"\n                                            Text=\"{Binding Base.Crc32Hex}\"\n                                            avalonia1:AsvPallete.Color=\"{CompiledBinding Base.Crc32Status, Converter={StaticResource Crc32StatusToColorConverter}}\"\n                                        ></TextBlock>\n                                        <TextBlock\n                                            Grid.Column=\"3\"\n                                            Margin=\"0,0,8,0\"\n                                            VerticalAlignment=\"Center\"\n                                            HorizontalAlignment=\"Right\"\n                                            Text=\"{Binding Base.Size}\"\n                                        />\n                                    </Grid>\n                                </TreeDataTemplate>\n                            </TreeView.ItemTemplate>\n                        </TreeView>\n                    </DockPanel>\n                </Border>\n            </Grid>\n            <Grid\n                Grid.Row=\"1\"\n                ColumnDefinitions=\"*, Auto\"\n                Margin=\"0 4 0 0\"\n                IsVisible=\"{Binding IsProgressVisible.Value}\"\n            >\n                <ProgressBar\n                    Grid.Column=\"0\"\n                    Height=\"10\"\n                    avalonia1:AsvPallete.Color=\"{StaticResource IconColor}\"\n                    Value=\"{Binding Progress.Value}\"\n                    Minimum=\"0\"\n                    Maximum=\"1\"\n                />\n                <Button\n                    Grid.Column=\"1\"\n                    VerticalAlignment=\"Center\"\n                    Command=\"{Binding CancelTransferCommand}\"\n                    IsEnabled=\"{Binding IsTransferInProgress.Value}\"\n                >\n                    <avalonia:MaterialIcon Kind=\"Close\" />\n                </Button>\n            </Grid>\n        </Grid>\n    </Panel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/FileBrowserView.axaml.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing Asv.Avalonia;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Input;\nusing Avalonia.Interactivity;\nusing Avalonia.VisualTree;\n\nnamespace Asv.Drones;\n\npublic class FileBrowserViewConfig : IGridColumnLayoutConfig\n{\n    public IDictionary<string, ColumnConfig> Columns { get; set; } =\n        new Dictionary<string, ColumnConfig>();\n}\n\npublic partial class FileBrowserView : UserControl\n{\n    private readonly ILayoutService _layoutService;\n\n    private int _realLocalFilesColumnOrder;\n    private int _realGridSplitterColumnOrder;\n    private int _realRemoteFilesColumnOrder;\n    private FileBrowserViewConfig? _config;\n\n    public FileBrowserView()\n        : this(NullLayoutService.Instance)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public FileBrowserView(ILayoutService layoutService)\n    {\n        _layoutService = layoutService;\n        InitializeComponent();\n    }\n\n    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)\n    {\n        LoadLayout();\n        base.OnAttachedToVisualTree(e);\n    }\n\n    private void GridSplitter_Dragged(object? sender, VectorEventArgs e)\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        if (sender is not GridSplitter)\n        {\n            return;\n        }\n\n        SaveLayout();\n    }\n\n    private void LoadLayout()\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        _config = _layoutService.Get<FileBrowserViewConfig>(this);\n\n        _realLocalFilesColumnOrder = Grid.GetColumn(LocalFilesColumn);\n        _realGridSplitterColumnOrder = Grid.GetColumn(GridSplitterColumn);\n        _realRemoteFilesColumnOrder = Grid.GetColumn(RemoteFilesColumn);\n\n        if (_config.Columns.Count == 0)\n        {\n            return;\n        }\n\n        if (\n            MainGrid.ColumnDefinitions.Count != _config.Columns.Keys.Count\n            || _config.Columns.All(kvp => kvp.Value.Width.Value == 0)\n            || !_config.Columns.TryGetValue(\n                LocalFilesColumn.Name ?? string.Empty,\n                out var localFilesColumn\n            )\n            || !_config.Columns.TryGetValue(\n                GridSplitterColumn.Name ?? string.Empty,\n                out var gridSplitterColumn\n            )\n            || !_config.Columns.TryGetValue(\n                RemoteFilesColumn.Name ?? string.Empty,\n                out var remoteFilesColumn\n            )\n            || _realLocalFilesColumnOrder != localFilesColumn.Order\n            || _realGridSplitterColumnOrder != gridSplitterColumn.Order\n            || _realRemoteFilesColumnOrder != remoteFilesColumn.Order\n        )\n        {\n            _config = new FileBrowserViewConfig();\n            return;\n        }\n\n        MainGrid.ColumnDefinitions[_realLocalFilesColumnOrder].Width = new GridLength(\n            localFilesColumn.Width.Value,\n            GridUnitType.Star\n        );\n\n        MainGrid.ColumnDefinitions[_realGridSplitterColumnOrder].Width = new GridLength(\n            gridSplitterColumn.Width.Value,\n            GridUnitType.Star\n        );\n\n        MainGrid.ColumnDefinitions[_realRemoteFilesColumnOrder].Width = new GridLength(\n            remoteFilesColumn.Width.Value,\n            GridUnitType.Star\n        );\n    }\n\n    private void SaveLayout()\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        if (_config is null)\n        {\n            return;\n        }\n\n        if (DataContext is null)\n        {\n            return;\n        }\n\n        if (\n            LocalFilesColumn.Name is null\n            || GridSplitterColumn.Name is null\n            || RemoteFilesColumn.Name is null\n        )\n        {\n            return;\n        }\n\n        _config.Columns = new Dictionary<string, ColumnConfig>\n        {\n            [LocalFilesColumn.Name] = new()\n            {\n                Order = _realLocalFilesColumnOrder,\n                Width = new GridLengthConfig\n                {\n                    Value = MainGrid.ColumnDefinitions[_realLocalFilesColumnOrder].ActualWidth,\n                    GridUnitType = GridUnitType.Star,\n                },\n            },\n            [GridSplitterColumn.Name] = new()\n            {\n                Order = _realGridSplitterColumnOrder,\n                Width = new GridLengthConfig\n                {\n                    Value = MainGrid.ColumnDefinitions[_realGridSplitterColumnOrder].ActualWidth,\n                    GridUnitType = GridUnitType.Star,\n                },\n            },\n            [RemoteFilesColumn.Name] = new()\n            {\n                Order = _realRemoteFilesColumnOrder,\n                Width = new GridLengthConfig\n                {\n                    Value = MainGrid.ColumnDefinitions[_realRemoteFilesColumnOrder].ActualWidth,\n                    GridUnitType = GridUnitType.Star,\n                },\n            },\n        };\n\n        _layoutService.SetInMemory(this, _config);\n    }\n\n    private void TreeView_OnPointerPressed(object? sender, PointerPressedEventArgs e)\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        if (sender is not TreeView tree)\n        {\n            return;\n        }\n\n        var hit = tree.InputHitTest(e.GetPosition(tree));\n\n        if (IsInsideTreeViewItem(hit))\n        {\n            return;\n        }\n\n        tree.SelectedItem = null;\n    }\n\n    private static bool IsInsideTreeViewItem(IInputElement? hit)\n    {\n        var v = hit as Visual;\n        while (v is not null && v is not TreeViewItem)\n        {\n            v = v.GetVisualParent();\n        }\n\n        return v is TreeViewItem;\n    }\n}\n\npublic static class TreeViewBehaviors\n{\n    public static readonly AttachedProperty<bool> IgnoreEnterProperty =\n        AvaloniaProperty.RegisterAttached<TreeView, bool>(\"IgnoreEnter\", typeof(TreeViewBehaviors));\n\n    static TreeViewBehaviors()\n    {\n        IgnoreEnterProperty.Changed.AddClassHandler<TreeView>(OnIgnoreEnterChanged);\n    }\n\n    public static void SetIgnoreEnter(TreeView element, bool value) =>\n        element.SetValue(IgnoreEnterProperty, value);\n\n    public static bool GetIgnoreEnter(TreeView element) => element.GetValue(IgnoreEnterProperty);\n\n    private static void OnIgnoreEnterChanged(TreeView tree, AvaloniaPropertyChangedEventArgs e)\n    {\n        var enabled = e.GetNewValue<bool>();\n        if (enabled)\n        {\n            tree.AddHandler(InputElement.KeyDownEvent, OnTreeKeyDown, RoutingStrategies.Tunnel);\n        }\n        else\n        {\n            tree.RemoveHandler(InputElement.KeyDownEvent, OnTreeKeyDown);\n        }\n    }\n\n    private static void OnTreeKeyDown(object? sender, KeyEventArgs e)\n    {\n        if (e.Key == Key.Enter)\n        {\n            e.Handled = true;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/FileBrowserViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Cfg;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Asv.Modeling;\nusing Material.Icons;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\nusing NuGet.Packaging;\nusing ObservableCollections;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic sealed class FileBrowserViewModelConfig\n{\n    public string LocalSearchText { get; set; } = string.Empty;\n    public string RemoteSearchText { get; set; } = string.Empty;\n    public string? LocalSelectedItemKey { get; set; }\n    public string? RemoteSelectedItemKey { get; set; }\n    public IDictionary<string, DirectoryItemViewModelConfig> LocalDirectories { get; set; } =\n        new Dictionary<string, DirectoryItemViewModelConfig>();\n    public IDictionary<string, DirectoryItemViewModelConfig> RemoteDirectories { get; set; } =\n        new Dictionary<string, DirectoryItemViewModelConfig>();\n}\n\npublic class FileBrowserViewModel\n    : DevicePageViewModel<IFileBrowserViewModel>,\n        IFileBrowserViewModel,\n        ISupportRefresh,\n        ITransferFtpEntries\n{\n    public const string PageId = \"files.browser\";\n    public const MaterialIconKind PageIcon = MaterialIconKind.FolderEye;\n    private const int SearchThrottleMs = 500;\n\n    private readonly LocalFilesService _localFilesService;\n\n    private readonly YesOrNoDialogPrefab _yesNoDialog;\n    private readonly ILoggerFactory _loggerFactory;\n    private readonly INavigationService _navigation;\n    private readonly ProgressWithLock _transfer;\n    private readonly string _localRootPath;\n    private readonly string _remoteRootPath;\n\n    private readonly ObservableDictionary<string, IFtpEntry> _rawRemoteEntries;\n\n    private readonly ObservableList<IBrowserItemViewModel> _localItems;\n    private readonly ObservableList<IBrowserItemViewModel> _remoteItems;\n\n    private FtpClientService? _ftpService;\n    private FileBrowserBackend? _backend;\n    private FileBrowserViewModelConfig? _config;\n\n    public FileBrowserViewModel()\n        : this(\n            DesignTime.CommandService,\n            NullDeviceManager.Instance,\n            AppHost.Instance.Services.GetRequiredService<IHostEnvironment>(),\n            NullLayoutService.Instance,\n            NullLoggerFactory.Instance,\n            DesignTime.Navigation,\n            NullDialogService.Instance,\n            NullExtensionService.Instance\n        )\n    {\n        RemoteItemsTree = new BrowserTree(\n            new ObservableList<IBrowserItemViewModel>\n            {\n                new DirectoryItemViewModel(\n                    NavigationId.Empty,\n                    \"/\",\n                    \"/folder/\",\n                    \"folder\",\n                    FtpBrowserSourceType.Remote,\n                    _loggerFactory\n                ),\n                new FileItemViewModel(\n                    NavigationId.Empty,\n                    \"/\",\n                    \"/file.txt\",\n                    \"file.txt\",\n                    111,\n                    FtpBrowserSourceType.Remote,\n                    _loggerFactory\n                ),\n                new FileItemViewModel(\n                    NavigationId.Empty,\n                    \"/\",\n                    \"/file2.txt\",\n                    \"file2.txt\",\n                    2222,\n                    FtpBrowserSourceType.Remote,\n                    _loggerFactory\n                ),\n                new FileItemViewModel(\n                    NavigationId.Empty,\n                    \"/\",\n                    \"/file3.txt\",\n                    \"file3.txt\",\n                    333333,\n                    FtpBrowserSourceType.Remote,\n                    _loggerFactory\n                ),\n                new FileItemViewModel(\n                    NavigationId.Empty,\n                    \"/\",\n                    \"/file4.txt\",\n                    \"file4.txt\",\n                    44444444,\n                    FtpBrowserSourceType.Remote,\n                    _loggerFactory\n                ),\n            },\n            \"/\"\n        );\n        LocalItemsTree = new BrowserTree(\n            new ObservableList<IBrowserItemViewModel>\n            {\n                new DirectoryItemViewModel(\n                    NavigationId.Empty,\n                    \"/\",\n                    \"/folder/\",\n                    \"folder\",\n                    FtpBrowserSourceType.Remote,\n                    _loggerFactory\n                ),\n                new FileItemViewModel(\n                    NavigationId.Empty,\n                    \"/\",\n                    \"/file.txt\",\n                    \"file.txt\",\n                    128,\n                    FtpBrowserSourceType.Remote,\n                    _loggerFactory\n                ),\n                new FileItemViewModel(\n                    NavigationId.Empty,\n                    \"/\",\n                    \"/file2.txt\",\n                    \"file2.txt\",\n                    64544,\n                    FtpBrowserSourceType.Remote,\n                    _loggerFactory\n                ),\n                new FileItemViewModel(\n                    NavigationId.Empty,\n                    \"/\",\n                    \"/file3.txt\",\n                    \"file3.txt\",\n                    23512612,\n                    FtpBrowserSourceType.Remote,\n                    _loggerFactory\n                ),\n                new FileItemViewModel(\n                    NavigationId.Empty,\n                    \"/\",\n                    \"/file4.txt\",\n                    \"file4.txt\",\n                    23423,\n                    FtpBrowserSourceType.Remote,\n                    _loggerFactory\n                ),\n            },\n            \"/\"\n        );\n    }\n\n    public FileBrowserViewModel(\n        ICommandService cmd,\n        IDeviceManager devices,\n        IHostEnvironment appPath,\n        ILayoutService layoutService,\n        ILoggerFactory loggerFactory,\n        INavigationService navigation,\n        IDialogService dialogService,\n        IExtensionService ext\n    )\n        : base(PageId, devices, cmd, layoutService, loggerFactory, dialogService, ext)\n    {\n        _localRootPath = appPath.ContentRootPath;\n        _remoteRootPath = MavlinkFtpHelper.DirectorySeparator.ToString();\n        _loggerFactory = loggerFactory;\n        _navigation = navigation;\n        _yesNoDialog = dialogService.GetDialogPrefab<YesOrNoDialogPrefab>();\n        _localFilesService = new LocalFilesService();\n        _transfer = new ProgressWithLock(loggerFactory).DisposeItWith(Disposable);\n        IsTransferInProgress = _transfer\n            .IsTransferInProgress.ToBindableReactiveProperty()\n            .DisposeItWith(Disposable);\n        IsProgressVisible = _transfer\n            .IsProgressVisible.ToBindableReactiveProperty()\n            .DisposeItWith(Disposable);\n        Progress = _transfer.Progress.ToBindableReactiveProperty().DisposeItWith(Disposable);\n\n        var watcher = new FileSystemWatcher(_localRootPath)\n        {\n            NotifyFilter =\n                NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.Size,\n            IncludeSubdirectories = true,\n            EnableRaisingEvents = true,\n        }.DisposeItWith(Disposable);\n\n        Observable\n            .Merge(\n                Observable.FromEvent<FileSystemEventHandler, Unit>(\n                    h => (_, _) => h(Unit.Default),\n                    h => watcher.Created += h,\n                    h => watcher.Created -= h\n                ),\n                Observable.FromEvent<FileSystemEventHandler, Unit>(\n                    h => (_, _) => h(Unit.Default),\n                    h => watcher.Deleted += h,\n                    h => watcher.Deleted -= h\n                ),\n                Observable.FromEvent<FileSystemEventHandler, Unit>(\n                    h => (_, _) => h(Unit.Default),\n                    h => watcher.Changed += h,\n                    h => watcher.Changed -= h\n                ),\n                Observable.FromEvent<RenamedEventHandler, Unit>(\n                    h => (_, _) => h(Unit.Default),\n                    h => watcher.Renamed += h,\n                    h => watcher.Renamed -= h\n                )\n            )\n            .ThrottleLast(TimeSpan.FromMilliseconds(150))\n            .Subscribe(_ => RefreshLocalCommand?.Execute(Unit.Default))\n            .DisposeItWith(Disposable);\n\n        _localItems = [];\n        _remoteItems = [];\n        _localItems.DisposeRemovedItems().DisposeItWith(Disposable);\n        _remoteItems.DisposeRemovedItems().DisposeItWith(Disposable);\n        _localItems.SetRoutableParent(this).DisposeItWith(Disposable);\n        _remoteItems.SetRoutableParent(this).DisposeItWith(Disposable);\n\n        // TODO: The sync may be done by ObservableTree in Asv.Avalonia instead\n        _rawRemoteEntries = new ObservableDictionary<string, IFtpEntry>();\n        _ = new RemoteEntriesSync(\n            _rawRemoteEntries,\n            _remoteItems,\n            RemoteEntryToBrowserItem,\n            _loggerFactory\n        ).DisposeItWith(Disposable);\n\n        LocalItemsTree = new BrowserTree(_localItems, _localRootPath).DisposeItWith(Disposable);\n        RemoteItemsTree = new BrowserTree(\n            _remoteItems,\n            MavlinkFtpHelper.DirectorySeparator.ToString()\n        ).DisposeItWith(Disposable);\n\n        LocalSelectedItem = new BindableReactiveProperty<BrowserNode?>(null).DisposeItWith(\n            Disposable\n        );\n        RemoteSelectedItem = new BindableReactiveProperty<BrowserNode?>(null).DisposeItWith(\n            Disposable\n        );\n\n        IsDownloadPopupOpen = new BindableReactiveProperty<bool>(false).DisposeItWith(Disposable);\n        IsUiBlocked = new BindableReactiveProperty<bool>(false).DisposeItWith(Disposable);\n\n        CanDownload\n            .Where(b => !b)\n            .Subscribe(_ => IsDownloadPopupOpen.OnNext(false))\n            .DisposeItWith(Disposable);\n\n        LocalSearch = new SearchBoxViewModel(\n            nameof(LocalSearch),\n            loggerFactory,\n            PerformLocalSearch,\n            TimeSpan.FromMilliseconds(SearchThrottleMs)\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        RemoteSearch = new SearchBoxViewModel(\n            nameof(RemoteSearch),\n            loggerFactory,\n            PerformRemoteSearch,\n            TimeSpan.FromMilliseconds(SearchThrottleMs)\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        InitCommands();\n\n        Events.Subscribe(InternalCatchEvent).DisposeItWith(Disposable);\n    }\n\n    #region Properties\n\n    public BrowserTree LocalItemsTree { get; }\n    public BrowserTree RemoteItemsTree { get; }\n    public BindableReactiveProperty<BrowserNode?> LocalSelectedItem { get; }\n    public BindableReactiveProperty<BrowserNode?> RemoteSelectedItem { get; }\n    public SearchBoxViewModel LocalSearch { get; }\n    public SearchBoxViewModel RemoteSearch { get; }\n    public BindableReactiveProperty<double> Progress { get; }\n    public BindableReactiveProperty<bool> IsDownloadPopupOpen { get; }\n    public BindableReactiveProperty<bool> IsUiBlocked { get; }\n    public BindableReactiveProperty<bool> IsProgressVisible { get; }\n    public BindableReactiveProperty<bool> IsTransferInProgress { get; }\n\n    #endregion\n\n    #region Commands\n\n    public ReactiveCommand ShowDownloadPopupCommand { get; private set; }\n    public ReactiveCommand<BrowserNode> UploadCommand { get; private set; }\n    public ReactiveCommand<BrowserNode> DownloadCommand { get; private set; }\n    public ReactiveCommand<BrowserNode> BurstDownloadCommand { get; private set; }\n    public ReactiveCommand<BrowserNode?> CreateRemoteFolderCommand { get; private set; }\n    public ReactiveCommand<BrowserNode?> CreateLocalFolderCommand { get; private set; }\n    public ReactiveCommand RefreshRemoteCommand { get; private set; }\n    public ReactiveCommand RefreshLocalCommand { get; private set; }\n    public ReactiveCommand<BrowserNode> RemoveLocalItemCommand { get; private set; }\n    public ReactiveCommand<BrowserNode> RemoveRemoteItemCommand { get; private set; }\n    public ReactiveCommand<BrowserNode> LocalRenameCommand { get; private set; }\n    public ReactiveCommand<BrowserNode> RemoteRenameCommand { get; private set; }\n    public ReactiveCommand<Unit> CompareSelectedItemsCommand { get; private set; }\n    public ReactiveCommand<Unit> FindFileOnLocalCommand { get; private set; }\n    public ReactiveCommand<BrowserNode> CalculateLocalCrc32Command { get; private set; }\n    public ReactiveCommand<BrowserNode> CalculateRemoteCrc32Command { get; private set; }\n    public ReactiveCommand CancelTransferCommand { get; private set; }\n\n    #endregion\n\n    #region Observables\n\n    private Observable<bool> CanUpload => LocalSelectedItem.Select(x => x is not null);\n\n    private Observable<bool> CanDownload => RemoteSelectedItem.Select(x => x is not null);\n\n    private Observable<bool> CanRemoveLocal =>\n        LocalSelectedItem.Select(x => x is { Base.EditMode: false });\n\n    private Observable<bool> CanRemoveRemote =>\n        RemoteSelectedItem.Select(x => x is { Base.EditMode: false });\n\n    private Observable<bool> CanFindFileOnLocal =>\n        RemoteSelectedItem.Select(x =>\n            x is { Base: { EditMode: false, FtpEntryType: FtpEntryType.File } }\n        );\n\n    private Observable<bool> CanCompareSelectedItems =>\n        LocalSelectedItem.CombineLatest(\n            RemoteSelectedItem,\n            (local, remote) =>\n                local is { Base: { EditMode: false, FtpEntryType: FtpEntryType.File } }\n                && remote is { Base: { EditMode: false, FtpEntryType: FtpEntryType.File } }\n        );\n\n    private Observable<bool> CanCalculateRemoteCrc32 =>\n        RemoteSelectedItem.Select(x =>\n            x is { Base: { EditMode: false, FtpEntryType: FtpEntryType.File } }\n        );\n\n    private Observable<bool> CanCalculateLocalCrc32 =>\n        LocalSelectedItem.Select(x =>\n            x is { Base: { EditMode: false, FtpEntryType: FtpEntryType.File } }\n        );\n\n    private Observable<bool> CanRenameLocal =>\n        LocalSelectedItem.Select(x => x is { Base.EditMode: false });\n\n    private Observable<bool> CanRenameRemote =>\n        RemoteSelectedItem.Select(x => x is { Base.EditMode: false });\n\n    #endregion\n\n    #region Commands Impl\n\n    private void InitCommands()\n    {\n        FindFileOnLocalCommand = CanFindFileOnLocal\n            .ToReactiveCommand<Unit>(\n                async (_, ct) =>\n                    await this.ExecuteCommand(FindFileCommand.Id, CommandArg.Empty, ct),\n                awaitOperation: AwaitOperation.Drop\n            )\n            .DisposeItWith(Disposable);\n        CompareSelectedItemsCommand = CanCompareSelectedItems\n            .ToReactiveCommand<Unit>(\n                async (_, ct) => await CompareSelectedItemsImpl(ct),\n                awaitOperation: AwaitOperation.Drop\n            )\n            .DisposeItWith(Disposable);\n        CalculateLocalCrc32Command = CanCalculateLocalCrc32\n            .ToReactiveCommand<BrowserNode>(\n                async (node, ct) => await CalculateCrc32Impl(node, ct),\n                awaitOperation: AwaitOperation.Drop\n            )\n            .DisposeItWith(Disposable);\n        CalculateRemoteCrc32Command = CanCalculateRemoteCrc32\n            .ToReactiveCommand<BrowserNode>(\n                async (node, ct) => await CalculateCrc32Impl(node, ct),\n                awaitOperation: AwaitOperation.Drop\n            )\n            .DisposeItWith(Disposable);\n        RefreshRemoteCommand = new ReactiveCommand(\n            async (_, ct) => await RefreshRemoteImpl(ct)\n        ).DisposeItWith(Disposable);\n        RefreshLocalCommand = new ReactiveCommand(RefreshLocalImpl).DisposeItWith(Disposable);\n\n        LocalRenameCommand = CanRenameLocal\n            .ToReactiveCommand<BrowserNode>(SetEditModeImpl, awaitOperation: AwaitOperation.Drop)\n            .DisposeItWith(Disposable);\n        RemoteRenameCommand = CanRenameRemote\n            .ToReactiveCommand<BrowserNode>(\n                async (node, ct) => await SetEditModeImpl(node, ct),\n                awaitOperation: AwaitOperation.Drop\n            )\n            .DisposeItWith(Disposable);\n\n        // TODO: Upload incorrectly processes cancellation (prob. something wrong at Asv.Mavlink)\n        UploadCommand = CanUpload\n            .ToReactiveCommand<BrowserNode>(async (node, ct) => await UploadImpl(node, ct))\n            .DisposeItWith(Disposable);\n        DownloadCommand = CanDownload\n            .ToReactiveCommand<BrowserNode>(async (node, ct) => await DownloadImpl(node, ct))\n            .DisposeItWith(Disposable);\n        BurstDownloadCommand = CanDownload\n            .ToReactiveCommand<BrowserNode>(async (node, ct) => await BurstDownloadImpl(node, ct))\n            .DisposeItWith(Disposable);\n\n        CancelTransferCommand = new ReactiveCommand(_ => _transfer.TryCancel()).DisposeItWith(\n            Disposable\n        );\n\n        CreateRemoteFolderCommand = new ReactiveCommand<BrowserNode?>(\n            async (node, ct) =>\n            {\n                if (node is null)\n                {\n                    var item = _remoteItems.FirstOrDefault(x => x.Path.Equals(_remoteRootPath));\n                    if (item != null)\n                    {\n                        await CreateFolderImpl(item, ct);\n                    }\n                }\n                else\n                {\n                    await CreateFolderImpl(node.Base, ct);\n                }\n            },\n            awaitOperation: AwaitOperation.Drop\n        ).DisposeItWith(Disposable);\n        CreateLocalFolderCommand = new ReactiveCommand<BrowserNode?>(\n            async (node, ct) =>\n            {\n                if (node is null)\n                {\n                    var item = _localItems.FirstOrDefault(x => x.Path.Equals(_localRootPath));\n                    if (item != null)\n                    {\n                        await CreateFolderImpl(item, ct);\n                    }\n                }\n                else\n                {\n                    await CreateFolderImpl(node.Base, ct);\n                }\n            },\n            awaitOperation: AwaitOperation.Drop\n        ).DisposeItWith(Disposable);\n        RemoveLocalItemCommand = CanRemoveLocal\n            .ToReactiveCommand<BrowserNode>(RemoveItemImpl, awaitOperation: AwaitOperation.Drop)\n            .DisposeItWith(Disposable);\n        RemoveRemoteItemCommand = CanRemoveRemote\n            .ToReactiveCommand<BrowserNode>(\n                async (node, ct) => await RemoveItemImpl(node, ct),\n                awaitOperation: AwaitOperation.Drop\n            )\n            .DisposeItWith(Disposable);\n\n        RefreshRemoteCommand.IgnoreOnErrorResume(e =>\n        {\n            if (e is not FtpNackEndOfFileException)\n            {\n                throw e;\n            }\n        });\n\n        ShowDownloadPopupCommand = CanDownload\n            .ToReactiveCommand(_ => IsDownloadPopupOpen.OnNext(!IsDownloadPopupOpen.Value))\n            .DisposeItWith(Disposable);\n\n        RefreshRemoteCommand.SubscribeOnUIThreadDispatcher();\n        RefreshLocalCommand.SubscribeOnUIThreadDispatcher();\n    }\n\n    private async Task UploadImpl(BrowserNode item, CancellationToken ct)\n    {\n        var payload = new YesOrNoDialogPayload\n        {\n            Title = RS.FileBrowserViewModel_UploadingDialog_Title,\n            Message = string.Format(\n                RS.FileBrowserViewModel_UploadingDialog_Message,\n                item.Base.Name\n            ),\n        };\n\n        var res = await _yesNoDialog.ShowDialogAsync(payload);\n\n        if (res)\n        {\n            string remoteDirectory;\n\n            var remoteNode = RemoteSelectedItem.Value;\n            var localName = LocalSelectedItem.Value?.Base.Name ?? FtpBrowserNamingPolicy.BlankName;\n            const char sep = MavlinkFtpHelper.DirectorySeparator;\n\n            if (remoteNode != null)\n            {\n                var remotePath = remoteNode.Base.Path;\n\n                if (remoteNode.Base.FtpEntryType is FtpEntryType.Directory)\n                {\n                    remoteDirectory = remotePath + localName;\n                }\n                else\n                {\n                    var parentDir = remotePath[..remotePath.LastIndexOf(sep)];\n                    remoteDirectory = parentDir + sep + localName;\n                }\n            }\n            else\n            {\n                remoteDirectory = sep + localName;\n            }\n\n            await this.ExecuteCommand(\n                UploadItemCommand.Id,\n                CommandArg.CreateDictionary(\n                    new Dictionary<string, CommandArg>\n                    {\n                        { TransferCommandBase.SourcePath, CommandArg.CreateString(item.Base.Path) },\n                        {\n                            TransferCommandBase.DestinationPath,\n                            CommandArg.CreateString(remoteDirectory)\n                        },\n                        {\n                            TransferCommandBase.EntryType,\n                            CommandArg.CreateString(item.Base.FtpEntryType.ToString())\n                        },\n                    }\n                ),\n                ct\n            );\n        }\n    }\n\n    private async ValueTask DownloadImpl(BrowserNode item, CancellationToken ct)\n    {\n        if (_ftpService is null)\n        {\n            return;\n        }\n\n        var localDirectory = _localRootPath;\n\n        if (RemoteSelectedItem.Value is null)\n        {\n            return;\n        }\n\n        if (LocalSelectedItem.Value != null)\n        {\n            if (LocalSelectedItem.Value.Base.HasChildren)\n            {\n                localDirectory = LocalSelectedItem.Value.Base.Path;\n            }\n            else\n            {\n                localDirectory = LocalSelectedItem.Value.Base.Path[\n                    ..LocalSelectedItem.Value.Base.Path.LastIndexOf(Path.DirectorySeparatorChar)\n                ];\n            }\n        }\n\n        var payload = new YesOrNoDialogPayload\n        {\n            Title = RS.FileBrowserViewModel_DownloadDialog_Title,\n            Message = string.Format(RS.FileBrowserViewModel_DownloadDialog_Message, item.Base.Name),\n        };\n\n        var res = await _yesNoDialog.ShowDialogAsync(payload);\n\n        if (res)\n        {\n            await this.ExecuteCommand(\n                BurstDownloadItemCommand.Id,\n                CommandArg.CreateDictionary(\n                    new Dictionary<string, CommandArg>\n                    {\n                        { TransferCommandBase.SourcePath, CommandArg.CreateString(item.Base.Path) },\n                        {\n                            TransferCommandBase.DestinationPath,\n                            CommandArg.CreateString(localDirectory)\n                        },\n                        {\n                            TransferCommandBase.PartSize,\n                            CommandArg.CreateInteger(MavlinkFtpHelper.MaxDataSize)\n                        },\n                        {\n                            TransferCommandBase.EntryType,\n                            CommandArg.CreateString(item.Base.FtpEntryType.ToString())\n                        },\n                    }\n                ),\n                ct\n            );\n        }\n    }\n\n    private async ValueTask BurstDownloadImpl(BrowserNode item, CancellationToken ct)\n    {\n        var localDirectory = _localRootPath;\n\n        if (LocalSelectedItem.Value != null)\n        {\n            if (LocalSelectedItem.Value.Base.HasChildren)\n            {\n                localDirectory = LocalSelectedItem.Value.Base.Path;\n            }\n            else\n            {\n                localDirectory = LocalSelectedItem.Value.Base.Path[\n                    ..LocalSelectedItem.Value.Base.Path.LastIndexOf(Path.DirectorySeparatorChar)\n                ];\n            }\n        }\n\n        using var viewModel = new BurstDownloadDialogViewModel(_loggerFactory);\n        var dialog = new ContentDialog(viewModel, _navigation)\n        {\n            Title = RS.FileBrowserViewModel_BurstDownloadDialog_Title,\n            PrimaryButtonText = RS.FileBrowserViewModel_BurstDownloadDialog_PrimaryButtonText,\n            SecondaryButtonText = RS.FileBrowserViewModel_BurstDownloadDialog_SecondaryButtonText,\n            IsPrimaryButtonEnabled = viewModel.IsValid.Value,\n            IsSecondaryButtonEnabled = true,\n        };\n        viewModel.ApplyDialog(dialog);\n        var result = await dialog.ShowAsync();\n        if (result == ContentDialogResult.Primary)\n        {\n            await this.ExecuteCommand(\n                BurstDownloadItemCommand.Id,\n                CommandArg.CreateDictionary(\n                    new Dictionary<string, CommandArg>\n                    {\n                        { TransferCommandBase.SourcePath, CommandArg.CreateString(item.Base.Path) },\n                        {\n                            TransferCommandBase.DestinationPath,\n                            CommandArg.CreateString(localDirectory)\n                        },\n                        {\n                            TransferCommandBase.PartSize,\n                            CommandArg.CreateInteger(\n                                viewModel.PacketSize.Value ?? MavlinkFtpHelper.MaxDataSize\n                            )\n                        },\n                        {\n                            TransferCommandBase.EntryType,\n                            CommandArg.CreateString(item.Base.FtpEntryType.ToString())\n                        },\n                    }\n                ),\n                ct\n            );\n        }\n    }\n\n    private async ValueTask RemoveItemImpl(BrowserNode item, CancellationToken ct)\n    {\n        var payload = new YesOrNoDialogPayload\n        {\n            Title = RS.FileBrowserViewModel_RemoveDialog_Title,\n            Message = RS.FileBrowserViewModel_RemoveDialog_Message,\n        };\n\n        var res = await _yesNoDialog.ShowDialogAsync(payload);\n        if (!res)\n        {\n            return;\n        }\n\n        await item.Base.ExecuteCommand(RemoveItemCommand.Id, CommandArg.Empty, ct);\n    }\n\n    private static async ValueTask CreateFolderImpl(\n        IBrowserItemViewModel item,\n        CancellationToken ct\n    )\n    {\n        await item.ExecuteCommand(CreateDirectoryCommand.Id, CommandArg.Empty, ct);\n    }\n\n    private async Task RefreshRemoteImpl(CancellationToken ct)\n    {\n        if (_ftpService is null)\n        {\n            return;\n        }\n\n        var items = await _ftpService.Refresh(ct);\n\n        var selected = RemoteSelectedItem.Value?.Key;\n        var toRemove = _rawRemoteEntries.Where(oldItem =>\n            items.All(newItem =>\n            {\n                if (oldItem.Key != newItem.Key)\n                {\n                    return true;\n                }\n\n                if (oldItem.Value is not FtpFile of || newItem.Value is not FtpFile nf)\n                {\n                    return false;\n                }\n\n                return of.Size != nf.Size;\n            })\n        );\n        foreach (var item in toRemove)\n        {\n            _rawRemoteEntries.Remove(item);\n        }\n\n        var toAdd = items.Where(newItem =>\n            _rawRemoteEntries.All(oldItem =>\n            {\n                if (newItem.Key != oldItem.Key)\n                {\n                    return true;\n                }\n\n                if (newItem.Value is not FtpFile nf || oldItem.Value is not FtpFile of)\n                {\n                    return false;\n                }\n\n                return of.Size != nf.Size;\n            })\n        );\n        _rawRemoteEntries.AddRange(toAdd);\n\n        if (selected is null)\n        {\n            return;\n        }\n\n        RemoteSelectedItem.Value =\n            RemoteItemsTree.FindNode(item => item.Key == selected) as BrowserNode;\n    }\n\n    private ValueTask RefreshLocalImpl(Unit arg, CancellationToken ct)\n    {\n        if (_backend is null)\n        {\n            return ValueTask.CompletedTask;\n        }\n\n        var newItems = _localFilesService.LoadBrowserItems(\n            _localRootPath,\n            _localRootPath,\n            _backend,\n            _loggerFactory,\n            _config?.LocalDirectories\n        );\n\n        var selected = LocalSelectedItem.Value?.Key;\n        var toRemove = _localItems\n            .Where(ls => newItems.All(n => n.Path != ls.Path || n.Size != ls.Size))\n            .ToArray();\n\n        foreach (var item in toRemove)\n        {\n            _localItems.Remove(item);\n        }\n\n        var existing = new HashSet<(string path, FileSize? size)>(\n            _localItems.Select(i => (i.Path, i.Size))\n        );\n\n        var toAdd = newItems.Where(n => !existing.Contains((n.Path, n.Size)));\n\n        var toDispose = newItems.Where(n => existing.Contains((n.Path, n.Size)));\n\n        _localItems.AddRange(toAdd);\n        toDispose.ForEach(i => i.Dispose());\n\n        if (selected is null)\n        {\n            return ValueTask.CompletedTask;\n        }\n\n        LocalSelectedItem.Value =\n            LocalItemsTree.FindNode(item => item.Key == selected) as BrowserNode;\n\n        return ValueTask.CompletedTask;\n    }\n\n    private ValueTask SetEditModeImpl(BrowserNode? node, CancellationToken ct)\n    {\n        if (node?.Base is not BrowserItemViewModel item)\n        {\n            return ValueTask.CompletedTask;\n        }\n\n        item.EditedName.Value = item.Name;\n        item.EditMode = true;\n\n        LocalSelectedItem.OnNext(node);\n\n        return ValueTask.CompletedTask;\n    }\n\n    private static async ValueTask CalculateCrc32Impl(BrowserNode item, CancellationToken ct)\n    {\n        if (item.Base is not FileItemViewModel fileItem)\n        {\n            return;\n        }\n\n        await fileItem.ExecuteCommand(CalculateCrc32Command.Id, CommandArg.Empty, ct);\n    }\n\n    private async ValueTask CompareSelectedItemsImpl(CancellationToken ct)\n    {\n        if (_ftpService is null)\n        {\n            return;\n        }\n\n        if (LocalSelectedItem.Value?.Base is not FileItemViewModel localFileItem)\n        {\n            return;\n        }\n\n        if (RemoteSelectedItem.Value?.Base is not FileItemViewModel remoteFileItem)\n        {\n            return;\n        }\n\n        if (localFileItem.Crc32 == null)\n        {\n            await localFileItem.ExecuteCommand(CalculateCrc32Command.Id, CommandArg.Empty, ct);\n        }\n        var localCrc32 = localFileItem.Crc32 ?? 0;\n\n        if (remoteFileItem.Crc32 == null)\n        {\n            await remoteFileItem.ExecuteCommand(CalculateCrc32Command.Id, CommandArg.Empty, ct);\n        }\n        var remoteCrc32 = remoteFileItem.Crc32 ?? 0;\n\n        if (localCrc32 == remoteCrc32 && (localCrc32 != 0 || remoteCrc32 != 0))\n        {\n            localFileItem.Crc32Status = Crc32Status.Correct;\n            remoteFileItem.Crc32Status = Crc32Status.Correct;\n        }\n        else\n        {\n            localFileItem.Crc32Status = Crc32Status.Incorrect;\n            remoteFileItem.Crc32Status = Crc32Status.Incorrect;\n        }\n    }\n\n    public void FindFileOnLocal()\n    {\n        if (RemoteSelectedItem.Value?.Base is not FileItemViewModel remoteFile)\n        {\n            return;\n        }\n\n        var foundNode = LocalItemsTree.FindNode(n =>\n            n.Base is FileItemViewModel lf\n            && string.Equals(lf.Name, remoteFile.Name, StringComparison.OrdinalIgnoreCase)\n        );\n\n        if (foundNode is not BrowserNode node)\n        {\n            Logger.LogWarning(\"Local file \\\"{Header}\\\" not found\", remoteFile.Name);\n            return;\n        }\n\n        ExpandParents(node);\n        LocalSelectedItem.OnNext(node);\n    }\n\n    #endregion\n\n    #region Transfer\n\n    public async ValueTask UploadItem(\n        string source,\n        string destination,\n        FtpEntryType type,\n        CancellationToken ct\n    )\n    {\n        if (_ftpService is null)\n        {\n            return;\n        }\n\n        using var transfer = _transfer.BeginScope(ct);\n\n        try\n        {\n            await _ftpService.UploadAsync(\n                source,\n                destination,\n                type,\n                transfer.Token,\n                transfer.Reporter\n            );\n        }\n        catch (OperationCanceledException)\n        {\n            Logger.LogWarning(\"Upload {Path} cancelled by user\", source);\n        }\n    }\n\n    public async ValueTask DownloadItem(\n        string source,\n        string destination,\n        byte partSize,\n        FtpEntryType type,\n        CancellationToken ct\n    )\n    {\n        if (_ftpService is null)\n        {\n            return;\n        }\n\n        using var transfer = _transfer.BeginScope(ct);\n        try\n        {\n            await _ftpService.DownloadAsync(\n                source,\n                destination,\n                type,\n                transfer.Token,\n                partSize,\n                transfer.Reporter\n            );\n        }\n        catch (OperationCanceledException)\n        {\n            Logger.LogWarning(\"Download {Path} cancelled by user\", source);\n        }\n    }\n\n    public async ValueTask BurstDownloadItem(\n        string source,\n        string destination,\n        byte partSize,\n        FtpEntryType type,\n        CancellationToken ct\n    )\n    {\n        if (_ftpService is null)\n        {\n            return;\n        }\n        using var transfer = _transfer.BeginScope(ct);\n        try\n        {\n            await _ftpService.BurstDownloadAsync(\n                source,\n                destination,\n                type,\n                transfer.Token,\n                partSize,\n                transfer.Reporter\n            );\n        }\n        catch (OperationCanceledException)\n        {\n            Logger.LogWarning(\"Burst-Download {Path} cancelled by user\", source);\n        }\n    }\n\n    #endregion\n\n    public void Refresh()\n    {\n        RefreshLocalCommand.Execute(Unit.Default);\n        RefreshRemoteCommand.Execute(Unit.Default);\n    }\n\n    #region Helpers\n\n    private Task PerformLocalSearch(\n        string? query,\n        IProgress<double> progress,\n        CancellationToken cancel\n    )\n    {\n        PerformSearch(LocalItemsTree, LocalSelectedItem, query);\n        return Task.CompletedTask;\n    }\n\n    private Task PerformRemoteSearch(\n        string? query,\n        IProgress<double> progress,\n        CancellationToken cancel\n    )\n    {\n        PerformSearch(RemoteItemsTree, RemoteSelectedItem, query);\n        return Task.CompletedTask;\n    }\n\n    private static void PerformSearch(\n        BrowserTree tree,\n        BindableReactiveProperty<BrowserNode?> target,\n        string? pattern\n    )\n    {\n        if (string.IsNullOrWhiteSpace(pattern))\n        {\n            return;\n        }\n\n        var found = tree.FindNode(n =>\n            n.Base is BrowserItemViewModel f\n            && f.Name.Contains(pattern, StringComparison.OrdinalIgnoreCase)\n        );\n\n        if (found is not BrowserNode node)\n        {\n            return;\n        }\n\n        ExpandParents(node);\n        target.OnNext(node);\n    }\n\n    private static void ExpandParents(BrowserNode node)\n    {\n        foreach (var ancestor in node.GetAllMenuFromRoot())\n        {\n            switch (ancestor.Base)\n            {\n                case BrowserItemViewModel bi:\n                {\n                    if (bi.IsExpanded)\n                    {\n                        bi.IsExpanded = false; // reset field to trigger UI\n                    }\n\n                    bi.IsExpanded = true;\n                    break;\n                }\n            }\n        }\n    }\n\n    private IBrowserItemViewModel RemoteEntryToBrowserItem(string key, IFtpEntry entry)\n    {\n        if (_backend == null)\n        {\n            throw new InvalidOperationException(\"Backend is not initialized\");\n        }\n\n        const char sep = MavlinkFtpHelper.DirectorySeparator;\n\n        IBrowserItemViewModel vm;\n\n        switch (entry.Type)\n        {\n            case FtpEntryType.Directory:\n            {\n                DirectoryItemViewModelConfig? config = null;\n                var dirPath = FtpBrowserPath.Normalize(key, true, sep);\n                _config?.RemoteDirectories.TryGetValue(dirPath, out config);\n                vm = new DirectoryItemViewModel(\n                    PathHelper.EncodePathToId(dirPath),\n                    FtpBrowserPath.ParentDirOf(dirPath, sep),\n                    dirPath,\n                    entry.Name,\n                    FtpBrowserSourceType.Remote,\n                    _loggerFactory,\n                    config\n                );\n                break;\n            }\n            case FtpEntryType.File:\n            {\n                var filePath = FtpBrowserPath.Normalize(key, false, sep);\n                vm = new FileItemViewModel(\n                    PathHelper.EncodePathToId(filePath),\n                    FtpBrowserPath.ParentDirOf(filePath, sep),\n                    filePath,\n                    entry.Name,\n                    ((FtpFile)entry).Size,\n                    FtpBrowserSourceType.Remote,\n                    _loggerFactory\n                );\n                break;\n            }\n            default:\n                throw new ArgumentOutOfRangeException(nameof(entry));\n        }\n\n        vm.AttachBackend(_backend);\n        return vm;\n    }\n\n    #endregion\n\n    #region Routable\n\n    private ValueTask InternalCatchEvent(IRoutable src, AsyncRoutedEvent<IRoutable> e)\n    {\n        switch (e)\n        {\n            case SaveLayoutEvent saveLayoutEvent:\n            {\n                if (!IsDeviceInitialized.Value)\n                {\n                    if (saveLayoutEvent.IsFlushToFile)\n                    {\n                        saveLayoutEvent.LayoutService.FlushFromMemoryViewModelAndView(this);\n                    }\n                    break;\n                }\n\n                if (_config is null)\n                {\n                    break;\n                }\n\n                this.HandleSaveLayout(\n                    saveLayoutEvent,\n                    _config,\n                    cfg =>\n                    {\n                        cfg.LocalSearchText = LocalSearch.Text.ViewValue.Value ?? string.Empty;\n                        cfg.RemoteSearchText = RemoteSearch.Text.ViewValue.Value ?? string.Empty;\n                        cfg.LocalSelectedItemKey = LocalSelectedItem.Value?.Key;\n                        cfg.RemoteSelectedItemKey = RemoteSelectedItem.Value?.Key;\n                        cfg.LocalDirectories = _localItems\n                            .Where(item =>\n                                item is { FtpEntryType: FtpEntryType.Directory, IsExpanded: true }\n                            )\n                            .ToDictionary(\n                                item => item.Path,\n                                item => new DirectoryItemViewModelConfig\n                                {\n                                    IsExpanded = item.IsExpanded,\n                                }\n                            );\n                        cfg.RemoteDirectories = _remoteItems\n                            .Where(item =>\n                                item is { FtpEntryType: FtpEntryType.Directory, IsExpanded: true }\n                            )\n                            .ToDictionary(\n                                item => item.Path,\n                                item => new DirectoryItemViewModelConfig\n                                {\n                                    IsExpanded = item.IsExpanded,\n                                }\n                            );\n                    },\n                    FlushingStrategy.FlushBothViewModelAndView\n                );\n                break;\n            }\n\n            case LoadLayoutEvent loadLayoutEvent:\n            {\n                _config = this.HandleLoadLayout<FileBrowserViewModelConfig>(\n                    loadLayoutEvent,\n                    cfg =>\n                    {\n                        LocalSearch.Text.ModelValue.Value = cfg.LocalSearchText;\n                        RemoteSearch.Text.ModelValue.Value = cfg.RemoteSearchText;\n                        LocalSelectedItem.Value =\n                            LocalItemsTree.FindNode(n => n.Key == cfg.LocalSelectedItemKey)\n                            as BrowserNode;\n                        RemoteSelectedItem.Value =\n                            RemoteItemsTree.FindNode(n => n.Key == cfg.LocalSelectedItemKey)\n                            as BrowserNode;\n                        _localItems\n                            .Where(item => item is { FtpEntryType: FtpEntryType.Directory })\n                            .ForEach(it =>\n                            {\n                                cfg.LocalDirectories.TryGetValue(it.Path, out var config);\n                                it.IsExpanded = config?.IsExpanded ?? it.IsExpanded;\n                            });\n                        _remoteItems\n                            .Where(item => item is { FtpEntryType: FtpEntryType.Directory })\n                            .ForEach(it =>\n                            {\n                                cfg.RemoteDirectories.TryGetValue(it.Path, out var config);\n                                it.IsExpanded = config?.IsExpanded ?? it.IsExpanded;\n                            });\n                    }\n                );\n                break;\n            }\n        }\n\n        return ValueTask.CompletedTask;\n    }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        yield return LocalSearch;\n        yield return RemoteSearch;\n\n        foreach (var item in _localItems)\n        {\n            yield return item;\n        }\n\n        foreach (var item in _remoteItems)\n        {\n            yield return item;\n        }\n    }\n\n    protected override void AfterLoadExtensions() { }\n\n    #endregion\n\n    #region Dispose\n\n    protected override void Dispose(bool disposing)\n    {\n        if (disposing)\n        {\n            _localItems.RemoveAll();\n            _remoteItems.RemoveAll();\n        }\n\n        base.Dispose(disposing);\n    }\n\n    #endregion\n\n    protected override void AfterDeviceInitialized(\n        IClientDevice device,\n        CancellationToken onDisconnectedToken\n    )\n    {\n        Title = $\"{RS.FileBrowserViewModel_Title}[{device.Id}]\";\n        Icon = DeviceIconMixin.GetIcon(device.Id) ?? PageIcon;\n\n        var client = device.GetMicroservice<IFtpClient>();\n        ArgumentNullException.ThrowIfNull(client);\n        var clientEx = device.GetMicroservice<IFtpClientEx>() ?? new FtpClientEx(client);\n\n        _ftpService = new FtpClientService(clientEx, _loggerFactory).DisposeItWith(Disposable);\n        _ftpService.RegisterTo(onDisconnectedToken);\n\n        _ftpService\n            .RemoteChanged.ThrottleLast(TimeSpan.FromMilliseconds(200))\n            .SubscribeAwait(async (_, _) => await RefreshRemoteImpl(onDisconnectedToken))\n            .RegisterTo(onDisconnectedToken);\n        _ftpService\n            .RemoteChanging.Subscribe(isBusy => IsUiBlocked.OnNext(isBusy))\n            .RegisterTo(onDisconnectedToken);\n\n        _backend = new FileBrowserBackend(_localFilesService, _ftpService);\n\n        onDisconnectedToken.Register(() =>\n        {\n            try\n            {\n                _transfer.TryCancel();\n            }\n            catch\n            {\n                // ignored\n            }\n\n            IsUiBlocked.OnNext(false);\n            IsProgressVisible.OnNext(false);\n            IsTransferInProgress.OnNext(false);\n            Progress.OnNext(0);\n\n            _rawRemoteEntries.Clear();\n            _remoteItems.Clear();\n            RemoteSelectedItem.OnNext(null);\n            _ftpService = null;\n        });\n\n        Refresh();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/HomePageFileBrowserDeviceItemAction.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic class HomePageFileBrowserDeviceItemAction(ILoggerFactory loggerFactory)\n    : HomePageDeviceItemAction\n{\n    protected override IActionViewModel? TryCreateAction(\n        IClientDevice device,\n        HomePageDeviceItem context\n    )\n    {\n        if (device.GetMicroservice<IFtpClient>() == null)\n        {\n            return null;\n        }\n\n        return new ActionViewModel(\"browser\", loggerFactory)\n        {\n            Header = OpenFileBrowserCommand.StaticInfo.Name,\n            Description = OpenFileBrowserCommand.StaticInfo.Description,\n            Icon = OpenFileBrowserCommand.StaticInfo.Icon,\n            Command = new BindableAsyncCommand(OpenFileBrowserCommand.Id, context),\n            CommandParameter = new StringArg($\"dev_id={device.Id}\"), // TODO: replate with DevicePageViewModelMixin.CreateOpenPageArgs\n        };\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/IO/FileSize.cs",
    "content": "﻿using System;\n\nnamespace Asv.Drones;\n\npublic readonly struct FileSize(long size) : IEquatable<FileSize>\n{\n    private const long OneKilobyte = 1024;\n    private const long OneMegabyte = OneKilobyte * 1024;\n    private const long OneGigabyte = OneMegabyte * 1024;\n    private const long OneTerabyte = OneGigabyte * 1024;\n\n    private long Size => size;\n\n    public override string ToString()\n    {\n        string unit;\n        double value;\n\n        switch (size)\n        {\n            case < OneKilobyte:\n                unit = RS.Unit_Byte_Abbreviation;\n                value = size;\n                break;\n            case < OneMegabyte:\n                unit = RS.Unit_Kilobyte_Abbreviation;\n                value = size / (double)OneKilobyte;\n                break;\n            case < OneGigabyte:\n                unit = RS.Unit_Megabyte_Abbreviation;\n                value = size / (double)OneMegabyte;\n                break;\n            case < OneTerabyte:\n                unit = RS.Unit_Gigabyte_Abbreviation;\n                value = size / (double)OneGigabyte;\n                break;\n            default:\n                unit = RS.Unit_Terabyte_Abbreviation;\n                value = size / (double)OneTerabyte;\n                break;\n        }\n\n        return $\"{value:0.##} {unit}\";\n    }\n\n    public int CompareTo(FileSize other)\n    {\n        return Size.CompareTo(other.Size);\n    }\n\n    public bool Equals(FileSize other)\n    {\n        return Size.Equals(other.Size);\n    }\n\n    public override bool Equals(object? obj)\n    {\n        return obj is FileSize fileSize && Equals(fileSize);\n    }\n\n    public static bool operator ==(FileSize left, FileSize right)\n    {\n        return left.Equals(right);\n    }\n\n    public static bool operator !=(FileSize left, FileSize right)\n    {\n        return !(left == right);\n    }\n\n    public override int GetHashCode()\n    {\n        return Size.GetHashCode();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/IO/FtpBrowserNamingPolicy.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing Asv.Common;\n\nnamespace Asv.Drones;\n\npublic static partial class FtpBrowserNamingPolicy\n{\n    // MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL spec. says that only 248 symbols allowed,\n    // that is an approximate value, not including a path length\n    public const int MaxNameLength = 100;\n    public const int MinNameLength = 1;\n    public static readonly string BlankName = Guid.NewGuid().ToString(); // TODO: prohibit empty names\n    private static readonly Regex AllowedChars = AllowedCharsRegex();\n    private const string AllowedCharsPattern = @\"[A-Za-z0-9_.\\-() ]\";\n    private const string AllowedNamePattern = \"^\" + AllowedCharsPattern + \"+$\";\n    private static readonly Regex AllowedName = AllowedNameRegex();\n\n    [GeneratedRegex(AllowedCharsPattern, RegexOptions.Compiled)]\n    private static partial Regex AllowedCharsRegex();\n\n    [GeneratedRegex(AllowedNamePattern, RegexOptions.Compiled)]\n    private static partial Regex AllowedNameRegex();\n\n    public static string SanitizeForDisplay(string? value)\n    {\n        if (string.IsNullOrEmpty(value))\n        {\n            return string.Empty;\n        }\n\n        var sb = new StringBuilder(value.Length);\n        foreach (var ch in value)\n        {\n            sb.Append(AllowedChars.IsMatch(ch.ToString()) ? ch : '*');\n        }\n\n        return sb.ToString();\n    }\n\n    public static ValidationResult Validate(string name)\n    {\n        if (string.IsNullOrWhiteSpace(name))\n        {\n            return ValidationResult.FailAsNullOrWhiteSpace;\n        }\n\n        if (name.Any(Path.GetInvalidFileNameChars().Contains))\n        {\n            return ValidationResult.FailAsInvalidCharacters;\n        }\n\n        if (!AllowedName.IsMatch(name))\n        {\n            return ValidationResult.FailAsInvalidCharacters;\n        }\n\n        if (name.Length > MaxNameLength)\n        {\n            return ValidationResult.FailAsOutOfRange(\n                MinNameLength.ToString(),\n                MaxNameLength.ToString()\n            );\n        }\n\n        return ValidationResult.Success;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/IO/FtpBrowserPath.cs",
    "content": "﻿namespace Asv.Drones;\n\n/// <summary>\n/// Utility methods for working with FTP browser paths.\n/// </summary>\npublic static class FtpBrowserPath\n{\n    /// <summary>\n    /// Normalizes a path ensuring it ends with the correct separator if it is a directory,\n    /// or trims trailing separators if it is a file.\n    /// </summary>\n    /// <param name=\"path\">The original path string.</param>\n    /// <param name=\"isDirectory\">True if the path is a directory; false if it is a file.</param>\n    /// <param name=\"sep\">Directory separator character.</param>\n    /// <returns>A normalized path string.</returns>\n    public static string Normalize(string path, bool isDirectory, char sep) =>\n        isDirectory ? EnsureDir(path, sep) : EnsureFile(path, sep);\n\n    /// <summary>\n    /// Combines a parent directory with a child directory name and ensures the result ends with a separator.\n    /// </summary>\n    /// <param name=\"parentDir\">The parent directory path.</param>\n    /// <param name=\"name\">The child directory name.</param>\n    /// <param name=\"sep\">Directory separator character.</param>\n    /// <returns>A full directory path ending with a separator.</returns>\n    public static string CombineDir(string parentDir, string name, char sep) =>\n        EnsureDir(parentDir, sep) + name + sep;\n\n    /// <summary>\n    /// Combines a parent directory with a file name.\n    /// </summary>\n    /// <param name=\"parentDir\">The parent directory path.</param>\n    /// <param name=\"name\">The file name.</param>\n    /// <param name=\"sep\">Directory separator character.</param>\n    /// <returns>A full file path.</returns>\n    public static string CombineFile(string parentDir, string name, char sep) =>\n        EnsureDir(parentDir, sep) + name;\n\n    /// <summary>\n    /// Gets the parent directory of the given path.\n    /// </summary>\n    /// <param name=\"path\">The full path string.</param>\n    /// <param name=\"sep\">Directory separator character.</param>\n    /// <returns>\n    /// Parent directory path (ending with a separator), or empty string if the path is root or invalid.\n    /// </returns>\n    public static string ParentDirOf(string path, char sep)\n    {\n        if (string.IsNullOrWhiteSpace(path) || (path.Length == 1 && path[0] == sep))\n        {\n            return string.Empty;\n        }\n\n        var isDir = path[^1] == sep;\n        var p = isDir ? path.TrimEnd(sep) : path;\n\n        var idx = p.LastIndexOf(sep);\n        return idx switch\n        {\n            < 0 => string.Empty,\n            0 => sep.ToString(),\n            _ => p[..(idx + 1)],\n        };\n    }\n\n    /// <summary>\n    /// Extracts the name (file or directory) from the given path.\n    /// </summary>\n    /// <param name=\"path\">The full path string.</param>\n    /// <param name=\"sep\">Directory separator character.</param>\n    /// <returns>The file or directory name, or empty string if invalid.</returns>\n    public static string NameOf(string? path, char sep)\n    {\n        if (string.IsNullOrEmpty(path) || (path.Length == 1 && path[0] == sep))\n        {\n            return string.Empty;\n        }\n\n        var end = path.Length;\n        while (end > 1 && path[end - 1] == sep)\n        {\n            end--;\n        }\n\n        var idx = path.LastIndexOf(sep, end - 1);\n        return idx < 0 ? path[..end] : path.Substring(idx + 1, end - (idx + 1));\n    }\n\n    /// <summary>\n    /// Ensures that the given path represents a directory (ends with a separator).\n    /// </summary>\n    private static string EnsureDir(string path, char sep)\n    {\n        if (string.IsNullOrWhiteSpace(path) || (path.Length == 1 && path[0] == sep))\n        {\n            return sep.ToString();\n        }\n\n        return path[^1] == sep ? path : path + sep;\n    }\n\n    /// <summary>\n    /// Ensures that the given path represents a file (removes trailing separators).\n    /// </summary>\n    private static string EnsureFile(string path, char sep)\n    {\n        if (string.IsNullOrWhiteSpace(path) || (path.Length == 1 && path[0] == sep))\n        {\n            return string.Empty;\n        }\n\n        var end = path.Length;\n        while (end > 1 && path[end - 1] == sep)\n        {\n            end--;\n        }\n\n        return path[..end];\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/IO/Types/Crc32Status.cs",
    "content": "﻿namespace Asv.Drones;\n\npublic enum Crc32Status\n{\n    /// <summary>\n    /// Basic status.\n    /// </summary>\n    Default,\n\n    /// <summary>\n    /// Indicates that the CRC32 check was successful.\n    /// </summary>\n    Correct,\n\n    /// <summary>\n    /// Indicates that the CRC32 check was unsuccessful.\n    /// </summary>\n    Incorrect,\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/IO/Types/FtpBrowserSourceType.cs",
    "content": "﻿namespace Asv.Drones;\n\npublic enum FtpBrowserSourceType\n{\n    Local,\n    Remote,\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/Tree/BrowserNode.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing Asv.Avalonia;\nusing ObservableCollections;\n\nnamespace Asv.Drones;\n\npublic class BrowserNode(\n    IBrowserItemViewModel baseItem,\n    IReadOnlyObservableList<IBrowserItemViewModel> source,\n    Func<IBrowserItemViewModel, string> keySelector,\n    Func<IBrowserItemViewModel, string> parentSelector,\n    IComparer<IBrowserItemViewModel> comparer,\n    CreateNodeDelegate<IBrowserItemViewModel, string> factory,\n    ObservableTreeNode<IBrowserItemViewModel, string>? parentNode = null\n)\n    : ObservableTreeNode<IBrowserItemViewModel, string>(\n        baseItem,\n        source,\n        keySelector,\n        parentSelector,\n        comparer,\n        factory,\n        parentNode\n    );\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/Tree/BrowserTree.cs",
    "content": "﻿using Asv.Avalonia;\nusing ObservableCollections;\n\nnamespace Asv.Drones;\n\npublic class BrowserTree(IReadOnlyObservableList<IBrowserItemViewModel> flatList, string rootKey)\n    : ObservableTree<IBrowserItemViewModel, string>(\n        flatList,\n        rootKey,\n        static x => x.Path,\n        static x =>\n        {\n            var p = x.ParentPath ?? string.Empty;\n            return p == x.Path ? string.Empty : p;\n        },\n        BrowserItemComparer.Instance,\n        NodeFactory\n    )\n{\n    private static readonly CreateNodeDelegate<IBrowserItemViewModel, string> NodeFactory = static (\n        item,\n        list,\n        key,\n        parent,\n        comparer,\n        factory,\n        parentNode\n    ) => new BrowserNode(item, list, key, parent, comparer, factory, parentNode);\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/Tree/Items/BrowserItemComparer.cs",
    "content": "﻿using System.Collections.Generic;\n\nnamespace Asv.Drones;\n\npublic sealed class BrowserItemComparer : IComparer<IBrowserItemViewModel>\n{\n    public static readonly BrowserItemComparer Instance = new();\n\n    private BrowserItemComparer() { }\n\n    public int Compare(IBrowserItemViewModel? x, IBrowserItemViewModel? y)\n    {\n        if (x is DirectoryItemViewModel && y is not DirectoryItemViewModel)\n        {\n            return -1;\n        }\n\n        if (y is DirectoryItemViewModel && x is not DirectoryItemViewModel)\n        {\n            return 1;\n        }\n\n        return x switch\n        {\n            DirectoryItemViewModel dirX when y is DirectoryItemViewModel dirY =>\n                DirectoryItemComparer.Instance.Compare(dirX, dirY),\n            FileItemViewModel fileX when y is FileItemViewModel fileY =>\n                FileItemComparer.Instance.Compare(fileX, fileY),\n            _ => 0,\n        };\n    }\n}\n\npublic sealed class DirectoryItemComparer : IComparer<DirectoryItemViewModel>\n{\n    public static readonly DirectoryItemComparer Instance = new();\n\n    private DirectoryItemComparer() { }\n\n    public int Compare(DirectoryItemViewModel? x, DirectoryItemViewModel? y)\n    {\n        switch (x)\n        {\n            case null when y == null:\n                return 0;\n            case null:\n                return -1;\n        }\n\n        if (y == null)\n        {\n            return 1;\n        }\n\n        var idComparison = x.Id.CompareTo(y.Id);\n        return idComparison != 0 ? idComparison : string.CompareOrdinal(x.ParentPath, y.ParentPath);\n    }\n}\n\npublic sealed class FileItemComparer : IComparer<FileItemViewModel>\n{\n    public static readonly FileItemComparer Instance = new();\n\n    private FileItemComparer() { }\n\n    public int Compare(FileItemViewModel? x, FileItemViewModel? y)\n    {\n        switch (x)\n        {\n            case null when y == null:\n                return 0;\n            case null:\n                return -1;\n        }\n\n        if (y == null)\n        {\n            return 1;\n        }\n\n        var idComparison = x.Id.CompareTo(y.Id);\n        if (idComparison != 0)\n        {\n            return idComparison;\n        }\n\n        var parentPathComparison = string.CompareOrdinal(x.ParentPath, y.ParentPath);\n        if (parentPathComparison != 0)\n        {\n            return parentPathComparison;\n        }\n\n        if (x.Size.HasValue && y.Size.HasValue)\n        {\n            return x.Size.Value.CompareTo(y.Size.Value);\n        }\n\n        if (x.Size.HasValue)\n        {\n            return 1;\n        }\n\n        if (y.Size.HasValue)\n        {\n            return -1;\n        }\n\n        return 0;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/Tree/Items/BrowserItemViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic abstract class BrowserItemViewModel : RoutableViewModel, IBrowserItemViewModel\n{\n    private readonly char _separator;\n    private IBrowserItemsOperations? _ops;\n    protected IBrowserItemsOperations ItemsOperations =>\n        _ops\n        ?? throw new InvalidOperationException(\n            $\"{nameof(IBrowserItemsOperations)} are not attached. Call {nameof(AttachBackend)} first.\"\n        );\n\n    protected BrowserItemViewModel(\n        NavigationId id,\n        string? parentPath,\n        string path,\n        FtpBrowserSourceType type,\n        ILoggerFactory loggerFactory\n    )\n        : base(id, loggerFactory)\n    {\n        ParentPath = parentPath;\n        Path = path;\n        Type = type;\n\n        _separator =\n            Type is FtpBrowserSourceType.Local\n                ? System.IO.Path.DirectorySeparatorChar\n                : MavlinkFtpHelper.DirectorySeparator;\n\n        EditedName = new BindableReactiveProperty<string>().DisposeItWith(Disposable);\n        EditedName\n            .EnableValidation(x =>\n            {\n                var result = FtpBrowserNamingPolicy.Validate(x);\n                return !result.IsSuccess ? result.ValidationException : null;\n            })\n            .ForceValidate();\n        CommitRename = CanRename\n            .ToReactiveCommand<Unit>(\n                async (_, ct) => await CommitRenameImpl(ct),\n                awaitOperation: AwaitOperation.Drop\n            )\n            .DisposeItWith(Disposable);\n    }\n\n    #region Properties\n\n    public string Name\n    {\n        get => FtpBrowserNamingPolicy.SanitizeForDisplay(field);\n        set => SetField(ref field, value);\n    } = string.Empty;\n\n    public string Path\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public string? ParentPath\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public FileSize? Size\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public bool HasChildren\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public bool IsExpanded\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public bool IsSelected\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public FtpBrowserSourceType Type\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public bool EditMode\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public string? Crc32Hex\n    {\n        get;\n        protected set => SetField(ref field, value);\n    }\n\n    public Crc32Status Crc32Status\n    {\n        get;\n        set => SetField(ref field, value);\n    } = Crc32Status.Default;\n\n    public FtpEntryType FtpEntryType\n    {\n        get;\n        protected init => SetField(ref field, value);\n    }\n\n    public BindableReactiveProperty<string> EditedName { get; set; }\n\n    #endregion\n\n    public Observable<bool> CanRename =>\n        EditedName.Select(_ => !EditedName.HasErrors).DistinctUntilChanged().Share();\n\n    #region Commands\n\n    public ReactiveCommand<Unit> CommitRename { get; }\n\n    #endregion\n\n    public abstract ValueTask<string> RenameAsync(\n        string oldValue,\n        string newValue,\n        CancellationToken ct\n    );\n\n    public abstract ValueTask RemoveAsync(CancellationToken ct);\n\n    public abstract ValueTask<uint> CalculateCrc32Async(CancellationToken ct);\n    public abstract ValueTask CreateDirectoryAsync(CancellationToken ct);\n\n    private async ValueTask CommitRenameImpl(CancellationToken ct)\n    {\n        var oldName = Name;\n        var oldPath = Path;\n        var newName = string.IsNullOrWhiteSpace(EditedName.Value)\n            ? FtpBrowserNamingPolicy.BlankName\n            : EditedName.Value;\n        var parentDir = FtpBrowserPath.ParentDirOf(oldPath, _separator);\n        var isDirectory = FtpEntryType == FtpEntryType.Directory;\n        var newPath = isDirectory\n            ? FtpBrowserPath.CombineDir(parentDir, newName, _separator)\n            : FtpBrowserPath.CombineFile(parentDir, newName, _separator);\n\n        EditMode = false;\n        if (\n            string.IsNullOrEmpty(newPath)\n            || string.Equals(newName, oldName, StringComparison.Ordinal)\n        )\n        {\n            EditedName.Value = oldName;\n            return;\n        }\n        await this.ExecuteCommand(\n            CommitRenameCommand.Id,\n            CommandArg.CreateDictionary(\n                new Dictionary<string, CommandArg>\n                {\n                    { CommitRenameCommand.NewValue, CommandArg.CreateString(newPath) },\n                    { CommitRenameCommand.OldValue, CommandArg.CreateString(oldPath) },\n                }\n            ),\n            ct\n        );\n    }\n\n    /// <summary>\n    /// Attach backend context once right after VM creation.\n    /// </summary>\n    /// <param name=\"backend\">Backend context from VM.</param>\n    public void AttachBackend(FileBrowserBackend backend)\n    {\n        _ops = backend.ResolveOps(Type);\n    }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/Tree/Items/DirectoryItemViewModel.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic class DirectoryItemViewModelConfig\n{\n    public bool IsExpanded { get; set; }\n}\n\npublic class DirectoryItemViewModel : BrowserItemViewModel\n{\n    public DirectoryItemViewModel(\n        NavigationId id,\n        string? parentPath,\n        string path,\n        string name,\n        FtpBrowserSourceType type,\n        ILoggerFactory loggerFactory,\n        DirectoryItemViewModelConfig? layoutConfig = null\n    )\n        : base(id, parentPath, path, type, loggerFactory)\n    {\n        HasChildren = true;\n        Name = name;\n        FtpEntryType = FtpEntryType.Directory;\n        IsExpanded = layoutConfig?.IsExpanded ?? IsExpanded;\n    }\n\n    public override async ValueTask<string> RenameAsync(\n        string oldValue,\n        string newValue,\n        CancellationToken ct\n    )\n    {\n        ArgumentException.ThrowIfNullOrEmpty(oldValue);\n        ArgumentException.ThrowIfNullOrEmpty(newValue);\n\n        var sep = ItemsOperations.Separator;\n\n        var oldPath = FtpBrowserPath.Normalize(oldValue, true, sep);\n        var newPath = FtpBrowserPath.Normalize(newValue, true, sep);\n\n        var result = await ItemsOperations.RenameDirectoryAsync(oldPath, newPath, Logger, ct);\n\n        EditMode = false;\n        EditedName.Value = FtpBrowserPath.NameOf(result, sep);\n        IsSelected = true;\n        return result;\n    }\n\n    public override async ValueTask RemoveAsync(CancellationToken ct)\n    {\n        await ItemsOperations.RemoveDirectoryAsync(Path, Logger, ct);\n    }\n\n    public override ValueTask<uint> CalculateCrc32Async(CancellationToken ct)\n    {\n        Logger.LogError(\"Cannot calculate CRC32 for directory\");\n        return new ValueTask<uint>(0);\n    }\n\n    public override async ValueTask CreateDirectoryAsync(CancellationToken ct)\n    {\n        await ItemsOperations.CreateDirectoryAsync(Path, Logger, ct);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/Tree/Items/FileItemViewModel.cs",
    "content": "﻿using System;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic class FileItemViewModel : BrowserItemViewModel\n{\n    public FileItemViewModel(\n        NavigationId id,\n        string parentPath,\n        string path,\n        string name,\n        long size,\n        FtpBrowserSourceType type,\n        ILoggerFactory loggerFactory\n    )\n        : base(id, parentPath, path, type, loggerFactory)\n    {\n        HasChildren = false;\n        Name = name;\n        Size = new FileSize(size);\n        FtpEntryType = FtpEntryType.File;\n    }\n\n    public uint? Crc32\n    {\n        get;\n        set\n        {\n            SetField(ref field, value);\n\n            if (value is null)\n            {\n                Crc32Hex = null;\n                return;\n            }\n\n            Crc32Hex = Crc32ToHex((uint)value);\n        }\n    }\n\n    private static string Crc32ToHex(uint crc32) => crc32.ToString(\"X8\");\n\n    public override async ValueTask<string> RenameAsync(\n        string oldValue,\n        string newValue,\n        CancellationToken ct\n    )\n    {\n        ArgumentException.ThrowIfNullOrEmpty(oldValue);\n        ArgumentException.ThrowIfNullOrEmpty(newValue);\n\n        var sep = ItemsOperations.Separator;\n\n        var oldPath = FtpBrowserPath.Normalize(oldValue, false, sep);\n        var newPath = FtpBrowserPath.Normalize(newValue, false, sep);\n\n        var result = await ItemsOperations.RenameFileAsync(oldPath, newPath, Logger, ct);\n\n        EditMode = false;\n        EditedName.Value = FtpBrowserPath.NameOf(result, sep);\n        IsSelected = true;\n        return newPath;\n    }\n\n    public override async ValueTask RemoveAsync(CancellationToken ct)\n    {\n        await ItemsOperations.RemoveFileAsync(Path, Logger, ct);\n    }\n\n    public override async ValueTask<uint> CalculateCrc32Async(CancellationToken ct)\n    {\n        var crc32 = await ItemsOperations.CalculateCrc32Async(Path, Logger, ct);\n        Crc32 = crc32;\n        Crc32Status = Crc32Status.Default;\n        return crc32;\n    }\n\n    public override async ValueTask CreateDirectoryAsync(CancellationToken ct)\n    {\n        var path = FtpBrowserPath.ParentDirOf(Path, ItemsOperations.Separator);\n        await ItemsOperations.CreateDirectoryAsync(path, Logger, ct);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/FileBrowser/Tree/Items/IBrowserItemViewModel.cs",
    "content": "﻿using System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Asv.Mavlink;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic interface IBrowserItemViewModel : ISupportRename, ISupportRemove\n{\n    string Name { get; set; }\n    string Path { get; set; }\n    string? ParentPath { get; set; }\n    FileSize? Size { get; }\n    bool HasChildren { get; }\n    bool IsExpanded { get; set; }\n    bool IsSelected { get; set; }\n    bool EditMode { get; set; }\n    FtpBrowserSourceType Type { get; }\n    string? Crc32Hex { get; }\n    Crc32Status Crc32Status { get; }\n    FtpEntryType FtpEntryType { get; }\n    ReactiveCommand<Unit> CommitRename { get; }\n    BindableReactiveProperty<string> EditedName { get; set; }\n\n    void AttachBackend(FileBrowserBackend backend);\n\n    ValueTask CreateDirectoryAsync(CancellationToken ct);\n    ValueTask<uint> CalculateCrc32Async(CancellationToken ct);\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/MavParams/Dialog/TryCloseWithApprovalDialogView.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"820\"\n    d:DesignHeight=\"20\"\n    x:Class=\"Asv.Drones.TryCloseWithApprovalDialogView\"\n    x:DataType=\"drones:TryCloseWithApprovalDialogViewModel\"\n>\n    <Design.DataContext>\n        <drones:TryCloseWithApprovalDialogViewModel />\n    </Design.DataContext>\n    <StackPanel>\n        <TextBlock Text=\"{CompiledBinding Message, Mode=OneTime}\" />\n    </StackPanel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/MavParams/Dialog/TryCloseWithApprovalDialogView.axaml.cs",
    "content": "using Asv.Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class TryCloseWithApprovalDialogView : UserControl\n{\n    public TryCloseWithApprovalDialogView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/MavParams/Dialog/TryCloseWithApprovalDialogViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing Asv.Avalonia;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic class TryCloseWithApprovalDialogViewModel : DialogViewModelBase\n{\n    public const string DialogId = $\"{BaseId}.close-with-approval\";\n\n    public TryCloseWithApprovalDialogViewModel()\n        : this(DesignTime.LoggerFactory)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public TryCloseWithApprovalDialogViewModel(ILoggerFactory loggerFactory)\n        : base(DialogId, loggerFactory)\n    {\n        Message = RS.ParamPageViewModel_DataLossDialog_Content;\n    }\n\n    public string Message { get; }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/MavParams/HomePageParamsDeviceItemAction.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic class HomePageParamsDeviceItemAction(ILoggerFactory loggerFactory) : HomePageDeviceItemAction\n{\n    protected override IActionViewModel? TryCreateAction(\n        IClientDevice device,\n        HomePageDeviceItem context\n    )\n    {\n        if (device.GetMicroservice<IParamsClientEx>() == null)\n        {\n            return null;\n        }\n\n        return new ActionViewModel(\"params\", loggerFactory)\n        {\n            Icon = MaterialIconKind.CogTransferOutline,\n            Header = RS.HomePageParamsDeviceItemAction_ActionViewModel_Header,\n            Description = RS.HomePageParamsDeviceItemAction_ActionViewModel_Description,\n            Command = new BindableAsyncCommand(OpenMavParamsCommand.Id, context),\n            CommandParameter = new StringArg($\"dev_id={device.Id}\"), // TODO: replace with DevicePageViewModelMixin.CreateOpenPageArgs\n        };\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/MavParams/MavParamsPageView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:avalonia=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    xmlns:avalonia1=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    mc:Ignorable=\"d\"\n    x:Class=\"Asv.Drones.MavParamsPageView\"\n    x:DataType=\"drones:MavParamsPageViewModel\"\n>\n    <Design.DataContext>\n        <drones:MavParamsPageViewModel />\n    </Design.DataContext>\n    <UserControl.Resources>\n        <x:Double x:Key=\"ButtonSize\">30</x:Double>\n    </UserControl.Resources>\n    <UserControl.Styles>\n        <Style Selector=\"Button\">\n            <Setter Property=\"CornerRadius\" Value=\"{DynamicResource ControlCornerRadius}\" />\n        </Style>\n        <Style Selector=\"ToggleButton\">\n            <Setter Property=\"CornerRadius\" Value=\"{DynamicResource ControlCornerRadius}\" />\n        </Style>\n        <Style Selector=\"ListBox\">\n            <Setter Property=\"CornerRadius\" Value=\"{DynamicResource ControlCornerRadius}\" />\n        </Style>\n        <Style Selector=\"ScrollViewer\">\n            <Setter Property=\"CornerRadius\" Value=\"{DynamicResource ControlCornerRadius}\" />\n        </Style>\n    </UserControl.Styles>\n    <Panel>\n        <avalonia1:AwaitingScreen\n            Header=\"{x:Static drones:RS.MavParamsPageView_AwaitingScreen_Header}\"\n            Description=\"{x:Static drones:RS.MavParamsPageView_AwaitingScreen_Description}\"\n            IsVisible=\"{Binding !IsDeviceInitialized.Value}\"\n        />\n        <Grid IsVisible=\"{Binding IsDeviceInitialized.Value}\" x:Name=\"MainGrid\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition MinWidth=\"220\" Width=\"*\" />\n                <ColumnDefinition Width=\"5\" />\n                <ColumnDefinition MinWidth=\"500\" Width=\"*\" />\n            </Grid.ColumnDefinitions>\n            <DockPanel Margin=\"5\" Grid.Column=\"0\" LastChildFill=\"True\" x:Name=\"AllParamsColumn\">\n                <Grid\n                    DockPanel.Dock=\"Top\"\n                    Grid.IsSharedSizeScope=\"True\"\n                    VerticalAlignment=\"Stretch\"\n                    HorizontalAlignment=\"Stretch\"\n                    ColumnDefinitions=\"*, Auto, Auto, Auto\"\n                    ColumnSpacing=\"6\"\n                    Margin=\"0 0 0 4\"\n                >\n                    <avalonia1:SearchBoxView\n                        Grid.Column=\"0\"\n                        x:Name=\"SearchBox\"\n                        Margin=\"0\"\n                        VerticalAlignment=\"Stretch\"\n                        DataContext=\"{Binding Search}\"\n                    />\n                    <Button\n                        Grid.Column=\"1\"\n                        ToolTip.Tip=\"{x:Static drones:RS.ParametersEditorPageView_PinsOffButton_ToolTip}\"\n                        Command=\"{Binding RemoveAllPins}\"\n                        Width=\"{CompiledBinding #SearchBox.Bounds.Height}\"\n                        Height=\"{CompiledBinding #SearchBox.Bounds.Height}\"\n                    >\n                        <avalonia:MaterialIcon\n                            Kind=\"PinOff\"\n                            HorizontalAlignment=\"Center\"\n                            VerticalAlignment=\"Center\"\n                        />\n                    </Button>\n                    <Button\n                        Grid.Column=\"2\"\n                        ToolTip.Tip=\"{x:Static drones:RS.ParametersEditorPageView_UpdateButton_ToolTip}\"\n                        Command=\"{Binding UpdateParams}\"\n                        Width=\"{CompiledBinding #SearchBox.Bounds.Height}\"\n                        Height=\"{CompiledBinding #SearchBox.Bounds.Height}\"\n                    >\n                        <avalonia:MaterialIcon\n                            Kind=\"Sync\"\n                            HorizontalAlignment=\"Center\"\n                            VerticalAlignment=\"Center\"\n                        />\n                    </Button>\n                    <ToggleButton\n                        Grid.Column=\"3\"\n                        ToolTip.Tip=\"{x:Static drones:RS.ParametersEditorPageView_StarsToggleButton_ToolTip}\"\n                        IsChecked=\"{CompiledBinding IsStarredOnly.ViewValue.Value}\"\n                        Width=\"{CompiledBinding #SearchBox.Bounds.Height}\"\n                        Height=\"{CompiledBinding #SearchBox.Bounds.Height}\"\n                    >\n                        <avalonia:MaterialIcon\n                            Kind=\"Star\"\n                            HorizontalAlignment=\"Center\"\n                            VerticalAlignment=\"Center\"\n                        />\n                    </ToggleButton>\n                </Grid>\n                <StackPanel DockPanel.Dock=\"Bottom\" Orientation=\"Horizontal\">\n                    <TextBlock Text=\"{Binding Total.Value, StringFormat={x:Static drones:RS.ParametersEditorPageViewModel_Total}}\" />\n                </StackPanel>\n                <ScrollViewer MaxHeight=\"\">\n                    <ListBox\n                        SelectedItem=\"{Binding SelectedItem.Value}\"\n                        ItemsSource=\"{Binding AllParams}\"\n                    >\n                        <ListBox.ItemTemplate>\n                            <DataTemplate DataType=\"drones:ParamItemViewModel\">\n                                <DockPanel\n                                    Background=\"Transparent\"\n                                    Name=\"ItemDockPanel\"\n                                    LastChildFill=\"True\"\n                                    DoubleTapped=\"ItemDockPanel_DoubleTapped\"\n                                >\n                                    <ToggleButton\n                                        ToolTip.Tip=\"{x:Static drones:RS.ParametersEditorPageView_StarButton_ToolTip}\"\n                                        IsChecked=\"{Binding IsStarred.ViewValue.Value}\"\n                                        Margin=\"10 0 0 0\"\n                                        BorderThickness=\"0\"\n                                        DockPanel.Dock=\"Right\"\n                                        Height=\"{StaticResource ButtonSize}\"\n                                        Width=\"{StaticResource ButtonSize}\"\n                                        IsEnabled=\"{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}\"\n                                        DoubleTapped=\"Button_DoubleTapped\"\n                                    >\n                                        <avalonia:MaterialIcon\n                                            Classes=\"medium\"\n                                            Kind=\"{Binding StarKind.Value}\"\n                                        />\n                                    </ToggleButton>\n                                    <avalonia:MaterialIcon\n                                        IsVisible=\"{Binding IsPinned.ViewValue.Value}\"\n                                        Classes=\"medium\"\n                                        Margin=\"10 0 0 0\"\n                                        DockPanel.Dock=\"Right\"\n                                        Kind=\"Pin\"\n                                    />\n                                    <avalonia:MaterialIcon\n                                        IsVisible=\"{Binding !IsSynced.Value}\"\n                                        Classes=\"medium\"\n                                        Margin=\"10 0 0 0\"\n                                        DockPanel.Dock=\"Right\"\n                                        Kind=\"FloppyDisk\"\n                                        avalonia1:AsvPallete.Color=\"Success\"\n                                    />\n                                    <TextBlock\n                                        HorizontalAlignment=\"Stretch\"\n                                        VerticalAlignment=\"Center\"\n                                        Text=\"{CompiledBinding Name}\"\n                                    />\n                                </DockPanel>\n                            </DataTemplate>\n                        </ListBox.ItemTemplate>\n                    </ListBox>\n                </ScrollViewer>\n            </DockPanel>\n            <GridSplitter\n                x:Name=\"GridSplitterColumn\"\n                Background=\"Transparent\"\n                Grid.Column=\"1\"\n                DragCompleted=\"GridSplitter_Dragged\"\n            />\n            <Panel Grid.Column=\"2\" x:Name=\"ViewedParamsColumn\">\n                <StackPanel\n                    IsVisible=\"{Binding IsRefreshing.Value}\"\n                    HorizontalAlignment=\"Center\"\n                    VerticalAlignment=\"Center\"\n                >\n                    <TextBlock Text=\"{x:Static drones:RS.MavParamsPageView_Loading_Text}\" />\n                    <ProgressBar\n                        Width=\"100\"\n                        Value=\"{Binding Progress.Value}\"\n                        Minimum=\"0\"\n                        Maximum=\"1\"\n                    />\n                    <Button\n                        Margin=\"0,10,0,0\"\n                        Content=\"{x:Static drones:RS.MavParamsPageView_CancelButton_Content}\"\n                        Command=\"{Binding StopUpdateParams}\"\n                    ></Button>\n                </StackPanel>\n                <ScrollViewer ClipToBounds=\"True\" IsVisible=\"{Binding !IsRefreshing.Value}\">\n                    <ItemsControl\n                        HorizontalAlignment=\"Stretch\"\n                        ItemsSource=\"{Binding ViewedParams}\"\n                    >\n                        <ItemsControl.ItemsPanel>\n                            <ItemsPanelTemplate>\n                                <WrapPanel Orientation=\"Horizontal\" />\n                            </ItemsPanelTemplate>\n                        </ItemsControl.ItemsPanel>\n                    </ItemsControl>\n                </ScrollViewer>\n            </Panel>\n        </Grid>\n    </Panel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/MavParams/MavParamsPageView.axaml.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Linq;\nusing Asv.Avalonia;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Input;\nusing Avalonia.Interactivity;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class MavParamsPageViewConfig : IGridColumnLayoutConfig\n{\n    public IDictionary<string, ColumnConfig> Columns { get; set; } =\n        new Dictionary<string, ColumnConfig>();\n}\n\npublic partial class MavParamsPageView : UserControl\n{\n    private readonly ILayoutService _layoutService;\n\n    private int _realAllParamsColumnOrder;\n    private int _realGridSplitterColumnOrder;\n    private int _realViewedParamsColumnOrder;\n    private MavParamsPageViewConfig? _config;\n\n    public MavParamsPageView()\n        : this(NullLayoutService.Instance)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public MavParamsPageView(ILayoutService layoutService)\n    {\n        _layoutService = layoutService;\n        InitializeComponent();\n    }\n\n    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)\n    {\n        LoadLayout();\n        base.OnAttachedToVisualTree(e);\n    }\n\n    private void GridSplitter_Dragged(object? sender, VectorEventArgs e)\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        if (sender is not GridSplitter)\n        {\n            return;\n        }\n\n        SaveLayout();\n    }\n\n    private void LoadLayout()\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        _config = _layoutService.Get<MavParamsPageViewConfig>(this);\n\n        _realAllParamsColumnOrder = Grid.GetColumn(AllParamsColumn);\n        _realGridSplitterColumnOrder = Grid.GetColumn(GridSplitterColumn);\n        _realViewedParamsColumnOrder = Grid.GetColumn(ViewedParamsColumn);\n\n        if (_config.Columns.Count == 0)\n        {\n            return;\n        }\n\n        if (\n            MainGrid.ColumnDefinitions.Count != _config.Columns.Keys.Count\n            || _config.Columns.All(kvp => kvp.Value.Width.Value == 0)\n            || !_config.Columns.TryGetValue(\n                AllParamsColumn.Name ?? string.Empty,\n                out var allParamsColumn\n            )\n            || !_config.Columns.TryGetValue(\n                GridSplitterColumn.Name ?? string.Empty,\n                out var gridSplitterColumn\n            )\n            || !_config.Columns.TryGetValue(\n                ViewedParamsColumn.Name ?? string.Empty,\n                out var viewedParamsColumn\n            )\n            || _realAllParamsColumnOrder != allParamsColumn.Order\n            || _realGridSplitterColumnOrder != gridSplitterColumn.Order\n            || _realViewedParamsColumnOrder != viewedParamsColumn.Order\n        )\n        {\n            _config = new MavParamsPageViewConfig();\n            return;\n        }\n\n        MainGrid.ColumnDefinitions[_realAllParamsColumnOrder].Width = new GridLength(\n            allParamsColumn.Width.Value,\n            GridUnitType.Star\n        );\n\n        MainGrid.ColumnDefinitions[_realGridSplitterColumnOrder].Width = new GridLength(\n            gridSplitterColumn.Width.Value,\n            GridUnitType.Star\n        );\n\n        MainGrid.ColumnDefinitions[_realViewedParamsColumnOrder].Width = new GridLength(\n            viewedParamsColumn.Width.Value,\n            GridUnitType.Star\n        );\n    }\n\n    private void SaveLayout()\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        if (_config is null)\n        {\n            return;\n        }\n\n        if (DataContext is null)\n        {\n            return;\n        }\n\n        if (\n            AllParamsColumn.Name is null\n            || GridSplitterColumn.Name is null\n            || ViewedParamsColumn.Name is null\n        )\n        {\n            return;\n        }\n\n        _config.Columns = new Dictionary<string, ColumnConfig>\n        {\n            [AllParamsColumn.Name] = new()\n            {\n                Order = _realAllParamsColumnOrder,\n                Width = new GridLengthConfig\n                {\n                    Value = MainGrid.ColumnDefinitions[_realAllParamsColumnOrder].ActualWidth,\n                    GridUnitType = GridUnitType.Star,\n                },\n            },\n            [GridSplitterColumn.Name] = new()\n            {\n                Order = _realGridSplitterColumnOrder,\n                Width = new GridLengthConfig\n                {\n                    Value = MainGrid.ColumnDefinitions[_realGridSplitterColumnOrder].ActualWidth,\n                    GridUnitType = GridUnitType.Star,\n                },\n            },\n            [ViewedParamsColumn.Name] = new()\n            {\n                Order = _realViewedParamsColumnOrder,\n                Width = new GridLengthConfig\n                {\n                    Value = MainGrid.ColumnDefinitions[_realViewedParamsColumnOrder].ActualWidth,\n                    GridUnitType = GridUnitType.Star,\n                },\n            },\n        };\n\n        _layoutService.SetInMemory(this, _config);\n    }\n\n    private void ItemDockPanel_DoubleTapped(object? sender, RoutedEventArgs e)\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        if (DataContext is not MavParamsPageViewModel viewModel)\n        {\n            return;\n        }\n\n        if (sender is not DockPanel { DataContext: { } item })\n        {\n            return;\n        }\n\n        if (ReferenceEquals(viewModel.SelectedItem.Value, item))\n        {\n            viewModel.SelectedItem.Value?.PinItem.Execute(Unit.Default);\n        }\n    }\n\n    private void Button_DoubleTapped(object? sender, RoutedEventArgs e)\n    {\n        e.Handled = true;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/MavParams/MavParamsPageViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Windows.Input;\nusing Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Cfg;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Asv.Modeling;\nusing Avalonia.Controls;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\nusing ObservableCollections;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic sealed class MavParamsPageViewModelConfig\n{\n    public IDictionary<string, ParamItemViewModelConfig> Params { get; set; } =\n        new Dictionary<string, ParamItemViewModelConfig>();\n    public string SearchText { get; set; } = string.Empty;\n    public bool IsStarredOnly { get; set; }\n}\n\npublic class MavParamsPageViewModel\n    : DevicePageViewModel<IMavParamsPageViewModel>,\n        IMavParamsPageViewModel\n{\n    public const string PageId = \"mav-params\";\n    public const MaterialIconKind PageIcon = MaterialIconKind.CogTransferOutline;\n\n    private readonly ILayoutService _layoutService;\n    private readonly ILoggerFactory _loggerFactory;\n    private readonly INavigationService _nav;\n    private readonly ObservableList<ParamItemViewModel> _viewedParamsList; // TODO: Separate views for this collection and all params\n    private readonly ReactiveProperty<bool> _isStarredOnly;\n    private readonly SynchronizedViewFilter<\n        KeyValuePair<string, ParamItem>,\n        ParamItemViewModel\n    > _fullFilter;\n    private readonly Lock _cancelLock = new();\n\n    private DeviceId _deviceId;\n    private IParamsClientEx? _paramsClient;\n    private CancellationTokenSource? _cancellationTokenSource;\n    private ISynchronizedView<KeyValuePair<string, ParamItem>, ParamItemViewModel> _view;\n    private MavParamsPageViewModelConfig? _config;\n\n    public MavParamsPageViewModel()\n        : this(\n            NullDeviceManager.Instance,\n            NullCommandService.Instance,\n            NullLoggerFactory.Instance,\n            NullLayoutService.Instance,\n            NullNavigationService.Instance,\n            DesignTime.DialogService,\n            DesignTime.ExtensionService\n        )\n    {\n        DesignTime.ThrowIfNotDesignMode();\n        var list = new ObservableList<ParamItemViewModel>\n        {\n            new() { DisplayName = \"Param 1\" },\n            new() { DisplayName = \"Param 2\" },\n            new() { DisplayName = \"Param 3\" },\n        };\n        var viewedList = new ObservableList<ParamItemViewModel> { list[0] };\n\n        AllParams = list.ToNotifyCollectionChangedSlim().DisposeItWith(Disposable);\n        ViewedParams = viewedList.ToNotifyCollectionChangedSlim().DisposeItWith(Disposable);\n    }\n\n    public MavParamsPageViewModel(\n        IDeviceManager devices,\n        ICommandService cmd,\n        ILoggerFactory loggerFactory,\n        ILayoutService layoutService,\n        INavigationService nav,\n        IDialogService dialogService,\n        IExtensionService ext\n    )\n        : base(PageId, devices, cmd, layoutService, loggerFactory, dialogService, ext)\n    {\n        ArgumentNullException.ThrowIfNull(devices);\n        ArgumentNullException.ThrowIfNull(cmd);\n        ArgumentNullException.ThrowIfNull(layoutService);\n        ArgumentNullException.ThrowIfNull(loggerFactory);\n        ArgumentNullException.ThrowIfNull(nav);\n\n        Title = RS.MavParamsPageViewModel_Title;\n\n        _layoutService = layoutService;\n        _loggerFactory = loggerFactory;\n        _nav = nav;\n        _isStarredOnly = new ReactiveProperty<bool>().DisposeItWith(Disposable);\n        _viewedParamsList = [];\n        ViewedParams = _viewedParamsList\n            .ToNotifyCollectionChangedSlim(SynchronizationContextCollectionEventDispatcher.Current)\n            .DisposeItWith(Disposable);\n\n        Search = new SearchBoxViewModel(\n            nameof(Search),\n            loggerFactory,\n            UpdateImpl,\n            TimeSpan.FromMilliseconds(500)\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        IsStarredOnly = new HistoricalBoolProperty(\n            nameof(IsStarredOnly),\n            _isStarredOnly,\n            loggerFactory\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n        IsRefreshing = new BindableReactiveProperty<bool>().DisposeItWith(Disposable);\n        SelectedItem = new BindableReactiveProperty<ParamItemViewModel?>().DisposeItWith(\n            Disposable\n        );\n        Progress = new BindableReactiveProperty<double>().DisposeItWith(Disposable);\n\n        SelectedItem\n            .Subscribe(value =>\n            {\n                var itemsToDelete = _viewedParamsList\n                    .Where(_ => !_.IsPinned.ViewValue.Value)\n                    .ToArray();\n\n                foreach (var item in itemsToDelete)\n                {\n                    _viewedParamsList.Remove(item);\n                }\n\n                if (value is null)\n                {\n                    return;\n                }\n\n                if (!_viewedParamsList.Contains(value))\n                {\n                    _viewedParamsList.Add(value);\n                }\n            })\n            .DisposeItWith(Disposable);\n\n        Disposable.AddAction(StopUpdateParamsImpl);\n\n        UpdateParams = new BindableAsyncCommand(UpdateParamsCommand.Id, this);\n\n        StopUpdateParams = new BindableAsyncCommand(StopUpdateParamsCommand.Id, this);\n\n        RemoveAllPins = new ReactiveCommand(\n            async (_, ct) =>\n            {\n                if (!AllParams?.Any(item => item.IsPinned.ViewValue.Value) ?? false)\n                {\n                    return;\n                }\n\n                await this.ExecuteCommand(\n                    RemoveAllPinsCommand.Id,\n                    CommandArg.CreateDictionary(),\n                    ct\n                );\n            }\n        ).DisposeItWith(Disposable);\n\n        IsStarredOnly\n            .ViewValue.ThrottleLast(TimeSpan.FromMilliseconds(500))\n            .Subscribe(_ => UpdateFilter())\n            .DisposeItWith(Disposable);\n\n        Progress\n            .Where(p => p.ApproximatelyEquals(1.0))\n            .Subscribe(_ => IsRefreshing.Value = false)\n            .DisposeItWith(Disposable);\n        IsRefreshing\n            .Where(isRefreshing => isRefreshing)\n            .Subscribe(_ => Progress.Value = 0)\n            .DisposeItWith(Disposable);\n\n        _fullFilter = new SynchronizedViewFilter<\n            KeyValuePair<string, ParamItem>,\n            ParamItemViewModel\n        >(\n            (_, model) =>\n                model.Filter(\n                    Search.Text.ViewValue.Value ?? string.Empty,\n                    IsStarredOnly.ViewValue.Value\n                )\n        );\n\n        Events.Subscribe(InternalCatchEvent).DisposeItWith(Disposable);\n    }\n\n    private Task UpdateImpl(string? query, IProgress<double> progress, CancellationToken cancel)\n    {\n        UpdateFilter();\n        return Task.CompletedTask;\n    }\n\n    private void UpdateFilter()\n    {\n        if (\n            string.IsNullOrWhiteSpace(Search.Text.ViewValue.Value) && !IsStarredOnly.ViewValue.Value\n        )\n        {\n            _view?.ResetFilter();\n            return;\n        }\n\n        _view?.AttachFilter(_fullFilter);\n    }\n\n    private void InternalInit(CancellationToken cancel)\n    {\n        if (_paramsClient is null)\n        {\n            throw new Exception($\"Service of type {nameof(IParamsClientEx)} was not found\");\n        }\n\n        Total = _paramsClient.RemoteCount.ToReadOnlyBindableReactiveProperty();\n        Total.RegisterTo(cancel);\n        _view = _paramsClient.Items.CreateView(kvp =>\n        {\n            ParamItemViewModelConfig? config = null;\n            _config?.Params.TryGetValue(kvp.Key, out config);\n\n            return new ParamItemViewModel(kvp.Key, kvp.Value, _loggerFactory, config);\n        });\n        _view.RegisterTo(cancel);\n        _view.DisposeMany().RegisterTo(cancel);\n        _view.SetRoutableParent(this).RegisterTo(cancel);\n\n        foreach (var item in _view.Where(item => item.IsPinned.ViewValue.Value))\n        {\n            if (_viewedParamsList.Contains(item))\n            {\n                continue;\n            }\n\n            _viewedParamsList.Add(item);\n        }\n\n        _view\n            .ObserveAdd(cancellationToken: cancel)\n            .SubscribeAwait(\n                async (e, ct) =>\n                {\n                    await e.Value.View.RequestLoadLayout(_layoutService, ct);\n\n                    if (!e.Value.View.IsPinned.ViewValue.Value)\n                    {\n                        return;\n                    }\n\n                    if (_viewedParamsList.Contains(e.Value.View))\n                    {\n                        return;\n                    }\n\n                    _viewedParamsList.Add(e.Value.View);\n                }\n            )\n            .RegisterTo(cancel);\n\n        AllParams = _view.ToNotifyCollectionChanged(\n            SynchronizationContextCollectionEventDispatcher.Current\n        );\n        AllParams.RegisterTo(cancel);\n        Search.Refresh();\n    }\n\n    internal void StopUpdateParamsImpl()\n    {\n        using (_cancelLock.EnterScope())\n        {\n            if (_cancellationTokenSource is not null)\n            {\n                _cancellationTokenSource?.Cancel();\n                _cancellationTokenSource?.Dispose();\n                _cancellationTokenSource = null;\n            }\n        }\n\n        if (!IsRefreshing.IsDisposed)\n        {\n            IsRefreshing.Value = false;\n        }\n    }\n\n    internal async Task UpdateParamsImpl(CancellationToken cancel = default)\n    {\n        if (IsRefreshing.Value)\n        {\n            return;\n        }\n\n        if (_paramsClient is null)\n        {\n            return;\n        }\n\n        cancel.ThrowIfCancellationRequested();\n        if (_cancellationTokenSource is not null)\n        {\n            StopUpdateParamsImpl();\n        }\n\n        SelectedItem.Value = null;\n        IsRefreshing.Value = true;\n\n        using (_cancelLock.EnterScope())\n        {\n            _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancel);\n        }\n\n        try\n        {\n            await _paramsClient.ReadAll(\n                new Progress<double>(i => Progress.Value = i),\n                cancel: _cancellationTokenSource.Token\n            );\n        }\n        catch (OperationCanceledException)\n        {\n            Logger.LogInformation(\"User canceled updating params\");\n        }\n        catch (Exception e)\n        {\n            Logger.LogError(e, \"Error to read all param items\");\n        }\n    }\n\n    protected override void AfterDeviceInitialized(IClientDevice device, CancellationToken cancel)\n    {\n        Title = $\"{RS.MavParamsPageViewModel_Title}[{device.Id}]\";\n        _paramsClient = device.GetMicroservice<IParamsClientEx>();\n        DeviceName = device\n            .Name.Select(x => x ?? RS.MavParamsPageViewModel_DeviceName_Unknown)\n            .ToReadOnlyBindableReactiveProperty<string>();\n        DeviceName.RegisterTo(cancel);\n        _deviceId = device.Id;\n        Icon = DeviceIconMixin.GetIcon(_deviceId) ?? PageIcon;\n        InternalInit(cancel);\n    }\n\n    public HistoricalBoolProperty IsStarredOnly { get; }\n    public BindableReactiveProperty<bool> IsRefreshing { get; }\n    public BindableReactiveProperty<double> Progress { get; }\n    public ICommand UpdateParams { get; }\n    public ICommand StopUpdateParams { get; }\n    public ReactiveCommand RemoveAllPins { get; }\n    public SearchBoxViewModel Search { get; }\n\n    public INotifyCollectionChangedSynchronizedViewList<ParamItemViewModel>? AllParams\n    {\n        get;\n        private set;\n    }\n\n    public IReadOnlyBindableReactiveProperty<int> Total { get; private set; }\n\n    public INotifyCollectionChangedSynchronizedViewList<ParamItemViewModel> ViewedParams { get; }\n\n    public IReadOnlyBindableReactiveProperty<string> DeviceName { get; private set; }\n\n    public BindableReactiveProperty<ParamItemViewModel?> SelectedItem { get; }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        yield return Search;\n        yield return IsStarredOnly;\n\n        if (AllParams is not null)\n        {\n            foreach (var paramItemViewModel in AllParams)\n            {\n                yield return paramItemViewModel;\n            }\n        }\n    }\n\n    protected override void AfterLoadExtensions() { }\n\n    private async ValueTask InternalCatchEvent(IRoutable src, AsyncRoutedEvent<IRoutable> e)\n    {\n        switch (e)\n        {\n            case ParamItemChangedEvent { Sender: ParamItemViewModel param } paramChanged:\n            {\n                if (paramChanged.TrackedObject is HistoricalBoolProperty caller)\n                {\n                    if (caller.Id == param.IsPinned.Id)\n                    {\n                        UpdateViewedItems(param);\n                    }\n                }\n\n                e.IsHandled = true;\n                break;\n            }\n\n            case PageCloseAttemptEvent { Sender: MavParamsPageViewModel } closeEvent:\n            {\n                var isCloseReady = await TryCloseWithApproval();\n                if (!isCloseReady)\n                {\n                    closeEvent.AddRestriction(new Restriction(this));\n                }\n\n                break;\n            }\n\n            case SaveLayoutEvent saveLayoutEvent:\n            {\n                if (!IsDeviceInitialized.Value)\n                {\n                    if (saveLayoutEvent.IsFlushToFile)\n                    {\n                        saveLayoutEvent.LayoutService.FlushFromMemoryViewModelAndView(this);\n                    }\n                    break;\n                }\n\n                if (_config is null)\n                {\n                    break;\n                }\n\n                this.HandleSaveLayout(\n                    saveLayoutEvent,\n                    _config,\n                    cfg =>\n                    {\n                        cfg.SearchText = Search.Text.ViewValue.Value ?? string.Empty;\n                        cfg.IsStarredOnly = IsStarredOnly.ViewValue.Value;\n                        cfg.Params = _view\n                            .Where(p => p.IsLayoutChanged.CurrentValue)\n                            .ToDictionary(\n                                p => p.Name,\n                                p => new ParamItemViewModelConfig\n                                {\n                                    Name = p.Name,\n                                    IsPinned = p.IsPinned.ViewValue.Value,\n                                    IsStarred = p.IsStarred.ViewValue.Value,\n                                }\n                            );\n                    },\n                    FlushingStrategy.FlushBothViewModelAndView\n                );\n                break;\n            }\n\n            case LoadLayoutEvent loadLayoutEvent:\n            {\n                _config = this.HandleLoadLayout<MavParamsPageViewModelConfig>(\n                    loadLayoutEvent,\n                    cfg =>\n                    {\n                        Search.Text.ModelValue.Value = cfg.SearchText;\n                        IsStarredOnly.ModelValue.Value = cfg.IsStarredOnly;\n                    }\n                );\n                break;\n            }\n        }\n    }\n\n    private void UpdateViewedItems(ParamItemViewModel param)\n    {\n        var itemsToDelete = _viewedParamsList\n            .Where(_ => !_.IsPinned.ViewValue.Value && _.Id != SelectedItem.Value?.Id)\n            .ToArray();\n\n        foreach (var item in itemsToDelete)\n        {\n            _viewedParamsList.Remove(item);\n            return;\n        }\n\n        if (!_viewedParamsList.Contains(param))\n        {\n            _viewedParamsList.Add(param);\n        }\n    }\n\n    private async Task<bool> TryCloseWithApproval(CancellationToken cancel = default)\n    {\n        cancel.ThrowIfCancellationRequested();\n        var notSyncedParams = _viewedParamsList.Where(param => !param.IsSynced.Value).ToArray();\n\n        if (notSyncedParams.Length == 0)\n        {\n            return true;\n        }\n\n        using var vm = new TryCloseWithApprovalDialogViewModel(_loggerFactory);\n        var dialog = new ContentDialog(vm, _nav)\n        {\n            Title = RS.ParamPageViewModel_DataLossDialog_Title,\n            IsSecondaryButtonEnabled = true,\n            PrimaryButtonText = RS.ParamPageViewModel_DataLossDialog_PrimaryButtonText,\n            SecondaryButtonText = RS.ParamPageViewModel_DataLossDialog_SecondaryButtonText,\n            CloseButtonText = RS.ParamPageViewModel_DataLossDialog_CloseButtonText,\n        };\n\n        var result = await dialog.ShowAsync();\n\n        if (result == ContentDialogResult.None)\n        {\n            return false;\n        }\n\n        if (result == ContentDialogResult.Primary)\n        {\n            foreach (var param in notSyncedParams)\n            {\n                param.Write.Execute(Unit.Default);\n            }\n        }\n\n        return true;\n    }\n}\n\nfile class ParamsKvpComparer : IComparer<KeyValuePair<string, ParamItem>>\n{\n    public static ParamsKvpComparer Instance { get; } = new();\n\n    public int Compare(KeyValuePair<string, ParamItem> x, KeyValuePair<string, ParamItem> y)\n    {\n        return string.CompareOrdinal(x.Value.Name, y.Value.Name);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/MavParams/ParamItem/ParamItemView.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:avalonia=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    xmlns:avalonia1=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    mc:Ignorable=\"d\"\n    x:Class=\"Asv.Drones.ParamItemView\"\n    x:CompileBindings=\"True\"\n    Width=\"400\"\n    MinHeight=\"300\"\n    x:DataType=\"drones:ParamItemViewModel\"\n>\n    <Design.DataContext>\n        <drones:ParamItemViewModel />\n    </Design.DataContext>\n    <Border\n        Padding=\"{DynamicResource ButtonPadding}\"\n        Background=\"Transparent\"\n        BorderBrush=\"{DynamicResource CardStrokeColorDefaultBrush}\"\n        BorderThickness=\"1\"\n        CornerRadius=\"{StaticResource ControlCornerRadius}\"\n        Margin=\"8\"\n    >\n        <StackPanel>\n            <DockPanel>\n                <Button\n                    Command=\"{CompiledBinding PinItem}\"\n                    ToolTip.Tip=\"{x:Static drones:RS.ParamItemView_PinToggleButton_ToolTip}\"\n                    Background=\"Transparent\"\n                    BorderThickness=\"0\"\n                    DockPanel.Dock=\"Right\"\n                    Height=\"30\"\n                    Width=\"30\"\n                    CornerRadius=\"{DynamicResource ControlCornerRadius}\"\n                >\n                    <Panel>\n                        <avalonia:MaterialIcon\n                            IsVisible=\"{CompiledBinding IsPinned.ViewValue.Value}\"\n                            Classes=\"medium\"\n                            Kind=\"Pin\"\n                        />\n                        <avalonia:MaterialIcon\n                            IsVisible=\"{CompiledBinding !IsPinned.ViewValue.Value}\"\n                            Classes=\"medium\"\n                            Kind=\"PinOutline\"\n                        />\n                    </Panel>\n                </Button>\n                <Panel DockPanel.Dock=\"Right\" IsVisible=\"{Binding !IsSynced.Value}\">\n                    <avalonia:MaterialIcon\n                        avalonia1:AsvPallete.Color=\"Success\"\n                        Kind=\"FloppyDisk\"\n                        Classes=\"big\"\n                    />\n                </Panel>\n                <TextBlock\n                    Text=\"{CompiledBinding Name}\"\n                    FontWeight=\"Bold\"\n                    VerticalAlignment=\"Center\"\n                    HorizontalAlignment=\"Center\"\n                />\n            </DockPanel>\n            <TextBlock\n                Text=\"{CompiledBinding DisplayName, Mode=OneTime}\"\n                TextWrapping=\"Wrap\"\n                Margin=\"10 5\"\n            />\n            <TextBox Margin=\"10 5\" Text=\"{CompiledBinding Value.Value}\">\n                <TextBox.InnerRightContent>\n                    <TextBlock\n                        Text=\"{CompiledBinding Units, Mode=OneTime}\"\n                        Margin=\"10 0\"\n                        VerticalAlignment=\"Center\"\n                    />\n                </TextBox.InnerRightContent>\n            </TextBox>\n            <Grid Margin=\"10 5\" ColumnDefinitions=\"*, 5, *\">\n                <Button\n                    ToolTip.Tip=\"{x:Static drones:RS.ParamItemView_UpdateButton_ToolTip}\"\n                    Command=\"{CompiledBinding Read}\"\n                    HorizontalAlignment=\"Stretch\"\n                    Grid.Column=\"0\"\n                    Content=\"{x:Static drones:RS.ParamItemView_UpdateButton_Text}\"\n                />\n                <Button\n                    ToolTip.Tip=\"{x:Static drones:RS.ParamItemView_WriteButton_ToolTip}\"\n                    Command=\"{CompiledBinding Write}\"\n                    HorizontalAlignment=\"Stretch\"\n                    Grid.Column=\"2\"\n                    Content=\"{x:Static drones:RS.ParamItemView_WriteButton_Text}\"\n                />\n            </Grid>\n            <TextBlock\n                Text=\"{CompiledBinding ValueDescription, Mode=OneTime}\"\n                TextWrapping=\"Wrap\"\n                Margin=\"10 5\"\n            />\n            <TextBlock\n                Text=\"{CompiledBinding Description, Mode=OneTime}\"\n                TextWrapping=\"Wrap\"\n                Margin=\"10 5\"\n            />\n            <Border\n                IsVisible=\"{CompiledBinding IsRebootRequired, Mode=OneTime}\"\n                Margin=\"10 5\"\n                HorizontalAlignment=\"Left\"\n                Padding=\"5\"\n                avalonia1:AsvPallete.Color=\"Error\"\n                CornerRadius=\"{StaticResource ControlCornerRadius}\"\n            >\n                <TextBlock\n                    FontWeight=\"Bold\"\n                    Text=\"{x:Static drones:RS.ParamItemView_InlineNotification_RebootRequired}\"\n                />\n            </Border>\n        </StackPanel>\n    </Border>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/MavParams/ParamItem/ParamItemView.axaml.cs",
    "content": "using Asv.Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class ParamItemView : UserControl\n{\n    public ParamItemView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/MavParams/ParamItem/ParamItemViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing System.Threading.Tasks;\nusing System.Windows.Input;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Mavlink;\nusing Asv.Mavlink.Common;\nusing Asv.Modeling;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class ParamItemViewModelConfig\n{\n    public string? Name { get; set; }\n    public bool IsStarred { get; set; }\n    public bool IsPinned { get; set; }\n}\n\npublic class ParamItemViewModel : RoutableViewModel\n{\n    private readonly ParamItem _paramItem;\n    private readonly ReactiveProperty<bool> _isPinned;\n    private readonly ReactiveProperty<bool> _isStarred;\n    private bool _internalUpdate;\n\n    public ParamItemViewModel()\n        : base(DesignTime.Id, DesignTime.LoggerFactory) // use base class instead of ParamItem, because there is no way to create an empty Param item\n    {\n        DesignTime.ThrowIfNotDesignMode();\n\n        Name = \"param\" + Guid.NewGuid();\n        DisplayName = Name;\n        Description = \"Design description\";\n        ValueDescription = \"Value design description\";\n        IsRebootRequired = true;\n    }\n\n    public ParamItemViewModel(\n        NavigationId id,\n        ParamItem paramItem,\n        ILoggerFactory loggerFactory,\n        ParamItemViewModelConfig? initialConfig = null\n    )\n        : base(id, loggerFactory)\n    {\n        ArgumentNullException.ThrowIfNull(paramItem);\n        ArgumentNullException.ThrowIfNull(loggerFactory);\n\n        _paramItem = paramItem;\n        Name = paramItem.Name;\n        DisplayName = paramItem.Info.DisplayName ?? string.Empty;\n        Units = paramItem.Info.Units ?? string.Empty;\n        Description = paramItem.Info.Description ?? string.Empty;\n        ValueDescription = paramItem.Info.UnitsDisplayName ?? string.Empty;\n        IsRebootRequired = paramItem.Info.IsRebootRequired;\n\n        _isPinned = new ReactiveProperty<bool>(initialConfig?.IsPinned ?? false);\n        _isStarred = new ReactiveProperty<bool>(initialConfig?.IsStarred ?? false);\n\n        IsPinned = new HistoricalBoolProperty(\n            nameof(IsPinned),\n            _isPinned,\n            loggerFactory\n        ).SetRoutableParent(this);\n        IsPinned.ForceValidate();\n        IsStarred = new HistoricalBoolProperty(\n            nameof(IsStarred),\n            _isStarred,\n            loggerFactory\n        ).SetRoutableParent(this);\n        IsStarred.ForceValidate();\n        IsLayoutChanged = Observable\n            .Merge(IsPinned.ViewValue, IsStarred.ViewValue)\n            .ToReadOnlyReactiveProperty();\n\n        Value = new BindableReactiveProperty<string?>();\n        IsSynced = new BindableReactiveProperty<bool>();\n        StarKind = new BindableReactiveProperty<MaterialIconKind>();\n\n        PinItem = new ReactiveCommand(_ => IsPinned.ViewValue.Value = !IsPinned.ViewValue.Value);\n\n        _sub = paramItem.IsSynced.AsObservable().Subscribe(_ => IsSynced.Value = _);\n\n        _sub1 = paramItem.Value.Subscribe(param =>\n        {\n            if (_internalUpdate)\n            {\n                return;\n            }\n\n            Value.Value = param.Type switch\n            {\n                MavParamType.MavParamTypeUint8 => ((byte)param).ToString(),\n                MavParamType.MavParamTypeInt8 => ((sbyte)param).ToString(),\n                MavParamType.MavParamTypeUint16 => ((ushort)param).ToString(),\n                MavParamType.MavParamTypeInt16 => ((short)param).ToString(),\n                MavParamType.MavParamTypeUint32 => ((uint)param).ToString(),\n                MavParamType.MavParamTypeInt32 or MavParamType.MavParamTypeInt64 => (\n                    (int)param\n                ).ToString(),\n                MavParamType.MavParamTypeUint64 => ((ulong)param).ToString(),\n                MavParamType.MavParamTypeReal32 => ((float)param).ToString(\n                    CultureInfo.InvariantCulture\n                ),\n                MavParamType.MavParamTypeReal64 => ((double)param).ToString(\n                    CultureInfo.InvariantCulture\n                ),\n                _ => Value.Value,\n            };\n        });\n\n        _sub2 = Value.Subscribe(val =>\n        {\n            _internalUpdate = true;\n            if (string.IsNullOrWhiteSpace(val))\n            {\n                paramItem.Value.OnNext(0);\n            }\n\n            switch (paramItem.Type)\n            {\n                case MavParamType.MavParamTypeUint8:\n                {\n                    if (byte.TryParse(val, out var result))\n                    {\n                        paramItem.Value.OnNext(result);\n                    }\n\n                    break;\n                }\n\n                case MavParamType.MavParamTypeInt8:\n                {\n                    if (sbyte.TryParse(val, out var result))\n                    {\n                        paramItem.Value.OnNext(result);\n                    }\n\n                    break;\n                }\n\n                case MavParamType.MavParamTypeUint16:\n                {\n                    if (ushort.TryParse(val, out var result))\n                    {\n                        paramItem.Value.OnNext(result);\n                    }\n\n                    break;\n                }\n\n                case MavParamType.MavParamTypeInt16:\n                {\n                    if (short.TryParse(val, out var result))\n                    {\n                        paramItem.Value.OnNext(result);\n                    }\n\n                    break;\n                }\n\n                case MavParamType.MavParamTypeUint32:\n                {\n                    if (uint.TryParse(val, out var result))\n                    {\n                        paramItem.Value.OnNext(result);\n                    }\n\n                    break;\n                }\n\n                case MavParamType.MavParamTypeInt32:\n                {\n                    if (int.TryParse(val, out var result))\n                    {\n                        paramItem.Value.OnNext(result);\n                    }\n\n                    break;\n                }\n\n                case MavParamType.MavParamTypeUint64:\n                {\n                    if (ulong.TryParse(val, out var result))\n                    {\n                        paramItem.Value.OnNext(result);\n                    }\n\n                    break;\n                }\n\n                case MavParamType.MavParamTypeInt64:\n                {\n                    if (long.TryParse(val, out var result))\n                    {\n                        paramItem.Value.OnNext(result);\n                    }\n\n                    break;\n                }\n\n                case MavParamType.MavParamTypeReal32:\n                case MavParamType.MavParamTypeReal64:\n                {\n                    if (\n                        float.TryParse(\n                            val?.Replace(\",\", \".\"),\n                            CultureInfo.InvariantCulture,\n                            out var result\n                        )\n                    )\n                    {\n                        paramItem.Value.OnNext(result);\n                    }\n\n                    break;\n                }\n\n                default:\n                    throw new ArgumentOutOfRangeException();\n            }\n\n            _internalUpdate = false;\n        });\n\n        Write = new ReactiveCommand(\n            async (_, ct) =>\n            {\n                try\n                {\n                    await Api.Commands.Mavlink.WriteParam(\n                        this,\n                        paramItem.Name,\n                        paramItem.Value.Value,\n                        ct\n                    );\n                    IsSynced.Value = true;\n                }\n                catch (Exception ex)\n                {\n                    Logger.LogError(ex, \"Write {Name} error\", Name);\n                }\n            }\n        ).DisposeItWith(Disposable);\n\n        Read = new ReactiveCommand(\n            async (_, ct) =>\n            {\n                try\n                {\n                    await Api.Commands.Mavlink.ReadParam(this, paramItem.Name, ct);\n                    IsSynced.Value = true;\n                }\n                catch (Exception ex)\n                {\n                    Logger.LogError(ex, \"Read {Name} error\", Name);\n                }\n            }\n        ).DisposeItWith(Disposable);\n\n        _sub3 = IsStarred.ViewValue.Subscribe(isStarted =>\n            StarKind.Value = isStarted ? MaterialIconKind.Star : MaterialIconKind.StarBorder\n        );\n\n        IsPinned\n            .ViewValue.SubscribeAwait(\n                async (_, ct) => await this.Rise(new ParamItemChangedEvent(this, IsPinned), ct)\n            )\n            .DisposeItWith(Disposable);\n        IsStarred\n            .ViewValue.SubscribeAwait(\n                async (_, ct) => await this.Rise(new ParamItemChangedEvent(this, IsStarred), ct)\n            )\n            .DisposeItWith(Disposable);\n\n        Events.Subscribe(InternalCatchEvent).DisposeItWith(Disposable);\n    }\n\n    public string Name { get; }\n\n    public string DisplayName\n    {\n        get;\n        init => SetField(ref field, value);\n    }\n\n    public string Units { get; }\n    public ICommand Read { get; }\n    public ReactiveCommand Write { get; }\n    public ReactiveCommand PinItem { get; }\n    public string ValueDescription { get; }\n    public string Description { get; }\n    public bool IsRebootRequired { get; }\n    public BindableReactiveProperty<bool> IsSynced { get; }\n    public BindableReactiveProperty<MaterialIconKind> StarKind { get; }\n    public HistoricalBoolProperty IsPinned { get; }\n    public BindableReactiveProperty<string?> Value { get; }\n    public HistoricalBoolProperty IsStarred { get; }\n    public ReadOnlyReactiveProperty<bool> IsLayoutChanged { get; }\n\n    public bool Filter(string? searchText, bool starredOnly)\n    {\n        if (starredOnly)\n        {\n            if (!IsStarred.ViewValue.Value)\n            {\n                return false;\n            }\n        }\n\n        if (string.IsNullOrWhiteSpace(searchText))\n        {\n            return true;\n        }\n\n        return Name.Contains(searchText, StringComparison.InvariantCultureIgnoreCase);\n    }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        yield return IsPinned;\n        yield return IsStarred;\n    }\n\n    #region Dispose\n\n    private readonly IDisposable _sub;\n    private readonly IDisposable _sub1;\n    private readonly IDisposable _sub2;\n    private readonly IDisposable _sub3;\n\n    protected override void Dispose(bool disposing)\n    {\n        if (disposing)\n        {\n            _sub.Dispose();\n            _sub1.Dispose();\n            _sub2.Dispose();\n            _sub3.Dispose();\n            _isPinned.Dispose();\n            _isStarred.Dispose();\n            IsSynced.Dispose();\n            IsStarred.Dispose();\n            IsPinned.Dispose();\n            Value.Dispose();\n            PinItem.Dispose();\n            StarKind.Dispose();\n        }\n\n        base.Dispose(disposing);\n    }\n\n    #endregion\n\n    private ValueTask InternalCatchEvent(IRoutable src, AsyncRoutedEvent<IRoutable> e)\n    {\n        switch (e)\n        {\n            case LoadLayoutEvent loadLayoutEvent:\n            {\n                if (Parent is not MavParamsPageViewModel parent)\n                {\n                    break;\n                }\n\n                parent.HandleLoadLayout<MavParamsPageViewModelConfig>(\n                    loadLayoutEvent,\n                    cfg =>\n                    {\n                        if (!cfg.Params.TryGetValue(Name, out var paramItemConfig))\n                        {\n                            return;\n                        }\n\n                        IsPinned.ModelValue.Value = paramItemConfig.IsPinned;\n                        IsStarred.ModelValue.Value = paramItemConfig.IsStarred;\n                    }\n                );\n                break;\n            }\n        }\n\n        return ValueTask.CompletedTask;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/MavParams/ParamItem/RoutedEvents/ParamItemChangedEvent.cs",
    "content": "using Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Modeling;\n\nnamespace Asv.Drones;\n\n/// <summary>\n/// Represents an event triggered when param item changes.\n/// </summary>\n/// <param name=\"source\">.</param>\npublic sealed class ParamItemChangedEvent(IRoutable source, object trackedObject)\n    : AsyncRoutedEvent<IRoutable>(source, RoutingStrategy.Bubble)\n{\n    public object TrackedObject => trackedObject;\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/DefaultSetupExtension.cs",
    "content": "using System.Collections.Generic;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Microsoft.Extensions.DependencyInjection;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class DefaultSetupExtension(\n    [FromKeyedServices(SetupPageViewModel.PageId)] IEnumerable<ITreePage> items\n) : IExtensionFor<ISetupPage>\n{\n    public void Extend(ISetupPage context, CompositeDisposable contextDispose)\n    {\n        foreach (var treePage in items)\n        {\n            context.Nodes.Add(treePage);\n            treePage.DisposeItWith(contextDispose);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/HomePageSetupDeviceItemAction.cs",
    "content": "using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.IO;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic class HomePageSetupDeviceItemAction(ILoggerFactory loggerFactory) : HomePageDeviceItemAction\n{\n    protected override IActionViewModel? TryCreateAction(\n        IClientDevice device,\n        HomePageDeviceItem context\n    )\n    {\n        return new ActionViewModel(SetupPageViewModel.PageId, loggerFactory)\n        {\n            Header = OpenSetupCommand.StaticInfo.Name,\n            Description = OpenSetupCommand.StaticInfo.Description,\n            Icon = OpenSetupCommand.StaticInfo.Icon,\n            Command = new BindableAsyncCommand(OpenSetupCommand.Id, context),\n            CommandParameter = DevicePageViewModelMixin.CreateOpenPageArgs(device.Id),\n        };\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/SetupMixin.cs",
    "content": "﻿using System;\nusing Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Avalonia.Controls;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\n\nnamespace Asv.Drones;\n\npublic static class SetupMixin\n{\n    extension(AsvDronesMixin.Builder builder)\n    {\n        public AsvDronesMixin.Builder UseSetupPage(Action<Builder>? configure = null)\n        {\n            configure ??= x => x.UseDefault();\n            var setup = new Builder(builder.Parent);\n            configure(setup);\n            return builder;\n        }\n    }\n\n    extension(ShellMixin.PageBuilder builder)\n    {\n        public Builder SetupPage => new(builder.Parent.Parent);\n    }\n\n    public class Builder(IHostApplicationBuilder builder)\n    {\n        public Builder UseDefault()\n        {\n            builder.Shell.Pages.Register<SetupPageViewModel, SetupPageView>(\n                SetupPageViewModel.PageId\n            );\n            builder.Shell.Pages.Home.UseItemExtension<HomePageSetupDeviceItemAction>();\n            builder.Extensions.Register<ISetupPage, DefaultSetupExtension>();\n            UseFrameTypeSubPage();\n            UseMotorSubPage();\n            return this;\n        }\n\n        public Builder UseFrameTypeSubPage()\n        {\n            builder.Extensions.Register<ISetupPage, FrameTypeSetupPageExtension>();\n            builder.ViewLocator.RegisterViewFor<DroneFrameItemViewModel, DroneFrameItemView>();\n            AddSubPage<SetupFrameTypeViewModel, SetupFrameTypeView>(SetupFrameTypeViewModel.PageId);\n            return this;\n        }\n\n        public Builder UseMotorSubPage()\n        {\n            builder.ViewLocator.RegisterViewFor<MotorItemViewModel, MotorItemView>();\n            AddSubPage<SetupMotorsViewModel, SetupMotorsView>(SetupMotorsViewModel.PageId);\n            builder.Extensions.Register<ISetupPage, MotorsSetupPageExtension>();\n            return this;\n        }\n\n        public Builder AddSubPage<TViewModel, TView>(string pageId)\n            where TViewModel : class, ISetupSubpage\n            where TView : Control\n        {\n            builder.ViewLocator.RegisterViewFor<TViewModel, TView>();\n            builder.Services.AddKeyedTransient<ISetupSubpage, TViewModel>(pageId);\n            return this;\n        }\n\n        public Builder AddSubPage<TViewModel, TView, TTreeMenu>(string pageId)\n            where TViewModel : class, ISetupSubpage\n            where TView : Control\n            where TTreeMenu : class, ITreePage\n        {\n            AddSubPage<TViewModel, TView>(pageId);\n            builder.Services.AddKeyedTransient<ITreePage, TTreeMenu>(SetupPageViewModel.PageId);\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/SetupPageView.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:avalonia=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"800\"\n    d:DesignHeight=\"450\"\n    x:DataType=\"drones:SetupPageViewModel\"\n    x:Class=\"Asv.Drones.SetupPageView\"\n>\n    <Grid>\n        <avalonia:AwaitingScreen\n            Header=\"{x:Static drones:RS.MavParamsPageView_AwaitingScreen_Header}\"\n            Description=\"{x:Static drones:RS.MavParamsPageView_AwaitingScreen_Description}\"\n            IsVisible=\"{Binding !IsDeviceInitialized.Value}\"\n        />\n        <avalonia:TreePageView IsVisible=\"{Binding IsDeviceInitialized.Value}\" />\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/SetupPageView.axaml.cs",
    "content": "using Asv.Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class SetupPageView : UserControl\n{\n    public SetupPageView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/SetupPageViewModel.cs",
    "content": "using System;\nusing System.Threading;\nusing Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic class SetupPageViewModel : TreeDevicePageViewModel<ISetupPage, ISetupSubpage>, ISetupPage\n{\n    public const string PageId = \"setup\";\n    public const MaterialIconKind PageIcon = MaterialIconKind.Cogs;\n\n    public SetupPageViewModel(\n        ICommandService cmd,\n        IDeviceManager devices,\n        IServiceProvider container,\n        ILayoutService layoutService,\n        ILoggerFactory loggerFactory,\n        IDialogService dialogService,\n        IExtensionService ext\n    )\n        : base(PageId, devices, cmd, container, layoutService, loggerFactory, dialogService, ext)\n    {\n        Icon = PageIcon;\n    }\n\n    protected override void AfterDeviceInitialized(\n        IClientDevice device,\n        CancellationToken onDisconnectedToken\n    )\n    {\n        Title = $\"{RS.SetupPageViewModel_Title}[{device.Id}]\";\n        TreeHeader = RS.SetupPageViewModel_Title;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/FrameType/DroneFrameItem/DroneFrameItemView.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    xmlns:avalonia=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"267\"\n    x:DataType=\"drones:DroneFrameItemViewModel\"\n    x:Class=\"Asv.Drones.DroneFrameItemView\"\n>\n    <Design.DataContext>\n        <drones:DroneFrameItemViewModel />\n    </Design.DataContext>\n    <Grid ColumnDefinitions=\"*, Auto\" ClipToBounds=\"False\" MaxHeight=\"100\">\n        <ScrollViewer\n            Grid.Column=\"0\"\n            HorizontalAlignment=\"Left\"\n            HorizontalScrollBarVisibility=\"Disabled\"\n            VerticalScrollBarVisibility=\"Disabled\"\n        >\n            <StackPanel Orientation=\"Horizontal\" VerticalAlignment=\"Center\">\n                <Border\n                    BorderBrush=\"{DynamicResource BorderBrush}\"\n                    BorderThickness=\"1\"\n                    Background=\"{DynamicResource SystemControlBackgroundListLowBrush}\"\n                    Height=\"90\"\n                    Width=\"90\"\n                    CornerRadius=\"8\"\n                    VerticalAlignment=\"Center\"\n                    HorizontalAlignment=\"Left\"\n                    Margin=\"0,0,12,0\"\n                >\n                    <ItemsControl\n                        ItemsSource=\"{Binding Model.Meta}\"\n                        HorizontalAlignment=\"Stretch\"\n                        Padding=\"6\"\n                    >\n                        <ItemsControl.ItemTemplate>\n                            <DataTemplate>\n                                <StackPanel Margin=\"2\" Orientation=\"Vertical\">\n                                    <TextBlock\n                                        Text=\"{Binding Key}\"\n                                        FontSize=\"9\"\n                                        FontWeight=\"SemiBold\"\n                                        TextTrimming=\"CharacterEllipsis\"\n                                    />\n                                    <TextBlock\n                                        Text=\"{Binding Value}\"\n                                        FontSize=\"10\"\n                                        TextWrapping=\"Wrap\"\n                                        Margin=\"0,0,0,3\"\n                                    />\n                                </StackPanel>\n                            </DataTemplate>\n                        </ItemsControl.ItemTemplate>\n                    </ItemsControl>\n                </Border>\n                <StackPanel\n                    Spacing=\"4\"\n                    Orientation=\"Horizontal\"\n                    Width=\"200\"\n                    HorizontalAlignment=\"Center\"\n                >\n                    <TextBlock\n                        VerticalAlignment=\"Center\"\n                        HorizontalAlignment=\"Left\"\n                        Text=\"{Binding Model.Id}\"\n                        FontSize=\"14\"\n                        FontWeight=\"Medium\"\n                        TextWrapping=\"Wrap\"\n                    />\n                    <avalonia:MaterialIcon\n                        Kind=\"CheckCircle\"\n                        Width=\"20\"\n                        Height=\"20\"\n                        VerticalAlignment=\"Center\"\n                        HorizontalAlignment=\"Left\"\n                        Foreground=\"Green\"\n                        IsVisible=\"{Binding IsCurrent.Value}\"\n                    />\n                </StackPanel>\n            </StackPanel>\n        </ScrollViewer>\n\n        <Button\n            Grid.Column=\"1\"\n            Command=\"{Binding ApplyCommand}\"\n            CommandParameter=\"{Binding}\"\n            IsEnabled=\"{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}\"\n            VerticalAlignment=\"Center\"\n            HorizontalAlignment=\"Right\"\n            FontSize=\"14\"\n        >\n            <StackPanel Orientation=\"Horizontal\" Spacing=\"6\">\n                <avalonia:MaterialIcon\n                    Kind=\"Check\"\n                    Width=\"16\"\n                    Height=\"16\"\n                    VerticalAlignment=\"Center\"\n                />\n                <TextBlock\n                    Text=\"{x:Static drones:RS.SetupFrameTypeView_ApplyFrame}\"\n                    VerticalAlignment=\"Center\"\n                    FontSize=\"14\"\n                />\n            </StackPanel>\n        </Button>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/FrameType/DroneFrameItem/DroneFrameItemView.axaml.cs",
    "content": "using Asv.Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class DroneFrameItemView : UserControl\n{\n    public DroneFrameItemView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/FrameType/DroneFrameItem/DroneFrameItemViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Mavlink;\nusing Asv.Modeling;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic sealed class DroneFrameItemViewModel : RoutableViewModel\n{\n    public const string BaseId = \"frame-item\";\n\n    public DroneFrameItemViewModel()\n        : this(NullDroneFrame.Instance, NullLoggerFactory.Instance)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n        ApplyCommand = new ReactiveCommand(\n            async (_, cancel) =>\n            {\n                if (!IsCurrent.Value)\n                {\n                    await Task.Delay(1000, cancel);\n                }\n            }\n        ).DisposeItWith(Disposable);\n    }\n\n    public DroneFrameItemViewModel(IDroneFrame model, ILoggerFactory loggerFactory)\n        : base(new NavigationId(BaseId, model.Id), loggerFactory)\n    {\n        Model = model;\n        IsCurrent = new BindableReactiveProperty<bool>(false).DisposeItWith(Disposable);\n\n        ApplyCommand = new ReactiveCommand(\n            async (_, ct) =>\n            {\n                if (!IsCurrent.Value)\n                {\n                    await this.Rise(new CurrentDroneFrameChangeEvent(this), ct);\n                }\n            }\n        ).DisposeItWith(Disposable);\n    }\n\n    public IDroneFrame Model { get; }\n    public BindableReactiveProperty<bool> IsCurrent { get; }\n    public ReactiveCommand ApplyCommand { get; }\n\n    public bool Filter(string? searchText)\n    {\n        if (string.IsNullOrWhiteSpace(searchText))\n        {\n            return true;\n        }\n\n        return Model.Id.Contains(searchText, StringComparison.InvariantCultureIgnoreCase);\n    }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/FrameType/DroneFrameItem/NullDroneFrame.cs",
    "content": "using System.Collections.Generic;\nusing Asv.Mavlink;\n\nnamespace Asv.Drones;\n\npublic class NullDroneFrame : IDroneFrame\n{\n    public static IDroneFrame Instance { get; } = new NullDroneFrame { Id = \"frame-id-1\" };\n\n    public required string Id { get; init; }\n    public IReadOnlyDictionary<string, string>? Meta { get; init; } =\n        new Dictionary<string, string> { [\"meta1\"] = \"val1\", [\"meta2\"] = \"val2\" };\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/FrameType/DroneFrameItem/RoutedEvents/CurrentDroneFrameChangeEvent.cs",
    "content": "using Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Modeling;\n\nnamespace Asv.Drones;\n\n/// <summary>\n/// Represents an event triggered when the current drone frame item changes.\n/// </summary>\n/// <param name=\"source\">.</param>\npublic sealed class CurrentDroneFrameChangeEvent(DroneFrameItemViewModel source)\n    : AsyncRoutedEvent<IRoutable>(source, RoutingStrategy.Bubble) { }\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/FrameType/FrameTypeSetupPageExtension.cs",
    "content": "﻿using System.Linq;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class FrameTypeSetupPageExtension(ILoggerFactory loggerFactory) : IExtensionFor<ISetupPage>\n{\n    public void Extend(ISetupPage context, CompositeDisposable contextDispose)\n    {\n        context\n            .Target.Where(w => w is not null)\n            .Subscribe(wrapper =>\n            {\n                if (wrapper is null)\n                {\n                    return;\n                }\n\n                var frameClient = wrapper.Value.Device.GetMicroservice<IFrameClient>();\n\n                if (\n                    frameClient is null\n                    || context.Nodes.Any(node => node.Id == SetupFrameTypeViewModel.PageId)\n                )\n                {\n                    return;\n                }\n\n                context.Nodes.Add(\n                    new TreePage(\n                        SetupFrameTypeViewModel.PageId,\n                        RS.SetupFrameTypeViewModel_Name,\n                        MaterialIconKind.ThemeLightDark,\n                        SetupFrameTypeViewModel.PageId,\n                        NavigationId.Empty,\n                        loggerFactory\n                    ).DisposeItWith(contextDispose)\n                );\n            })\n            .DisposeItWith(contextDispose);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/FrameType/SetupFrameTypeView.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    xmlns:avalonia=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    xmlns:icons=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"900\"\n    d:DesignHeight=\"700\"\n    x:DataType=\"drones:SetupFrameTypeViewModel\"\n    x:Class=\"Asv.Drones.SetupFrameTypeView\"\n>\n    <Design.DataContext>\n        <drones:SetupFrameTypeViewModel />\n    </Design.DataContext>\n    <Panel>\n        <avalonia:AwaitingScreen\n            Header=\"{x:Static drones:RS.SetupFrameTypeView_Loader_Header}\"\n            Description=\"{x:Static drones:RS.SetupFrameTypeView_Loader_Description}\"\n            IsVisible=\"{CompiledBinding IsUpdating.Value}\"\n        />\n\n        <Grid\n            HorizontalAlignment=\"Stretch\"\n            VerticalAlignment=\"Stretch\"\n            Margin=\"2\"\n            RowDefinitions=\"Auto,8,*\"\n            ColumnDefinitions=\"1*,8,Auto\"\n            IsVisible=\"{Binding !IsUpdating.Value}\"\n        >\n            <Grid\n                Grid.Column=\"0\"\n                Grid.Row=\"0\"\n                HorizontalAlignment=\"Stretch\"\n                ColumnDefinitions=\"*,Auto\"\n                ColumnSpacing=\"6\"\n            >\n                <avalonia:SearchBoxView\n                    Grid.Column=\"0\"\n                    x:Name=\"FrameSearchBox\"\n                    Margin=\"0\"\n                    HorizontalAlignment=\"Stretch\"\n                    DataContext=\"{Binding Search}\"\n                />\n                <Button\n                    Grid.Column=\"1\"\n                    Command=\"{Binding RefreshCommand}\"\n                    ToolTip.Tip=\"{x:Static drones:RS.SetupFrameTypeView_CurrentFrame_Refresh}\"\n                    CornerRadius=\"{StaticResource ControlCornerRadius}\"\n                    Width=\"{Binding #FrameSearchBox.Bounds.Height}\"\n                    Height=\"{Binding #FrameSearchBox.Bounds.Height}\"\n                    Margin=\"0\"\n                >\n                    <icons:MaterialIcon Kind=\"Sync\" />\n                </Button>\n            </Grid>\n\n            <ListBox\n                Grid.Column=\"0\"\n                Grid.Row=\"2\"\n                CornerRadius=\"{StaticResource ControlCornerRadius}\"\n                ItemsSource=\"{Binding Frames}\"\n                SelectedItem=\"{Binding SelectedFrame.Value}\"\n                HorizontalAlignment=\"Stretch\"\n                VerticalAlignment=\"Stretch\"\n                Margin=\"0\"\n            />\n\n            <Border\n                Grid.Column=\"2\"\n                Grid.Row=\"2\"\n                VerticalAlignment=\"Top\"\n                Padding=\"8\"\n                Background=\"{DynamicResource SystemControlBackgroundListLowBrush}\"\n                BorderBrush=\"{DynamicResource ThemeAccentBrush}\"\n                BorderThickness=\"1\"\n                CornerRadius=\"{StaticResource ControlCornerRadius}\"\n            >\n                <Grid ColumnDefinitions=\"*\" RowDefinitions=\"*,12,*\" HorizontalAlignment=\"Left\">\n                    <StackPanel\n                        Grid.Column=\"0\"\n                        Grid.Row=\"0\"\n                        Orientation=\"Vertical\"\n                        Spacing=\"4\"\n                        VerticalAlignment=\"Top\"\n                    >\n                        <TextBlock\n                            Text=\"{x:Static drones:RS.SetupFrameTypeView_CurrentFrame}\"\n                            TextWrapping=\"Wrap\"\n                            VerticalAlignment=\"Center\"\n                            HorizontalAlignment=\"Left\"\n                            FontWeight=\"SemiBold\"\n                            FontSize=\"18\"\n                        />\n                        <TextBlock\n                            Text=\"{Binding CurrentFrameLabel.Value, FallbackValue='-'}\"\n                            TextWrapping=\"Wrap\"\n                            VerticalAlignment=\"Center\"\n                            FontSize=\"16\"\n                        />\n                    </StackPanel>\n\n                    <StackPanel Grid.Column=\"0\" Grid.Row=\"2\" Orientation=\"Horizontal\" Spacing=\"16\">\n                        <Border\n                            BorderBrush=\"{DynamicResource BorderBrush}\"\n                            BorderThickness=\"1\"\n                            Background=\"{DynamicResource SystemControlBackgroundListLowBrush}\"\n                            Height=\"180\"\n                            Width=\"180\"\n                            CornerRadius=\"8\"\n                            VerticalAlignment=\"Stretch\"\n                            HorizontalAlignment=\"Left\"\n                        >\n                            <ItemsControl\n                                ItemsSource=\"{CompiledBinding CurrentFrame.Value.Meta, \n                                            FallbackValue={CompiledBinding MetaFallBack}}\"\n                                HorizontalAlignment=\"Stretch\"\n                                Padding=\"6\"\n                            >\n                                <ItemsControl.ItemTemplate>\n                                    <DataTemplate>\n                                        <StackPanel Margin=\"2\" Orientation=\"Vertical\">\n                                            <TextBlock\n                                                Text=\"{Binding Key}\"\n                                                FontSize=\"12\"\n                                                FontWeight=\"SemiBold\"\n                                                TextTrimming=\"CharacterEllipsis\"\n                                            />\n                                            <TextBlock\n                                                Text=\"{Binding Value}\"\n                                                FontSize=\"13\"\n                                                TextWrapping=\"Wrap\"\n                                                Margin=\"0,0,0,3\"\n                                            />\n                                        </StackPanel>\n                                    </DataTemplate>\n                                </ItemsControl.ItemTemplate>\n                            </ItemsControl>\n                        </Border>\n\n                        <StackPanel Orientation=\"Vertical\" Spacing=\"4\">\n                            <TextBlock\n                                Text=\"{x:Static drones:RS.SetupFrameTypeView_CurrentFrame_Meta}\"\n                                FontSize=\"16\"\n                                FontWeight=\"SemiBold\"\n                                TextTrimming=\"CharacterEllipsis\"\n                            />\n                            <ItemsControl\n                                ItemsSource=\"{CompiledBinding CurrentFrame.Value.Meta, \n                                            FallbackValue={CompiledBinding MetaFallBack}}\"\n                                HorizontalAlignment=\"Stretch\"\n                            >\n                                <ItemsControl.ItemTemplate>\n                                    <DataTemplate>\n                                        <StackPanel Orientation=\"Vertical\">\n                                            <TextBlock\n                                                Text=\"{CompiledBinding Key}\"\n                                                FontSize=\"13\"\n                                                FontWeight=\"SemiBold\"\n                                                TextTrimming=\"CharacterEllipsis\"\n                                            />\n                                            <TextBlock\n                                                Text=\"{CompiledBinding Value}\"\n                                                FontSize=\"14\"\n                                                TextWrapping=\"Wrap\"\n                                                Margin=\"0,0,0,3\"\n                                            />\n                                        </StackPanel>\n                                    </DataTemplate>\n                                </ItemsControl.ItemTemplate>\n                            </ItemsControl>\n                        </StackPanel>\n                    </StackPanel>\n                </Grid>\n            </Border>\n        </Grid>\n    </Panel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/FrameType/SetupFrameTypeView.axaml.cs",
    "content": "using Asv.Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class SetupFrameTypeView : UserControl\n{\n    public SetupFrameTypeView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/FrameType/SetupFrameTypeViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Asv.Modeling;\nusing Avalonia.Threading;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\nusing ObservableCollections;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic sealed class SetupFrameTypeViewModel : SetupSubpage\n{\n    public const string PageId = \"frame-type\";\n    private readonly SynchronizedReactiveProperty<bool> _isRefreshing;\n    private readonly SynchronizedReactiveProperty<bool> _isChangingFrame;\n    private readonly ILoggerFactory _loggerFactory;\n\n    private readonly YesOrNoDialogPrefab _yesOrNoDialog;\n\n    private IFrameClient? _frameClient;\n\n    private ISynchronizedView<\n        KeyValuePair<string, IDroneFrame>,\n        DroneFrameItemViewModel\n    >? _framesView;\n    private readonly ISynchronizedViewFilter<\n        KeyValuePair<string, IDroneFrame>,\n        DroneFrameItemViewModel\n    > _framesViewFilter;\n\n    public SetupFrameTypeViewModel()\n        : this(NullLoggerFactory.Instance, NullDialogService.Instance)\n    {\n        var frames = new ObservableList<DroneFrameItemViewModel>(\n            Enumerable\n                .Range(1, 10)\n                .Select(n => new DroneFrameItemViewModel(\n                    new NullDroneFrame { Id = $\"frame-id-{n}\" },\n                    _loggerFactory\n                ))\n        );\n\n        CurrentFrame = new BindableReactiveProperty<IDroneFrame?>(frames[0].Model).DisposeItWith(\n            Disposable\n        );\n        CurrentFrameLabel = new BindableReactiveProperty<string>(\n            CurrentFrame?.Value?.Id ?? RS.SetupFrameTypeViewModel_CurrentFrame_Unknown\n        ).DisposeItWith(Disposable);\n\n        Frames = frames.ToNotifyCollectionChangedSlim();\n\n        RefreshCommand = new ReactiveCommand<Unit>(\n            async (_, cancel) =>\n            {\n                _isRefreshing.Value = true;\n                await Task.Delay(500, cancel);\n                _isRefreshing.Value = false;\n            }\n        ).DisposeItWith(Disposable);\n    }\n\n    public SetupFrameTypeViewModel(ILoggerFactory loggerFactory, IDialogService dialogService)\n        : base(PageId, loggerFactory)\n    {\n        _yesOrNoDialog = dialogService.GetDialogPrefab<YesOrNoDialogPrefab>();\n        _loggerFactory = loggerFactory;\n\n        _isRefreshing = new SynchronizedReactiveProperty<bool>(false).DisposeItWith(Disposable);\n        _isChangingFrame = new SynchronizedReactiveProperty<bool>(false).DisposeItWith(Disposable);\n        IsUpdating = _isRefreshing\n            .ObserveOnUIThreadDispatcher()\n            .CombineLatest(_isChangingFrame, (r, c) => r || c)\n            .ToReadOnlyBindableReactiveProperty()\n            .DisposeItWith(Disposable);\n\n        SelectedFrame = new BindableReactiveProperty<DroneFrameItemViewModel?>(null).DisposeItWith(\n            Disposable\n        );\n\n        RefreshCommand = new ReactiveCommand(Refresh, AwaitOperation.Drop).DisposeItWith(\n            Disposable\n        );\n\n        Search = new SearchBoxViewModel(\n            nameof(Search),\n            loggerFactory,\n            UpdateImpl,\n            TimeSpan.FromMilliseconds(500)\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        _framesViewFilter = new SynchronizedViewFilter<\n            KeyValuePair<string, IDroneFrame>,\n            DroneFrameItemViewModel\n        >((_, model) => model.Filter(Search.Text.ViewValue.Value));\n\n        Events.Subscribe(InternalCatchEvent).DisposeItWith(Disposable);\n    }\n\n    public IReadOnlyBindableReactiveProperty<bool> IsUpdating { get; }\n    public IReadOnlyBindableReactiveProperty<string>? CurrentFrameLabel { get; private set; }\n    public IReadOnlyBindableReactiveProperty<IDroneFrame?>? CurrentFrame { get; private set; }\n    public SearchBoxViewModel Search { get; }\n    public IReadOnlyDictionary<string, string> MetaFallBack =>\n        ImmutableDictionary<string, string>.Empty;\n\n    public INotifyCollectionChangedSynchronizedViewList<DroneFrameItemViewModel>? Frames\n    {\n        get;\n        private set;\n    }\n\n    public BindableReactiveProperty<DroneFrameItemViewModel?> SelectedFrame { get; }\n\n    public ReactiveCommand<Unit> RefreshCommand { get; }\n\n    public override ValueTask Init(ISetupPage context)\n    {\n        _frameClient =\n            context.Target.CurrentValue?.Device.GetMicroservice<IFrameClient>()\n            ?? throw new Exception($\"{nameof(IFrameClient)} should not be null\");\n\n        CurrentFrame = _frameClient\n            .CurrentFrame.ToReadOnlyBindableReactiveProperty()\n            .DisposeItWith(Disposable);\n        CurrentFrameLabel = _frameClient\n            .CurrentFrame.ObserveOnUIThreadDispatcher()\n            .Select(droneFrame =>\n                string.IsNullOrWhiteSpace(droneFrame?.Id)\n                    ? RS.SetupFrameTypeViewModel_CurrentFrame_Unknown\n                    : droneFrame.Id\n            )\n            .ToReadOnlyBindableReactiveProperty<string>()\n            .DisposeItWith(Disposable);\n\n        _framesView = _frameClient\n            .Frames.CreateView(droneFrame => new DroneFrameItemViewModel(\n                droneFrame.Value,\n                _loggerFactory\n            ))\n            .DisposeItWith(Disposable);\n        _framesView.SetRoutableParent(this).DisposeItWith(Disposable);\n        _framesView.DisposeMany().DisposeItWith(Disposable);\n\n        Frames = _framesView\n            .ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current)\n            .DisposeItWith(Disposable);\n\n        _frameClient\n            .CurrentFrame.ObserveOnUIThreadDispatcher()\n            .Subscribe(currentFrame =>\n            {\n                if (currentFrame is null)\n                {\n                    return;\n                }\n\n                foreach (var frame in Frames)\n                {\n                    frame.IsCurrent.Value = frame.Model.Id == currentFrame.Id;\n                }\n            })\n            .DisposeItWith(Disposable);\n\n        return ValueTask.CompletedTask;\n    }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        yield return Search;\n\n        foreach (var childRoutable in base.GetChildren())\n        {\n            yield return childRoutable;\n        }\n\n        if (Frames is not null)\n        {\n            foreach (var vm in Frames)\n            {\n                yield return vm;\n            }\n        }\n    }\n\n    internal async Task ChangeFrameType(string frameId, CancellationToken cancel = default)\n    {\n        if (_frameClient is null)\n        {\n            return;\n        }\n\n        if (_frameClient.Frames.TryGetValue(frameId, out var droneFrame))\n        {\n            try\n            {\n                var payload = new YesOrNoDialogPayload\n                {\n                    Title = RS.SetupFrameTypeViewModel_ApplyConfirmation_Title,\n                    Message = RS.SetupFrameTypeViewModel_ApplyConfirmation_Message,\n                };\n                var saveChanges = await _yesOrNoDialog.ShowDialogAsync(payload);\n\n                if (!saveChanges)\n                {\n                    return;\n                }\n\n                _isChangingFrame.Value = true;\n\n                await _frameClient.SetFrame(droneFrame, cancel);\n\n                Logger.LogInformation(\"Frame set to: {FrameId}\", droneFrame.Id);\n            }\n            catch (Exception ex)\n            {\n                Logger.LogError(ex, \"Failed to set frame\");\n            }\n            finally\n            {\n                _isChangingFrame.Value = false;\n            }\n        }\n    }\n\n    private async ValueTask InternalCatchEvent(IRoutable src, AsyncRoutedEvent<IRoutable> e)\n    {\n        switch (e)\n        {\n            case CurrentDroneFrameChangeEvent { Sender: DroneFrameItemViewModel param }:\n            {\n                await this.ExecuteCommand(\n                    ChangeFrameTypeCommand.Id,\n                    CommandArg.CreateString(param.Model.Id)\n                );\n\n                e.IsHandled = true;\n                break;\n            }\n        }\n    }\n\n    private Task UpdateImpl(string? query, IProgress<double> progress, CancellationToken cancel)\n    {\n        if (string.IsNullOrWhiteSpace(query))\n        {\n            _framesView?.ResetFilter();\n            return Task.CompletedTask;\n        }\n\n        _framesView?.AttachFilter(_framesViewFilter);\n\n        return Task.CompletedTask;\n    }\n\n    private async ValueTask Refresh(Unit unit, CancellationToken cancel = default)\n    {\n        if (_frameClient is null)\n        {\n            return;\n        }\n\n        try\n        {\n            _isRefreshing.Value = true;\n\n            await _frameClient.RefreshCurrentFrame(cancel);\n            await _frameClient.RefreshAvailableFrames(cancel);\n        }\n        catch (Exception ex)\n        {\n            Logger.LogError(ex, \"Failed to refresh frame\");\n        }\n        finally\n        {\n            _isRefreshing.Value = false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/Motors/MotorItem/MotorItemView.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    xmlns:avalonia=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"800\"\n    x:DataType=\"drones:MotorItemViewModel\"\n    x:Class=\"Asv.Drones.MotorItemView\"\n>\n    <UserControl.Styles>\n        <Style Selector=\"ToggleButton ContentPresenter.tbchecked\">\n            <Setter Property=\"IsVisible\" Value=\"False\" />\n        </Style>\n        <Style Selector=\"ToggleButton:checked ContentPresenter.tbchecked\">\n            <Setter Property=\"IsVisible\" Value=\"True\" />\n        </Style>\n        <Style Selector=\"ToggleButton ContentPresenter.tbunchecked\">\n            <Setter Property=\"IsVisible\" Value=\"True\" />\n        </Style>\n        <Style Selector=\"ToggleButton:checked ContentPresenter.tbunchecked\">\n            <Setter Property=\"IsVisible\" Value=\"False\" />\n        </Style>\n    </UserControl.Styles>\n    <Design.DataContext>\n        <drones:MotorItemViewModel />\n    </Design.DataContext>\n\n    <Grid ColumnDefinitions=\"Auto,*,Auto,Auto\" MaxHeight=\"100\" ClipToBounds=\"False\" Margin=\"8\">\n        <StackPanel\n            Grid.Column=\"0\"\n            Orientation=\"Horizontal\"\n            VerticalAlignment=\"Center\"\n            Spacing=\"4\"\n            MaxWidth=\"80\"\n        >\n            <Label\n                Content=\"{x:Static drones:RS.MotorItemView_Label_MotorId}\"\n                FontSize=\"14\"\n                FontWeight=\"Medium\"\n                VerticalAlignment=\"Center\"\n            />\n            <TextBlock\n                Text=\"{Binding Motor.Id}\"\n                FontSize=\"14\"\n                FontWeight=\"Medium\"\n                VerticalAlignment=\"Center\"\n            />\n        </StackPanel>\n\n        <Grid\n            Grid.Column=\"1\"\n            MaxWidth=\"800\"\n            VerticalAlignment=\"Center\"\n            Margin=\"12,0\"\n            RowDefinitions=\"Auto,Auto\"\n        >\n            <ProgressBar\n                Grid.Row=\"0\"\n                Minimum=\"1000\"\n                Maximum=\"2000\"\n                Value=\"{Binding Pwm.Value}\"\n                ShowProgressText=\"True\"\n                HorizontalAlignment=\"Stretch\"\n                Margin=\"0,0,0,6\"\n            />\n\n            <Grid Grid.Row=\"1\" ColumnDefinitions=\"*,*\" HorizontalAlignment=\"Stretch\">\n                <StackPanel\n                    Grid.Column=\"0\"\n                    Orientation=\"Horizontal\"\n                    HorizontalAlignment=\"Center\"\n                    VerticalAlignment=\"Center\"\n                    Spacing=\"4\"\n                >\n                    <Label\n                        Content=\"{x:Static drones:RS.MotorItemView_MonitoringLabel_Servo}\"\n                        FontSize=\"12\"\n                    />\n                    <Label Content=\"{Binding ServoChannel}\" FontSize=\"12\" />\n                </StackPanel>\n                <StackPanel\n                    Grid.Column=\"1\"\n                    Orientation=\"Horizontal\"\n                    HorizontalAlignment=\"Center\"\n                    VerticalAlignment=\"Center\"\n                    Spacing=\"4\"\n                >\n                    <Label\n                        Content=\"{x:Static drones:RS.MotorItemView_MonitoringLabel_Pwm}\"\n                        FontSize=\"12\"\n                    />\n                    <Label Content=\"{Binding Pwm.Value}\" FontSize=\"12\" />\n                </StackPanel>\n            </Grid>\n        </Grid>\n\n        <StackPanel\n            Grid.Column=\"2\"\n            Orientation=\"Horizontal\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Center\"\n            Spacing=\"1\"\n        >\n            <TextBox\n                VerticalContentAlignment=\"Center\"\n                HorizontalContentAlignment=\"Right\"\n                Text=\"{CompiledBinding Throttle.ViewValue.Value}\"\n                Width=\"32\"\n                FontSize=\"14\"\n            />\n            <Label\n                VerticalAlignment=\"Center\"\n                Content=\"{CompiledBinding ThrottleSymbol.Value}\"\n                FontSize=\"14\"\n            />\n        </StackPanel>\n\n        <ToggleButton\n            Grid.Column=\"3\"\n            Margin=\"4,0\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Center\"\n            IsChecked=\"{Binding IsTestIdle.Value}\"\n            Command=\"{Binding RunTestCommand}\"\n            CommandParameter=\"{Binding}\"\n            IsEnabled=\"{Binding IsEnabled.Value}\"\n            ToolTip.Tip=\"{x:Static drones:RS.MotorItemView_ToolTip_StartStop}\"\n        >\n            <Panel>\n                <ContentPresenter Classes=\"tbchecked\">\n                    <ContentPresenter.Content>\n                        <avalonia:MaterialIcon Kind=\"Play\" />\n                    </ContentPresenter.Content>\n                </ContentPresenter>\n                <ContentPresenter Classes=\"tbunchecked\">\n                    <ContentPresenter.Content>\n                        <avalonia:MaterialIcon Kind=\"Stop\" />\n                    </ContentPresenter.Content>\n                </ContentPresenter>\n            </Panel>\n        </ToggleButton>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/Motors/MotorItem/MotorItemView.axaml.cs",
    "content": "using Asv.Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class MotorItemView : UserControl\n{\n    public MotorItemView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/Motors/MotorItem/MotorItemViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic sealed class MotorItemViewModel : RoutableViewModel\n{\n    public const string BaseId = \"motor-item\";\n\n    private readonly SynchronizedReactiveProperty<double> _throttle;\n    private readonly SynchronizedReactiveProperty<bool> _isEnabled;\n\n    public MotorItemViewModel()\n        : base(new NavigationId(BaseId, \"1\"), NullLoggerFactory.Instance)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n\n        IsEnabled = new BindableReactiveProperty<bool>(true);\n        Pwm = new BindableReactiveProperty<ushort>(1000);\n        ServoChannel = 1;\n        IsTestIdle = new BindableReactiveProperty<bool>(true);\n    }\n\n    public MotorItemViewModel(\n        ITestMotor motor,\n        ReactiveProperty<double> duration,\n        IUnitService unit,\n        ILoggerFactory loggerFactory\n    )\n        : base(new NavigationId(BaseId, motor.Id.ToString()), loggerFactory)\n    {\n        Motor = motor;\n        Timeout = duration;\n\n        _isEnabled = new SynchronizedReactiveProperty<bool>().DisposeItWith(Disposable);\n        IsEnabled = _isEnabled.ToBindableReactiveProperty().DisposeItWith(Disposable);\n\n        _throttle = new SynchronizedReactiveProperty<double>(0).DisposeItWith(Disposable);\n        Throttle = new HistoricalUnitProperty(\n            nameof(Throttle),\n            _throttle,\n            unit.Units[ThrottleUnit.Id],\n            loggerFactory\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        Throttle\n            .ViewValue.Subscribe(_ => _isEnabled.Value = !Throttle.ViewValue.HasErrors)\n            .DisposeItWith(Disposable);\n\n        Pwm = motor.Pwm.ToBindableReactiveProperty().DisposeItWith(Disposable);\n        ServoChannel = motor.ServoChannel;\n        IsTestIdle = motor\n            .IsTestRun.ObserveOnUIThreadDispatcher()\n            .Select(x => !x)\n            .ToReadOnlyBindableReactiveProperty()\n            .DisposeItWith(Disposable);\n\n        ThrottleSymbol = Throttle\n            .Unit.CurrentUnitItem.Select(item => item.Symbol)\n            .ToReadOnlyBindableReactiveProperty<string>()\n            .DisposeItWith(Disposable);\n\n        RunTestCommand = new ReactiveCommand(\n            async (_, cts) => await RunMotorTest(motor, cts)\n        ).DisposeItWith(Disposable);\n    }\n\n    public ITestMotor Motor { get; }\n    public BindableReactiveProperty<ushort> Pwm { get; }\n    public int ServoChannel { get; }\n    public IReadOnlyBindableReactiveProperty<bool> IsTestIdle { get; }\n    public ReactiveCommand RunTestCommand { get; }\n    public HistoricalUnitProperty Throttle { get; }\n    public IReadOnlyBindableReactiveProperty<string> ThrottleSymbol { get; }\n\n    public ReactiveProperty<double> Timeout { get; }\n    public BindableReactiveProperty<bool> IsEnabled { get; }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        yield return Throttle;\n    }\n\n    private async Task RunMotorTest(ITestMotor motor, CancellationToken cts)\n    {\n        if (IsTestIdle.Value)\n        {\n            await motor.StartTest((int)Throttle.ModelValue.CurrentValue, (int)Timeout.Value, cts);\n            return;\n        }\n\n        await motor.StopTest(cts);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/Motors/MotorsSetupPageExtension.cs",
    "content": "﻿using System.Linq;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class MotorsSetupPageExtension(ILoggerFactory loggerFactory) : IExtensionFor<ISetupPage>\n{\n    public void Extend(ISetupPage context, CompositeDisposable contextDispose)\n    {\n        context\n            .Target.Where(w => w is not null)\n            .Subscribe(wrapper =>\n            {\n                if (wrapper is null)\n                {\n                    return;\n                }\n\n                var client = wrapper.Value.Device.GetMicroservice<IMotorTestClient>();\n\n                if (\n                    client is null\n                    || context.Nodes.Any(node => node.Id == SetupMotorsViewModel.PageId)\n                )\n                {\n                    return;\n                }\n\n                context.Nodes.Add(\n                    new TreePage(\n                        SetupMotorsViewModel.PageId,\n                        RS.SetupMotorsViewModel_Name,\n                        SetupMotorsViewModel.Icon,\n                        SetupMotorsViewModel.PageId,\n                        NavigationId.Empty,\n                        loggerFactory\n                    ).DisposeItWith(contextDispose)\n                );\n            })\n            .DisposeItWith(contextDispose);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/Motors/SetupMotorsView.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"900\"\n    d:DesignHeight=\"700\"\n    x:DataType=\"drones:SetupMotorsViewModel\"\n    x:Class=\"Asv.Drones.SetupMotorsView\"\n>\n    <Design.DataContext>\n        <drones:SetupMotorsViewModel />\n    </Design.DataContext>\n    <Panel>\n        <Grid\n            HorizontalAlignment=\"Stretch\"\n            VerticalAlignment=\"Stretch\"\n            Margin=\"2\"\n            RowDefinitions=\"Auto,*\"\n        >\n            <StackPanel\n                Grid.Column=\"0\"\n                Grid.Row=\"0\"\n                Spacing=\"4\"\n                Orientation=\"Horizontal\"\n                Width=\"200\"\n                HorizontalAlignment=\"Left\"\n            >\n                <TextBlock\n                    FontWeight=\"Bold\"\n                    VerticalAlignment=\"Center\"\n                    Text=\"{x:Static drones:RS.SetupMotorsView_TestDuration}\"\n                />\n                <TextBox\n                    VerticalAlignment=\"Center\"\n                    HorizontalContentAlignment=\"Right\"\n                    Text=\"{CompiledBinding Duration.ViewValue.Value}\"\n                />\n                <Label\n                    FontWeight=\"Bold\"\n                    VerticalAlignment=\"Center\"\n                    Content=\"{CompiledBinding DurationSymbol.Value}\"\n                />\n            </StackPanel>\n            <ListBox\n                Grid.Column=\"0\"\n                Grid.Row=\"1\"\n                IsEnabled=\"{CompiledBinding IsEnabled.Value }\"\n                CornerRadius=\"{StaticResource ControlCornerRadius}\"\n                ItemsSource=\"{Binding Motors}\"\n                HorizontalAlignment=\"Stretch\"\n                VerticalAlignment=\"Stretch\"\n                Margin=\"0\"\n            />\n        </Grid>\n    </Panel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/Motors/SetupMotorsView.axaml.cs",
    "content": "using Asv.Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class SetupMotorsView : UserControl\n{\n    public SetupMotorsView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/Motors/SetupMotorsViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Asv.Modeling;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\nusing ObservableCollections;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic sealed class SetupMotorsViewModel : SetupSubpage\n{\n    public const string PageId = \"motor-test\";\n    public const MaterialIconKind Icon = MaterialIconKind.Motor;\n    private const int DefaultTestDurationInSeconds = 3;\n    private readonly ILoggerFactory _loggerFactory;\n    private readonly IUnitService _unit;\n    private readonly ReactiveProperty<double> _duration;\n    private readonly SynchronizedReactiveProperty<bool> _isEnabled;\n\n    private ISynchronizedView<ITestMotor, MotorItemViewModel>? _motorsView;\n    private IMotorTestClient? _motorTestClient;\n\n    public SetupMotorsViewModel()\n        : this(NullLoggerFactory.Instance, NullUnitService.Instance) { }\n\n    public SetupMotorsViewModel(ILoggerFactory loggerFactory, IUnitService unit)\n        : base(PageId, loggerFactory)\n    {\n        _loggerFactory = loggerFactory;\n        _unit = unit;\n\n        _isEnabled = new SynchronizedReactiveProperty<bool>().DisposeItWith(Disposable);\n        IsEnabled = _isEnabled.ToBindableReactiveProperty().DisposeItWith(Disposable);\n\n        _duration = new ReactiveProperty<double>(DefaultTestDurationInSeconds).DisposeItWith(\n            Disposable\n        );\n        Duration = new HistoricalUnitProperty(\n            nameof(Duration),\n            _duration,\n            unit.Units[TimeSpanUnit.Id],\n            loggerFactory\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        Duration\n            .ViewValue.Subscribe(_ => _isEnabled.Value = !Duration.ViewValue.HasErrors)\n            .DisposeItWith(Disposable);\n\n        DurationSymbol = Duration\n            .Unit.CurrentUnitItem.Select(item => item.Symbol)\n            .ToReadOnlyBindableReactiveProperty<string>()\n            .DisposeItWith(Disposable);\n    }\n\n    public HistoricalUnitProperty Duration { get; }\n    public IReadOnlyBindableReactiveProperty<string> DurationSymbol { get; }\n\n    public BindableReactiveProperty<bool> IsEnabled { get; }\n\n    public INotifyCollectionChangedSynchronizedViewList<MotorItemViewModel>? Motors\n    {\n        get;\n        private set;\n    }\n\n    public override ValueTask Init(ISetupPage context)\n    {\n        _motorTestClient =\n            context.Target.CurrentValue?.Device.GetMicroservice<IMotorTestClient>()\n            ?? throw new Exception($\"{nameof(IMotorTestClient)} should not be null\");\n\n        _motorsView = _motorTestClient\n            .TestMotors.CreateView(motor => new MotorItemViewModel(\n                motor,\n                _duration,\n                _unit,\n                _loggerFactory\n            ))\n            .DisposeItWith(Disposable);\n\n        _motorsView.SetRoutableParent(this).DisposeItWith(Disposable);\n        _motorsView.DisposeMany().DisposeItWith(Disposable);\n\n        Motors = _motorsView\n            .ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current)\n            .DisposeItWith(Disposable);\n\n        return ValueTask.CompletedTask;\n    }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        foreach (var childRoutable in base.GetChildren())\n        {\n            yield return childRoutable;\n        }\n\n        if (Motors is null)\n        {\n            yield break;\n        }\n\n        foreach (var vm in Motors)\n        {\n            yield return vm;\n        }\n\n        yield return Duration;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Device/Setup/Subpage/SetupSubpage.cs",
    "content": "using Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic abstract class SetupSubpage : TreeSubpage<ISetupPage>, ISetupSubpage\n{\n    protected SetupSubpage(NavigationId id, ILoggerFactory loggerFactory)\n        : base(id, loggerFactory) { }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/Anchors/FlightUavAnchorsExtension.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Asv.Modeling;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class FlightUavAnchorsExtension(IDeviceManager conn, ILoggerFactory loggerFactory)\n    : IExtensionFor<IFlightMode>\n{\n    public void Extend(IFlightMode context, CompositeDisposable contextDispose)\n    {\n        conn.Explorer.InitializedDevices.PopulateTo(\n                context.MapViewModel.Anchors,\n                TryCreateAnchor,\n                RemoveAnchor\n            )\n            .DisposeItWith(contextDispose);\n    }\n\n    private UavAnchor? TryCreateAnchor(IClientDevice device)\n    {\n        var pos = device.GetMicroservice<IPositionClientEx>();\n        return pos != null ? new UavAnchor(device.Id, conn, device, pos, loggerFactory) : null;\n    }\n\n    private static bool RemoveAnchor(IClientDevice dev, UavAnchor anchor)\n    {\n        return anchor.DeviceId == dev.Id;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/Anchors/MissionAnchor.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Avalonia.GeoMap;\nusing Asv.Common;\nusing Avalonia.Media;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic class MissionAnchor : MapAnchor<MissionAnchor>\n{\n    public MissionAnchor(int index, GeoPoint current, GeoPoint next, ILoggerFactory loggerFactory)\n        : base($\"wayPoint{index}\", loggerFactory) // TODO: Use a more descriptive ID with drone ID\n    {\n        Location = current;\n        Title = index.ToString();\n        IsReadOnly = true;\n        IsVisible = true;\n        Icon = MaterialIconKind.MapMarker;\n        CenterY = new VerticalOffset(VerticalOffsetEnum.Bottom, 0);\n        Foreground = Brushes.Red;\n        Polygon.Add(current);\n        Polygon.Add(next);\n    }\n\n    public MissionAnchor(int index, GeoPoint current, ILoggerFactory loggerFactory)\n        : base($\"wayPoint{index}\", loggerFactory)\n    {\n        Location = current;\n        Title = index.ToString();\n        IsReadOnly = true;\n        IsVisible = true;\n        Icon = MaterialIconKind.MapMarker;\n        CenterY = new VerticalOffset(VerticalOffsetEnum.Bottom, 0);\n        Foreground = Brushes.Red;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/Anchors/UavAnchor.cs",
    "content": "﻿using System;\nusing Asv.Avalonia;\nusing Asv.Avalonia.GeoMap;\nusing Asv.Avalonia.IO;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class UavAnchor : MapAnchor<UavAnchor>\n{\n    public const string UavAnchorIdBase = \"uav\";\n    private const uint CurrentUavPositionChangeThrottleMs = 200;\n    public DeviceId DeviceId { get; }\n\n    public UavAnchor()\n        : base(DesignTime.Id, DesignTime.LoggerFactory)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public UavAnchor(\n        DeviceId deviceId,\n        IDeviceManager mng,\n        IClientDevice dev,\n        IPositionClientEx pos,\n        ILoggerFactory loggerFactory\n    )\n        : base(UavAnchorIdBase, loggerFactory)\n    {\n        DeviceId = deviceId;\n        InitArgs(deviceId.AsString());\n        IsReadOnly = true;\n        IsVisible = true;\n        Icon = mng.GetIcon(deviceId) ?? MaterialIconKind.Memory;\n        IconColor = mng.GetDeviceColor(deviceId);\n        CenterX = DeviceIconMixin.GetIconCenterX(deviceId);\n        CenterY = DeviceIconMixin.GetIconCenterY(deviceId);\n        UseMapRotation = true;\n        dev.Name.ObserveOnUIThreadDispatcher()\n            .Subscribe(x => Title = x ?? string.Empty)\n            .DisposeItWith(Disposable);\n        pos.Current.ObserveOnUIThreadDispatcher()\n            .Subscribe(x => Location = x)\n            .DisposeItWith(Disposable);\n        pos.Yaw.ObserveOnUIThreadDispatcher().Subscribe(x => Azimuth = x).DisposeItWith(Disposable);\n        var currentUavLocation = pos.Current.CurrentValue;\n        var currentHomeLocation = pos.Home.CurrentValue ?? GeoPoint.Zero;\n        pos.Home.ObserveOnUIThreadDispatcher()\n            .Subscribe(x =>\n            {\n                Polygon.Remove(currentHomeLocation);\n                if (x is null)\n                {\n                    return;\n                }\n\n                Polygon.Add(x.Value);\n                currentHomeLocation = x.Value;\n            })\n            .DisposeItWith(Disposable);\n        pos.Current.ObserveOnUIThreadDispatcher()\n            .ThrottleLast(TimeSpan.FromMilliseconds(CurrentUavPositionChangeThrottleMs))\n            .Subscribe(x =>\n            {\n                Polygon.Remove(currentUavLocation);\n                Polygon.Add(x);\n                currentUavLocation = x;\n            })\n            .DisposeItWith(Disposable);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/FlightPageView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:avalonia=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    xmlns:avalonia1=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    xmlns:geoMap=\"clr-namespace:Asv.Avalonia.GeoMap;assembly=Asv.Avalonia.GeoMap\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"1024\"\n    d:DesignHeight=\"500\"\n    x:Class=\"Asv.Drones.FlightPageView\"\n    x:DataType=\"drones:FlightPageViewModel\"\n>\n    <Design.DataContext>\n        <drones:FlightPageViewModel />\n    </Design.DataContext>\n    <Panel>\n        <geoMap:MapView DataContext=\"{CompiledBinding MapViewModel}\" />\n        <ItemsControl ItemsSource=\"{Binding WidgetsView}\">\n            <ItemsControl.ItemsPanel>\n                <ItemsPanelTemplate>\n                    <avalonia1:WorkspacePanel\n                        AttachedToVisualTree=\"WorkspacePanel_OnAttachedToVisualTree\"\n                        WorkspaceChanged=\"OnWorkspaceChanged\"\n                    />\n                </ItemsPanelTemplate>\n            </ItemsControl.ItemsPanel>\n            <ItemsControl.ItemTemplate>\n                <DataTemplate>\n                    <Border\n                        Background=\"{DynamicResource SystemAltMediumHighColor}\"\n                        Opacity=\"0.9\"\n                        BorderThickness=\"{DynamicResource ThemeBorderThickness}\"\n                        CornerRadius=\"{DynamicResource ControlCornerRadius}\"\n                        Padding=\"8\"\n                        BorderBrush=\"{DynamicResource SystemControlTransientBorderBrush}\"\n                        HorizontalAlignment=\"Stretch\"\n                    >\n                        <DockPanel>\n                            <Grid ColumnDefinitions=\"Auto,5,*, Auto\" DockPanel.Dock=\"Top\">\n                                <avalonia:MaterialIcon\n                                    Kind=\"{Binding Icon}\"\n                                    avalonia1:AsvPallete.Color=\"{CompiledBinding IconColor}\"\n                                />\n                                <TextBlock\n                                    VerticalAlignment=\"Center\"\n                                    Grid.Column=\"2\"\n                                    Text=\"{Binding Header}\"\n                                />\n                                <ToggleButton\n                                    IsChecked=\"True\"\n                                    x:Name=\"PART_Expand\"\n                                    Grid.Column=\"3\"\n                                    Padding=\"0\"\n                                    Theme=\"{StaticResource TransparentButton}\"\n                                >\n                                    <Panel Width=\"20\" Height=\"20\">\n                                        <avalonia:MaterialIcon\n                                            IsVisible=\"{Binding ElementName=PART_Expand, Path=IsChecked}\"\n                                            Kind=\"MenuUp\"\n                                        />\n                                        <avalonia:MaterialIcon\n                                            IsVisible=\"{Binding ElementName=PART_Expand, Path=!IsChecked}\"\n                                            Kind=\"MenuDown\"\n                                        />\n                                    </Panel>\n                                </ToggleButton>\n                            </Grid>\n                            <ContentControl\n                                Margin=\"0,4,0,0\"\n                                IsVisible=\"{Binding ElementName=PART_Expand, Path=IsChecked}\"\n                                Content=\"{Binding}\"\n                            />\n                        </DockPanel>\n                    </Border>\n                </DataTemplate>\n            </ItemsControl.ItemTemplate>\n        </ItemsControl>\n        <geoMap:MapCompass\n            Rotation=\"{Binding MapViewModel.Rotation.Value}\"\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Bottom\"\n            Margin=\"8\"\n            TouchpadRotationSensitivity=\"1\"\n        />\n    </Panel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/FlightPageView.axaml.cs",
    "content": "﻿using Asv.Avalonia;\nusing Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic sealed class FlightPageViewConfig\n{\n    public double LeftColumnActualWidth { get; set; } = -1;\n    public double CenterColumnActualWidth { get; set; } = -1;\n    public double RightColumnActualWidth { get; set; } = -1;\n    public double CenterRowActualHeight { get; set; } = -1;\n    public double BottomRowActualHeight { get; set; } = -1;\n}\n\npublic partial class FlightPageView : UserControl\n{\n    private readonly ILayoutService _layoutService;\n    private FlightPageViewConfig? _config;\n    private WorkspacePanel? _workspace;\n\n    public FlightPageView()\n        : this(NullLayoutService.Instance)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public FlightPageView(ILayoutService layoutService)\n    {\n        _layoutService = layoutService;\n        InitializeComponent();\n    }\n\n    private void WorkspacePanel_OnAttachedToVisualTree(\n        object? sender,\n        VisualTreeAttachmentEventArgs e\n    )\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        _workspace = sender as WorkspacePanel;\n        LoadLayout();\n    }\n\n    private void OnWorkspaceChanged(object? sender, WorkspaceEventArgs workspaceEventArgs)\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        SaveLayout(workspaceEventArgs);\n    }\n\n    private void LoadLayout()\n    {\n        _config = _layoutService.Get<FlightPageViewConfig>(this);\n\n        if (_workspace is null)\n        {\n            return;\n        }\n\n        if (_config.LeftColumnActualWidth >= 0)\n        {\n            _workspace.LeftWidth = new GridLength(_config.LeftColumnActualWidth);\n        }\n\n        if (_config.CenterColumnActualWidth >= 0)\n        {\n            _workspace.CentralWidth = new GridLength(_config.CenterColumnActualWidth);\n        }\n\n        if (_config.RightColumnActualWidth >= 0)\n        {\n            _workspace.RightWidth = new GridLength(_config.RightColumnActualWidth);\n        }\n\n        if (_config.CenterRowActualHeight >= 0)\n        {\n            _workspace.CentralHeight = new GridLength(_config.CenterRowActualHeight);\n        }\n\n        if (_config.BottomRowActualHeight >= 0)\n        {\n            _workspace.BottomHeight = new GridLength(_config.BottomRowActualHeight);\n        }\n    }\n\n    private void SaveLayout(WorkspaceEventArgs workspaceEventArgs)\n    {\n        if (_config is null)\n        {\n            return;\n        }\n\n        if (DataContext is null)\n        {\n            return;\n        }\n\n        _config.LeftColumnActualWidth = workspaceEventArgs.LeftColumnActualWidth;\n        _config.CenterColumnActualWidth = workspaceEventArgs.CenterColumnActualWidth;\n        _config.RightColumnActualWidth = workspaceEventArgs.RightColumnActualWidth;\n        _config.CenterRowActualHeight = workspaceEventArgs.CenterRowActualHeight;\n        _config.BottomRowActualHeight = workspaceEventArgs.BottomRowActualHeight;\n        _layoutService.SetInMemory(this, _config);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/FlightPageViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Avalonia.GeoMap;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.Modeling;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing ObservableCollections;\n\nnamespace Asv.Drones;\n\npublic sealed class FlightPageViewModelConfig\n{\n    public GeoPoint MapCenter { get; set; } = GeoPoint.Zero;\n    public int Zoom { get; set; } = IZoomService.MinZoomLevel;\n    public double Rotation { get; set; } = 0.0;\n}\n\npublic class FlightPageViewModel : PageViewModel<IFlightMode>, IFlightMode\n{\n    public const string PageId = \"flight\";\n    public const MaterialIconKind PageIcon = MaterialIconKind.MapSearch;\n\n    private FlightPageViewModelConfig? _config;\n\n    public FlightPageViewModel()\n        : this(\n            NullMapService.Instance,\n            DesignTime.CommandService,\n            DesignTime.LoggerFactory,\n            DesignTime.DialogService,\n            DesignTime.ExtensionService\n        )\n    {\n        DesignTime.ThrowIfNotDesignMode();\n        var drone = new MapAnchor<IMapAnchor>(DesignTime.Id, DesignTime.LoggerFactory)\n        {\n            Icon = MaterialIconKind.Navigation,\n            Location = new GeoPoint(53, 53, 100),\n        };\n        MapViewModel.Anchors.Add(drone);\n        var azimuth = 0;\n        TimeProvider.System.CreateTimer(\n            x =>\n            {\n                drone.Azimuth = (azimuth++ * 10) % 360;\n            },\n            null,\n            TimeSpan.FromSeconds(1),\n            TimeSpan.FromSeconds(1)\n        );\n        Widgets.Add(new UavWidgetViewModel { Header = \"Device11\" });\n    }\n\n    public FlightPageViewModel(\n        IMapService mapService,\n        ICommandService cmd,\n        ILoggerFactory loggerFactory,\n        IDialogService dialogService,\n        IExtensionService ext\n    )\n        : base(PageId, cmd, loggerFactory, dialogService, ext)\n    {\n        Title = RS.FlightPageViewModel_Title;\n        Icon = PageIcon;\n        MapViewModel = new MapViewModel(nameof(MapViewModel), loggerFactory, mapService)\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        Widgets = [];\n        Widgets.SetRoutableParent(this).DisposeItWith(Disposable);\n        Widgets.DisposeRemovedItems().DisposeItWith(Disposable);\n        WidgetsView = Widgets.ToNotifyCollectionChangedSlim().DisposeItWith(Disposable);\n\n        Events.Subscribe(InternalCatchEvent).DisposeItWith(Disposable);\n    }\n\n    public NotifyCollectionChangedSynchronizedViewList<IUavFlightWidget> WidgetsView { get; }\n    public ObservableList<IUavFlightWidget> Widgets { get; }\n    public IMap MapViewModel { get; }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        yield return MapViewModel;\n\n        foreach (var widget in WidgetsView)\n        {\n            yield return widget;\n        }\n    }\n\n    private ValueTask InternalCatchEvent(IRoutable src, AsyncRoutedEvent<IRoutable> e)\n    {\n        switch (e)\n        {\n            case SaveLayoutEvent saveLayoutEvent:\n                if (_config is null)\n                {\n                    break;\n                }\n\n                this.HandleSaveLayout(\n                    saveLayoutEvent,\n                    _config,\n                    cfg =>\n                    {\n                        cfg.MapCenter = MapViewModel.CenterMap.Value;\n                        cfg.Zoom = MapViewModel.Zoom.Value;\n                        cfg.Rotation = MapViewModel.Rotation.Value;\n                    },\n                    FlushingStrategy.FlushBothViewModelAndView\n                );\n                break;\n            case LoadLayoutEvent loadLayoutEvent:\n                _config = this.HandleLoadLayout<FlightPageViewModelConfig>(\n                    loadLayoutEvent,\n                    cfg =>\n                    {\n                        MapViewModel.CenterMap.Value = cfg.MapCenter;\n                        MapViewModel.Zoom.Value = cfg.Zoom switch\n                        {\n                            < IZoomService.MinZoomLevel => IZoomService.MinZoomLevel,\n                            > IZoomService.MaxZoomLevel => IZoomService.MaxZoomLevel,\n                            _ => cfg.Zoom,\n                        };\n                        MapViewModel.Rotation.Value = cfg.Rotation;\n                    }\n                );\n                break;\n        }\n\n        return ValueTask.CompletedTask;\n    }\n\n    protected override void AfterLoadExtensions()\n    {\n        // nothing to do\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/HomePageFlightExtension.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Common;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class HomePageFlightExtension(ILoggerFactory loggerFactory) : IExtensionFor<IHomePage>\n{\n    public void Extend(IHomePage context, CompositeDisposable contextDispose)\n    {\n        context.Tools.Add(\n            OpenFlightModeCommand\n                .StaticInfo.CreateAction(loggerFactory)\n                .DisposeItWith(contextDispose)\n        );\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/Widgets/FlightWidgetsExtension.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Asv.Modeling;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class FlightWidgetsExtension(\n    IDeviceManager conn,\n    INavigationService navigationService,\n    IUnitService unitService,\n    ILoggerFactory loggerFactory\n) : IExtensionFor<IFlightMode>\n{\n    public void Extend(IFlightMode context, CompositeDisposable contextDispose)\n    {\n        conn.Explorer.InitializedDevices.PopulateTo(context.Widgets, TryCreateWidget, RemoveWidget)\n            .DisposeItWith(contextDispose);\n    }\n\n    private UavWidgetViewModel? TryCreateWidget(IClientDevice device)\n    {\n        var isInit = device.State.CurrentValue == ClientDeviceState.Complete;\n\n        if (!isInit)\n        {\n            return null;\n        }\n\n        if (device.GetMicroservice<IPositionClientEx>() is null)\n        {\n            return null;\n        }\n\n        return new UavWidgetViewModel(device, navigationService, unitService, conn, loggerFactory);\n    }\n\n    private bool RemoveWidget(IClientDevice model, UavWidgetViewModel vm)\n    {\n        return model.Id == vm.Device.Id;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/Widgets/UavWidget/Dialogs/SetAltitudeDialogView.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    x:Class=\"Asv.Drones.SetAltitudeDialogView\"\n    x:DataType=\"drones:SetAltitudeDialogViewModel\"\n>\n    <Design.DataContext>\n        <drones:SetAltitudeDialogViewModel />\n    </Design.DataContext>\n    <Panel>\n        <StackPanel>\n            <Grid ColumnDefinitions=\"*, Auto\">\n                <TextBox\n                    Grid.Column=\"0\"\n                    Focusable=\"True\"\n                    Name=\"AltitudeTextBox\"\n                    Text=\"{Binding Altitude.Value}\"\n                />\n                <TextBlock\n                    VerticalAlignment=\"Center\"\n                    Margin=\"8 0\"\n                    Grid.Column=\"1\"\n                    Text=\"{Binding AltitudeUnitSymbol.Value}\"\n                />\n            </Grid>\n        </StackPanel>\n    </Panel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/Widgets/UavWidget/Dialogs/SetAltitudeDialogView.axaml.cs",
    "content": "using Asv.Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class SetAltitudeDialogView : UserControl\n{\n    public SetAltitudeDialogView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/Widgets/UavWidget/Dialogs/SetAltitudeDialogViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class SetAltitudeDialogViewModel : DialogViewModelBase\n{\n    public const string DialogId = $\"{BaseId}.altitude\";\n\n    public SetAltitudeDialogViewModel()\n        : this(\n            NullUnitService.Instance.Units.Values.First(u => u.UnitId == AltitudeUnit.Id),\n            DesignTime.LoggerFactory\n        )\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public SetAltitudeDialogViewModel(IUnit unit, ILoggerFactory loggerFactory)\n        : base(DialogId, loggerFactory)\n    {\n        var altitudeUnit =\n            unit as AltitudeUnit\n            ?? throw new InvalidCastException($\"Unit must be an {nameof(AltitudeUnit)}\");\n\n        Altitude = new BindableReactiveProperty<string>(string.Empty).DisposeItWith(Disposable);\n        AltitudeUnitSymbol = altitudeUnit\n            .CurrentUnitItem.ObserveOnUIThreadDispatcher()\n            .Select(item => item.Symbol)\n            .ToReadOnlyBindableReactiveProperty<string>()\n            .DisposeItWith(Disposable);\n\n        Altitude\n            .EnableValidationRoutable(\n                s =>\n                {\n                    var valid = altitudeUnit.CurrentUnitItem.Value.ValidateValue(s);\n                    return valid;\n                },\n                this,\n                true\n            )\n            .DisposeItWith(Disposable);\n    }\n\n    public override void ApplyDialog(ContentDialog dialog)\n    {\n        _sub2.Disposable = IsValid.Subscribe(enabled => dialog.IsPrimaryButtonEnabled = enabled);\n    }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        return [];\n    }\n\n    public BindableReactiveProperty<string> Altitude { get; }\n    public IReadOnlyBindableReactiveProperty<string> AltitudeUnitSymbol { get; }\n\n    #region Dispose\n\n    private readonly SerialDisposable _sub2 = new();\n\n    protected override void Dispose(bool disposing)\n    {\n        if (disposing)\n        {\n            _sub2.Dispose();\n        }\n\n        base.Dispose(disposing);\n    }\n\n    #endregion\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/Widgets/UavWidget/MissionProgress/MissionProgressView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:avalonia=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    xmlns:avalonia1=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    x:Class=\"Asv.Drones.MissionProgressView\"\n    x:DataType=\"drones:MissionProgressViewModel\"\n>\n    <Design.DataContext>\n        <drones:MissionProgressViewModel />\n    </Design.DataContext>\n    <DockPanel HorizontalAlignment=\"Left\" LastChildFill=\"True\">\n        <StackPanel\n            DockPanel.Dock=\"Top\"\n            Spacing=\"10\"\n            Orientation=\"Vertical\"\n            HorizontalAlignment=\"Stretch\"\n            IsVisible=\"{Binding  !IsDownloaded.Value}\"\n        >\n            <Button Command=\"{Binding UpdateMission}\" Theme=\"{StaticResource TransparentButton}\">\n                <StackPanel Orientation=\"Horizontal\" Spacing=\"5\">\n                    <avalonia1:MaterialIcon Kind=\"Download\" />\n                    <TextBlock Text=\"{x:Static drones:RS.MissionProgressView_DownloadButton_Text}\" />\n                </StackPanel>\n            </Button>\n        </StackPanel>\n        <StackPanel\n            DockPanel.Dock=\"Top\"\n            HorizontalAlignment=\"Stretch\"\n            IsVisible=\"{Binding  IsDownloaded.Value}\"\n        >\n            <Button Command=\"{Binding UpdateMission}\" Theme=\"{StaticResource TransparentButton}\">\n                <StackPanel Orientation=\"Horizontal\" Spacing=\"5\">\n                    <avalonia1:MaterialIcon Kind=\"Reload\" />\n                    <TextBlock Text=\"{x:Static drones:RS.MissionProgressView_RefreshButton_Text}\" />\n                </StackPanel>\n            </Button>\n            <drones:RouteUavIndicator\n                HorizontalAlignment=\"Stretch\"\n                Progress=\"{Binding PathProgress.Value}\"\n                StatusText=\"{Binding MissionFlightTime.Value}\"\n                Title=\"{x:Static drones:RS.MissionProgressView_Title}\"\n                FontSize=\"{DynamicResource FontSizeLarge}\"\n                SubStatusText=\"{x:Static drones:RS.MissionProgressView_SubStatusText}\"\n            />\n            <StackPanel\n                VerticalAlignment=\"Top\"\n                Orientation=\"Horizontal\"\n                HorizontalAlignment=\"Center\"\n                Spacing=\"4\"\n            >\n                <Viewbox>\n                    <avalonia:SplitDigitRttBoxView DataContext=\"{CompiledBinding  MissionDistanceRttBox}\" />\n                </Viewbox>\n                <Viewbox>\n                    <avalonia:SplitDigitRttBoxView DataContext=\"{CompiledBinding TotalDistanceRttBox}\" />\n                </Viewbox>\n                <Viewbox>\n                    <avalonia:SplitDigitRttBoxView DataContext=\"{CompiledBinding HomeDistanceRttBox}\" />\n                </Viewbox>\n                <Viewbox>\n                    <avalonia:SplitDigitRttBoxView DataContext=\"{CompiledBinding TargetDistanceRttBox}\" />\n                </Viewbox>\n            </StackPanel>\n        </StackPanel>\n    </DockPanel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/Widgets/UavWidget/MissionProgress/MissionProgressView.axaml.cs",
    "content": "﻿using Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class MissionProgressView : UserControl\n{\n    public MissionProgressView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/Widgets/UavWidget/MissionProgress/MissionProgressViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Asv.Mavlink.Common;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class MissionProgressViewModel : RoutableViewModel\n{\n    public const string ViewModelId = \"mission.progress\";\n    private const AsvColorKind DefaultStatusColor = AsvColorKind.Info5;\n\n    private readonly IClientDevice _device;\n    private readonly IPositionClientEx _positionClient;\n    private readonly IMissionClientEx _missionClient;\n    private readonly IGnssClientEx _gnssClientEx;\n    private readonly SynchronizedReactiveProperty<ushort> _currentIndex;\n    private readonly SynchronizedReactiveProperty<ushort> _reachedIndex;\n    private double _passedDistance;\n    private bool _isOnMission;\n\n    public MissionProgressViewModel()\n        : base(ViewModelId, DesignTime.LoggerFactory)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n        new CancellationTokenSource().DisposeItWith(Disposable);\n        _currentIndex = new SynchronizedReactiveProperty<ushort>(0).DisposeItWith(Disposable);\n        _reachedIndex = new SynchronizedReactiveProperty<ushort>(0).DisposeItWith(Disposable);\n        var missionDistance = new ReactiveProperty<double>(1000).DisposeItWith(Disposable);\n        var targetDistance = new ReactiveProperty<double>(100).DisposeItWith(Disposable);\n        var totalDistance = new ReactiveProperty<double>(1100).DisposeItWith(Disposable);\n        var homeDistance = new ReactiveProperty<double>(100).DisposeItWith(Disposable);\n\n        PathProgress = new BindableReactiveProperty<double>(0).DisposeItWith(Disposable);\n        IsDownloaded = new BindableReactiveProperty<bool>(false).DisposeItWith(Disposable);\n        DownloadProgress = new BindableReactiveProperty<double>().DisposeItWith(Disposable);\n        MissionFlightTime = new BindableReactiveProperty<string>(\n            $\"- {RS.MissionProgressViewModel_MissionFlightTime_Symbol}\"\n        ).DisposeItWith(Disposable);\n\n        var unitService = NullUnitService.Instance;\n\n        MissionDistanceRttBox = new SplitDigitRttBoxViewModel(\n            nameof(MissionDistanceRttBox),\n            DesignTime.LoggerFactory,\n            unitService,\n            DistanceUnit.Id,\n            missionDistance,\n            null\n        )\n        {\n            Icon = MaterialIconKind.MapMarkerDistance,\n            Header = RS.MissionProgressView_MissionDistanceRTT,\n            Status = DefaultStatusColor,\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        TotalDistanceRttBox = new SplitDigitRttBoxViewModel(\n            nameof(TotalDistanceRttBox),\n            DesignTime.LoggerFactory,\n            unitService,\n            DistanceUnit.Id,\n            totalDistance,\n            null\n        )\n        {\n            Icon = MaterialIconKind.LocationDistance,\n            Header = RS.MissionProgressView_TotalDistanceRTT,\n            Status = DefaultStatusColor,\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        HomeDistanceRttBox = new SplitDigitRttBoxViewModel(\n            nameof(HomeDistanceRttBox),\n            DesignTime.LoggerFactory,\n            unitService,\n            DistanceUnit.Id,\n            homeDistance,\n            null\n        )\n        {\n            Icon = MaterialIconKind.Home,\n            Header = RS.MissionProgressView_HomeDistance,\n            Status = DefaultStatusColor,\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        TargetDistanceRttBox = new SplitDigitRttBoxViewModel(\n            nameof(TargetDistanceRttBox),\n            DesignTime.LoggerFactory,\n            unitService,\n            DistanceUnit.Id,\n            targetDistance,\n            null\n        )\n        {\n            Icon = MaterialIconKind.Target,\n            Header = RS.MissionProgressView_TargetDistance,\n            Status = DefaultStatusColor,\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        IsDownloaded.Value = true;\n        MissionFlightTime.Value = \"15 min\";\n        PathProgress.Value = 0.7;\n    }\n\n    public MissionProgressViewModel(\n        IClientDevice device,\n        IUnitService unitService,\n        ILoggerFactory loggerFactory\n    )\n        : base(ViewModelId, loggerFactory)\n    {\n        ArgumentNullException.ThrowIfNull(device);\n        ArgumentNullException.ThrowIfNull(unitService);\n        ArgumentNullException.ThrowIfNull(loggerFactory);\n        _device = device;\n        _missionClient =\n            device.GetMicroservice<IMissionClientEx>()\n            ?? throw new Exception($\"Unable to load {nameof(IMissionClientEx)} from {device.Id}\");\n        _gnssClientEx =\n            device.GetMicroservice<IGnssClientEx>()\n            ?? throw new Exception($\"Unable to load {nameof(IGnssClientEx)} from {device.Id}\");\n        _positionClient =\n            device.GetMicroservice<IPositionClientEx>()\n            ?? throw new Exception($\"Unable to load {nameof(IPositionClientEx)} from {device.Id}\");\n        var mode =\n            device.GetMicroservice<IModeClient>()\n            ?? throw new Exception($\"Unable to load {nameof(IModeClient)} from {device.Id}\");\n\n        UpdateMission = new BindableAsyncCommand(UpdateMissionCommand.Id, this);\n\n        _currentIndex = new SynchronizedReactiveProperty<ushort>(0).DisposeItWith(Disposable);\n        _reachedIndex = new SynchronizedReactiveProperty<ushort>(0).DisposeItWith(Disposable);\n        var missionDistance = new ReactiveProperty<double>().DisposeItWith(Disposable);\n        var targetDistance = new ReactiveProperty<double>().DisposeItWith(Disposable);\n        var totalDistance = new ReactiveProperty<double>().DisposeItWith(Disposable);\n        var homeDistance = new ReactiveProperty<double>().DisposeItWith(Disposable);\n\n        MissionDistanceRttBox = new SplitDigitRttBoxViewModel(\n            nameof(MissionDistanceRttBox),\n            loggerFactory,\n            unitService,\n            DistanceUnit.Id,\n            missionDistance,\n            null\n        )\n        {\n            Icon = MaterialIconKind.MapMarkerDistance,\n            Header = RS.MissionProgressView_MissionDistanceRTT,\n            Status = DefaultStatusColor,\n            FormatString = \"F2\",\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        TotalDistanceRttBox = new SplitDigitRttBoxViewModel(\n            nameof(TotalDistanceRttBox),\n            loggerFactory,\n            unitService,\n            DistanceUnit.Id,\n            totalDistance,\n            null\n        )\n        {\n            Icon = MaterialIconKind.LocationDistance,\n            Header = RS.MissionProgressView_TotalDistanceRTT,\n            Status = DefaultStatusColor,\n            FormatString = \"F2\",\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        HomeDistanceRttBox = new SplitDigitRttBoxViewModel(\n            nameof(HomeDistanceRttBox),\n            loggerFactory,\n            unitService,\n            DistanceUnit.Id,\n            homeDistance,\n            null\n        )\n        {\n            Icon = MaterialIconKind.Home,\n            Header = RS.MissionProgressView_HomeDistance,\n            Status = DefaultStatusColor,\n            FormatString = \"F2\",\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        TargetDistanceRttBox = new SplitDigitRttBoxViewModel(\n            nameof(TargetDistanceRttBox),\n            loggerFactory,\n            unitService,\n            DistanceUnit.Id,\n            targetDistance,\n            null\n        )\n        {\n            Icon = MaterialIconKind.Target,\n            Header = RS.MissionProgressView_TargetDistance,\n            Status = DefaultStatusColor,\n            FormatString = \"F2\",\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        PathProgress = new BindableReactiveProperty<double>(0).DisposeItWith(Disposable);\n        IsDownloaded = new BindableReactiveProperty<bool>(false).DisposeItWith(Disposable);\n        DownloadProgress = new BindableReactiveProperty<double>().DisposeItWith(Disposable);\n        MissionFlightTime = new BindableReactiveProperty<string>(\n            $\"- {RS.MissionProgressViewModel_MissionFlightTime_Symbol}\"\n        ).DisposeItWith(Disposable);\n\n        _missionClient\n            .AllMissionsDistance.ObserveOnUIThreadDispatcher()\n            .Subscribe(d =>\n            {\n                missionDistance.Value = d * 1000;\n                var start = _missionClient.MissionItems.FirstOrDefault();\n                var stop = _missionClient.MissionItems.LastOrDefault(missionItem =>\n                    missionItem.Command.Value != MavCmd.MavCmdNavReturnToLaunch\n                );\n                if (start != null && stop != null)\n                {\n                    missionDistance.Value += GeoMath.Distance(\n                        start.Location.Value,\n                        _positionClient.Home.CurrentValue\n                    );\n                    missionDistance.Value += GeoMath.Distance(\n                        stop.Location.Value,\n                        _positionClient.Home.CurrentValue\n                    );\n                }\n\n                if (missionDistance.Value < 1)\n                {\n                    missionDistance.Value = d * 1000;\n                }\n            })\n            .DisposeItWith(Disposable);\n        _positionClient\n            .HomeDistance.ObserveOnUIThreadDispatcher()\n            .Subscribe(d => homeDistance.Value = d)\n            .DisposeItWith(Disposable);\n        _positionClient\n            .TargetDistance.ObserveOnUIThreadDispatcher()\n            .Subscribe(d => targetDistance.Value = d)\n            .DisposeItWith(Disposable);\n\n        _missionClient\n            .AllMissionsDistance.ObserveOnUIThreadDispatcher()\n            .Select(v => v * 1000)\n            .Subscribe(v =>\n            {\n                var distanceBeforeMission =\n                    _missionClient.MissionItems.Count == 0\n                        ? 0\n                        : GeoMath.Distance(\n                            _positionClient.Current.CurrentValue,\n                            _missionClient.MissionItems[0].Location.Value\n                        );\n\n                var rtl = _missionClient.MissionItems.FirstOrDefault(_ =>\n                    _.Command.Value == MavCmd.MavCmdNavReturnToLaunch\n                );\n                if (rtl is not null)\n                {\n                    totalDistance.Value = v + (distanceBeforeMission * 2000);\n                    return;\n                }\n\n                totalDistance.Value = v + (distanceBeforeMission * 1000);\n            })\n            .DisposeItWith(Disposable);\n\n        mode.CurrentMode.ObserveOnUIThreadDispatcher()\n            .Subscribe(m =>\n            {\n                if (m == ArduCopterMode.Auto || m == ArduPlaneMode.Auto)\n                {\n                    if (_isOnMission)\n                    {\n                        return;\n                    }\n\n                    _isOnMission = true;\n                    return;\n                }\n\n                if (m == ArduCopterMode.Rtl || m == ArduPlaneMode.Rtl)\n                {\n                    _isOnMission = false;\n                    _reachedIndex.Value = 0;\n                    PathProgress.Value = 0;\n                }\n            })\n            .DisposeItWith(Disposable);\n\n        PathProgress\n            .ObserveOnUIThreadDispatcher()\n            .Where(x => x is < 0 or > 1)\n            .Subscribe(p =>\n            {\n                var clamped = Math.Clamp(p, 0, 1);\n                PathProgress.Value = clamped;\n            })\n            .DisposeItWith(Disposable);\n\n        _currentIndex\n            .ObserveOnUIThreadDispatcher()\n            .Subscribe(c =>\n            {\n                if (_missionClient.MissionItems.Count == 0)\n                {\n                    return;\n                }\n\n                _passedDistance = 0;\n                var items = _missionClient\n                    .MissionItems.Where(item =>\n                        item.Index <= c && item.Command.Value != MavCmd.MavCmdDoChangeSpeed\n                    )\n                    .ToList();\n                if (items.Count < 2)\n                {\n                    return;\n                }\n\n                for (var i = 1; i < items.Count; i++)\n                {\n                    _passedDistance += GeoMath.Distance(\n                        items[i - 1].Location.Value,\n                        items[i].Location.Value\n                    );\n                }\n            })\n            .DisposeItWith(Disposable);\n\n        _missionClient\n            .Reached.ObserveOnUIThreadDispatcher()\n            .Subscribe(i => _reachedIndex.Value = i)\n            .DisposeItWith(Disposable);\n        _missionClient\n            .Current.ObserveOnUIThreadDispatcher()\n            .Subscribe(i => _currentIndex.Value = i)\n            .DisposeItWith(Disposable);\n\n        Observable\n            .Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))\n            .ObserveOnUIThreadDispatcher()\n            .Subscribe(_ => CalculateMissionProgress())\n            .DisposeItWith(Disposable);\n    }\n\n    public BindableAsyncCommand UpdateMission { get; set; }\n    public BindableReactiveProperty<string> MissionFlightTime { get; }\n    public BindableReactiveProperty<double> DownloadProgress { get; }\n    public BindableReactiveProperty<bool> IsDownloaded { get; }\n    public BindableReactiveProperty<double> PathProgress { get; }\n\n    public SplitDigitRttBoxViewModel MissionDistanceRttBox { get; }\n    public SplitDigitRttBoxViewModel TotalDistanceRttBox { get; }\n    public SplitDigitRttBoxViewModel HomeDistanceRttBox { get; }\n    public SplitDigitRttBoxViewModel TargetDistanceRttBox { get; }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        yield return MissionDistanceRttBox;\n        yield return TotalDistanceRttBox;\n        yield return HomeDistanceRttBox;\n        yield return TargetDistanceRttBox;\n    }\n\n    internal async Task InitiateMissionPoints(CancellationToken cancel)\n    {\n        await DownloadMissionsImpl(cancel);\n    }\n\n    private async ValueTask DownloadMissionsImpl(CancellationToken cancel)\n    {\n        await _missionClient.Download(cancel, p => DownloadProgress.Value = p * 100);\n\n        double homeAlt = 0;\n\n        if (_positionClient.Home.CurrentValue is not null)\n        {\n            homeAlt = _positionClient.Home.CurrentValue.Value.Altitude;\n        }\n\n        IsDownloaded.Value = true;\n        for (var i = 0; i < _missionClient.MissionItems.Count; i++)\n        {\n            if (i == 0 && _device is ArduPlaneClientDevice)\n            {\n                continue;\n            }\n\n            var item = _missionClient.MissionItems[i];\n\n            if (\n                item.Command.Value == MavCmd.MavCmdNavWaypoint\n                && item.Location.Value.Altitude <= homeAlt\n            )\n            {\n                // TODO: Notify user on alt lower than start value\n            }\n        }\n    }\n\n    private void CalculateMissionProgress()\n    {\n        if (!_isOnMission)\n        {\n            return;\n        }\n\n        var toTargetDistance = GeoMath.Distance(\n            _positionClient.Target.CurrentValue,\n            _positionClient.Current.CurrentValue\n        );\n        var missionDistance = _missionClient.AllMissionsDistance.CurrentValue * 1000;\n        var distance = Math.Abs(missionDistance - _passedDistance + toTargetDistance);\n        var time = distance / _gnssClientEx.Main.GroundVelocity.CurrentValue;\n\n        PathProgress.Value = CalculatePathProgressValue(missionDistance, distance);\n        MissionFlightTime.Value = CalculateMissionFlightTime(time);\n    }\n\n    private string CalculateMissionFlightTime(double time)\n    {\n        if (time is double.NaN or double.PositiveInfinity || !_isOnMission)\n        {\n            return $\"- {RS.MissionProgressViewModel_MissionFlightTime_Symbol}\";\n        }\n\n        var minute = Math.Round(time / 60);\n\n        if (minute < 1)\n        {\n            return $\"<1 {RS.MissionProgressViewModel_MissionFlightTime_Symbol}\";\n        }\n\n        return $\"{minute} {RS.MissionProgressViewModel_MissionFlightTime_Symbol}\";\n    }\n\n    private double CalculatePathProgressValue(double missionDistance, double distance) // TODO: extend logic for plane clients\n    {\n        if (!_isOnMission)\n        {\n            return Math.Abs((missionDistance - distance) / missionDistance);\n        }\n\n        switch (_device)\n        {\n            case ArduCopterClientDevice:\n                if (_isOnMission && _reachedIndex.Value > 0)\n                {\n                    return Math.Abs((missionDistance - distance) / missionDistance);\n                }\n\n                if (_currentIndex.Value == _missionClient.MissionItems.Count)\n                {\n                    return 1;\n                }\n\n                return 0;\n            default:\n                return Math.Abs((missionDistance - distance) / missionDistance);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/Widgets/UavWidget/UavWidgetView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:avalonia=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    xmlns:avalonia1=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    xmlns:oldAttitudeIndicator=\"clr-namespace:Asv.Drones.OldAttitudeIndicator\"\n    mc:Ignorable=\"d\"\n    d:DesignHeight=\"1000\"\n    x:Class=\"Asv.Drones.UavWidgetView\"\n    x:DataType=\"drones:UavWidgetViewModel\"\n>\n    <Design.DataContext>\n        <drones:UavWidgetViewModel />\n    </Design.DataContext>\n    <Grid RowDefinitions=\"Auto,Auto,*\" Margin=\"5\" RowSpacing=\"10\" HorizontalAlignment=\"Left\">\n        <StackPanel\n            Grid.Column=\"0\"\n            Grid.Row=\"0\"\n            Margin=\"5 0 0 0\"\n            HorizontalAlignment=\"Stretch\"\n            Orientation=\"Horizontal\"\n        >\n            <oldAttitudeIndicator:AttitudeIndicator\n                MinWidth=\"150\"\n                MaxWidth=\"250\"\n                Name=\"Attitude\"\n                VerticalAlignment=\"Top\"\n                CornerRadius=\"{StaticResource ControlCornerRadius}\"\n                Altitude=\"{Binding AltitudeAgl.Value}\"\n                RollAngle=\"{Binding Roll.Value}\"\n                PitchAngle=\"{Binding Pitch.Value}\"\n                Velocity=\"{Binding Velocity.Value}\"\n                Heading=\"{Binding Heading.Value}\"\n                HomeAzimuth=\"{Binding HomeAzimuth.Value}\"\n                IsArmed=\"{Binding IsArmed.Value}\"\n                ArmedTime=\"{Binding ArmedTime.Value}\"\n                StatusText=\"{Binding StatusText.Value}\"\n                VibrationX=\"{Binding VibrationX.Value}\"\n                RightStatusText=\"{Binding StatusText.Value}\"\n                VibrationY=\"{Binding VibrationY.Value}\"\n                VibrationZ=\"{Binding VibrationZ.Value}\"\n                Clipping0=\"{Binding Clipping0.Value}\"\n                Clipping1=\"{Binding Clipping1.Value}\"\n                Clipping2=\"{Binding Clipping2.Value}\"\n            />\n            <StackPanel Orientation=\"Vertical\" VerticalAlignment=\"Top\">\n                <StackPanel.Styles>\n                    <Style Selector=\"Button\">\n                        <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n                        <Setter Property=\"Theme\" Value=\"{StaticResource TransparentButton}\" />\n                        <Setter\n                            Property=\"CornerRadius\"\n                            Value=\"{StaticResource ControlCornerRadius}\"\n                        />\n                    </Style>\n                </StackPanel.Styles>\n                <Button HorizontalContentAlignment=\"Stretch\" Command=\"{Binding AutoMode}\">\n                    <StackPanel Spacing=\"5\" Orientation=\"Horizontal\">\n                        <avalonia1:MaterialIcon Kind=\"Automatic\" />\n                        <TextBlock Text=\"{x:Static drones:RS.UavAction_AutoMode_Name}\" />\n                    </StackPanel>\n                </Button>\n                <Button HorizontalContentAlignment=\"Stretch\" Command=\"{Binding Guided}\">\n                    <StackPanel Spacing=\"5\" Orientation=\"Horizontal\">\n                        <avalonia1:MaterialIcon Kind=\"Controller\" />\n                        <TextBlock Text=\"{x:Static drones:RS.UavAction_GuidedMode}\" />\n                    </StackPanel>\n                </Button>\n                <Button HorizontalContentAlignment=\"Stretch\" Command=\"{Binding TakeOff}\">\n                    <StackPanel Spacing=\"5\" Orientation=\"Horizontal\">\n                        <avalonia1:MaterialIcon Kind=\"AeroplaneTakeoff\" />\n                        <TextBlock Text=\"{x:Static drones:RS.UavAction_TakeOff}\" />\n                    </StackPanel>\n                </Button>\n                <Button HorizontalContentAlignment=\"Stretch\" Command=\"{Binding Land}\">\n                    <StackPanel Spacing=\"5\" Orientation=\"Horizontal\">\n                        <avalonia1:MaterialIcon Kind=\"FlightLand\" />\n                        <TextBlock Text=\"{x:Static drones:RS.UavAction_Land}\" />\n                    </StackPanel>\n                </Button>\n                <Button HorizontalContentAlignment=\"Stretch\" Command=\"{Binding Rtl}\">\n                    <StackPanel Spacing=\"5\" Orientation=\"Horizontal\">\n                        <avalonia1:MaterialIcon Kind=\"Home\" />\n                        <TextBlock Text=\"{x:Static drones:RS.UavAction_Rtl_Name}\" />\n                    </StackPanel>\n                </Button>\n                <Button HorizontalContentAlignment=\"Stretch\" Command=\"{Binding StartMission}\">\n                    <StackPanel Spacing=\"5\" Orientation=\"Horizontal\">\n                        <avalonia1:MaterialIcon Kind=\"MapMarkerPath\" />\n                        <TextBlock Text=\"{x:Static drones:RS.UavAction_StartMission}\" />\n                    </StackPanel>\n                </Button>\n            </StackPanel>\n        </StackPanel>\n        <StackPanel\n            Grid.Column=\"0\"\n            Grid.Row=\"1\"\n            Orientation=\"Vertical\"\n            HorizontalAlignment=\"Left\"\n            VerticalAlignment=\"Top\"\n            Spacing=\"3\"\n            Width=\"700\"\n        >\n            <StackPanel\n                Orientation=\"Vertical\"\n                HorizontalAlignment=\"Left\"\n                VerticalAlignment=\"Top\"\n                Spacing=\"4\"\n            >\n                <StackPanel\n                    Orientation=\"Horizontal\"\n                    HorizontalAlignment=\"Left\"\n                    VerticalAlignment=\"Top\"\n                    Spacing=\"4\"\n                >\n                    <Viewbox VerticalAlignment=\"Top\">\n                        <avalonia:SingleRttBoxView DataContext=\"{CompiledBinding CurrentFlightModeRttBox}\" />\n                    </Viewbox>\n                    <Viewbox VerticalAlignment=\"Top\">\n                        <avalonia:SplitDigitRttBoxView DataContext=\"{CompiledBinding VelocityRttBox}\" />\n                    </Viewbox>\n                </StackPanel>\n                <StackPanel\n                    Orientation=\"Horizontal\"\n                    HorizontalAlignment=\"Left\"\n                    VerticalAlignment=\"Top\"\n                    Spacing=\"4\"\n                >\n                    <Viewbox VerticalAlignment=\"Top\">\n                        <avalonia:SplitDigitRttBoxView DataContext=\"{CompiledBinding AzimuthRttBox}\" />\n                    </Viewbox>\n                    <Viewbox VerticalAlignment=\"Top\">\n                        <avalonia:SplitDigitRttBoxView DataContext=\"{CompiledBinding LinkQualityRttBox}\" />\n                    </Viewbox>\n                </StackPanel>\n            </StackPanel>\n            <StackPanel\n                Orientation=\"Horizontal\"\n                HorizontalAlignment=\"Left\"\n                VerticalAlignment=\"Top\"\n                Spacing=\"4\"\n            >\n                <Viewbox VerticalAlignment=\"Top\">\n                    <avalonia:KeyValueRttBoxView DataContext=\"{CompiledBinding BatteryRttBox}\" />\n                </Viewbox>\n                <Viewbox VerticalAlignment=\"Top\">\n                    <avalonia:KeyValueRttBoxView DataContext=\"{CompiledBinding GnssRttBox}\" />\n                </Viewbox>\n            </StackPanel>\n            <StackPanel\n                Orientation=\"Horizontal\"\n                HorizontalAlignment=\"Left\"\n                VerticalAlignment=\"Top\"\n                Spacing=\"4\"\n            >\n                <Viewbox VerticalAlignment=\"Top\">\n                    <avalonia:TwoColumnRttBoxView DataContext=\"{CompiledBinding AltitudeRttBox}\" />\n                </Viewbox>\n            </StackPanel>\n        </StackPanel>\n        <drones:MissionProgressView\n            Grid.Column=\"0\"\n            Grid.Row=\"2\"\n            DataContext=\"{Binding MissionProgress}\"\n        />\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/Widgets/UavWidget/UavWidgetView.axaml.cs",
    "content": "﻿using Asv.Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class UavWidgetView : UserControl\n{\n    public UavWidgetView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/Flight/Widgets/UavWidget/UavWidgetViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Windows.Input;\nusing Asv.Avalonia;\nusing Asv.Avalonia.GeoMap;\nusing Asv.Avalonia.IO;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing R3;\nusing Observable = R3.Observable;\n\nnamespace Asv.Drones;\n\npublic class UavWidgetViewModel : MapWidget, IUavFlightWidget\n{\n    private const string WidgetId = \"widget-uav\";\n    private const AsvColorKind DefaultStatusColor = AsvColorKind.Info5;\n\n    private readonly IUnit _altitudeUnit;\n    private readonly IUnit _capacityUnit;\n    private readonly IUnit _amperageUnit;\n    private readonly IUnit _voltageUnit;\n    private readonly IUnit _progressUnit;\n\n    private readonly IGnssClientEx? _gnssClient;\n    private const int CriticalAltitude = 40;\n    private const int DangerHighSpeed = 10;\n    private const int DangerSatelliteCount = 10;\n    private static readonly Range WarningSatelliteAmount = 15..20;\n\n#pragma warning disable SA1313\n    private record BatteryRttBoxData(\n        double Charge,\n        double Amperage,\n        double Voltage,\n        double Consumed,\n        IUnitItem ProgressUnit,\n        IUnitItem AmperageUnit,\n        IUnitItem CapacityUnit,\n        IUnitItem VoltageUnit\n    );\n\n    private record AltitudeRttBoxData(\n        double AltitudeAgl,\n        double AltitudeMsl,\n        IUnitItem AltitudeUnit\n    );\n\n    private record GnssRttBoxData(\n        int Sattelites,\n        double HdopCount,\n        double VdopCount,\n        Mavlink.Common.GpsFixType Mode\n    );\n#pragma warning restore SA1313\n\n    public UavWidgetViewModel()\n        : base(WidgetId, DesignTime.LoggerFactory, NullMapService.Instance)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n        TakeOff = new ReactiveCommand();\n        AutoMode = new ReactiveCommand();\n        Rtl = new ReactiveCommand();\n        Guided = new ReactiveCommand();\n        StartMission = new ReactiveCommand();\n        Land = new ReactiveCommand();\n        InitArgs(\"1\");\n        MissionProgress = new MissionProgressViewModel().DisposeItWith(Disposable);\n\n        NullUnitService.Instance.Extend(\n            new VelocityUnit(\n                DesignTime.Configuration,\n                [new VelocityMetersPerSecondUnitItem(), new VelocityMilesPerHourUnitItem()]\n            )\n        );\n        NullUnitService.Instance.Extend(\n            new ProgressUnit(\n                DesignTime.Configuration,\n                [new ProgressPercentUnitItem(), new ProgressInPartsUnitItem()]\n            )\n        );\n        NullUnitService.Instance.Extend(\n            new CapacityUnit(DesignTime.Configuration, [new CapacityMilliAmperePerHourUnitItem()])\n        );\n        NullUnitService.Instance.Extend(\n            new AmperageUnit(\n                DesignTime.Configuration,\n                [new AmperageAmpereUnitItem(), new AmperageMilliAmpereUnitItem()]\n            )\n        );\n        NullUnitService.Instance.Extend(\n            new VoltageUnit(\n                DesignTime.Configuration,\n                [new VoltageVoltUnitItem(), new VoltageMilliVoltUnitItem()]\n            )\n        );\n\n        var unitService = NullUnitService.Instance;\n\n        Icon = MaterialIconKind.AccountFile;\n        IconColor = AsvColorKind.Info5;\n\n        _altitudeUnit = unitService.Units[AltitudeUnit.Id];\n        _capacityUnit = unitService.Units[CapacityUnit.Id];\n        _amperageUnit = unitService.Units[AmperageUnit.Id];\n        _voltageUnit = unitService.Units[VoltageUnit.Id];\n        _progressUnit = unitService.Units[ProgressUnit.Id];\n\n        var linkQuality = new ReactiveProperty<double>(100).DisposeItWith(Disposable);\n        var altitudeAgl = new ReactiveProperty<double>(10).DisposeItWith(Disposable);\n        var altitudeMsl = new ReactiveProperty<double>(14).DisposeItWith(Disposable);\n        var heading = new ReactiveProperty<double>(29).DisposeItWith(Disposable);\n        var azimuth = new ReactiveProperty<double>(39).DisposeItWith(Disposable);\n        var homeAzimuth = new ReactiveProperty<double>(30).DisposeItWith(Disposable);\n        var satelliteCount = new ReactiveProperty<int>(10).DisposeItWith(Disposable);\n        var hdopCount = new ReactiveProperty<double>(2).DisposeItWith(Disposable);\n        var vdopCount = new ReactiveProperty<double>(4).DisposeItWith(Disposable);\n        var gpsFixType = new ReactiveProperty<Mavlink.Common.GpsFixType>(\n            Mavlink.Common.GpsFixType.GpsFixTypeDgps\n        ).DisposeItWith(Disposable);\n        var velocity = new ReactiveProperty<double>(199).DisposeItWith(Disposable);\n        var batteryAmperage = new ReactiveProperty<double>(39).DisposeItWith(Disposable);\n        var batteryVoltage = new ReactiveProperty<double>(34).DisposeItWith(Disposable);\n        var batteryCharge = new ReactiveProperty<double>(123).DisposeItWith(Disposable);\n        var batteryConsumed = new ReactiveProperty<double>(39).DisposeItWith(Disposable);\n        var currentFlightMode = new ReactiveProperty<string>(\"Unknown\").DisposeItWith(Disposable);\n\n        CurrentFlightModeRttBox = new SingleRttBoxViewModel<string>(\n            nameof(CurrentFlightModeRttBox),\n            DesignTime.LoggerFactory,\n            currentFlightMode,\n            null\n        )\n        {\n            Header = RS.UavRttItem_Mode,\n            Icon = MaterialIconKind.FlightMode,\n            UpdateAction = (model, mode) => model.ValueString = mode,\n            Status = DefaultStatusColor,\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        AltitudeRttBox = new TwoColumnRttBoxViewModel<AltitudeRttBoxData>(\n            nameof(AltitudeRttBox),\n            DesignTime.LoggerFactory,\n            altitudeAgl\n                .CombineLatest(\n                    altitudeMsl,\n                    _altitudeUnit.CurrentUnitItem,\n                    (agl, msl, unit) => new AltitudeRttBoxData(agl, msl, unit)\n                )\n                .ObserveOnUIThreadDispatcher()\n                .ThrottleLast(TimeSpan.FromMilliseconds(200)),\n            null\n        )\n        {\n            Header = RS.UavRttItem_Altitude,\n            Icon = MaterialIconKind.Altimeter,\n            UpdateAction = (model, changes) =>\n            {\n                model.Left.ValueString = changes.AltitudeUnit.PrintFromSi(\n                    changes.AltitudeAgl,\n                    \"F2\"\n                );\n                model.Right.ValueString = changes.AltitudeUnit.PrintFromSi(\n                    changes.AltitudeMsl,\n                    \"F2\"\n                );\n\n                model.Left.UnitSymbol = changes.AltitudeUnit.Symbol;\n                model.Right.UnitSymbol = changes.AltitudeUnit.Symbol;\n            },\n            Status = DefaultStatusColor,\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n        AltitudeRttBox.Left.Header = \"AGL\";\n        AltitudeRttBox.Right.Header = \"MSL\";\n\n        VelocityRttBox = new SplitDigitRttBoxViewModel(\n            nameof(VelocityRttBox),\n            DesignTime.LoggerFactory,\n            unitService,\n            VelocityUnit.Id,\n            velocity,\n            null\n        )\n        {\n            Header = RS.UavRttItem_Velocity,\n            ShortHeader = \"GS\",\n            Icon = MaterialIconKind.Speedometer,\n            Status = DefaultStatusColor,\n            FormatString = \"F2\",\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        AzimuthRttBox = new SplitDigitRttBoxViewModel(\n            nameof(AzimuthRttBox),\n            DesignTime.LoggerFactory,\n            unitService,\n            AngleUnit.Id,\n            azimuth,\n            null\n        )\n        {\n            Header = RS.UavRttItem_Azimuth,\n            Icon = MaterialIconKind.SunAzimuth,\n            Status = DefaultStatusColor,\n            FormatString = \"F2\",\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        BatteryRttBox = new KeyValueRttBoxViewModel<BatteryRttBoxData>(\n            nameof(BatteryRttBox),\n            DesignTime.LoggerFactory,\n            batteryCharge\n                .CombineLatest(\n                    batteryAmperage,\n                    batteryVoltage,\n                    batteryConsumed,\n                    _progressUnit.CurrentUnitItem,\n                    _amperageUnit.CurrentUnitItem,\n                    _capacityUnit.CurrentUnitItem,\n                    _voltageUnit.CurrentUnitItem,\n                    (bC, bA, bV, bCo, prog, amp, cap, vol) =>\n                        new BatteryRttBoxData(bC, bA, bV, bCo, prog, amp, cap, vol)\n                )\n                .ObserveOnUIThreadDispatcher()\n                .ThrottleLast(TimeSpan.FromMilliseconds(200)),\n            null\n        )\n        {\n            Header = RS.UavRttItem_Battery,\n            Icon = MaterialIconKind.Battery10,\n            UpdateAction = (model, changes) =>\n            {\n                model[\n                    0,\n                    RS.UavWidgetViewModel_BatteryRttBox_BatteryCharge_Header,\n                    changes.ProgressUnit.Symbol\n                ].ValueString = changes.ProgressUnit.PrintFromSi(changes.Charge, \"F2\");\n                model[\n                    1,\n                    RS.UavWidgetViewModel_BatteryRttBox_BatteryAmperage_Header,\n                    changes.AmperageUnit.Symbol\n                ].ValueString = changes.AmperageUnit.PrintFromSi(changes.Amperage, \"F2\");\n                model[\n                    2,\n                    RS.UavWidgetViewModel_BatteryRttBox_BatteryVoltage_Header,\n                    changes.VoltageUnit.Symbol\n                ].ValueString = changes.VoltageUnit.PrintFromSi(changes.Voltage, \"F2\");\n                model[\n                    3,\n                    RS.UavWidgetViewModel_BatteryRttBox_BatteryConsumed_Header,\n                    changes.CapacityUnit.Symbol\n                ].ValueString = changes.CapacityUnit.PrintFromSi(changes.Consumed, \"F2\");\n\n                ChangeBatteryStatus(changes.Charge);\n            },\n            Status = DefaultStatusColor,\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        GnssRttBox = new KeyValueRttBoxViewModel<GnssRttBoxData>(\n            nameof(GnssRttBox),\n            DesignTime.LoggerFactory,\n            satelliteCount\n                .CombineLatest(\n                    hdopCount,\n                    vdopCount,\n                    gpsFixType,\n                    (satellites, hdops, vdops, mode) =>\n                        new GnssRttBoxData(satellites, hdops, vdops, mode)\n                )\n                .ObserveOnUIThreadDispatcher()\n                .ThrottleLast(TimeSpan.FromMilliseconds(200)),\n            null\n        )\n        {\n            Header = RS.UavRttItem_GNSS,\n            Icon = MaterialIconKind.GpsFixed,\n            UpdateAction = (model, changes) =>\n            {\n                model[\n                    0,\n                    RS.UavWidgetViewModel_GnssRttBox_SatellitesCount_Header,\n                    null\n                ].ValueString = changes.Sattelites.ToString();\n                model[1, RS.UavWidgetViewModel_GnssRttBox_Hdop_Header, null].ValueString =\n                    changes.HdopCount.ToString(\"F2\");\n                model[2, RS.UavWidgetViewModel_GnssRttBox_Vdop_Header, null].ValueString =\n                    changes.VdopCount.ToString(\"F2\");\n                model[3, RS.UavWidgetViewModel_GnssRttBox_Mode_Header, null].ValueString =\n                    GpsFixTypeToString(changes.Mode);\n                ChangeGnssStatus(changes.Sattelites, changes.Mode);\n            },\n            Status = DefaultStatusColor,\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        LinkQualityRttBox = new SplitDigitRttBoxViewModel(\n            nameof(LinkQualityRttBox),\n            DesignTime.LoggerFactory,\n            unitService,\n            ProgressUnit.Id,\n            linkQuality,\n            null\n        )\n        {\n            Header = RS.UavRttItem_Link,\n            Icon = MaterialIconKind.Wifi,\n            Status = DefaultStatusColor,\n            FormatString = \"F2\",\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        AltitudeAgl = altitudeAgl.ToReadOnlyBindableReactiveProperty().DisposeItWith(Disposable);\n        AltitudeMsl = altitudeMsl.ToReadOnlyBindableReactiveProperty().DisposeItWith(Disposable);\n        Azimuth = azimuth.ToReadOnlyBindableReactiveProperty().DisposeItWith(Disposable);\n        Heading = heading.ToReadOnlyBindableReactiveProperty().DisposeItWith(Disposable);\n        HomeAzimuth = homeAzimuth.ToReadOnlyBindableReactiveProperty().DisposeItWith(Disposable);\n        Velocity = velocity.ToReadOnlyBindableReactiveProperty().DisposeItWith(Disposable);\n        Roll = new BindableReactiveProperty<double>().DisposeItWith(Disposable);\n        Pitch = new BindableReactiveProperty<double>().DisposeItWith(Disposable);\n        IsArmed = new BindableReactiveProperty<bool>().DisposeItWith(Disposable);\n        StatusText = new BindableReactiveProperty<string>().DisposeItWith(Disposable);\n        VibrationX = new BindableReactiveProperty<float>().DisposeItWith(Disposable);\n        VibrationY = new BindableReactiveProperty<float>().DisposeItWith(Disposable);\n        VibrationZ = new BindableReactiveProperty<float>().DisposeItWith(Disposable);\n        Clipping0 = new BindableReactiveProperty<uint>().DisposeItWith(Disposable);\n        Clipping1 = new BindableReactiveProperty<uint>().DisposeItWith(Disposable);\n        Clipping2 = new BindableReactiveProperty<uint>().DisposeItWith(Disposable);\n        ArmedTime = new BindableReactiveProperty<TimeSpan>().DisposeItWith(Disposable);\n        Roll = new BindableReactiveProperty<double>().DisposeItWith(Disposable);\n        Pitch = new BindableReactiveProperty<double>().DisposeItWith(Disposable);\n        IsArmed = new BindableReactiveProperty<bool>().DisposeItWith(Disposable);\n        StatusText = new BindableReactiveProperty<string>().DisposeItWith(Disposable);\n        VibrationX = new BindableReactiveProperty<float>().DisposeItWith(Disposable);\n        VibrationY = new BindableReactiveProperty<float>().DisposeItWith(Disposable);\n        VibrationZ = new BindableReactiveProperty<float>().DisposeItWith(Disposable);\n        Clipping0 = new BindableReactiveProperty<uint>().DisposeItWith(Disposable);\n        Clipping1 = new BindableReactiveProperty<uint>().DisposeItWith(Disposable);\n        Clipping2 = new BindableReactiveProperty<uint>().DisposeItWith(Disposable);\n        ArmedTime = new BindableReactiveProperty<TimeSpan>().DisposeItWith(Disposable);\n    }\n\n    public UavWidgetViewModel(\n        IClientDevice device,\n        INavigationService navigation,\n        IUnitService unitService,\n        IDeviceManager dev,\n        ILoggerFactory loggerFactory\n    )\n        : base(WidgetId, loggerFactory, NullMapService.Instance)\n    {\n        ArgumentNullException.ThrowIfNull(device);\n        Device = device;\n        Position = WorkspaceDock.Left;\n        Icon = dev.GetIcon(device.Id);\n        IconColor = dev.GetDeviceColor(device.Id);\n        _altitudeUnit = unitService.Units[AltitudeUnit.Id];\n        _capacityUnit = unitService.Units[CapacityUnit.Id];\n        _amperageUnit = unitService.Units[AmperageUnit.Id];\n        _voltageUnit = unitService.Units[VoltageUnit.Id];\n        _progressUnit = unitService.Units[ProgressUnit.Id];\n        device.Name.Subscribe(x => Header = x).DisposeItWith(Disposable);\n        InitArgs(device.Id.AsString());\n        MissionProgress = new MissionProgressViewModel(device, unitService, loggerFactory)\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n        var positionClientEx =\n            device.GetMicroservice<IPositionClientEx>()\n            ?? throw new ArgumentException(\n                $\"Unable to load {nameof(PositionClientEx)} from {device.Id}\"\n            );\n        _gnssClient =\n            device.GetMicroservice<IGnssClientEx>()\n            ?? throw new ArgumentException(\n                $\"Unable to load {nameof(GnssClientEx)} from {device.Id}\"\n            );\n        var telemetryClient =\n            device.GetMicroservice<ITelemetryClientEx>()\n            ?? throw new ArgumentException(\n                $\"Unable to load {nameof(TelemetryClientEx)} from {device.Id}\"\n            );\n        var heartbeatClient =\n            device.GetMicroservice<IHeartbeatClient>()\n            ?? throw new ArgumentException(\n                $\"Unable to load {nameof(HeartbeatClient)} from {device.Id}\"\n            );\n        var modeClient =\n            device.GetMicroservice<IModeClient>()\n            ?? throw new ArgumentException($\"Unable to load {nameof(IModeClient)}\");\n\n        TakeOff = new ReactiveCommand(\n            async (_, ct) =>\n            {\n                using var vm = new SetAltitudeDialogViewModel(_altitudeUnit, loggerFactory);\n                var dialog = new ContentDialog(vm, navigation)\n                {\n                    Title = RS.UavWidgetViewModel_SetAltitudeDialog_Title,\n                    PrimaryButtonText =\n                        RS.SetAltitudeDialogViewModel_ApplyDialog_PrimaryButton_TakeOff,\n                    SecondaryButtonText =\n                        RS.SetAltitudeDialogViewModel_ApplyDialog_SecondaryButton_Cancel,\n                    IsSecondaryButtonEnabled = true,\n                };\n\n                var result = await dialog.ShowAsync();\n\n                if (result == ContentDialogResult.Primary)\n                {\n                    await this.ExecuteCommand(\n                        TakeOffCommand.Id,\n                        new DoubleArg(\n                            _altitudeUnit.CurrentUnitItem.Value.ParseToSi(vm.Altitude.Value)\n                        ),\n                        ct\n                    );\n                }\n            }\n        ).DisposeItWith(Disposable);\n        Rtl = new BindableAsyncCommand(RTLCommand.Id, this);\n        Land = new BindableAsyncCommand(LandCommand.Id, this);\n        Guided = new BindableAsyncCommand(GuidedModeCommand.Id, this);\n        AutoMode = new BindableAsyncCommand(AutoModeCommand.Id, this);\n        StartMission = new BindableAsyncCommand(StartMissionCommand.Id, this);\n\n        var altitudeAgl = positionClientEx\n            .Base.GlobalPosition.ObserveOnUIThreadDispatcher()\n            .Select(pld => Math.Truncate((pld?.RelativeAlt ?? double.NaN) / 1000d));\n\n        var altitudeMsl = positionClientEx\n            .Base.GlobalPosition.ObserveOnUIThreadDispatcher()\n            .Select(pld => Math.Truncate((pld?.Alt ?? double.NaN) / 1000d));\n\n        var velocity = _gnssClient\n            .Main.GroundVelocity.ObserveOnUIThreadDispatcher()\n            .Select(Math.Truncate);\n\n        var azimuth = positionClientEx\n            .Yaw.ObserveOnUIThreadDispatcher()\n            .Select(d => Math.Round(d, 2));\n        var heading = positionClientEx.Yaw.ObserveOnUIThreadDispatcher().Select(Math.Truncate);\n\n        var homeAzimuth = positionClientEx\n            .Current.ObserveOnUIThreadDispatcher()\n            .Where(_ => positionClientEx.Home.CurrentValue.HasValue)\n            .ThrottleLast(TimeSpan.FromMilliseconds(200))\n            .Select(p => p.Azimuth(positionClientEx.Home.CurrentValue ?? GeoPoint.NaN));\n\n        var batteryAmperage = telemetryClient.BatteryCurrent.ObserveOnUIThreadDispatcher();\n        var batteryCharge = telemetryClient.BatteryCharge.ObserveOnUIThreadDispatcher();\n        var batteryVoltage = telemetryClient.BatteryVoltage.ObserveOnUIThreadDispatcher();\n        var batteryConsumed = telemetryClient\n            .BatteryCurrent.ObserveOnUIThreadDispatcher()\n            .Select(d =>\n                d == 0\n                    ? double.NaN\n                    : Math.Round(d * positionClientEx.ArmedTime.CurrentValue.TotalHours, 2)\n            );\n        var vdop = _gnssClient\n            .Main.Info.ObserveOnUIThreadDispatcher()\n            .Select(info => info.Vdop ?? double.NaN);\n        var hdop = _gnssClient\n            .Main.Info.Select(info => info.Hdop ?? double.NaN)\n            .ObserveOnUIThreadDispatcher();\n        var satelliteCount = _gnssClient\n            .Main.Info.Select(info => info.SatellitesVisible)\n            .ObserveOnUIThreadDispatcher();\n        var rtkMode = _gnssClient\n            .Main.Info.Select(gpsInfo => gpsInfo.FixType)\n            .ObserveOnUIThreadDispatcher();\n\n        AltitudeAgl = altitudeAgl.ToReadOnlyBindableReactiveProperty().DisposeItWith(Disposable);\n        AltitudeMsl = altitudeMsl.ToReadOnlyBindableReactiveProperty().DisposeItWith(Disposable);\n        Azimuth = azimuth.ToReadOnlyBindableReactiveProperty().DisposeItWith(Disposable);\n        Heading = heading.ToReadOnlyBindableReactiveProperty().DisposeItWith(Disposable);\n        Velocity = velocity.ToReadOnlyBindableReactiveProperty().DisposeItWith(Disposable);\n        HomeAzimuth = homeAzimuth.ToReadOnlyBindableReactiveProperty().DisposeItWith(Disposable);\n        Roll = positionClientEx.Roll.ToBindableReactiveProperty().DisposeItWith(Disposable);\n        Pitch = positionClientEx.Pitch.ToBindableReactiveProperty().DisposeItWith(Disposable);\n        IsArmed = positionClientEx\n            .IsArmed.DistinctUntilChanged()\n            .ObserveOnUIThreadDispatcher()\n            .Select(b => b)\n            .ToBindableReactiveProperty()\n            .DisposeItWith(Disposable);\n        StatusText = positionClientEx\n            .IsArmed.ObserveOnUIThreadDispatcher()\n            .Select(b =>\n                b\n                    ? RS.UavWidgetViewModel_StatusText_Armed\n                    : RS.UavWidgetViewModel_StatusText_DisArmed\n            )\n            .ToBindableReactiveProperty<string>()\n            .DisposeItWith(Disposable);\n        VibrationX = new BindableReactiveProperty<float>().DisposeItWith(Disposable);\n        VibrationY = new BindableReactiveProperty<float>().DisposeItWith(Disposable);\n        VibrationZ = new BindableReactiveProperty<float>().DisposeItWith(Disposable);\n        Clipping0 = new BindableReactiveProperty<uint>().DisposeItWith(Disposable);\n        Clipping1 = new BindableReactiveProperty<uint>().DisposeItWith(Disposable);\n        Clipping2 = new BindableReactiveProperty<uint>().DisposeItWith(Disposable);\n        ArmedTime = new BindableReactiveProperty<TimeSpan>().DisposeItWith(Disposable);\n        Observable\n            .Timer(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(4))\n            .Subscribe(_ => StatusText.Value = string.Empty)\n            .DisposeItWith(Disposable);\n        heartbeatClient\n            .Link.State.ObserveOnUIThreadDispatcher()\n            .Skip(1)\n            .Subscribe(ChangeLinkStatus)\n            .DisposeItWith(Disposable);\n\n        CurrentFlightModeRttBox = new SingleRttBoxViewModel<string>(\n            nameof(CurrentFlightModeRttBox),\n            loggerFactory,\n            modeClient.CurrentMode.Select(mode => mode.Name),\n            null\n        )\n        {\n            Header = RS.UavRttItem_Mode,\n            Icon = MaterialIconKind.FlightMode,\n            UpdateAction = (model, mode) => model.ValueString = mode,\n            Status = DefaultStatusColor,\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        AltitudeRttBox = new TwoColumnRttBoxViewModel<AltitudeRttBoxData>(\n            nameof(AltitudeRttBox),\n            loggerFactory,\n            altitudeAgl\n                .CombineLatest(\n                    altitudeMsl,\n                    _altitudeUnit.CurrentUnitItem,\n                    (agl, msl, unit) => new AltitudeRttBoxData(agl, msl, unit)\n                )\n                .ObserveOnUIThreadDispatcher()\n                .ThrottleLast(TimeSpan.FromMilliseconds(200)),\n            null\n        )\n        {\n            Header = RS.UavRttItem_Altitude,\n            Icon = MaterialIconKind.Altimeter,\n            UpdateAction = (model, changes) =>\n            {\n                model.Left.ValueString = changes.AltitudeUnit.PrintFromSi(\n                    changes.AltitudeAgl,\n                    \"F2\"\n                );\n                model.Right.ValueString = changes.AltitudeUnit.PrintFromSi(\n                    changes.AltitudeMsl,\n                    \"F2\"\n                );\n\n                model.Left.UnitSymbol = changes.AltitudeUnit.Symbol;\n                model.Right.UnitSymbol = changes.AltitudeUnit.Symbol;\n\n                CheckSpeedAltitude(\n                    changes.AltitudeAgl,\n                    Math.Round(_gnssClient.Main.GroundVelocity.CurrentValue)\n                );\n            },\n            Status = DefaultStatusColor,\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n        AltitudeRttBox.Left.Header = \"AGL\";\n        AltitudeRttBox.Right.Header = \"MSL\";\n\n        VelocityRttBox = new SplitDigitRttBoxViewModel(\n            nameof(VelocityRttBox),\n            loggerFactory,\n            unitService,\n            VelocityUnit.Id,\n            velocity,\n            null\n        )\n        {\n            Header = RS.UavRttItem_Velocity,\n            ShortHeader = \"GS\",\n            Icon = MaterialIconKind.Speedometer,\n            Status = DefaultStatusColor,\n            FormatString = \"F2\",\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        AzimuthRttBox = new SplitDigitRttBoxViewModel(\n            nameof(AzimuthRttBox),\n            loggerFactory,\n            unitService,\n            AngleUnit.Id,\n            azimuth,\n            null\n        )\n        {\n            Header = RS.UavRttItem_Azimuth,\n            Icon = MaterialIconKind.SunAzimuth,\n            Status = DefaultStatusColor,\n            FormatString = \"F2\",\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        BatteryRttBox = new KeyValueRttBoxViewModel<BatteryRttBoxData>(\n            nameof(BatteryRttBox),\n            loggerFactory,\n            batteryCharge\n                .CombineLatest(\n                    batteryAmperage,\n                    batteryVoltage,\n                    batteryConsumed,\n                    _progressUnit.CurrentUnitItem,\n                    _amperageUnit.CurrentUnitItem,\n                    _capacityUnit.CurrentUnitItem,\n                    _voltageUnit.CurrentUnitItem,\n                    (bC, bA, bV, bCo, prog, amp, cap, vol) =>\n                        new BatteryRttBoxData(bC, bA, bV, bCo, prog, amp, cap, vol)\n                )\n                .ObserveOnUIThreadDispatcher()\n                .ThrottleLast(TimeSpan.FromMilliseconds(200)),\n            null\n        )\n        {\n            Header = RS.UavRttItem_Battery,\n            Icon = MaterialIconKind.Battery10,\n            UpdateAction = (model, changes) =>\n            {\n                model[\n                    0,\n                    RS.UavWidgetViewModel_BatteryRttBox_BatteryCharge_Header,\n                    changes.ProgressUnit.Symbol\n                ].ValueString = changes.ProgressUnit.PrintFromSi(changes.Charge, \"F2\");\n                model[\n                    1,\n                    RS.UavWidgetViewModel_BatteryRttBox_BatteryAmperage_Header,\n                    changes.AmperageUnit.Symbol\n                ].ValueString = changes.AmperageUnit.PrintFromSi(changes.Amperage, \"F2\");\n                model[\n                    2,\n                    RS.UavWidgetViewModel_BatteryRttBox_BatteryVoltage_Header,\n                    changes.VoltageUnit.Symbol\n                ].ValueString = changes.VoltageUnit.PrintFromSi(changes.Voltage, \"F2\");\n                model[\n                    3,\n                    RS.UavWidgetViewModel_BatteryRttBox_BatteryConsumed_Header,\n                    changes.CapacityUnit.Symbol\n                ].ValueString = changes.CapacityUnit.PrintFromSi(changes.Consumed, \"F2\");\n\n                ChangeBatteryStatus(changes.Charge);\n            },\n            Status = DefaultStatusColor,\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        GnssRttBox = new KeyValueRttBoxViewModel<GnssRttBoxData>(\n            nameof(GnssRttBox),\n            loggerFactory,\n            satelliteCount\n                .CombineLatest(\n                    hdop,\n                    vdop,\n                    rtkMode,\n                    (satellites, hdopValue, vdopValue, mode) =>\n                        new GnssRttBoxData(satellites, hdopValue, vdopValue, mode)\n                )\n                .ObserveOnUIThreadDispatcher()\n                .ThrottleLast(TimeSpan.FromMilliseconds(200)),\n            null\n        )\n        {\n            Header = RS.UavRttItem_GNSS,\n            Icon = MaterialIconKind.GpsFixed,\n            UpdateAction = (model, changes) =>\n            {\n                model[\n                    0,\n                    RS.UavWidgetViewModel_GnssRttBox_SatellitesCount_Header,\n                    null\n                ].ValueString = changes.Sattelites.ToString();\n                model[1, RS.UavWidgetViewModel_GnssRttBox_Hdop_Header, null].ValueString =\n                    changes.HdopCount.ToString(\"F2\");\n                model[2, RS.UavWidgetViewModel_GnssRttBox_Vdop_Header, null].ValueString =\n                    changes.VdopCount.ToString(\"F2\");\n                model[3, RS.UavWidgetViewModel_GnssRttBox_Mode_Header, null].ValueString =\n                    GpsFixTypeToString(changes.Mode);\n                ChangeGnssStatus(changes.Sattelites, changes.Mode);\n            },\n            Status = DefaultStatusColor,\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        LinkQualityRttBox = new SplitDigitRttBoxViewModel(\n            nameof(LinkQualityRttBox),\n            loggerFactory,\n            unitService,\n            ProgressUnit.Id,\n            heartbeatClient.LinkQuality,\n            null\n        )\n        {\n            Header = RS.UavRttItem_Link,\n            Icon = MaterialIconKind.Wifi,\n            Status = DefaultStatusColor,\n            FormatString = \"F2\",\n        }\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n    }\n\n    public ReactiveCommand TakeOff { get; }\n    public ICommand AutoMode { get; }\n    public ICommand Rtl { get; }\n    public ICommand Land { get; }\n    public ICommand Guided { get; }\n    public ICommand StartMission { get; }\n\n    public MissionProgressViewModel MissionProgress { get; }\n\n    public SingleRttBoxViewModel<string> CurrentFlightModeRttBox { get; }\n    public TwoColumnRttBoxViewModel AltitudeRttBox { get; }\n    public SplitDigitRttBoxViewModel VelocityRttBox { get; }\n    public SplitDigitRttBoxViewModel AzimuthRttBox { get; }\n    public SplitDigitRttBoxViewModel LinkQualityRttBox { get; }\n    public KeyValueRttBoxViewModel BatteryRttBox { get; }\n    public KeyValueRttBoxViewModel GnssRttBox { get; }\n\n    public BindableReactiveProperty<float> VibrationX { get; }\n    public BindableReactiveProperty<float> VibrationY { get; }\n    public BindableReactiveProperty<float> VibrationZ { get; }\n    public BindableReactiveProperty<uint> Clipping0 { get; }\n    public BindableReactiveProperty<uint> Clipping1 { get; }\n    public BindableReactiveProperty<uint> Clipping2 { get; }\n    public BindableReactiveProperty<double> Roll { get; }\n    public BindableReactiveProperty<double> Pitch { get; }\n    public IReadOnlyBindableReactiveProperty<double> Velocity { get; }\n    public IReadOnlyBindableReactiveProperty<double> AltitudeAgl { get; }\n    public IReadOnlyBindableReactiveProperty<double> AltitudeMsl { get; }\n    public IReadOnlyBindableReactiveProperty<double> Heading { get; }\n    public IReadOnlyBindableReactiveProperty<double> HomeAzimuth { get; }\n    public IReadOnlyBindableReactiveProperty<double> Azimuth { get; }\n    public BindableReactiveProperty<string> StatusText { get; }\n    public BindableReactiveProperty<bool> IsArmed { get; }\n    public BindableReactiveProperty<TimeSpan> ArmedTime { get; }\n\n    public IClientDevice Device { get; }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        yield return MissionProgress;\n        yield return CurrentFlightModeRttBox;\n        yield return AltitudeRttBox;\n        yield return VelocityRttBox;\n        yield return AzimuthRttBox;\n        yield return LinkQualityRttBox;\n        yield return BatteryRttBox;\n        yield return GnssRttBox;\n    }\n\n    private void ChangeBatteryStatus(double percent)\n    {\n        BatteryRttBox.Status = percent switch\n        {\n            > 0.7d => DefaultStatusColor,\n            > 0.5d => AsvColorKind.Warning,\n            > 0.4d => AsvColorKind.Warning | AsvColorKind.Blink,\n            < 0.3d => AsvColorKind.Error | AsvColorKind.Blink,\n            _ => DefaultStatusColor,\n        };\n    }\n\n    private void ChangeLinkStatus(LinkState state)\n    {\n        LinkQualityRttBox.Status = state switch\n        {\n            Common.LinkState.Connected => AsvColorKind.Success,\n            Common.LinkState.Downgrade => AsvColorKind.Warning,\n            Common.LinkState.Disconnected => AsvColorKind.Error,\n            _ => AsvColorKind.None,\n        };\n    }\n\n    private void CheckSpeedAltitude(double alt, double gs)\n    {\n        if (gs > DangerHighSpeed && alt < CriticalAltitude)\n        {\n            StatusText.Value = RS.UavWidgetViewModel_StatusText_PullUp;\n            AltitudeRttBox.StatusText = RS.UavWidgetViewModel_StatusText_PullUp;\n            AltitudeRttBox.Status = AsvColorKind.Warning | AsvColorKind.Blink;\n        }\n        else\n        {\n            StatusText.Value = string.Empty;\n            AltitudeRttBox.StatusText = string.Empty;\n            AltitudeRttBox.Status = DefaultStatusColor;\n        }\n    }\n\n    private void ChangeGnssStatus(int satellitesCount, Mavlink.Common.GpsFixType mode)\n    {\n        if (_gnssClient is null)\n        {\n            return;\n        }\n\n        if (\n            mode == Mavlink.Common.GpsFixType.GpsFixTypeRtkFloat\n            || satellitesCount > WarningSatelliteAmount.Start.Value\n            || satellitesCount < WarningSatelliteAmount.End.Value\n        )\n        {\n            GnssRttBox.Status = AsvColorKind.Warning;\n            return;\n        }\n\n        if (\n            mode != Mavlink.Common.GpsFixType.GpsFixTypeRtkFixed\n            || _gnssClient.Main.Info.CurrentValue.SatellitesVisible < DangerSatelliteCount\n        )\n        {\n            GnssRttBox.Status = AsvColorKind.Error;\n            return;\n        }\n\n        GnssRttBox.Status = DefaultStatusColor;\n    }\n\n    private string GpsFixTypeToString(Mavlink.Common.GpsFixType type)\n    {\n        return type switch\n        {\n            Mavlink.Common.GpsFixType.GpsFixType2dFix => RS.GpsFixType_GpsFixType2dFix,\n            Mavlink.Common.GpsFixType.GpsFixTypeRtkFloat => RS.GpsFixType_GpsFixTypeRtkFloat,\n            Mavlink.Common.GpsFixType.GpsFixTypeRtkFixed => RS.GpsFixType_GpsFixTypeRtkFixed,\n            Mavlink.Common.GpsFixType.GpsFixTypeDgps => RS.GpsFixType_GpsFixTypeDgps,\n            Mavlink.Common.GpsFixType.GpsFixTypePpp => RS.GpsFixType_GpsFixTypePpp,\n            Mavlink.Common.GpsFixType.GpsFixType3dFix => RS.GpsFixType_GpsFixType3dFix,\n            Mavlink.Common.GpsFixType.GpsFixTypeStatic => RS.GpsFixType_GpsFixTypeStatic,\n            Mavlink.Common.GpsFixType.GpsFixTypeNoGps => RS.GpsFixType_GpsFixTypeNoGps,\n            _ => string.Empty,\n        };\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Anchors/FlightModeAnchorsExtension.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Asv.Modeling;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class FlightModeAnchorsExtension(IDeviceManager conn, ILoggerFactory loggerFactory)\n    : IExtensionFor<IFlightModePage>\n{\n    public void Extend(IFlightModePage context, CompositeDisposable contextDispose)\n    {\n        conn.Explorer.InitializedDevices.PopulateTo(\n                context.Map.Anchors,\n                TryCreateAnchor,\n                RemoveAnchor\n            )\n            .DisposeItWith(contextDispose);\n    }\n\n    private UavAnchor? TryCreateAnchor(IClientDevice device)\n    {\n        var pos = device.GetMicroservice<IPositionClientEx>();\n        return pos != null ? new UavAnchor(device.Id, conn, device, pos, loggerFactory) : null;\n    }\n\n    private static bool RemoveAnchor(IClientDevice dev, UavAnchor anchor)\n    {\n        return anchor.DeviceId == dev.Id;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/FlightModePageView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:avalonia=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    xmlns:avalonia1=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    xmlns:geoMap=\"clr-namespace:Asv.Avalonia.GeoMap;assembly=Asv.Avalonia.GeoMap\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"1024\"\n    d:DesignHeight=\"500\"\n    x:Class=\"Asv.Drones.FlightModePageView\"\n    x:DataType=\"drones:FlightModePageViewModel\"\n>\n    <Design.DataContext>\n        <drones:FlightModePageViewModel />\n    </Design.DataContext>\n    <Panel>\n        <geoMap:MapView DataContext=\"{CompiledBinding Map}\" />\n        <ItemsControl ItemsSource=\"{Binding WidgetsView}\">\n            <ItemsControl.ItemsPanel>\n                <ItemsPanelTemplate>\n                    <avalonia1:WorkspacePanel\n                        AttachedToVisualTree=\"WorkspacePanel_OnAttachedToVisualTree\"\n                        WorkspaceChanged=\"OnWorkspaceChanged\"\n                    />\n                </ItemsPanelTemplate>\n            </ItemsControl.ItemsPanel>\n            <ItemsControl.ItemTemplate>\n                <DataTemplate>\n                    <Border\n                        Background=\"{DynamicResource SystemAltMediumHighColor}\"\n                        Opacity=\"0.9\"\n                        BorderThickness=\"{DynamicResource ThemeBorderThickness}\"\n                        CornerRadius=\"{DynamicResource ControlCornerRadius}\"\n                        Padding=\"8\"\n                        BorderBrush=\"{DynamicResource SystemControlTransientBorderBrush}\"\n                        HorizontalAlignment=\"Stretch\"\n                    >\n                        <DockPanel>\n                            <Grid ColumnDefinitions=\"Auto,5,*, Auto\" DockPanel.Dock=\"Top\">\n                                <avalonia:MaterialIcon\n                                    Kind=\"{Binding Icon}\"\n                                    avalonia1:AsvPallete.Color=\"{CompiledBinding IconColor}\"\n                                />\n                                <TextBlock\n                                    VerticalAlignment=\"Center\"\n                                    Grid.Column=\"2\"\n                                    Text=\"{Binding Header}\"\n                                />\n                                <ToggleButton\n                                    IsChecked=\"True\"\n                                    x:Name=\"PART_Expand\"\n                                    Grid.Column=\"3\"\n                                    Padding=\"0\"\n                                    Theme=\"{StaticResource TransparentButton}\"\n                                >\n                                    <Panel Width=\"20\" Height=\"20\">\n                                        <avalonia:MaterialIcon\n                                            IsVisible=\"{Binding ElementName=PART_Expand, Path=IsChecked}\"\n                                            Kind=\"MenuUp\"\n                                        />\n                                        <avalonia:MaterialIcon\n                                            IsVisible=\"{Binding ElementName=PART_Expand, Path=!IsChecked}\"\n                                            Kind=\"MenuDown\"\n                                        />\n                                    </Panel>\n                                </ToggleButton>\n                            </Grid>\n                            <ContentControl\n                                Margin=\"0,4,0,0\"\n                                IsVisible=\"{Binding ElementName=PART_Expand, Path=IsChecked}\"\n                                Content=\"{Binding}\"\n                            />\n                        </DockPanel>\n                    </Border>\n                </DataTemplate>\n            </ItemsControl.ItemTemplate>\n        </ItemsControl>\n        <geoMap:MapCompass\n            Rotation=\"{Binding Map.Rotation.Value}\"\n            HorizontalAlignment=\"Right\"\n            VerticalAlignment=\"Bottom\"\n            Margin=\"8\"\n            TouchpadRotationSensitivity=\"1\"\n        />\n    </Panel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/FlightModePageView.axaml.cs",
    "content": "﻿using Asv.Avalonia;\nusing Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic sealed class FlightModePageViewConfig\n{\n    public double LeftColumnActualWidth { get; set; } = -1;\n    public double CenterColumnActualWidth { get; set; } = -1;\n    public double RightColumnActualWidth { get; set; } = -1;\n    public double CenterRowActualHeight { get; set; } = -1;\n    public double BottomRowActualHeight { get; set; } = -1;\n}\n\npublic partial class FlightModePageView : UserControl\n{\n    private readonly ILayoutService _layoutService;\n    private FlightModePageViewConfig? _config;\n    private WorkspacePanel? _workspace;\n\n    public FlightModePageView()\n        : this(NullLayoutService.Instance)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public FlightModePageView(ILayoutService layoutService)\n    {\n        _layoutService = layoutService;\n        InitializeComponent();\n    }\n\n    private void WorkspacePanel_OnAttachedToVisualTree(\n        object? sender,\n        VisualTreeAttachmentEventArgs e\n    )\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        _workspace = sender as WorkspacePanel;\n        LoadLayout();\n    }\n\n    private void OnWorkspaceChanged(object? sender, WorkspaceEventArgs workspaceEventArgs)\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        SaveLayout(workspaceEventArgs);\n    }\n\n    private void LoadLayout()\n    {\n        _config = _layoutService.Get<FlightModePageViewConfig>(this);\n\n        if (_workspace is null)\n        {\n            return;\n        }\n\n        if (_config.LeftColumnActualWidth >= 0)\n        {\n            _workspace.LeftWidth = new GridLength(_config.LeftColumnActualWidth);\n        }\n\n        if (_config.CenterColumnActualWidth >= 0)\n        {\n            _workspace.CentralWidth = new GridLength(_config.CenterColumnActualWidth);\n        }\n\n        if (_config.RightColumnActualWidth >= 0)\n        {\n            _workspace.RightWidth = new GridLength(_config.RightColumnActualWidth);\n        }\n\n        if (_config.CenterRowActualHeight >= 0)\n        {\n            _workspace.CentralHeight = new GridLength(_config.CenterRowActualHeight);\n        }\n\n        if (_config.BottomRowActualHeight >= 0)\n        {\n            _workspace.BottomHeight = new GridLength(_config.BottomRowActualHeight);\n        }\n    }\n\n    private void SaveLayout(WorkspaceEventArgs workspaceEventArgs)\n    {\n        if (_config is null)\n        {\n            return;\n        }\n\n        if (DataContext is null)\n        {\n            return;\n        }\n\n        _config.LeftColumnActualWidth = workspaceEventArgs.LeftColumnActualWidth;\n        _config.CenterColumnActualWidth = workspaceEventArgs.CenterColumnActualWidth;\n        _config.RightColumnActualWidth = workspaceEventArgs.RightColumnActualWidth;\n        _config.CenterRowActualHeight = workspaceEventArgs.CenterRowActualHeight;\n        _config.BottomRowActualHeight = workspaceEventArgs.BottomRowActualHeight;\n        _layoutService.SetInMemory(this, _config);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/FlightModePageViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Avalonia.GeoMap;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.Modeling;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing ObservableCollections;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic sealed class FlightModePageViewModelConfig\n{\n    public GeoPoint MapCenter { get; set; } = GeoPoint.Zero;\n    public int Zoom { get; set; } = 0;\n}\n\npublic class FlightModePageViewModel : PageViewModel<IFlightModePage>, IFlightModePage\n{\n    public const string PageId = \"flight-mode\";\n    public const MaterialIconKind PageIcon = MaterialIconKind.MapSearch;\n\n    private FlightModePageViewModelConfig? _config;\n\n    public FlightModePageViewModel()\n        : this(\n            NullMapService.Instance,\n            DesignTime.CommandService,\n            DesignTime.LoggerFactory,\n            DesignTime.DialogService,\n            DesignTime.ExtensionService\n        ) { }\n\n    public FlightModePageViewModel(\n        IMapService mapService,\n        ICommandService cmd,\n        ILoggerFactory loggerFactory,\n        IDialogService dialogService,\n        IExtensionService ext\n    )\n        : base(PageId, cmd, loggerFactory, dialogService, ext)\n    {\n        Title = \"Flight (BETA)\";\n        Icon = PageIcon;\n\n        Widgets = [];\n        Widgets.SetRoutableParent(this).DisposeItWith(Disposable);\n        Widgets.DisposeRemovedItems().DisposeItWith(Disposable);\n        Widgets\n            .ObserveAdd()\n            .ObserveOnUIThreadDispatcher()\n            .Subscribe(_ => Widgets.Sort(FlightWidgetsComparer.Instance))\n            .DisposeItWith(Disposable);\n\n        WidgetsView = Widgets.ToNotifyCollectionChangedSlim().DisposeItWith(Disposable);\n\n        Map = new MapViewModel(nameof(Map), loggerFactory, mapService)\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        Events.Subscribe(InternalCatchEvent).DisposeItWith(Disposable);\n    }\n\n    public ObservableList<IFlightWidget> Widgets { get; }\n    public NotifyCollectionChangedSynchronizedViewList<IFlightWidget> WidgetsView { get; }\n    public IMap Map { get; }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        yield return Map;\n\n        foreach (var widget in WidgetsView)\n        {\n            yield return widget;\n        }\n    }\n\n    private ValueTask InternalCatchEvent(IRoutable src, AsyncRoutedEvent<IRoutable> e)\n    {\n        switch (e)\n        {\n            case SaveLayoutEvent saveLayoutEvent:\n                if (_config is null)\n                {\n                    break;\n                }\n\n                this.HandleSaveLayout(\n                    saveLayoutEvent,\n                    _config,\n                    cfg =>\n                    {\n                        cfg.MapCenter = Map.CenterMap.Value;\n                        cfg.Zoom = Map.Zoom.Value;\n                    },\n                    FlushingStrategy.FlushBothViewModelAndView\n                );\n                break;\n            case LoadLayoutEvent loadLayoutEvent:\n                _config = this.HandleLoadLayout<FlightModePageViewModelConfig>(\n                    loadLayoutEvent,\n                    cfg =>\n                    {\n                        Map.CenterMap.Value = cfg.MapCenter;\n                        Map.Zoom.Value = cfg.Zoom switch\n                        {\n                            < IZoomService.MinZoomLevel => IZoomService.MinZoomLevel,\n                            > IZoomService.MaxZoomLevel => IZoomService.MaxZoomLevel,\n                            _ => cfg.Zoom,\n                        };\n                    }\n                );\n                break;\n        }\n\n        return ValueTask.CompletedTask;\n    }\n\n    protected override void AfterLoadExtensions()\n    {\n        // nothing to do\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/HomePageFlightModeExtension.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Common;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class HomePageFlightModeExtension(ILoggerFactory loggerFactory) : IExtensionFor<IHomePage>\n{\n    public void Extend(IHomePage context, CompositeDisposable contextDispose)\n    {\n        context.Tools.Add(\n            OpenFlightCommand.StaticInfo.CreateAction(loggerFactory).DisposeItWith(contextDispose)\n        );\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/FlightModeClientDeviceWidgetExtension.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Modeling;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class FlightModeClientDeviceWidgetExtension(\n    IDeviceManager conn,\n    IClientDeviceWidgetFactory factory\n) : IExtensionFor<IFlightModePage>\n{\n    public void Extend(IFlightModePage context, CompositeDisposable contextDispose)\n    {\n        conn.Explorer.InitializedDevices.PopulateTo(\n                context.Widgets,\n                TryCreateWidget,\n                IsRemoveWidget\n            )\n            .DisposeItWith(contextDispose);\n    }\n\n    private IFlightWidget? TryCreateWidget(IClientDevice device)\n    {\n        return factory.CreateWidget(in device);\n    }\n\n    private static bool IsRemoveWidget(IClientDevice model, IFlightWidget vm)\n    {\n        return model.Id.ToString() == vm.Id.Args;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/DroneFlightWidgetViewModel.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Drones.Api;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\n\nnamespace Asv.Drones;\n\npublic class DroneFlightWidgetViewModel(\n    IDeviceManager deviceManager,\n    ILoggerFactory loggerFactory,\n    IExtensionService ext\n)\n    : DroneFlightWidgetViewModelBase<MavlinkClientDevice, IDroneFlightWidget>(\n        WidgetId,\n        deviceManager,\n        loggerFactory,\n        ext\n    ),\n        IDroneFlightWidget\n{\n    public const string WidgetId = \"drone\";\n\n    public DroneFlightWidgetViewModel()\n        : this(\n            NullDeviceManager.Instance,\n            NullLoggerFactory.Instance,\n            NullExtensionService.Instance\n        ) { }\n\n    protected override void AfterLoadExtensions()\n    {\n        // nothing to do\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/DroneWidgetCreationHandler.cs",
    "content": "﻿using System;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace Asv.Drones;\n\npublic class DroneWidgetCreationHandler(IServiceProvider services)\n    : IClientDeviceWidgetCreationHandler\n{\n    public Type DeviceType => typeof(MavlinkClientDevice);\n\n    public IFlightWidget? Create(in IClientDevice device)\n    {\n        if (device.GetMicroservice<IControlClient>() is not null)\n        {\n            if (device is not MavlinkClientDevice mavlinkDevice)\n            {\n                return null;\n            }\n\n            var widget = services.GetService<IDroneFlightWidget>();\n\n            widget?.InitWith(mavlinkDevice);\n\n            return widget;\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Plane/PlaneWidgetCreationHandler.cs",
    "content": "﻿using System;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace Asv.Drones.Plane;\n\npublic class PlaneWidgetCreationHandler(IServiceProvider services)\n    : IClientDeviceWidgetCreationHandler\n{\n    public Type DeviceType => typeof(ArduPlaneClientDevice);\n\n    public IFlightWidget? Create(in IClientDevice device)\n    {\n        if (device.GetMicroservice<IControlClient>() is not null)\n        {\n            if (device is not ArduPlaneClientDevice plane)\n            {\n                return null;\n            }\n\n            var widget = services.GetService<IPlaneWidget>();\n\n            widget?.InitWith(plane);\n\n            return widget;\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Plane/PlaneWidgetViewModel.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Drones.Api;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones.Plane;\n\npublic interface IPlaneWidget : IPlaneWidget<ArduPlaneClientDevice> { }\n\npublic interface IPlaneWidget<TPlane> : IDroneFlightWidget<TPlane>\n    where TPlane : ArduPlaneClientDevice { }\n\npublic class PlaneWidgetViewModel\n    : PlaneWidgetViewModelBase<ArduPlaneClientDevice, IPlaneWidget>,\n        IPlaneWidget\n{\n    public const string WidgetId = \"plane\";\n\n    public PlaneWidgetViewModel(\n        IDeviceManager deviceManager,\n        ILoggerFactory loggerFactory,\n        IExtensionService ext\n    )\n        : base(WidgetId, deviceManager, loggerFactory, ext) { }\n}\n\npublic abstract class PlaneWidgetViewModelBase<TPlane, TSelf>\n    : DroneFlightWidgetViewModelBase<TPlane, TSelf>\n    where TSelf : class, IFlightWidget<ArduPlaneClientDevice>\n    where TPlane : ArduPlaneClientDevice\n{\n    protected PlaneWidgetViewModelBase(\n        NavigationId id,\n        IDeviceManager deviceManager,\n        ILoggerFactory loggerFactory,\n        IExtensionService ext\n    )\n        : base(id, deviceManager, loggerFactory, ext) { }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Plane/Sections/PlaneSectionExtension.cs",
    "content": "﻿using System;\nusing Asv.Avalonia;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace Asv.Drones.Plane;\n\npublic class PlaneSectionExtension(IServiceProvider services) : IExtensionFor<IPlaneWidget>\n{\n    public void Extend(IPlaneWidget context, R3.CompositeDisposable contextDispose)\n    {\n        var section = services.GetRequiredKeyedService<PlaneSectionViewModel>(\n            PlaneSectionViewModel.SectionId\n        );\n        context.Sections.Add(section);\n\n        section.InitWith(context.Device ?? throw new NullReferenceException());\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Plane/Sections/PlaneSectionView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:plane=\"clr-namespace:Asv.Drones.Plane\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"800\"\n    d:DesignHeight=\"450\"\n    x:Class=\"Asv.Drones.Plane.PlaneSectionView\"\n    x:DataType=\"plane:PlaneSectionViewModel\"\n>\n    <StackPanel Orientation=\"Vertical\">\n        <TextBlock Text=\"{CompiledBinding Type}\" />\n        <TextBlock Text=\"{CompiledBinding Text}\" />\n    </StackPanel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Plane/Sections/PlaneSectionView.axaml.cs",
    "content": "﻿using Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Markup.Xaml;\n\nnamespace Asv.Drones.Plane;\n\npublic partial class PlaneSectionView : UserControl\n{\n    public PlaneSectionView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Plane/Sections/PlaneSectionViewModel.cs",
    "content": "﻿using System.Collections.Generic;\nusing Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones.Plane;\n\npublic class PlaneSectionViewModel : RoutableViewModel, IFlightWidgetSection<ArduPlaneClientDevice>\n{\n    public const string SectionId = \"plane-widget-section\";\n\n    public PlaneSectionViewModel()\n        : this(DesignTime.LoggerFactory) { }\n\n    public PlaneSectionViewModel(ILoggerFactory loggerFactory)\n        : base(SectionId, loggerFactory)\n    {\n        Order = -1;\n    }\n\n    public string? Type\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public string? Text\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public void InitWith(ArduPlaneClientDevice context)\n    {\n        Type = context.GetType().Name;\n        Text = context.Name.CurrentValue;\n    }\n\n    public int Order { get; }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Sections/AttitudeIndicator/AttitudeIndicatorSectionView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    x:Class=\"Asv.Drones.AttitudeIndicatorSectionView\"\n    x:DataType=\"drones:AttitudeIndicatorSectionViewModel\"\n>\n    <WrapPanel Orientation=\"Horizontal\" ItemWidth=\"220\">\n        <Viewbox>\n            <drones:UavAngleIndicator\n                RollAngle=\"{Binding Roll.Value}\"\n                PitchAngle=\"{Binding Pitch.Value}\"\n                CornerRadius=\"500\"\n            />\n        </Viewbox>\n        <Viewbox>\n            <drones:CompassUavIndicator\n                Heading=\"{CompiledBinding Heading.Value}\"\n                HomeAzimuth=\"{CompiledBinding HomeAzimuth.Value}\"\n            />\n        </Viewbox>\n    </WrapPanel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Sections/AttitudeIndicator/AttitudeIndicatorSectionView.axaml.cs",
    "content": "﻿using Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Markup.Xaml;\n\nnamespace Asv.Drones;\n\npublic partial class AttitudeIndicatorSectionView : UserControl\n{\n    public AttitudeIndicatorSectionView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Sections/AttitudeIndicator/AttitudeIndicatorSectionViewModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class AttitudeIndicatorSectionViewModel\n    : RoutableViewModel,\n        IFlightWidgetSection<MavlinkClientDevice>\n{\n    public const string SectionId = \"alt-indicator-widget-section\";\n\n    public AttitudeIndicatorSectionViewModel(ILoggerFactory loggerFactory)\n        : base(SectionId, loggerFactory) { }\n\n    public IReadOnlyBindableReactiveProperty<double> Roll { get; private set; }\n    public IReadOnlyBindableReactiveProperty<double> Pitch { get; private set; }\n    public IReadOnlyBindableReactiveProperty<double> Heading { get; private set; }\n    public IReadOnlyBindableReactiveProperty<double> HomeAzimuth { get; private set; }\n\n    public int Order => 0;\n\n    public void InitWith(MavlinkClientDevice device)\n    {\n        ArgumentNullException.ThrowIfNull(device);\n\n        var positionClientEx =\n            device.GetMicroservice<IPositionClientEx>()\n            ?? throw new ArgumentException(\n                $\"Unable to load {nameof(PositionClientEx)} from {device.Id}\"\n            );\n\n        Roll = positionClientEx.Roll.ToReadOnlyBindableReactiveProperty().DisposeItWith(Disposable);\n        Pitch = positionClientEx\n            .Pitch.ToReadOnlyBindableReactiveProperty()\n            .DisposeItWith(Disposable);\n\n        Heading = positionClientEx\n            .Yaw.ObserveOnUIThreadDispatcher()\n            .ThrottleLast(TimeSpan.FromMilliseconds(200))\n            .Select(Math.Truncate)\n            .ToReadOnlyBindableReactiveProperty()\n            .DisposeItWith(Disposable);\n\n        HomeAzimuth = positionClientEx\n            .Current.ObserveOnUIThreadDispatcher()\n            .Where(_ => positionClientEx.Home.CurrentValue.HasValue)\n            .ThrottleLast(TimeSpan.FromMilliseconds(200))\n            .Select(p => p.Azimuth(positionClientEx.Home.CurrentValue ?? GeoPoint.NaN))\n            .ToReadOnlyBindableReactiveProperty()\n            .DisposeItWith(Disposable);\n    }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Sections/AttitudeIndicator/DroneFlightWidgetExtensionAttitudeIndicatorSection.cs",
    "content": "﻿using System;\nusing Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic sealed class DroneFlightWidgetExtensionAttitudeIndicatorSection(\n    ILoggerFactory loggerFactory,\n    IServiceProvider services\n) : IExtensionFor<IDroneFlightWidget>\n{\n    public void Extend(IDroneFlightWidget context, CompositeDisposable contextDispose)\n    {\n        var section = services.GetRequiredKeyedService<AttitudeIndicatorSectionViewModel>(\n            AttitudeIndicatorSectionViewModel.SectionId\n        );\n        context.Sections.Add(section);\n        section.InitWith(context.Device ?? throw new NullReferenceException());\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Sections/FlightControl/DroneFlightWidgetFlightControlSectionExtension.cs",
    "content": "﻿using System;\nusing Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Microsoft.Extensions.DependencyInjection;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic sealed class DroneFlightWidgetFlightControlSectionExtension(IServiceProvider services)\n    : IExtensionFor<IDroneFlightWidget>\n{\n    public void Extend(IDroneFlightWidget context, CompositeDisposable contextDispose)\n    {\n        var flightControlViewModel =\n            services.GetRequiredKeyedService<FlightControlSectionViewModel>(\n                FlightControlSectionViewModel.SectionId\n            );\n        context.Sections.Add(flightControlViewModel);\n\n        flightControlViewModel.InitWith(context.Device ?? throw new NullReferenceException());\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Sections/FlightControl/FlightControlSectionView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:avalonia=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    xmlns:avalonia1=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    d:DesignHeight=\"300\"\n    x:Class=\"Asv.Drones.FlightControlSectionView\"\n    x:DataType=\"drones:FlightControlSectionViewModel\"\n>\n    <Design.DataContext>\n        <drones:FlightControlSectionViewModel />\n    </Design.DataContext>\n    <!-- <Grid RowDefinitions=\"Auto,Auto,*\" Margin=\"5\" RowSpacing=\"10\" HorizontalAlignment=\"Left\"> -->\n    <StackPanel\n        Grid.Column=\"0\"\n        Grid.Row=\"0\"\n        Margin=\"5 0 0 0\"\n        HorizontalAlignment=\"Stretch\"\n        Orientation=\"Horizontal\"\n    >\n        <StackPanel Orientation=\"Vertical\" VerticalAlignment=\"Top\">\n            <StackPanel.Styles>\n                <Style Selector=\"Button\">\n                    <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />\n                    <Setter Property=\"Theme\" Value=\"{StaticResource TransparentButton}\" />\n                    <Setter Property=\"CornerRadius\" Value=\"{StaticResource ControlCornerRadius}\" />\n                </Style>\n            </StackPanel.Styles>\n            <Button HorizontalContentAlignment=\"Stretch\" Command=\"{Binding AutoMode}\">\n                <StackPanel Spacing=\"5\" Orientation=\"Horizontal\">\n                    <avalonia1:MaterialIcon Kind=\"Automatic\" />\n                    <TextBlock Text=\"{x:Static drones:RS.UavAction_AutoMode_Name}\" />\n                </StackPanel>\n            </Button>\n            <Button HorizontalContentAlignment=\"Stretch\" Command=\"{Binding Guided}\">\n                <StackPanel Spacing=\"5\" Orientation=\"Horizontal\">\n                    <avalonia1:MaterialIcon Kind=\"Controller\" />\n                    <TextBlock Text=\"{x:Static drones:RS.UavAction_GuidedMode}\" />\n                </StackPanel>\n            </Button>\n            <Button HorizontalContentAlignment=\"Stretch\" Command=\"{Binding TakeOff}\">\n                <StackPanel Spacing=\"5\" Orientation=\"Horizontal\">\n                    <avalonia1:MaterialIcon Kind=\"AeroplaneTakeoff\" />\n                    <TextBlock Text=\"{x:Static drones:RS.UavAction_TakeOff}\" />\n                </StackPanel>\n            </Button>\n            <Button HorizontalContentAlignment=\"Stretch\" Command=\"{Binding Land}\">\n                <StackPanel Spacing=\"5\" Orientation=\"Horizontal\">\n                    <avalonia1:MaterialIcon Kind=\"FlightLand\" />\n                    <TextBlock Text=\"{x:Static drones:RS.UavAction_Land}\" />\n                </StackPanel>\n            </Button>\n            <Button HorizontalContentAlignment=\"Stretch\" Command=\"{Binding Rtl}\">\n                <StackPanel Spacing=\"5\" Orientation=\"Horizontal\">\n                    <avalonia1:MaterialIcon Kind=\"Home\" />\n                    <TextBlock Text=\"{x:Static drones:RS.UavAction_Rtl_Name}\" />\n                </StackPanel>\n            </Button>\n            <!-- <Button HorizontalContentAlignment=\"Stretch\" Command=\"{Binding StartMission}\"> -->\n            <!--     <StackPanel Spacing=\"5\" Orientation=\"Horizontal\"> -->\n            <!--         <avalonia1:MaterialIcon Kind=\"MapMarkerPath\" /> -->\n            <!--         <TextBlock Text=\"{x:Static drones:RS.UavAction_StartMission}\" /> -->\n            <!--     </StackPanel> -->\n            <!-- </Button> -->\n        </StackPanel>\n    </StackPanel>\n\n    <!-- </Grid> -->\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Sections/FlightControl/FlightControlSectionView.axaml.cs",
    "content": "﻿using Asv.Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class FlightControlSectionView : UserControl\n{\n    public FlightControlSectionView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Sections/FlightControl/FlightControlSectionViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Windows.Input;\nusing Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class FlightControlSectionViewModel\n    : RoutableViewModel,\n        IFlightWidgetSection<MavlinkClientDevice>\n{\n    public const string SectionId = \"flight-control-widget-section\";\n\n    private readonly IUnit _altitudeUnit;\n\n    public FlightControlSectionViewModel()\n        : base(SectionId, DesignTime.LoggerFactory)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n        TakeOff = new ReactiveCommand();\n        AutoMode = new ReactiveCommand();\n        Rtl = new ReactiveCommand();\n        Guided = new ReactiveCommand();\n\n        Land = new ReactiveCommand();\n        InitArgs(\"1\");\n    }\n\n    public FlightControlSectionViewModel(\n        INavigationService navigation,\n        IUnitService unitService,\n        ILoggerFactory loggerFactory\n    )\n        : base(SectionId, loggerFactory)\n    {\n        _altitudeUnit = unitService.Units[AltitudeUnit.Id];\n\n        TakeOff = new ReactiveCommand(\n            async (_, ct) =>\n            {\n                using var vm = new SetAltitudeDialogViewModel(_altitudeUnit, loggerFactory);\n                var dialog = new ContentDialog(vm, navigation)\n                {\n                    Title = RS.UavWidgetViewModel_SetAltitudeDialog_Title,\n                    PrimaryButtonText =\n                        RS.SetAltitudeDialogViewModel_ApplyDialog_PrimaryButton_TakeOff,\n                    SecondaryButtonText =\n                        RS.SetAltitudeDialogViewModel_ApplyDialog_SecondaryButton_Cancel,\n                    IsSecondaryButtonEnabled = true,\n                };\n\n                var result = await dialog.ShowAsync();\n\n                if (result == ContentDialogResult.Primary)\n                {\n                    await this.ExecuteCommand(\n                        TakeOffCommand.Id,\n                        new DoubleArg(\n                            _altitudeUnit.CurrentUnitItem.Value.ParseToSi(vm.Altitude.Value)\n                        ),\n                        ct\n                    );\n                }\n            }\n        ).DisposeItWith(Disposable);\n        Rtl = new BindableAsyncCommand(RTLCommand.Id, this);\n        Land = new BindableAsyncCommand(LandCommand.Id, this);\n        Guided = new BindableAsyncCommand(GuidedModeCommand.Id, this);\n        AutoMode = new BindableAsyncCommand(AutoModeCommand.Id, this);\n    }\n\n    public ReactiveCommand TakeOff { get; }\n    public ICommand AutoMode { get; }\n    public ICommand Rtl { get; }\n    public ICommand Land { get; }\n    public ICommand Guided { get; }\n    public int Order => 1;\n\n    public IClientDevice Device { get; private set; }\n\n    public void InitWith(MavlinkClientDevice device)\n    {\n        ArgumentNullException.ThrowIfNull(device);\n\n        Device = device;\n        InitArgs(device.Id.AsString());\n    }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Sections/Telemetry/DroneFlightWidgetTelemetrySectionExtension.cs",
    "content": "﻿using System;\nusing Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Microsoft.Extensions.DependencyInjection;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic sealed class DroneFlightWidgetTelemetrySectionExtension(IServiceProvider services)\n    : IExtensionFor<IDroneFlightWidget>\n{\n    public void Extend(IDroneFlightWidget context, CompositeDisposable contextDispose)\n    {\n        var vm = services.GetRequiredKeyedService<TelemetrySectionViewModel>(\n            TelemetrySectionViewModel.SectionId\n        );\n        context.Sections.Add(vm);\n        vm.InitWith(context.Device ?? throw new NullReferenceException());\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Sections/Telemetry/TelemetrySectionView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    d:DesignHeight=\"300\"\n    x:Class=\"Asv.Drones.TelemetrySectionView\"\n    x:DataType=\"drones:TelemetrySectionViewModel\"\n>\n    <Design.DataContext>\n        <drones:TelemetrySectionViewModel />\n    </Design.DataContext>\n\n    <WrapPanel\n        ItemSpacing=\"4\"\n        LineSpacing=\"4\"\n        Orientation=\"Horizontal\"\n        HorizontalAlignment=\"Left\"\n        VerticalAlignment=\"Top\"\n    >\n        <drones:BatteryUavIndicator DataContext=\"{CompiledBinding BatteryUavIndicator}\" />\n        <drones:AltitudeUavIndicator DataContext=\"{CompiledBinding AltitudeUavIndicator}\" />\n        <drones:VelocityUavIndicator DataContext=\"{CompiledBinding VelocityUavIndicator}\" />\n        <drones:AngleUavRttIndicator DataContext=\"{CompiledBinding AngleUavRttIndicator}\" />\n    </WrapPanel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Sections/Telemetry/TelemetrySectionView.axaml.cs",
    "content": "﻿using Asv.Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class TelemetrySectionView : UserControl\n{\n    public TelemetrySectionView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/Sections/Telemetry/TelemetrySectionViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class TelemetrySectionViewModel\n    : RoutableViewModel,\n        IFlightWidgetSection<MavlinkClientDevice>\n{\n    public const string SectionId = \"telemetry-widget-sectioin\";\n\n    private readonly ILoggerFactory _loggerFactory;\n    private readonly IUnitService _unitService;\n    private const AsvColorKind DefaultStatusColor = AsvColorKind.Info5;\n\n    public TelemetrySectionViewModel()\n        : base(SectionId, DesignTime.LoggerFactory)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n\n        InitArgs(\"1\");\n    }\n\n    public TelemetrySectionViewModel(ILoggerFactory loggerFactory, IUnitService unitService)\n        : base(SectionId, loggerFactory)\n    {\n        _loggerFactory = loggerFactory;\n        _unitService = unitService;\n    }\n\n    public AltitudeUavIndicatorViewModel AltitudeUavIndicator { get; private set; }\n    public BatteryUavIndicatorViewModel BatteryUavIndicator { get; private set; }\n    public VelocityUavIndicatorViewModel VelocityUavIndicator { get; private set; }\n    public AngleUavRttIndicatorViewModel AngleUavRttIndicator { get; private set; }\n    public int Order => 2;\n\n    public void InitWith(MavlinkClientDevice device)\n    {\n        ArgumentNullException.ThrowIfNull(device);\n        InitArgs(device.Id.AsString());\n\n        var positionClientEx =\n            device.GetMicroservice<IPositionClientEx>()\n            ?? throw new ArgumentException(\n                $\"Unable to load {nameof(PositionClientEx)} from {device.Id}\"\n            );\n\n        var gnssClient =\n            device.GetMicroservice<IGnssClientEx>()\n            ?? throw new ArgumentException(\n                $\"Unable to load {nameof(GnssClientEx)} from {device.Id}\"\n            );\n        var telemetryClient =\n            device.GetMicroservice<ITelemetryClientEx>()\n            ?? throw new ArgumentException(\n                $\"Unable to load {nameof(TelemetryClientEx)} from {device.Id}\"\n            );\n        var heartbeatClient =\n            device.GetMicroservice<IHeartbeatClient>()\n            ?? throw new ArgumentException(\n                $\"Unable to load {nameof(HeartbeatClient)} from {device.Id}\"\n            );\n        var modeClient =\n            device.GetMicroservice<IModeClient>()\n            ?? throw new ArgumentException($\"Unable to load {nameof(IModeClient)}\");\n\n        var altitudeAgl = new ReactiveProperty<double>(10).DisposeItWith(Disposable);\n        var altitudeMsl = new ReactiveProperty<double>(14).DisposeItWith(Disposable);\n\n        var velocity = new ReactiveProperty<double>(199).DisposeItWith(Disposable);\n        var batteryAmperage = new ReactiveProperty<double>(39).DisposeItWith(Disposable);\n        var batteryVoltage = new ReactiveProperty<double>(34).DisposeItWith(Disposable);\n        var batteryCharge = new ReactiveProperty<double>(123).DisposeItWith(Disposable);\n        var batteryConsumed = new ReactiveProperty<double>(39).DisposeItWith(Disposable);\n\n        var roll = new ReactiveProperty<double>(10).DisposeItWith(Disposable);\n        var pitch = new ReactiveProperty<double>(30).DisposeItWith(Disposable);\n\n        positionClientEx\n            .Base.GlobalPosition.ObserveOnUIThreadDispatcher()\n            .Select(pld => Math.Truncate((pld?.RelativeAlt ?? double.NaN) / 1000d))\n            .Subscribe(x => altitudeAgl.Value = x)\n            .DisposeItWith(Disposable);\n\n        positionClientEx\n            .Base.GlobalPosition.ObserveOnUIThreadDispatcher()\n            .Select(pld => Math.Truncate((pld?.Alt ?? double.NaN) / 1000d))\n            .Subscribe(x => altitudeMsl.Value = x)\n            .DisposeItWith(Disposable);\n\n        gnssClient\n            .Main.GroundVelocity.ObserveOnUIThreadDispatcher()\n            .Select(Math.Truncate)\n            .Subscribe(x => velocity.Value = x)\n            .DisposeItWith(Disposable);\n\n        telemetryClient\n            .BatteryCurrent.ObserveOnUIThreadDispatcher()\n            .Subscribe(x => batteryAmperage.Value = x)\n            .DisposeItWith(Disposable);\n\n        telemetryClient\n            .BatteryVoltage.ObserveOnUIThreadDispatcher()\n            .Subscribe(x => batteryVoltage.Value = x)\n            .DisposeItWith(Disposable);\n\n        telemetryClient\n            .BatteryCharge.ObserveOnUIThreadDispatcher()\n            .Subscribe(x => batteryCharge.Value = x)\n            .DisposeItWith(Disposable);\n\n        telemetryClient\n            .BatteryCurrent.ObserveOnUIThreadDispatcher()\n            .Select(d =>\n                d == 0\n                    ? double.NaN\n                    : Math.Round(d * positionClientEx.ArmedTime.CurrentValue.TotalHours, 2)\n            )\n            .Subscribe(x => batteryConsumed.Value = x)\n            .DisposeItWith(Disposable);\n\n        positionClientEx.Roll.Subscribe(x => roll.Value = x).DisposeItWith(Disposable);\n        positionClientEx.Pitch.Subscribe(x => pitch.Value = x).DisposeItWith(Disposable);\n\n        var altitudeUnit = _unitService.Units[AltitudeUnit.Id];\n        var capacityUnit = _unitService.Units[CapacityUnit.Id];\n        var amperageUnit = _unitService.Units[AmperageUnit.Id];\n        var voltageUnit = _unitService.Units[VoltageUnit.Id];\n        var progressUnit = _unitService.Units[ProgressUnit.Id];\n        var angleUnit = _unitService.Units[AngleUnit.Id];\n\n        AltitudeUavIndicator = new AltitudeUavIndicatorViewModel(\n            nameof(AltitudeUavIndicator),\n            _loggerFactory,\n            altitudeAgl,\n            altitudeMsl,\n            altitudeUnit.CurrentUnitItem,\n            DefaultStatusColor\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        BatteryUavIndicator = new BatteryUavIndicatorViewModel(\n            nameof(BatteryUavIndicator),\n            _loggerFactory,\n            batteryCharge,\n            batteryAmperage,\n            batteryVoltage,\n            batteryConsumed,\n            progressUnit.CurrentUnitItem,\n            amperageUnit.CurrentUnitItem,\n            capacityUnit.CurrentUnitItem,\n            voltageUnit.CurrentUnitItem,\n            DefaultStatusColor\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        VelocityUavIndicator = new VelocityUavIndicatorViewModel(\n            nameof(VelocityUavIndicator),\n            _loggerFactory,\n            _unitService,\n            velocity,\n            DefaultStatusColor\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        AngleUavRttIndicator = new AngleUavRttIndicatorViewModel(\n            nameof(AngleUavRttIndicator),\n            _loggerFactory,\n            pitch,\n            roll,\n            angleUnit.CurrentUnitItem,\n            DefaultStatusColor\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n    }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        yield return AltitudeUavIndicator;\n        yield return BatteryUavIndicator;\n        yield return VelocityUavIndicator;\n        yield return AngleUavRttIndicator;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Test/PluginFlightItemView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"800\"\n    d:DesignHeight=\"450\"\n    x:Class=\"Asv.Drones.PluginFlightItemView\"\n    x:DataType=\"drones:PluginFlightItemViewModel\"\n>\n\n\n\t\t\t\t\tPlugin exposes some info\n\t\t\n</UserControl\n>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Test/PluginFlightItemView.axaml.cs",
    "content": "﻿using Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Markup.Xaml;\n\nnamespace Asv.Drones;\n\npublic partial class PluginFlightItemView : UserControl\n{\n    public PluginFlightItemView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Test/PluginFlightItemViewModel.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing ObservableCollections;\n\nnamespace Asv.Drones;\n\npublic class PluginFlightItemViewModel : FlightWidgetViewModel<PluginFlightItemViewModel> // TODO: Remove on new FlightMode release\n{\n    private const string WidgetId = \"test-widget\";\n\n    public PluginFlightItemViewModel(ILoggerFactory loggerFactory, IExtensionService ext)\n        : base(WidgetId, loggerFactory, ext) { }\n\n    public override int Order => 1;\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/FlightMode/Widgets/Test/PluginFlightItemWidgetExtension.cs",
    "content": "﻿using System;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Avalonia.GeoMap;\nusing Asv.Avalonia.IO;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class PluginFlightItemWidgetExtension(ILoggerFactory loggerFactory, IExtensionService ext)\n    : IExtensionFor<IFlightModePage>\n{\n    public void Extend(IFlightModePage context, CompositeDisposable contextDispose)\n    {\n#if RELEASE\n        return;\n#endif\n        var vm = new PluginFlightItemViewModel(loggerFactory, ext);\n\n        vm.Header = \"Plugin payload for something\";\n\n        context.Widgets.Add(vm);\n\n        Task.Run(async () =>\n        {\n            while (!contextDispose.IsDisposed)\n            {\n                if (context.IsDisposed)\n                {\n                    return;\n                }\n\n                await Task.Delay(5000);\n                context.Widgets.Remove(vm);\n                await Task.Delay(5000);\n                context.Widgets.Add(vm);\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/Dialogs/SavePacketMessagesDialogView.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    Width=\"200\"\n    Height=\"140\"\n    x:CompileBindings=\"True\"\n    x:DataType=\"drones:SavePacketMessagesDialogViewModel\"\n    x:Class=\"Asv.Drones.SavePacketMessagesDialogView\"\n>\n    <Design.DataContext>\n        <drones:SavePacketMessagesDialogViewModel />\n    </Design.DataContext>\n    <Panel>\n        <StackPanel Spacing=\"10\" Margin=\"10\">\n            <RadioButton Content=\";\" IsChecked=\"{CompiledBinding IsSemicolon.Value, Mode=TwoWay}\" />\n            <RadioButton Content=\",\" IsChecked=\"{CompiledBinding IsComa.Value, Mode=TwoWay}\" />\n            <RadioButton\n                Content=\"{x:Static drones:RS.SavePacketMessagesDialogView_Separator_Tab}\"\n                IsChecked=\"{CompiledBinding IsTab.Value, Mode=TwoWay}\"\n            />\n        </StackPanel>\n    </Panel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/Dialogs/SavePacketMessagesDialogView.axaml.cs",
    "content": "using Asv.Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class SavePacketMessagesDialogView : UserControl\n{\n    public SavePacketMessagesDialogView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/Dialogs/SavePacketMessagesDialogViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Collections.Specialized;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class SavePacketMessagesDialogViewModel : DialogViewModelBase\n{\n    public const string ViewModelId = $\"{PacketViewerViewModel.PageId}.{BaseId}.separator\";\n    public const string DefaultSeparator = \";\";\n    public const string DefaultShieldSymbol = \",\";\n\n    public SavePacketMessagesDialogViewModel()\n        : this(DesignTime.LoggerFactory)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public SavePacketMessagesDialogViewModel(ILoggerFactory loggerFactory)\n        : base(ViewModelId, loggerFactory)\n    {\n        IsSemicolon = new BindableReactiveProperty<bool>(true).DisposeItWith(Disposable);\n        IsComa = new BindableReactiveProperty<bool>(false).DisposeItWith(Disposable);\n        IsTab = new BindableReactiveProperty<bool>(false).DisposeItWith(Disposable);\n        Separator = DefaultSeparator;\n        ShieldSymbol = DefaultShieldSymbol;\n\n        _sub2 = IsSemicolon\n            .Where(value => value)\n            .Subscribe(_ =>\n            {\n                Separator = DefaultSeparator;\n                ShieldSymbol = DefaultShieldSymbol;\n            });\n\n        _sub3 = IsComa\n            .Where(value => value)\n            .Subscribe(_ =>\n            {\n                Separator = \",\";\n                ShieldSymbol = \";\";\n            });\n\n        _sub4 = IsTab\n            .Where(value => value)\n            .Subscribe(_ =>\n            {\n                Separator = \"\\t\";\n                ShieldSymbol = DefaultShieldSymbol;\n            });\n    }\n\n    public BindableReactiveProperty<bool> IsSemicolon { get; }\n    public BindableReactiveProperty<bool> IsComa { get; }\n    public BindableReactiveProperty<bool> IsTab { get; }\n\n    public string Separator\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public string ShieldSymbol\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public override void ApplyDialog(ContentDialog dialog)\n    {\n        ArgumentNullException.ThrowIfNull(dialog);\n\n        _sub5.Disposable = IsValid.Subscribe(isValid =>\n        {\n            dialog.IsPrimaryButtonEnabled = isValid;\n        });\n    }\n\n    public override IEnumerable<IRoutable> GetChildren() => [];\n\n    #region Dispose\n\n    private readonly IDisposable _sub2;\n    private readonly IDisposable _sub3;\n    private readonly IDisposable _sub4;\n    private readonly SerialDisposable _sub5 = new();\n\n    protected override void Dispose(bool isDisposing)\n    {\n        if (isDisposing)\n        {\n            _sub2.Dispose();\n            _sub3.Dispose();\n            _sub4.Dispose();\n            _sub5.Dispose();\n        }\n\n        base.Dispose(isDisposing);\n    }\n\n    #endregion\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/Filters/Comparers/PacketFilterComparerBase.cs",
    "content": "using System;\nusing System.Collections.Generic;\n\nnamespace Asv.Drones;\n\npublic abstract class PacketFilterComparerBase<TFilter>\n    : IEqualityComparer<PacketFilterViewModelBase<TFilter>>\n    where TFilter : PacketFilterViewModelBase<TFilter>\n{\n    public virtual bool Equals(\n        PacketFilterViewModelBase<TFilter>? x,\n        PacketFilterViewModelBase<TFilter>? y\n    )\n    {\n        if (ReferenceEquals(x, y))\n        {\n            return true;\n        }\n\n        if (x is null)\n        {\n            return false;\n        }\n\n        if (y is null)\n        {\n            return false;\n        }\n\n        if (x.GetType() != y.GetType())\n        {\n            return false;\n        }\n\n        return x.Id.Equals(y.Id)\n            && x.FilterValue.Equals(y.FilterValue)\n            && x.MessageRateText.ModelValue.Value.Equals(y.MessageRateText.ModelValue.Value);\n    }\n\n    public virtual int GetHashCode(PacketFilterViewModelBase<TFilter> obj)\n    {\n        return HashCode.Combine(obj.Id, obj.FilterValue, obj.MessageRateText.ModelValue);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/Filters/Comparers/SourcePacketFilterComparer.cs",
    "content": "namespace Asv.Drones;\n\npublic class SourcePacketFilterComparer : PacketFilterComparerBase<SourcePacketFilterViewModel>\n{\n    public static SourcePacketFilterComparer Instance => new();\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/Filters/Comparers/TypePacketFilterComparer.cs",
    "content": "namespace Asv.Drones;\n\npublic class TypePacketFilterComparer : PacketFilterComparerBase<TypePacketFilterViewModel>\n{\n    public static TypePacketFilterComparer Instance => new();\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/Filters/PacketFilterViewModelBase.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Modeling;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class PacketFilterViewModelBaseConfig\n{\n    public bool IsChecked { get; set; } = true;\n}\n\npublic abstract class PacketFilterViewModelBase<TFilter> : RoutableViewModel\n    where TFilter : PacketFilterViewModelBase<TFilter>\n{\n    public static string BaseId => $\"packet-filter.{typeof(TFilter).Name}\";\n    private const int BaseMovingAverageSize = 3;\n\n    private readonly IUnit _unit;\n    private readonly ReactiveProperty<bool> _isChecked;\n    private readonly ReactiveProperty<double> _messageRate;\n    private readonly IncrementalRateCounter _packetRate = new(BaseMovingAverageSize);\n    private volatile int _cnt;\n    protected PacketFilterViewModelBaseConfig? Config;\n\n    public abstract string FilterValue { get; }\n\n    public IReadOnlyBindableReactiveProperty<string> MessageRateTextUnit { get; }\n    public HistoricalUnitProperty MessageRateText { get; }\n    public HistoricalBoolProperty IsChecked { get; }\n\n    protected PacketFilterViewModelBase(\n        string idArg,\n        IUnitService unitService,\n        ILoggerFactory loggerFactory\n    )\n        : base(new NavigationId(BaseId, idArg), loggerFactory)\n    {\n        _unit = unitService.Units[FrequencyUnit.Id];\n        _isChecked = new ReactiveProperty<bool>(true).DisposeItWith(Disposable);\n        _messageRate = new ReactiveProperty<double>().DisposeItWith(Disposable);\n        MessageRateText = new HistoricalUnitProperty(\n            nameof(MessageRateText),\n            _messageRate,\n            _unit,\n            loggerFactory,\n            \"F1\"\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n        IsChecked = new HistoricalBoolProperty(nameof(IsChecked), _isChecked, loggerFactory)\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n        MessageRateTextUnit = MessageRateText\n            .Unit.CurrentUnitItem.Select(item => item.Symbol)\n            .ToBindableReactiveProperty<string>()\n            .DisposeItWith(Disposable);\n        IncreaseRatesCounterSafe();\n\n        Observable\n            .Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1))\n            .Subscribe(_ => UpdateRateText())\n            .DisposeItWith(Disposable);\n\n        Events.Subscribe(InternalCatchEvent).DisposeItWith(Disposable);\n    }\n\n    public void IncreaseRatesCounterSafe()\n    {\n        Interlocked.Increment(ref _cnt);\n    }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        yield return IsChecked;\n    }\n\n    private ValueTask InternalCatchEvent(IRoutable src, AsyncRoutedEvent<IRoutable> e)\n    {\n        switch (e)\n        {\n            case SaveLayoutEvent saveLayoutEvent:\n                if (Config is null)\n                {\n                    break;\n                }\n\n                this.HandleSaveLayout(\n                    saveLayoutEvent,\n                    Config,\n                    cfg => cfg.IsChecked = IsChecked.ViewValue.Value\n                );\n                break;\n            case LoadLayoutEvent loadLayoutEvent:\n                Config = this.HandleLoadLayout<PacketFilterViewModelBaseConfig>(\n                    loadLayoutEvent,\n                    cfg => IsChecked.ModelValue.Value = cfg.IsChecked\n                );\n                break;\n        }\n\n        return ValueTask.CompletedTask;\n    }\n\n    private void UpdateRateText()\n    {\n        MessageRateText.ModelValue.Value = Math.Round(_packetRate.Calculate(_cnt), 1);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/Filters/SourcePacketFilterViewModel.cs",
    "content": "﻿using Asv.Avalonia;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic sealed class SourcePacketFilterViewModel(\n    PacketMessageViewModel pkt,\n    IUnitService unitService,\n    ILoggerFactory loggerFactory\n) : PacketFilterViewModelBase<SourcePacketFilterViewModel>(pkt.Source, unitService, loggerFactory)\n{\n    public override string FilterValue { get; } = pkt.Source;\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/Filters/TypePacketFilterViewModel.cs",
    "content": "﻿using Asv.Avalonia;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic sealed class TypePacketFilterViewModel(\n    PacketMessageViewModel pkt,\n    IUnitService unitService,\n    ILoggerFactory loggerFactory\n) : PacketFilterViewModelBase<TypePacketFilterViewModel>(pkt.Type, unitService, loggerFactory)\n{\n    public override string FilterValue { get; } = pkt.Type;\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/HomePacketViewerExtension.cs",
    "content": "using Asv.Avalonia;\nusing Asv.Common;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic class HomePacketViewerExtension(ILoggerFactory loggerFactory) : IExtensionFor<IHomePage>\n{\n    public void Extend(IHomePage context, CompositeDisposable contextDispose)\n    {\n        context.Tools.Add(\n            OpenPacketViewerCommand\n                .StaticInfo.CreateAction(loggerFactory)\n                .DisposeItWith(contextDispose)\n        );\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/PacketConverter/DefaultMavlinkPacketConverter.cs",
    "content": "using System;\nusing Asv.Drones.Api;\nusing Asv.Mavlink;\nusing Newtonsoft.Json;\n\nnamespace Asv.Drones;\n\n/// <summary>\n/// Default packet converter. Used when there is no specialized converter for some packet type.\n/// </summary>\npublic class DefaultMavlinkPacketConverter : IPacketConverter\n{\n    public int Order => int.MaxValue;\n\n    public bool CanConvert(MavlinkMessage packet)\n    {\n        if (packet == null)\n        {\n            throw new ArgumentException(\"Incoming packet was not initialized!\");\n        }\n\n        return true;\n    }\n\n    public string Convert(\n        MavlinkMessage packet,\n        PacketFormatting formatting = PacketFormatting.None\n    )\n    {\n        if (packet == null)\n        {\n            throw new ArgumentException(\"Incoming packet was not initialized!\");\n        }\n\n        if (!CanConvert(packet))\n        {\n            throw new ArgumentException(\"Converter can not convert incoming packet!\");\n        }\n\n        return formatting switch\n        {\n            PacketFormatting.None => JsonConvert.SerializeObject(\n                packet.GetPayload(),\n                Formatting.None\n            ),\n            PacketFormatting.Indented => JsonConvert.SerializeObject(\n                packet.GetPayload(),\n                Formatting.Indented\n            ),\n            _ => throw new ArgumentException(\"Wrong packet formatting!\"),\n        };\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/PacketMessage/PacketMessageView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"450\"\n    d:DesignHeight=\"25\"\n    x:Class=\"Asv.Drones.PacketMessageView\"\n    x:DataType=\"drones:PacketMessageViewModel\"\n>\n    <Design.DataContext>\n        <drones:PacketMessageViewModel />\n    </Design.DataContext>\n    <!--Todo: visually separate columns-->\n    <Grid ToolTip.Tip=\"{CompiledBinding Description}\">\n        <Grid.ColumnDefinitions>\n            <ColumnDefinition SharedSizeGroup=\"Date\" Width=\"Auto\" />\n            <ColumnDefinition SharedSizeGroup=\"Source\" Width=\"Auto\" />\n            <ColumnDefinition SharedSizeGroup=\"Size\" Width=\"Auto\" />\n            <ColumnDefinition SharedSizeGroup=\"Type\" Width=\"Auto\" />\n            <ColumnDefinition SharedSizeGroup=\"Message\" Width=\"Auto\" />\n        </Grid.ColumnDefinitions>\n        <Panel Grid.ColumnSpan=\"4\" Background=\"#5779C1\" IsVisible=\"{Binding Highlight}\" />\n        <TextBlock\n            FontFamily=\"Consolas\"\n            Margin=\"5,0\"\n            Grid.Column=\"0\"\n            Text=\"{Binding DateTime, StringFormat={}{0:hh:mm:ss.fff}}\"\n        />\n        <TextBlock FontFamily=\"Consolas\" Margin=\"5,0\" Grid.Column=\"1\" Text=\"{Binding Source}\" />\n        <TextBlock FontFamily=\"Consolas\" Margin=\"5,0\" Grid.Column=\"2\" Text=\"{Binding Size}\" />\n        <TextBlock FontFamily=\"Consolas\" Margin=\"5,0\" Grid.Column=\"3\" Text=\"{Binding Type}\" />\n        <TextBlock FontFamily=\"Consolas\" Margin=\"5,0\" Grid.Column=\"4\" Text=\"{Binding Message}\" />\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/PacketMessage/PacketMessageView.axaml.cs",
    "content": "﻿using Asv.Avalonia;\nusing Avalonia.Controls;\n\nnamespace Asv.Drones;\n\npublic partial class PacketMessageView : UserControl\n{\n    public PacketMessageView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/PacketMessage/PacketMessageViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing Asv.Avalonia;\nusing Asv.Drones.Api;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones;\n\npublic class PacketMessageViewModel : RoutableViewModel\n{\n    public const string PageId = \"packet-message\";\n\n    public DateTime DateTime { get; }\n    public string Source { get; }\n    public int Size { get; }\n    public string Message { get; }\n    public string Type { get; }\n    public string Description { get; }\n\n    public bool Highlight\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public PacketMessageViewModel()\n        : base(DesignTime.Id, DesignTime.LoggerFactory)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n        DateTime = DateTime.Now;\n        Source = \"[1,1]\";\n        Message = \"[1000] information\";\n        Description = \"Some description\";\n        Type = \"HEARTBEAT\";\n        Size = 10;\n    }\n\n    public PacketMessageViewModel(\n        MavlinkMessage packet,\n        IPacketConverter converter,\n        ILoggerFactory loggerFactory\n    )\n        : base(\n            NavigationId.GenerateByHash(\n                packet.SystemId,\n                packet.ComponentId,\n                packet.Sequence,\n                packet.Id\n            ),\n            loggerFactory\n        )\n    {\n        DateTime = DateTime.Now;\n        Source = $\"[{packet.SystemId},{packet.ComponentId}]\";\n        Message = $\"[{packet.Sequence:000}] {converter.Convert(packet)}\";\n        Description = converter.Convert(packet, PacketFormatting.Indented);\n        Type = packet.Name;\n        Size = packet.GetByteSize();\n    }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/PacketViewerView.axaml",
    "content": "<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:avalonia=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    xmlns:drones=\"clr-namespace:Asv.Drones\"\n    xmlns:avalonia1=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"800\"\n    d:DesignHeight=\"450\"\n    x:DataType=\"drones:PacketViewerViewModel\"\n    x:Class=\"Asv.Drones.PacketViewerView\"\n>\n    <UserControl.Styles>\n        <Style Selector=\"ToggleButton ContentPresenter.tbchecked\">\n            <Setter Property=\"IsVisible\" Value=\"False\" />\n        </Style>\n        <Style Selector=\"ToggleButton:checked ContentPresenter.tbchecked\">\n            <Setter Property=\"IsVisible\" Value=\"True\" />\n        </Style>\n        <Style Selector=\"ToggleButton ContentPresenter.tbunchecked\">\n            <Setter Property=\"IsVisible\" Value=\"True\" />\n        </Style>\n        <Style Selector=\"ToggleButton:checked ContentPresenter.tbunchecked\">\n            <Setter Property=\"IsVisible\" Value=\"False\" />\n        </Style>\n        <Style Selector=\"Button.saveButton\">\n            <Setter Property=\"Opacity\" Value=\"1.0\" />\n        </Style>\n        <Style Selector=\"Button.saveButton:disabled\">\n            <Setter Property=\"Opacity\" Value=\"0.3\" />\n        </Style>\n        <Style Selector=\"Expander\">\n            <Setter Property=\"CornerRadius\" Value=\"0\" />\n        </Style>\n    </UserControl.Styles>\n    <Design.DataContext>\n        <drones:PacketViewerViewModel />\n    </Design.DataContext>\n    <Grid ColumnDefinitions=\"* Auto\" RowDefinitions=\"Auto 4 *\" Margin=\"8\">\n        <DockPanel Grid.Row=\"0\" Grid.ColumnSpan=\"2\" Margin=\"2 2 2 0\">\n            <Button\n                DockPanel.Dock=\"Right\"\n                HorizontalAlignment=\"Stretch\"\n                VerticalAlignment=\"Stretch\"\n                Margin=\"2 0 0 0\"\n                Command=\"{Binding ExportToCsv}\"\n                ToolTip.Tip=\"{x:Static drones:RS.PacketViewerView_ToolTip_Save}\"\n                IsEnabled=\"{Binding IsPaused.ViewValue.Value}\"\n                Classes=\"saveButton\"\n            >\n                <avalonia:MaterialIcon Kind=\"ContentSave\" />\n            </Button>\n            <Button\n                DockPanel.Dock=\"Right\"\n                HorizontalAlignment=\"Stretch\"\n                VerticalAlignment=\"Stretch\"\n                Command=\"{CompiledBinding ClearAll}\"\n                Margin=\"2 0 0 0\"\n                ToolTip.Tip=\"{x:Static drones:RS.PacketViewerView_ToolTip_ClearAll}\"\n            >\n                <avalonia:MaterialIcon Kind=\"Bin\" />\n            </Button>\n            <ToggleButton\n                DockPanel.Dock=\"Right\"\n                Margin=\"2 0 0 0\"\n                HorizontalAlignment=\"Stretch\"\n                VerticalAlignment=\"Stretch\"\n                IsChecked=\"{Binding IsPaused.ViewValue.Value, Mode=TwoWay}\"\n                ToolTip.Tip=\"{x:Static drones:RS.PacketViewerView_ToolTip_PlayPause}\"\n            >\n                <Panel>\n                    <ContentPresenter Classes=\"tbchecked\">\n                        <ContentPresenter.Content>\n                            <avalonia:MaterialIcon Kind=\"Play\" />\n                        </ContentPresenter.Content>\n                    </ContentPresenter>\n                    <ContentPresenter Classes=\"tbunchecked\">\n                        <ContentPresenter.Content>\n                            <avalonia:MaterialIcon Kind=\"Pause\" />\n                        </ContentPresenter.Content>\n                    </ContentPresenter>\n                </Panel>\n            </ToggleButton>\n            <avalonia1:SearchBoxView\n                DockPanel.Dock=\"Right\"\n                MinWidth=\"200\"\n                Margin=\"0\"\n                DataContext=\"{Binding Search}\"\n            />\n            <avalonia:MaterialIcon DockPanel.Dock=\"Left\" Height=\"30\" Width=\"30\" Kind=\"Package\" />\n        </DockPanel>\n        <ListBox\n            IsHitTestVisible=\"{CompiledBinding IsPaused.ViewValue.Value}\"\n            ScrollViewer.HorizontalScrollBarVisibility=\"Visible\"\n            Grid.Column=\"0\"\n            Grid.Row=\"2\"\n            Margin=\"8,1,8,0\"\n            ItemsSource=\"{CompiledBinding Packets}\"\n            SelectedItem=\"{CompiledBinding SelectedPacket.Value}\"\n            Grid.IsSharedSizeScope=\"True\"\n        >\n            <ListBox.Styles>\n                <Style Selector=\"ListBoxItem\">\n                    <Setter Property=\"MinHeight\" Value=\"25\"></Setter>\n                </Style>\n            </ListBox.Styles>\n        </ListBox>\n        <Grid Grid.Column=\"1\" Grid.Row=\"2\" MinWidth=\"340\" Margin=\"2 0 2 0\">\n            <Grid.RowDefinitions>\n                <RowDefinition Height=\"Auto\" />\n                <RowDefinition Height=\"*\" />\n            </Grid.RowDefinitions>\n            <Expander\n                x:Name=\"SourcesExpander\"\n                Grid.Row=\"0\"\n                Header=\"{x:Static drones:RS.PacketViewerView_ExpanderFilterBySources_Header}\"\n                VerticalAlignment=\"Top\"\n                HorizontalAlignment=\"Stretch\"\n                Expanded=\"Expander_StateChanged\"\n                Collapsed=\"Expander_StateChanged\"\n            >\n                <Grid RowDefinitions=\"Auto,*\" ColumnDefinitions=\"*\">\n                    <CheckBox\n                        Grid.Row=\"0\"\n                        HorizontalAlignment=\"Left\"\n                        IsChecked=\"{CompiledBinding IsCheckedAllSources.ViewValue.Value}\"\n                        Content=\"{x:Static drones:RS.PacketViewerView_Filters_CheckAll}\"\n                    />\n                    <ListBox\n                        Grid.Row=\"1\"\n                        HorizontalAlignment=\"Stretch\"\n                        x:Name=\"SourceFilters\"\n                        ItemsSource=\"{Binding FiltersBySource}\"\n                        ScrollViewer.VerticalScrollBarVisibility=\"Visible\"\n                        Grid.IsSharedSizeScope=\"True\"\n                    >\n                        <ListBox.ItemTemplate>\n                            <DataTemplate>\n                                <Grid>\n                                    <Grid.ColumnDefinitions>\n                                        <ColumnDefinition />\n                                        <ColumnDefinition Width=\"8\" />\n                                        <ColumnDefinition Width=\"Auto\" SharedSizeGroup=\"Rate\" />\n                                    </Grid.ColumnDefinitions>\n                                    <CheckBox\n                                        VerticalAlignment=\"Center\"\n                                        IsChecked=\"{Binding IsChecked.ViewValue.Value, Mode=TwoWay}\"\n                                        Padding=\"4 2 0 0\"\n                                    >\n                                        <CheckBox.Content>\n                                            <TextBlock\n                                                VerticalAlignment=\"Center\"\n                                                Text=\"{Binding FilterValue}\"\n                                            />\n                                        </CheckBox.Content>\n                                    </CheckBox>\n                                    <StackPanel\n                                        Grid.Column=\"2\"\n                                        Orientation=\"Horizontal\"\n                                        VerticalAlignment=\"Center\"\n                                    >\n                                        <TextBlock Text=\"{Binding MessageRateText.ViewValue.Value}\" />\n                                        <TextBlock\n                                            Text=\"{Binding MessageRateTextUnit.Value}\"\n                                            Margin=\"5,0,0,0\"\n                                        />\n                                    </StackPanel>\n                                </Grid>\n                            </DataTemplate>\n                        </ListBox.ItemTemplate>\n                    </ListBox>\n                </Grid>\n            </Expander>\n            <Expander\n                Grid.Row=\"1\"\n                x:Name=\"TypesExpander\"\n                Header=\"{x:Static drones:RS.PacketViewerView_ExpanderFilterByTypes_Header}\"\n                VerticalAlignment=\"Stretch\"\n                HorizontalAlignment=\"Stretch\"\n                Expanded=\"Expander_StateChanged\"\n                Collapsed=\"Expander_StateChanged\"\n            >\n                <Grid RowDefinitions=\"Auto,*\" ColumnDefinitions=\"*\">\n                    <CheckBox\n                        Grid.Row=\"0\"\n                        HorizontalAlignment=\"Left\"\n                        IsChecked=\"{CompiledBinding IsCheckedAllTypes.ViewValue.Value}\"\n                        Content=\"{x:Static drones:RS.PacketViewerView_Filters_CheckAll}\"\n                    />\n                    <ListBox\n                        Grid.Row=\"1\"\n                        HorizontalAlignment=\"Stretch\"\n                        x:Name=\"TypeFilters\"\n                        ItemsSource=\"{Binding FiltersByType}\"\n                        ScrollViewer.VerticalScrollBarVisibility=\"Visible\"\n                        Grid.IsSharedSizeScope=\"True\"\n                    >\n                        <ListBox.ItemTemplate>\n                            <DataTemplate>\n                                <Grid>\n                                    <Grid.ColumnDefinitions>\n                                        <ColumnDefinition />\n                                        <ColumnDefinition Width=\"8\" />\n                                        <ColumnDefinition Width=\"Auto\" SharedSizeGroup=\"Rate\" />\n                                    </Grid.ColumnDefinitions>\n                                    <CheckBox\n                                        VerticalAlignment=\"Center\"\n                                        IsChecked=\"{Binding IsChecked.ViewValue.Value, Mode=TwoWay}\"\n                                        Padding=\"4 2 0 0\"\n                                    >\n                                        <CheckBox.Content>\n                                            <TextBlock Text=\"{Binding FilterValue}\" />\n                                        </CheckBox.Content>\n                                    </CheckBox>\n                                    <StackPanel\n                                        Grid.Column=\"2\"\n                                        Orientation=\"Horizontal\"\n                                        VerticalAlignment=\"Center\"\n                                    >\n                                        <TextBlock Text=\"{Binding MessageRateText.ViewValue.Value}\" />\n                                        <TextBlock\n                                            Text=\"{Binding MessageRateTextUnit.Value}\"\n                                            Margin=\"5,0,0,0\"\n                                        />\n                                    </StackPanel>\n                                </Grid>\n                            </DataTemplate>\n                        </ListBox.ItemTemplate>\n                    </ListBox>\n                </Grid>\n            </Expander>\n        </Grid>\n    </Grid>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/PacketViewerView.axaml.cs",
    "content": "using Asv.Avalonia;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Interactivity;\n\nnamespace Asv.Drones;\n\npublic sealed class PacketViewerViewConfig\n{\n    public bool IsSourcesExpanded { get; set; } = true;\n    public bool IsTypesExpanded { get; set; } = true;\n}\n\npublic partial class PacketViewerView : UserControl\n{\n    private readonly ILayoutService _layoutService;\n\n    private PacketViewerViewConfig? _config;\n\n    public PacketViewerView()\n        : this(NullLayoutService.Instance)\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public PacketViewerView(ILayoutService layoutService)\n    {\n        _layoutService = layoutService;\n        InitializeComponent();\n    }\n\n    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)\n    {\n        LoadLayout();\n        base.OnAttachedToVisualTree(e);\n    }\n\n    private void Expander_StateChanged(object? sender, RoutedEventArgs e)\n    {\n        if (sender is not Expander exp)\n        {\n            return;\n        }\n\n        SaveLayout();\n    }\n\n    private void LoadLayout()\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        _config = _layoutService.Get<PacketViewerViewConfig>(this);\n        SourcesExpander.IsExpanded = _config.IsSourcesExpanded;\n        TypesExpander.IsExpanded = _config.IsTypesExpanded;\n    }\n\n    private void SaveLayout()\n    {\n        if (Design.IsDesignMode)\n        {\n            return;\n        }\n\n        if (_config is null)\n        {\n            return;\n        }\n\n        if (DataContext is null)\n        {\n            return;\n        }\n\n        if (!HasChanges())\n        {\n            return;\n        }\n\n        _config.IsSourcesExpanded = SourcesExpander.IsExpanded;\n        _config.IsTypesExpanded = TypesExpander.IsExpanded;\n        _layoutService.SetInMemory(this, _config);\n    }\n\n    private bool HasChanges()\n    {\n        if (\n            _config?.IsSourcesExpanded == SourcesExpander.IsExpanded\n            && _config.IsTypesExpanded == TypesExpander.IsExpanded\n        )\n        {\n            return false;\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones/Shell/Pages/PacketViewer/PacketViewerViewModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Collections.Immutable;\nusing System.IO;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Windows.Input;\nusing Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Asv.Modeling;\nusing Material.Icons;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Logging.Abstractions;\nusing ObservableCollections;\nusing R3;\n\nnamespace Asv.Drones;\n\npublic sealed class PacketViewerViewModelConfig\n{\n    public string SearchText { get; set; } = string.Empty;\n    public bool IsCheckedAllSources { get; set; } = true;\n    public bool IsCheckedAllTypes { get; set; } = true;\n}\n\npublic class PacketViewerViewModel : PageViewModel<PacketViewerViewModel>\n{\n    public const string PageId = \"packet-viewer\";\n    public const MaterialIconKind PageIcon = MaterialIconKind.Package;\n    private const int MaxPacketsAmount = 1000;\n    private const int PacketsReceiveDelayInSeconds = 1;\n\n    private readonly ReactiveProperty<bool> _isPaused;\n    private readonly ReactiveProperty<bool> _isCheckedAllSources;\n    private readonly ReactiveProperty<bool> _isCheckedAllTypes;\n    private readonly IHostEnvironment _app;\n    private readonly ILoggerFactory _loggerFactory;\n    private readonly IUnitService _unit;\n    private readonly IDeviceManager _deviceManager;\n    private readonly INavigationService _navigationService;\n    private readonly IEnumerable<IPacketConverter> _converters;\n    private readonly ObservableFixedSizeRingBuffer<PacketMessageViewModel> _packetsBuffer;\n    private readonly ObservableHashSet<SourcePacketFilterViewModel> _filtersBySourceSet;\n    private readonly ObservableHashSet<TypePacketFilterViewModel> _filtersByTypeSet;\n    private readonly ReactiveProperty<bool> _filterChangeTrigger;\n    private PacketViewerViewModelConfig? _config;\n\n    public ICommand ClearAll { get; }\n    public ICommand ExportToCsv { get; }\n    public HistoricalBoolProperty IsPaused { get; }\n    public SearchBoxViewModel Search { get; }\n    public HistoricalBoolProperty IsCheckedAllSources { get; }\n    public HistoricalBoolProperty IsCheckedAllTypes { get; }\n    public BindableReactiveProperty<PacketMessageViewModel?> SelectedPacket { get; }\n    public INotifyCollectionChangedSynchronizedViewList<PacketMessageViewModel> Packets { get; }\n    public INotifyCollectionChangedSynchronizedViewList<SourcePacketFilterViewModel> FiltersBySource { get; }\n    public INotifyCollectionChangedSynchronizedViewList<TypePacketFilterViewModel> FiltersByType { get; }\n\n    public PacketViewerViewModel()\n        : this(\n            DesignTime.CommandService,\n            AppHost.Instance.Services.GetRequiredService<IHostEnvironment>(),\n            NullLayoutService.Instance,\n            NullLoggerFactory.Instance,\n            NullUnitService.Instance,\n            [],\n            NullDeviceManager.Instance,\n            DesignTime.Navigation,\n            DesignTime.DialogService,\n            DesignTime.ExtensionService\n        )\n    {\n        DesignTime.ThrowIfNotDesignMode();\n        _packetsBuffer.AddLastRange(\n            new[]\n            {\n                new PacketMessageViewModel(),\n                new PacketMessageViewModel(),\n                new PacketMessageViewModel(),\n            }\n        );\n    }\n\n    public PacketViewerViewModel(\n        ICommandService cmd,\n        IHostEnvironment app,\n        ILayoutService layoutService,\n        ILoggerFactory loggerFactory,\n        IUnitService unit,\n        IEnumerable<IPacketConverter> converters,\n        IDeviceManager deviceManager,\n        INavigationService navigationService,\n        IDialogService dialogService,\n        IExtensionService ext\n    )\n        : base(PageId, cmd, loggerFactory, dialogService, ext)\n    {\n        Title = RS.PacketViewerViewModel_Title;\n        _app = app;\n        _unit = unit;\n        _converters = converters;\n        _deviceManager = deviceManager;\n        _navigationService = navigationService;\n        _loggerFactory = loggerFactory;\n        _filterChangeTrigger = new ReactiveProperty<bool>(false).DisposeItWith(Disposable);\n\n        _isPaused = new ReactiveProperty<bool>().DisposeItWith(Disposable);\n        _isCheckedAllSources = new ReactiveProperty<bool>(true).DisposeItWith(Disposable);\n        _isCheckedAllTypes = new ReactiveProperty<bool>(true).DisposeItWith(Disposable);\n\n        _packetsBuffer = new ObservableFixedSizeRingBuffer<PacketMessageViewModel>(\n            MaxPacketsAmount\n        );\n        _filtersBySourceSet = new ObservableHashSet<SourcePacketFilterViewModel>(\n            SourcePacketFilterComparer.Instance\n        );\n        _filtersByTypeSet = new ObservableHashSet<TypePacketFilterViewModel>(\n            TypePacketFilterComparer.Instance\n        );\n        _packetsBuffer.SetRoutableParent(this).DisposeItWith(Disposable);\n        _filtersBySourceSet.SetRoutableParent(this).DisposeItWith(Disposable);\n        _filtersByTypeSet.SetRoutableParent(this).DisposeItWith(Disposable);\n        _packetsBuffer.DisposeRemovedItems().DisposeItWith(Disposable);\n        _filtersBySourceSet.DisposeRemovedItems().DisposeItWith(Disposable);\n        _filtersByTypeSet.DisposeRemovedItems().DisposeItWith(Disposable);\n\n        var packetsView = _packetsBuffer.CreateView(x => x).DisposeItWith(Disposable);\n        Packets = packetsView\n            .ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current)\n            .DisposeItWith(Disposable);\n        FiltersBySource = _filtersBySourceSet\n            .ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current)\n            .DisposeItWith(Disposable);\n        FiltersByType = _filtersByTypeSet\n            .ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current)\n            .DisposeItWith(Disposable);\n\n        IsPaused = new HistoricalBoolProperty(nameof(IsPaused), _isPaused, loggerFactory)\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n        Search = new SearchBoxViewModel(\n            nameof(Search),\n            loggerFactory,\n            (_, _, _) => Task.CompletedTask,\n            TimeSpan.FromMilliseconds(500)\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n\n        IsCheckedAllSources = new HistoricalBoolProperty(\n            nameof(IsCheckedAllSources),\n            _isCheckedAllSources,\n            loggerFactory\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n        IsCheckedAllTypes = new HistoricalBoolProperty(\n            nameof(IsCheckedAllTypes),\n            _isCheckedAllTypes,\n            loggerFactory\n        )\n            .SetRoutableParent(this)\n            .DisposeItWith(Disposable);\n        SelectedPacket = new BindableReactiveProperty<PacketMessageViewModel?>(null).DisposeItWith(\n            Disposable\n        );\n\n        ExportToCsv = new BindableAsyncCommand(ExportPacketsToCsvCommand.Id, this);\n        ClearAll = new BindableAsyncCommand(ClearAllPacketsCommand.Id, this);\n\n        IsPaused.ViewValue.Subscribe(_ => SelectedPacket.Value = null).DisposeItWith(Disposable);\n        IsCheckedAllSources\n            .ViewValue.Subscribe(isChecked =>\n            {\n                foreach (var filter in _filtersBySourceSet)\n                {\n                    filter.IsChecked.ModelValue.Value = isChecked;\n                }\n            })\n            .DisposeItWith(Disposable);\n        IsCheckedAllTypes\n            .ViewValue.Subscribe(isChecked =>\n            {\n                foreach (var filter in _filtersByTypeSet)\n                {\n                    filter.IsChecked.ModelValue.Value = isChecked;\n                }\n            })\n            .DisposeItWith(Disposable);\n\n        _packetsBuffer\n            .ObserveAdd()\n            .Subscribe(item => UpdateFilters(item.Value))\n            .DisposeItWith(Disposable);\n        _deviceManager\n            .Router.OnRxMessage.Where(_ => !IsPaused.ViewValue.Value)\n            .FilterByType<MavlinkMessage>()\n            .Chunk(TimeSpan.FromSeconds(PacketsReceiveDelayInSeconds))\n            .Select(ConvertToPacketMessage)\n            .Subscribe(_packetsBuffer.AddLastRange)\n            .DisposeItWith(Disposable);\n        _deviceManager\n            .Router.OnTxMessage.Where(_ => !IsPaused.ViewValue.Value)\n            .FilterByType<MavlinkMessage>()\n            .Chunk(TimeSpan.FromSeconds(PacketsReceiveDelayInSeconds))\n            .Select(ConvertToPacketMessage)\n            .Subscribe(_packetsBuffer.AddLastRange)\n            .DisposeItWith(Disposable);\n\n        SelectedPacket\n            .WhereNotNull()\n            .Subscribe(selectedPacket =>\n            {\n                foreach (var item in _packetsBuffer)\n                {\n                    item.Highlight = item.Type == selectedPacket.Type;\n                }\n            })\n            .DisposeItWith(Disposable);\n\n        var viewFilter = CreateSynchronizedViewFilter();\n\n        _filtersBySourceSet // TODO: Switch to a special routable event when ready\n            .ObserveAdd()\n            .SubscribeAwait(\n                async (filter, ct) =>\n                {\n                    await filter.Value.RequestLoadLayout(layoutService, ct);\n                    filter\n                        .Value.IsChecked.ViewValue.Subscribe(_ =>\n                            _filterChangeTrigger.Value = !_filterChangeTrigger.Value\n                        )\n                        .DisposeItWith(Disposable);\n                }\n            )\n            .DisposeItWith(Disposable);\n        _filtersByTypeSet // TODO: Switch to a special routable event when ready\n            .ObserveAdd()\n            .SubscribeAwait(\n                async (filter, ct) =>\n                {\n                    await filter.Value.RequestLoadLayout(layoutService, ct);\n                    filter\n                        .Value.IsChecked.ViewValue.Subscribe(_ =>\n                            _filterChangeTrigger.Value = !_filterChangeTrigger.Value\n                        )\n                        .DisposeItWith(Disposable);\n                }\n            )\n            .DisposeItWith(Disposable);\n\n        Observable\n            .Merge(\n                _filtersBySourceSet.ObserveChanged().Select(_ => Unit.Default),\n                _filtersByTypeSet.ObserveChanged().Select(_ => Unit.Default),\n                Search.Text.ViewValue.Select(_ => Unit.Default),\n                _filterChangeTrigger.Select(_ => Unit.Default)\n            )\n            .ThrottleLast(TimeSpan.FromMilliseconds(500))\n            .Subscribe(_ =>\n            {\n                var allSourcesSelected = _filtersBySourceSet.All(x => x.IsChecked.ViewValue.Value);\n                var allTypesSelected = _filtersByTypeSet.All(x => x.IsChecked.ViewValue.Value);\n                var hasNoSearchString = string.IsNullOrEmpty(Search.Text.ViewValue.Value);\n\n                if (hasNoSearchString && allSourcesSelected && allTypesSelected)\n                {\n                    packetsView.ResetFilter();\n                    return;\n                }\n\n                packetsView.AttachFilter(viewFilter);\n            })\n            .DisposeItWith(Disposable);\n\n        Events.Subscribe(InternalCatchEvent).DisposeItWith(Disposable);\n    }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        foreach (var item in _packetsBuffer)\n        {\n            yield return item;\n        }\n\n        foreach (var item in _filtersBySourceSet)\n        {\n            yield return item;\n        }\n\n        foreach (var item in _filtersByTypeSet)\n        {\n            yield return item;\n        }\n\n        yield return IsPaused;\n        yield return IsCheckedAllSources;\n        yield return IsCheckedAllTypes;\n        yield return Search;\n    }\n\n    internal void ClearAllImpl()\n    {\n        _packetsBuffer.RemoveAll();\n    }\n\n    internal async ValueTask ExportToCsvImpl(CancellationToken cancel = default)\n    {\n        cancel.ThrowIfCancellationRequested();\n        using var vm = new SavePacketMessagesDialogViewModel(_loggerFactory);\n        var dialog = new ContentDialog(vm, _navigationService)\n        {\n            Title = RS.PacketViewerViewModel_SavePacketMessagesDialog_Title,\n            CloseButtonText = RS.PacketViewerViewModel_SavePacketMessagesDialog_CloseButtonText,\n            PrimaryButtonText = RS.PacketViewerViewModel_SavePacketMessagesDialog_PrimaryButtonText,\n            DefaultButton = ContentDialogButton.Primary,\n        };\n\n        var res = await dialog.ShowAsync();\n\n        if (res != ContentDialogResult.Primary)\n        {\n            return;\n        }\n\n        var filePath = Path.Join(\n            _app.ContentRootPath,\n            $\"packets{DateTime.Now:yyyy-M-d h-mm-ss}.csv\"\n        );\n\n        try\n        {\n            CsvHelper.SaveToCsv(\n                _packetsBuffer.ToImmutableList(),\n                filePath,\n                vm.Separator,\n                vm.ShieldSymbol,\n                new CsvColumn<PacketMessageViewModel>(\n                    RS.PacketMessageViewModel_CsvColumn_Date,\n                    x => x.DateTime.ToString(\"G\")\n                ),\n                new CsvColumn<PacketMessageViewModel>(\n                    RS.PacketMessageViewModel_CsvColumn_Type,\n                    x => x.Type\n                ),\n                new CsvColumn<PacketMessageViewModel>(\n                    RS.PacketMessageViewModel_CsvColumn_Source,\n                    x => x.Source\n                ),\n                new CsvColumn<PacketMessageViewModel>(\n                    RS.PacketMessageViewModel_CsvColumn_Message,\n                    x => x.Message\n                )\n            );\n\n            Logger.LogInformation(\"Export file saved to: {filePath}\", filePath);\n        }\n        catch (Exception ex)\n        {\n            Logger.LogError(ex, \"Аn error occurred while saving the file: {filePath}\", filePath);\n        }\n    }\n\n    protected override void AfterLoadExtensions() { }\n\n    private ISynchronizedViewFilter<\n        PacketMessageViewModel,\n        PacketMessageViewModel\n    > CreateSynchronizedViewFilter()\n    {\n        return new SynchronizedViewFilter<PacketMessageViewModel, PacketMessageViewModel>(\n            (_, packet) =>\n            {\n                var hasRequiredType = _filtersByTypeSet.Any(f =>\n                    f.IsChecked.ViewValue.Value && f.FilterValue == packet.Type\n                );\n\n                var hasRequiredSource = _filtersBySourceSet.Any(f =>\n                    f.IsChecked.ViewValue.Value && f.FilterValue == packet.Source\n                );\n\n                if (!hasRequiredSource)\n                {\n                    return false;\n                }\n\n                if (!hasRequiredType)\n                {\n                    return false;\n                }\n\n                if (_filtersBySourceSet.All(x => !x.IsChecked.ViewValue.Value))\n                {\n                    return false;\n                }\n\n                if (_filtersByTypeSet.All(x => !x.IsChecked.ViewValue.Value))\n                {\n                    return false;\n                }\n\n                if (string.IsNullOrWhiteSpace(Search.Text.ViewValue.Value))\n                {\n                    return true;\n                }\n\n                return packet.Message.Contains(\n                    Search.Text.ViewValue.Value,\n                    StringComparison.OrdinalIgnoreCase\n                );\n            }\n        );\n    }\n\n    private ValueTask InternalCatchEvent(IRoutable src, AsyncRoutedEvent<IRoutable> e)\n    {\n        switch (e)\n        {\n            case SaveLayoutEvent saveLayoutEvent:\n                if (_config is null)\n                {\n                    break;\n                }\n\n                this.HandleSaveLayout(\n                    saveLayoutEvent,\n                    _config,\n                    cfg =>\n                    {\n                        cfg.SearchText = Search.Text.ViewValue.Value ?? string.Empty;\n                        cfg.IsCheckedAllSources = IsCheckedAllSources.ViewValue.Value;\n                        cfg.IsCheckedAllTypes = IsCheckedAllTypes.ViewValue.Value;\n                    },\n                    FlushingStrategy.FlushBothViewModelAndView\n                );\n                break;\n            case LoadLayoutEvent loadLayoutEvent:\n                _config = this.HandleLoadLayout<PacketViewerViewModelConfig>(\n                    loadLayoutEvent,\n                    cfg =>\n                    {\n                        Search.Text.ModelValue.Value = cfg.SearchText;\n                        IsCheckedAllSources.ModelValue.Value = cfg.IsCheckedAllSources;\n                        IsCheckedAllTypes.ModelValue.Value = cfg.IsCheckedAllTypes;\n                    }\n                );\n                break;\n        }\n\n        return ValueTask.CompletedTask;\n    }\n\n    private IEnumerable<PacketMessageViewModel> ConvertToPacketMessage(\n        IEnumerable<MavlinkMessage> messages\n    )\n    {\n        foreach (var packet in messages)\n        {\n            var converter =\n                _converters.FirstOrDefault(c => c.CanConvert(packet))\n                ?? new DefaultMavlinkPacketConverter();\n            var vm = new PacketMessageViewModel(packet, converter, _loggerFactory);\n            yield return vm;\n        }\n    }\n\n    private void UpdateFilters(PacketMessageViewModel vm)\n    {\n        UpdateSourceFilters(vm);\n        UpdateTypeFilters(vm);\n    }\n\n    private void UpdateSourceFilters(PacketMessageViewModel vm)\n    {\n        var filter = _filtersBySourceSet.FirstOrDefault(x => x.FilterValue == vm.Source);\n        if (filter is not null)\n        {\n            filter.IncreaseRatesCounterSafe();\n            return;\n        }\n\n        var newFilter = new SourcePacketFilterViewModel(vm, _unit, _loggerFactory);\n        var isAdded = _filtersBySourceSet.Add(newFilter);\n\n        if (!isAdded)\n        {\n            newFilter.Dispose();\n        }\n\n        Logger.LogInformation(\"Added new source filter: {Source}\", vm.Source);\n    }\n\n    private void UpdateTypeFilters(PacketMessageViewModel vm)\n    {\n        var filter = _filtersByTypeSet.FirstOrDefault(x => x.FilterValue == vm.Type);\n        if (filter is not null)\n        {\n            filter.IncreaseRatesCounterSafe();\n            return;\n        }\n\n        var newFilter = new TypePacketFilterViewModel(vm, _unit, _loggerFactory);\n        var isAdded = _filtersByTypeSet.Add(newFilter);\n\n        if (!isAdded)\n        {\n            newFilter.Dispose();\n        }\n\n        Logger.LogInformation(\"Added new type filter: {Type}\", vm.Type);\n    }\n\n    #region Dispose\n\n    protected override void Dispose(bool disposing)\n    {\n        if (disposing)\n        {\n            _packetsBuffer.RemoveAll();\n            _filtersBySourceSet.ClearWithItemsDispose();\n            _filtersByTypeSet.ClearWithItemsDispose();\n        }\n\n        base.Dispose(disposing);\n    }\n\n    #endregion\n}\n"
  },
  {
    "path": "src/Asv.Drones.Android/Asv.Drones.Android.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n    <PropertyGroup>\n        <OutputType>Exe</OutputType>\n        <TargetFramework>net9.0-android</TargetFramework>\n        <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>\n        <Nullable>enable</Nullable>\n        <ApplicationId>me.asv.drones</ApplicationId>\n        <ApplicationVersion>1</ApplicationVersion>\n        <ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>\n        <AndroidPackageFormat>apk</AndroidPackageFormat>\n        <AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>\n        <CodeAnalysisRuleSet>../CodeStyle.ruleset</CodeAnalysisRuleSet>\n        <WarningsAsErrors>\n            CS0169,\n            CS0618,\n            CS1502,\n            CS1503,\n            CS8524,\n            CS8600,\n            CS8601,\n            CS8602,\n            CS8603,\n            CS8604,\n            CS8625,\n            CS8629,\n            CS8762,\n            CA1510,\n            CA1851\n        </WarningsAsErrors>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <AndroidResource Include=\"Icon.png\">\n            <Link>Resources\\drawable\\Icon.png</Link>\n        </AndroidResource>\n    </ItemGroup>\n\n    <ItemGroup>\n        <PackageReference Include=\"Avalonia.Android\" Version=\"$(AvaloniaVersion)\"/>\n        <PackageReference Include=\"Microsoft.NET.ILLink.Tasks\" Version=\"9.0.7\" />\n        <PackageReference Include=\"Roslynator.Analyzers\" Version=\"4.14.0\">\n          <PrivateAssets>all</PrivateAssets>\n          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n        </PackageReference>\n        <PackageReference Include=\"StyleCop.Analyzers\" Version=\"1.1.118\">\n          <PrivateAssets>all</PrivateAssets>\n          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n        </PackageReference>\n        <PackageReference Include=\"Xamarin.AndroidX.Core.SplashScreen\" Version=\"1.0.1.16\" />\n    </ItemGroup>\n\n    <ItemGroup>\n        <ProjectReference Include=\"..\\Asv.Drones\\Asv.Drones.csproj\"/>\n    </ItemGroup>\n</Project>\n"
  },
  {
    "path": "src/Asv.Drones.Android/MainActivity.cs",
    "content": "﻿using Android.App;\nusing Android.Content.PM;\nusing Android.OS;\nusing Avalonia;\nusing Avalonia.Android;\n\nnamespace Asv.Drones.Android;\n\n[Activity(\n    Label = \"Asv.Drones.Android\",\n    Theme = \"@style/MyTheme.NoActionBar\",\n    Icon = \"@drawable/icon\",\n    MainLauncher = true,\n    ConfigurationChanges = ConfigChanges.Orientation\n        | ConfigChanges.ScreenSize\n        | ConfigChanges.UiMode\n)]\npublic class MainActivity : AvaloniaMainActivity<App>\n{\n    protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)\n    {\n        // this is required to use the AndroidHttpClientHandler in main thread\n        StrictMode.SetThreadPolicy(new StrictMode.ThreadPolicy.Builder().PermitAll().Build());\n\n        return base.CustomizeAppBuilder(builder).WithInterFont();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Android/Properties/AndroidManifest.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" android:installLocation=\"auto\">\n\t<uses-permission android:name=\"android.permission.INTERNET\" />\n\t<application android:label=\"Asv.Drones\" android:icon=\"@drawable/Icon\" />\n</manifest>\n"
  },
  {
    "path": "src/Asv.Drones.Android/Resources/AboutResources.txt",
    "content": "Images, layout descriptions, binary blobs and string dictionaries can be included \nin your application as resource files.  Various Android APIs are designed to \noperate on the resource IDs instead of dealing with images, strings or binary blobs \ndirectly.\n\nFor example, a sample Android app that contains a user interface layout (main.axml),\nan internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) \nwould keep its resources in the \"Resources\" directory of the application:\n\nResources/\n    drawable/\n        icon.png\n\n    layout/\n        main.axml\n\n    values/\n        strings.xml\n\nIn order to get the build system to recognize Android resources, set the build action to\n\"AndroidResource\".  The native Android APIs do not operate directly with filenames, but \ninstead operate on resource IDs.  When you compile an Android application that uses resources, \nthe build system will package the resources for distribution and generate a class called \"R\" \n(this is an Android convention) that contains the tokens for each one of the resources \nincluded. For example, for the above Resources layout, this is what the R class would expose:\n\npublic class R {\n    public class drawable {\n        public const int icon = 0x123;\n    }\n\n    public class layout {\n        public const int main = 0x456;\n    }\n\n    public class strings {\n        public const int first_string = 0xabc;\n        public const int second_string = 0xbcd;\n    }\n}\n\nYou would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main \nto reference the layout/main.axml file, or R.strings.first_string to reference the first \nstring in the dictionary file values/strings.xml."
  },
  {
    "path": "src/Asv.Drones.Android/Resources/drawable/splash_screen.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n  <item>\n    <color android:color=\"@color/splash_background\"/>\n  </item>\n\n  <item android:drawable=\"@drawable/icon\"\n        android:width=\"120dp\"\n        android:height=\"120dp\"\n        android:gravity=\"center\" />\n\n</layer-list>\n"
  },
  {
    "path": "src/Asv.Drones.Android/Resources/drawable-night-v31/avalonia_anim.xml",
    "content": "<animated-vector\n  xmlns:android=\"http://schemas.android.com/apk/res/android\"\n  xmlns:aapt=\"http://schemas.android.com/aapt\">\n  <aapt:attr name=\"android:drawable\">\n    <vector\n      android:name=\"vector\"\n      android:width=\"128dp\"\n      android:height=\"128dp\"\n      android:viewportWidth=\"128\"\n      android:viewportHeight=\"128\">\n      <group\n        android:name=\"wrapper\"\n        android:translateX=\"21\"\n        android:translateY=\"21\">\n        <group android:name=\"group\">\n          <path\n            android:name=\"path\"\n            android:pathData=\"M 74.853 85.823 L 75.368 85.823 C 80.735 85.823 85.144 81.803 85.761 76.602 L 85.836 41.76 C 85.225 18.593 66.254 0 42.939 0 C 19.24 0 0.028 19.212 0.028 42.912 C 0.028 66.357 18.831 85.418 42.18 85.823 L 74.853 85.823 Z\"\n            android:strokeWidth=\"1\"/>\n          <path\n            android:name=\"path_1\"\n            android:pathData=\"M 43.059 14.614 C 29.551 14.614 18.256 24.082 15.445 36.743 C 18.136 37.498 20.109 39.968 20.109 42.899 C 20.109 45.831 18.136 48.301 15.445 49.055 C 18.256 61.716 29.551 71.184 43.059 71.184 C 47.975 71.184 52.599 69.93 56.628 67.723 L 56.628 70.993 L 71.344 70.993 L 71.344 44.072 C 71.357 43.714 71.344 43.26 71.344 42.899 C 71.344 27.278 58.68 14.614 43.059 14.614 Z M 29.51 42.899 C 29.51 35.416 35.576 29.35 43.059 29.35 C 50.541 29.35 56.607 35.416 56.607 42.899 C 56.607 50.382 50.541 56.448 43.059 56.448 C 35.576 56.448 29.51 50.382 29.51 42.899 Z\"\n            android:strokeWidth=\"1\"\n            android:fillType=\"evenOdd\"/>\n          <path\n            android:name=\"path_2\"\n            android:pathData=\"M 18.105 42.88 C 18.105 45.38 16.078 47.407 13.579 47.407 C 11.079 47.407 9.052 45.38 9.052 42.88 C 9.052 40.381 11.079 38.354 13.579 38.354 C 16.078 38.354 18.105 40.381 18.105 42.88 Z\"\n            android:strokeWidth=\"1\"/>\n        </group>\n      </group>\n    </vector>\n  </aapt:attr>\n  <target android:name=\"path\">\n    <aapt:attr name=\"android:animation\">\n      <objectAnimator\n        android:propertyName=\"fillColor\"\n        android:duration=\"1000\"\n        android:valueFrom=\"#00ffffff\"\n        android:valueTo=\"#161c2d\"\n        android:valueType=\"colorType\"\n        android:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n    </aapt:attr>\n  </target>\n  <target android:name=\"path_1\">\n    <aapt:attr name=\"android:animation\">\n      <objectAnimator\n        android:propertyName=\"fillColor\"\n        android:duration=\"1000\"\n        android:valueFrom=\"#00ffffff\"\n        android:valueTo=\"#f9f9fb\"\n        android:valueType=\"colorType\"\n        android:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n    </aapt:attr>\n  </target>\n  <target android:name=\"path_2\">\n    <aapt:attr name=\"android:animation\">\n      <objectAnimator\n        android:propertyName=\"fillColor\"\n        android:duration=\"1000\"\n        android:valueFrom=\"#00ffffff\"\n        android:valueTo=\"#f9f9fb\"\n        android:valueType=\"colorType\"\n        android:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n    </aapt:attr>\n  </target>\n</animated-vector>\n"
  },
  {
    "path": "src/Asv.Drones.Android/Resources/drawable-v31/avalonia_anim.xml",
    "content": "<animated-vector\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"128dp\"\n            android:height=\"128dp\"\n            android:viewportWidth=\"128\"\n            android:viewportHeight=\"128\">\n            <group\n                android:name=\"wrapper\"\n                android:translateX=\"21\"\n                android:translateY=\"21\">\n                <group android:name=\"group\">\n                    <path\n                        android:name=\"path\"\n                        android:pathData=\"M 74.853 85.823 L 75.368 85.823 C 80.735 85.823 85.144 81.803 85.761 76.602 L 85.836 41.76 C 85.225 18.593 66.254 0 42.939 0 C 19.24 0 0.028 19.212 0.028 42.912 C 0.028 66.357 18.831 85.418 42.18 85.823 L 74.853 85.823 Z\"\n                        android:fillColor=\"#00ffffff\"\n                        android:strokeWidth=\"1\"/>\n                    <path\n                        android:name=\"path_1\"\n                        android:pathData=\"M 43.059 14.614 C 29.551 14.614 18.256 24.082 15.445 36.743 C 18.136 37.498 20.109 39.968 20.109 42.899 C 20.109 45.831 18.136 48.301 15.445 49.055 C 18.256 61.716 29.551 71.184 43.059 71.184 C 47.975 71.184 52.599 69.93 56.628 67.723 L 56.628 70.993 L 71.344 70.993 L 71.344 44.072 C 71.357 43.714 71.344 43.26 71.344 42.899 C 71.344 27.278 58.68 14.614 43.059 14.614 Z M 29.51 42.899 C 29.51 35.416 35.576 29.35 43.059 29.35 C 50.541 29.35 56.607 35.416 56.607 42.899 C 56.607 50.382 50.541 56.448 43.059 56.448 C 35.576 56.448 29.51 50.382 29.51 42.899 Z\"\n                        android:fillColor=\"#00ffffff\"\n                        android:strokeWidth=\"1\"\n                        android:fillType=\"evenOdd\"/>\n                    <path\n                        android:name=\"path_2\"\n                        android:pathData=\"M 18.105 42.88 C 18.105 45.38 16.078 47.407 13.579 47.407 C 11.079 47.407 9.052 45.38 9.052 42.88 C 9.052 40.381 11.079 38.354 13.579 38.354 C 16.078 38.354 18.105 40.381 18.105 42.88 Z\"\n                        android:fillColor=\"#00ffffff\"\n                        android:strokeWidth=\"1\"/>\n                </group>\n            </group>\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path_2\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:propertyName=\"fillColor\"\n                android:startOffset=\"100\"\n                android:duration=\"900\"\n                android:valueFrom=\"#00ffffff\"\n                android:valueTo=\"#161c2d\"\n                android:valueType=\"colorType\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n        </aapt:attr>\n    </target>\n    <target android:name=\"path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:propertyName=\"fillColor\"\n                android:duration=\"500\"\n                android:valueFrom=\"#00ffffff\"\n                android:valueTo=\"#f9f9fb\"\n                android:valueType=\"colorType\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n        </aapt:attr>\n    </target>\n    <target android:name=\"path_1\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:propertyName=\"fillColor\"\n                android:startOffset=\"100\"\n                android:duration=\"900\"\n                android:valueFrom=\"#00ffffff\"\n                android:valueTo=\"#161c2d\"\n                android:valueType=\"colorType\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "src/Asv.Drones.Android/Resources/values/colors.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <color name=\"splash_background\">#FFFFFF</color>\n</resources>\n"
  },
  {
    "path": "src/Asv.Drones.Android/Resources/values/styles.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<resources>\n\n  <style name=\"MyTheme\">\n  </style>\n\n  <style name=\"MyTheme.NoActionBar\" parent=\"@style/Theme.AppCompat.DayNight.NoActionBar\">\n    <item name=\"android:windowActionBar\">false</item>\n    <item name=\"android:windowBackground\">@drawable/splash_screen</item>\n    <item name=\"android:windowNoTitle\">true</item>\n  </style>\n</resources>\n"
  },
  {
    "path": "src/Asv.Drones.Android/Resources/values-night/colors.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <color name=\"splash_background\">#212121</color>\n</resources>\n"
  },
  {
    "path": "src/Asv.Drones.Android/Resources/values-v31/styles.xml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<resources>\n\n  <style name=\"MyTheme\">\n  </style>\n\n  <style name=\"MyTheme.NoActionBar\" parent=\"@style/Theme.AppCompat.NoActionBar\">\n    <item name=\"android:windowActionBar\">false</item>\n    <item name=\"android:windowBackground\">@null</item>\n    <item name=\"android:windowNoTitle\">true</item>\n    <item name=\"android:windowSplashScreenBackground\">@color/splash_background</item>\n    <item name=\"android:windowSplashScreenAnimatedIcon\">@drawable/avalonia_anim</item>\n    <item name=\"android:windowSplashScreenAnimationDuration\">1000</item>\n    <item name=\"postSplashScreenTheme\">@style/MyTheme.Main</item>\n\n  </style>\n  <style name=\"MyTheme.Main\"\n         parent =\"MyTheme.NoActionBar\">\n    <item name=\"android:windowIsTranslucent\">true</item>\n  </style>\n</resources>\n"
  },
  {
    "path": "src/Asv.Drones.Api/Asv.Drones.Api.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n    <PropertyGroup>\n        <TargetFramework>$(TargetFramework)</TargetFramework>\n        <FileVersion>$(ApiVersion)</FileVersion>\n        <Version>$(ApiVersion)</Version>\n        <PackageVersion>$(ApiVersion)</PackageVersion>\n\n        <Authors>https://github.com/asv-soft</Authors>\n        <Company>https://github.com/asv-soft</Company>\n        <Copyright>https://github.com/asv-soft</Copyright>\n\n        <GeneratePackageOnBuild>true</GeneratePackageOnBuild>\n\n        <LangVersion>preview</LangVersion>\n        <ImplicitUsings>enable</ImplicitUsings>\n        <Nullable>enable</Nullable>\n        <Title>Asv.Drones.Api</Title>\n        <Copyright>Asv Soft</Copyright>\n        <PackageProjectUrl>https://github.com/asv-soft/asv-drones</PackageProjectUrl>\n        <PackageLicenseUrl>https://github.com/asv-soft/asv-drones/blob/main/LICENSE</PackageLicenseUrl>\n        <RepositoryUrl>https://github.com/asv-soft/asv-drones</RepositoryUrl>\n        <RepositoryType>git</RepositoryType>\n        <CodeAnalysisRuleSet>../CodeStyle.ruleset</CodeAnalysisRuleSet>\n        <WarningsAsErrors>\n            CS0169,\n            CS0618,\n            CS1502,\n            CS1503,\n            CS8524,\n            CS8600,\n            CS8601,\n            CS8602,\n            CS8603,\n            CS8604,\n            CS8625,\n            CS8629,\n            CS8762,\n            CA1510,\n            CA1851\n        </WarningsAsErrors>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <PackageReference Include=\"Asv.Avalonia\" Version=\"$(AsvAvaloniaVersion)\" />\n        <PackageReference Include=\"Asv.Avalonia.GeoMap\" Version=\"$(AsvAvaloniaVersion)\" />\n        <PackageReference Include=\"Asv.Avalonia.IO\" Version=\"$(AsvAvaloniaVersion)\" />\n        <PackageReference Include=\"Asv.Avalonia.Plugins\" Version=\"$(AsvAvaloniaVersion)\" />\n        <PackageReference Include=\"Asv.Mavlink\" Version=\"$(AsvMavlinkVersion)\" />\n        <PackageReference Include=\"Asv.Gnss\" Version=\"$(AsvGnssVersion)\" />\n        <PackageReference Include=\"ScottPlot.Avalonia\" Version=\"$(ScottPlotVersion)\" />\n    </ItemGroup>\n\n    <ItemGroup>\n      <PackageReference Include=\"Roslynator.Analyzers\" Version=\"4.14.0\">\n        <PrivateAssets>all</PrivateAssets>\n        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n      </PackageReference>\n      <PackageReference Include=\"StyleCop.Analyzers\" Version=\"1.1.118\">\n        <PrivateAssets>all</PrivateAssets>\n        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n      </PackageReference>\n    </ItemGroup>\n\n    <ItemGroup>\n      <Folder Include=\"Core\\Services\\Devices\\\" />\n      <Folder Include=\"Shell\\\" />\n      <Folder Include=\"Tools\\Design\\\" />\n    </ItemGroup>\n\n    <ItemGroup>\n      <EmbeddedResource Update=\"RS.resx\">\n        <Generator>ResXFileCodeGenerator</Generator>\n        <LastGenOutput>RS.Designer.cs</LastGenOutput>\n      </EmbeddedResource>\n    </ItemGroup>\n\n    <ItemGroup>\n      <Compile Update=\"RS.Designer.cs\">\n        <DesignTime>True</DesignTime>\n        <AutoGen>True</AutoGen>\n        <DependentUpon>RS.resx</DependentUpon>\n      </Compile>\n      <Compile Update=\"Core\\Controls\\FlightWidget\\FlightWidgetView.axaml.cs\">\n        <DependentUpon>FlightWidgetView.axaml</DependentUpon>\n        <SubType>Code</SubType>\n      </Compile>\n      <Compile Update=\"Core\\Controls\\FlightWidget\\FlightWidgetView.axaml.cs\">\n        <DependentUpon>FlightWidgetView.axaml</DependentUpon>\n        <SubType>Code</SubType>\n      </Compile>\n    </ItemGroup>\n\n    <ItemGroup>\n      <AdditionalFiles Include=\"Core\\Controls\\MavParam\\Button\\MavParamButtonView.axaml\" />\n      <AdditionalFiles Include=\"Core\\Controls\\MavParam\\ComboBox\\MavParamComboBoxView.axaml\" />\n      <AdditionalFiles Include=\"Core\\Controls\\MavParam\\TextBox\\MavParamTextBoxView.axaml\" />\n    </ItemGroup>\n\n\n\n</Project>\n"
  },
  {
    "path": "src/Asv.Drones.Api/Asv.Drones.Api.csproj.DotSettings",
    "content": "﻿<wpf:ResourceDictionary xml:space=\"preserve\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:s=\"clr-namespace:System;assembly=mscorlib\" xmlns:ss=\"urn:shemas-jetbrains-com:settings-storage-xaml\" xmlns:wpf=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cflight/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=commands_005Cmavlink/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cftpbrowser/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cmavparam/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cmavparam_005Cbutton/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cmavparam_005Ccombobox/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cmavparam_005Cgeopoint/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cmavparam_005Cstring/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cmavparam_005Ctextbox/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cmavparam_005Ctoggleswitch/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controls_005Cparams/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cbehaviour/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cbehaviour_005Cremove/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cbehaviour_005Crename/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cflight/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands_005Cmavlink/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccommands/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflightwidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cflightwidget_005Csection/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cmavparam_005Cbutton/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cmavparam_005Ccombobox/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cmavparam_005Cgeopoint/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cmavparam_005Cstring/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cmavparam_005Ctextbox/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols_005Cmavparam/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Ccontrols/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cservices_005Cclientdevicewidgetfactory/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cservices_005Cconverters/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cservices_005Cdevices/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cservices_005Cdevices_005Cmavlink/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cservices_005Cmavlink/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core_005Cservices/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=mavparams/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cfilebrowser/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Cwidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Cwidgets/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cflight_005Cwidgets_005Cuavwidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cftpbrowser/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Cmavparams/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Csetup/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pages_005Csetup_005Csubpage/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=params/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cconverters/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cmavlink/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cfilebrowser/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink_005Cdrone/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cdevice_005Cmavlink/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cmavlink/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflightmode_005Cwidgets_005Cmavlink_005Cdrone/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflight_005Cwidgets_005Cuavwidget/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflight_005Cwidgets/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cflight/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Cmavparams/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Csetup_005Csubpage/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages_005Csetup/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shell_005Cpages/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=tools/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=tools_005Cdesign/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=tools_005Cmavlink/@EntryIndexedValue\">True</s:Boolean>\n\t<s:Boolean x:Key=\"/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=_005Fmove_005Fto_005Fmavlink/@EntryIndexedValue\">True</s:Boolean></wpf:ResourceDictionary>"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Commands/Behaviour/Remove/IRemoveItemCommand.cs",
    "content": "using Asv.Avalonia;\n\nnamespace Asv.Drones.Api;\n\npublic interface IRemoveItemCommand\n{\n    public const string CommandId = $\"{AsyncCommand.BaseId}.remove\";\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Commands/Behaviour/Remove/ISupportRemove.cs",
    "content": "﻿using Asv.Avalonia;\n\nnamespace Asv.Drones.Api;\n\npublic interface ISupportRemove : IRoutable\n{\n    ValueTask RemoveAsync(CancellationToken ct);\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Commands/Behaviour/Rename/ICommitRenameCommand.cs",
    "content": "using Asv.Avalonia;\n\nnamespace Asv.Drones.Api;\n\npublic interface ICommitRenameCommand\n{\n    public const string CommandId = $\"{AsyncCommand.BaseId}.rename.commit\";\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Commands/Behaviour/Rename/ISupportRename.cs",
    "content": "﻿using Asv.Avalonia;\n\nnamespace Asv.Drones.Api;\n\npublic interface ISupportRename : IRoutable\n{\n    public const string CommandId = \"cmd.rename.commit\";\n    ValueTask<string> RenameAsync(string oldValue, string newValue, CancellationToken ct);\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Commands/Commands.cs",
    "content": "﻿using Asv.Avalonia;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace Asv.Drones.Api;\n\npublic static class Commands\n{\n    private static IFlightModeCommands? _flightMode;\n    private static IMavlinkCommands? _mavlink;\n\n    public static IMavlinkCommands Mavlink =>\n        _mavlink ??= AppHost.Instance.Services.GetRequiredService<IMavlinkCommands>();\n\n    public static IFlightModeCommands FlightMode =>\n        _flightMode ??= AppHost.Instance.Services.GetRequiredService<IFlightModeCommands>();\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Commands/Flight/IFlightModeCommands.cs",
    "content": "﻿namespace Asv.Drones.Api;\n\npublic interface IFlightModeCommands { }\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Commands/Mavlink/IMavlinkCommands.cs",
    "content": "using Asv.Avalonia;\nusing Asv.Mavlink;\n\nnamespace Asv.Drones.Api;\n\npublic interface IMavlinkCommands\n{\n    ICommandInfo WriteParamInfo { get; }\n    ValueTask WriteParam(\n        IRoutable context,\n        string name,\n        MavParamValue value,\n        CancellationToken cancel = default\n    );\n    ICommandInfo ReadParamInfo { get; }\n    ValueTask ReadParam(IRoutable context, string name, CancellationToken cancel = default);\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Commands/MavlinkMicroserviceCommand.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.IO;\n\nnamespace Asv.Drones.Api;\n\npublic abstract class MavlinkMicroserviceCommand<TMicroservice, TArg>\n    : ContextCommand<IDevicePage, TArg>\n    where TArg : CommandArg\n    where TMicroservice : class\n{\n    public override bool CanExecute(\n        IRoutable context,\n        CommandArg parameter,\n        out IRoutable targetContext\n    )\n    {\n        if (base.CanExecute(context, parameter, out targetContext) == false)\n        {\n            return false;\n        }\n\n        if (targetContext is IDevicePage devicePage)\n        {\n            return devicePage.Target.CurrentValue?.Device.GetMicroservice<TMicroservice>() != null;\n        }\n\n        return false;\n    }\n\n    public override ValueTask<TArg?> InternalExecute(\n        IDevicePage context,\n        TArg arg,\n        CancellationToken cancel\n    )\n    {\n        var microservice = context.Target.CurrentValue?.Device.GetMicroservice<TMicroservice>();\n        if (microservice == null)\n        {\n            throw new Exception(\n                $\"Error to execute command {GetType().Name}[{Info.Id}]: device microservice {nameof(TMicroservice)} not found\"\n            );\n        }\n        return InternalExecute(microservice, arg, cancel);\n    }\n\n    protected abstract ValueTask<TArg?> InternalExecute(\n        TMicroservice microservice,\n        TArg arg,\n        CancellationToken cancel\n    );\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/FlightWidget/FlightWidgetView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:api=\"clr-namespace:Asv.Drones.Api\"\n    mc:Ignorable=\"d\"\n    d:DesignHeight=\"1000\"\n    x:Class=\"Asv.Drones.Api.FlightWidgetView\"\n    x:DataType=\"api:IFlightWidget\"\n>\n    <ItemsControl ItemsSource=\"{Binding SectionsView}\">\n        <ItemsControl.ItemsPanel>\n            <ItemsPanelTemplate>\n                <WrapPanel ItemSpacing=\"4\" LineSpacing=\"4\" />\n            </ItemsPanelTemplate>\n        </ItemsControl.ItemsPanel>\n        <ItemsControl.ItemTemplate>\n            <DataTemplate>\n                <ContentControl Margin=\"5\" Content=\"{Binding}\" />\n            </DataTemplate>\n        </ItemsControl.ItemTemplate>\n    </ItemsControl>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/FlightWidget/FlightWidgetView.axaml.cs",
    "content": "﻿using Avalonia.Controls;\n\nnamespace Asv.Drones.Api;\n\npublic partial class FlightWidgetView : UserControl\n{\n    public FlightWidgetView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/FlightWidget/FlightWidgetViewModel.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Modeling;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing ObservableCollections;\nusing R3;\n\nnamespace Asv.Drones.Api;\n\npublic abstract class FlightWidgetViewModel<TContext, TSelf>(\n    NavigationId id,\n    ILoggerFactory loggerFactory,\n    IExtensionService ext\n) : FlightWidgetViewModel<TSelf>(id, loggerFactory, ext), IFlightWidget<TContext>\n    where TContext : class\n    where TSelf : class, IFlightWidget<TContext>\n{\n    public abstract void InitWith(TContext context);\n}\n\npublic abstract class FlightWidgetViewModel<TSelf> : ExtendableViewModel<TSelf>, IFlightWidget\n    where TSelf : class, IFlightWidget\n{\n    protected FlightWidgetViewModel(\n        NavigationId id,\n        ILoggerFactory loggerFactory,\n        IExtensionService ext\n    )\n        : base(id, loggerFactory, ext)\n    {\n        Menu.SetRoutableParent(this).DisposeItWith(Disposable);\n        Menu.DisposeRemovedItems().DisposeItWith(Disposable);\n        MenuView = new MenuTree(Menu).DisposeItWith(Disposable);\n\n        Sections = [];\n        Sections.SetRoutableParent(this).DisposeItWith(Disposable);\n        Sections.DisposeRemovedItems().DisposeItWith(Disposable);\n        Sections\n            .ObserveAdd()\n            .ObserveOnUIThreadDispatcher()\n            .Subscribe(_ => Sections.Sort(FlightWidgetSectionsComparer.Instance))\n            .DisposeItWith(Disposable);\n\n        SectionsView = Sections.ToNotifyCollectionChangedSlim().DisposeItWith(Disposable);\n    }\n\n    public MaterialIconKind? Icon\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public AsvColorKind IconColor\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public string? Header\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public WorkspaceDock Position\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public bool IsExpanded\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public bool CanExpand\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public bool IsVisible\n    {\n        get;\n        set => SetField(ref field, value);\n    } = true;\n\n    public ObservableList<IMenuItem> Menu { get; } = [];\n\n    public MenuTree? MenuView { get; }\n\n    public abstract int Order { get; }\n\n    public ObservableList<IFlightWidgetSection> Sections { get; }\n    public INotifyCollectionChangedSynchronizedViewList<IFlightWidgetSection> SectionsView { get; }\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        foreach (var item in SectionsView)\n        {\n            yield return item;\n        }\n\n        foreach (var item in Menu)\n        {\n            yield return item;\n        }\n    }\n\n    protected override void AfterLoadExtensions()\n    {\n        // nothing to do\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/FlightWidget/FlightWidgetsComparer.cs",
    "content": "namespace Asv.Drones.Api;\n\npublic class FlightWidgetsComparer : IComparer<IFlightWidget>\n{\n    public static readonly FlightWidgetsComparer Instance = new();\n\n    public int Compare(IFlightWidget? x, IFlightWidget? y)\n    {\n        if (x is null && y is null)\n        {\n            return 0;\n        }\n\n        if (x is null)\n        {\n            return -1;\n        }\n\n        if (y is null)\n        {\n            return 1;\n        }\n\n        var typeComparison = CompareWidgetGroups(x, y);\n        if (typeComparison != 0)\n        {\n            return typeComparison;\n        }\n\n        return x.Order.CompareTo(y.Order);\n    }\n\n    private static int CompareWidgetGroups(IFlightWidget x, IFlightWidget y)\n    {\n        return StringComparer.Ordinal.Compare(GetGroupKey(x), GetGroupKey(y));\n    }\n\n    private static string GetGroupKey(IFlightWidget widget)\n    {\n        return widget.GetType().FullName ?? string.Empty;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/FlightWidget/IFlightWidget.cs",
    "content": "﻿using Asv.Avalonia;\nusing ObservableCollections;\n\nnamespace Asv.Drones.Api;\n\npublic interface IFlightWidget<in TContext> : IFlightWidget\n    where TContext : class\n{\n    void InitWith(TContext context);\n}\n\npublic interface IFlightWidget : IWorkspaceWidget\n{\n    int Order { get; }\n    ObservableList<IFlightWidgetSection> Sections { get; }\n    INotifyCollectionChangedSynchronizedViewList<IFlightWidgetSection> SectionsView { get; }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/FlightWidget/Section/FlightWidgetSectionsComparer.cs",
    "content": "﻿namespace Asv.Drones.Api;\n\npublic class FlightWidgetSectionsComparer : IComparer<IFlightWidgetSection>\n{\n    public static readonly FlightWidgetSectionsComparer Instance = new();\n\n    public int Compare(IFlightWidgetSection? x, IFlightWidgetSection? y)\n    {\n        if (x is null && y is null)\n        {\n            return 0;\n        }\n\n        if (x is null)\n        {\n            return -1;\n        }\n\n        if (y is null)\n        {\n            return 1;\n        }\n\n        return x.Order.CompareTo(y.Order);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/FlightWidget/Section/IFlightWidgetSection.cs",
    "content": "﻿using Asv.Avalonia;\n\nnamespace Asv.Drones.Api;\n\npublic interface IAsyncFlightWidgetSection<in TContext> : IFlightWidgetSection\n    where TContext : class\n{\n    ValueTask InitWithAsync(TContext context, CancellationToken cancel = default);\n}\n\npublic interface IFlightWidgetSection<in TContext> : IFlightWidgetSection\n    where TContext : class\n{\n    void InitWith(TContext context);\n}\n\npublic interface IFlightWidgetSection : IRoutable\n{\n    int Order { get; }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/Button/MavParamButtonView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:api=\"clr-namespace:Asv.Drones.Api\"\n    xmlns:avalonia=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    xmlns:avalonia1=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"200\"\n    x:Class=\"Asv.Drones.Api.MavParamButtonView\"\n    x:DataType=\"api:MavParamButtonViewModel\"\n>\n    <Design.DataContext>\n        <api:MavParamButtonViewModel />\n    </Design.DataContext>\n    <Button\n        Padding=\"8,4,0,4\"\n        HorizontalContentAlignment=\"Left\"\n        HorizontalAlignment=\"Stretch\"\n        VerticalAlignment=\"Top\"\n        IsEnabled=\"{Binding !IsBusy}\"\n        Command=\"{Binding InternalWrite}\"\n    >\n        <ToolTip.Tip>\n            <StackPanel Spacing=\"8\">\n                <TextBlock Classes=\"h1\" Text=\"{Binding Info.Title}\" />\n                <TextBlock Classes=\"description\" Text=\"{Binding Info.Description}\" />\n            </StackPanel>\n        </ToolTip.Tip>\n        <Panel HorizontalAlignment=\"Stretch\">\n            <avalonia:MaterialIcon\n                ToolTip.Tip=\"{Binding NetworkErrorMessage}\"\n                IsVisible=\"{Binding IsNetworkError}\"\n                Classes=\"blink\"\n                HorizontalAlignment=\"Left\"\n                Kind=\"CloseNetwork\"\n                Foreground=\"{DynamicResource AsvForegroundErrorBrush}\"\n                Margin=\"4,0,0,0\"\n                Width=\"18\"\n                Height=\"18\"\n            />\n            <Ellipse\n                Opacity=\"0\"\n                Margin=\"10,0,0,0\"\n                Classes.fadeout=\"{Binding !IsRemoteChange}\"\n                VerticalAlignment=\"Center\"\n                HorizontalAlignment=\"Left\"\n                Width=\"5\"\n                Height=\"5\"\n                Fill=\"{DynamicResource AsvForegroundInfo3Brush}\"\n            />\n            <ProgressBar\n                IsVisible=\"{Binding IsBusy}\"\n                VerticalAlignment=\"Stretch\"\n                IsIndeterminate=\"True\"\n            />\n            <StackPanel Margin=\"20,0,0,0\" Orientation=\"Horizontal\" Spacing=\"4\">\n                <avalonia:MaterialIcon\n                    IsVisible=\"{Binding Info.Icon, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                    Margin=\"8,0\"\n                    Width=\"25\"\n                    Height=\"25\"\n                    Kind=\"{Binding Info.Icon}\"\n                    avalonia1:AsvPallete.Color=\"{Binding Info.IconColor}\"\n                />\n                <TextBlock VerticalAlignment=\"Center\" Text=\"{Binding Info.Title}\" />\n            </StackPanel>\n        </Panel>\n    </Button>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/Button/MavParamButtonView.axaml.cs",
    "content": "﻿using Asv.Avalonia;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Markup.Xaml;\n\nnamespace Asv.Drones.Api;\n\npublic partial class MavParamButtonView : UserControl\n{\n    public MavParamButtonView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/Button/MavParamButtonViewModel.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Mavlink;\nusing Asv.Mavlink.Common;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones.Api;\n\npublic class MavParamButtonViewModel : MavParamViewModel\n{\n    public MavParamButtonViewModel()\n        : this(\n            new MavParamInfo(\n                new MavParamTypeMetadata(\n                    \"A\"\n                        + NavigationId\n                            .GenerateRandomAsString(15)\n                            .Replace('.', '_')\n                            .Replace('-', '_'),\n                    MavParamType.MavParamTypeInt32\n                )\n                {\n                    Units = \"MHz\",\n                    RebootRequired = false,\n                    Volatile = false,\n                    MinValue = new MavParamValue(-100),\n                    ShortDesc = \"Test param\",\n                    LongDesc = \"Long description for test param [icon=power]\",\n                    Group = \"System\",\n                    Category = \"System\",\n                    MaxValue = new MavParamValue(100),\n                    DefaultValue = new MavParamValue(50),\n                    Increment = new MavParamValue(1),\n                }\n            ),\n            new Subject<MavParamValue>(),\n            (_, _) => ValueTask.FromResult(new MavParamValue(100)),\n            DesignTime.LoggerFactory\n        )\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public MavParamButtonViewModel(\n        MavParamInfo info,\n        Observable<MavParamValue> update,\n        InitialReadParamDelegate initReadCallback,\n        ILoggerFactory loggerFactory\n    )\n        : base(info, update, initReadCallback, loggerFactory)\n    {\n        ShowHeader = false;\n    }\n\n    public void InternalWrite()\n    {\n        Value.Value = 0;\n        Value.Value = 1;\n        Write();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/ComboBox/MavParamComboBoxView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:api=\"clr-namespace:Asv.Drones.Api\"\n    xmlns:avalonia=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    xmlns:avalonia1=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"250\"\n    d:DesignHeight=\"300\"\n    x:Class=\"Asv.Drones.Api.MavParamComboBoxView\"\n    x:DataType=\"api:MavParamComboBoxViewModel\"\n>\n    <Design.DataContext>\n        <api:MavParamComboBoxViewModel />\n    </Design.DataContext>\n    <Panel VerticalAlignment=\"Top\" HorizontalAlignment=\"Stretch\">\n        <ComboBox\n            HorizontalAlignment=\"Stretch\"\n            HorizontalContentAlignment=\"Stretch\"\n            SelectedItem=\"{Binding SelectedItem, Mode=TwoWay}\"\n            ItemsSource=\"{Binding Items}\"\n            IsEnabled=\"{Binding !IsBusy}\"\n        >\n            <ComboBox.ItemTemplate>\n                <DataTemplate>\n                    <StackPanel\n                        IsVisible=\"{Binding Converter={x:Static ObjectConverters.IsNotNull}}\"\n                        Margin=\"10,0,0,0\"\n                        Orientation=\"Horizontal\"\n                        Spacing=\"8\"\n                    >\n                        <avalonia:MaterialIcon\n                            Foreground=\"{StaticResource SystemAccentColor}\"\n                            IsVisible=\"{Binding Icon, Converter={x:Static ObjectConverters.IsNotNull}}\"\n                            Kind=\"{Binding Icon}\"\n                            avalonia1:AsvPallete.Color=\"{Binding IconColor}\"\n                        />\n                        <TextBlock VerticalAlignment=\"Center\" Text=\"{Binding Title}\" />\n                    </StackPanel>\n                </DataTemplate>\n            </ComboBox.ItemTemplate>\n            <ToolTip.Tip>\n                <StackPanel Spacing=\"8\">\n                    <TextBlock Classes=\"h1\" Text=\"{Binding Info.Title}\" />\n                    <TextBlock Classes=\"description\" Text=\"{Binding Info.Description}\" />\n                </StackPanel>\n            </ToolTip.Tip>\n        </ComboBox>\n        <Ellipse\n            Opacity=\"0\"\n            Margin=\"10,0,0,0\"\n            Classes.fadeout=\"{Binding !IsRemoteChange}\"\n            VerticalAlignment=\"Center\"\n            HorizontalAlignment=\"Left\"\n            Width=\"5\"\n            Height=\"5\"\n            Fill=\"{DynamicResource AsvForegroundInfo3Brush}\"\n        />\n        <ProgressBar\n            Opacity=\"0.5\"\n            IsVisible=\"{Binding IsBusy}\"\n            VerticalAlignment=\"Stretch\"\n            IsIndeterminate=\"True\"\n        />\n        <avalonia:MaterialIcon\n            ToolTip.Tip=\"{Binding NetworkErrorMessage}\"\n            IsVisible=\"{Binding IsNetworkError}\"\n            Classes=\"blink\"\n            HorizontalAlignment=\"Left\"\n            Kind=\"CloseNetwork\"\n            Foreground=\"{DynamicResource AsvForegroundErrorBrush}\"\n            Margin=\"4,0,0,0\"\n            Width=\"18\"\n            Height=\"18\"\n        />\n    </Panel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/ComboBox/MavParamComboBoxView.axaml.cs",
    "content": "﻿using Asv.Avalonia;\nusing Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Markup.Xaml;\n\nnamespace Asv.Drones.Api;\n\npublic partial class MavParamComboBoxView : UserControl\n{\n    public MavParamComboBoxView()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/ComboBox/MavParamComboboxViewModel.cs",
    "content": "using Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Mavlink;\nusing Asv.Mavlink.Common;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones.Api;\n\npublic class MavParamComboBoxViewModel : MavParamViewModel\n{\n    private bool _internalChange;\n\n    public MavParamComboBoxViewModel()\n        : this(\n            new MavParamInfo(\n                new MavParamTypeMetadata(\n                    \"A\"\n                        + NavigationId\n                            .GenerateRandomAsString(15)\n                            .Replace('.', '_')\n                            .Replace('-', '_'),\n                    MavParamType.MavParamTypeInt32\n                )\n                {\n                    Units = \"MHz\",\n                    RebootRequired = false,\n                    Volatile = false,\n                    MinValue = new MavParamValue(-100),\n                    ShortDesc = \"Test param\",\n                    LongDesc = \"Long description for test param\",\n                    Group = \"System\",\n                    Category = \"System\",\n                    MaxValue = new MavParamValue(100),\n                    DefaultValue = new MavParamValue(50),\n                    Increment = new MavParamValue(1),\n                    Values =\n                    [\n                        new ValueTuple<MavParamValue, string>(\n                            1,\n                            \"TX1 [icon=numeric-1-box-outline]\"\n                        ),\n                        new ValueTuple<MavParamValue, string>(\n                            2,\n                            \"TX2 [icon=numeric-2-box-outline]\"\n                        ),\n                    ],\n                }\n            ),\n            new Subject<MavParamValue>(),\n            (_, _) => ValueTask.FromResult(new MavParamValue(100)),\n            DesignTime.LoggerFactory\n        )\n    {\n        DesignTime.ThrowIfNotDesignMode();\n    }\n\n    public MavParamComboBoxViewModel(\n        MavParamInfo info,\n        Observable<MavParamValue> update,\n        InitialReadParamDelegate initReadCallback,\n        ILoggerFactory loggerFactory\n    )\n        : base(info, update, initReadCallback, loggerFactory)\n    {\n        Items = info.GetPredefinedValues().ToArray();\n        Value\n            .Where(_ => _internalChange == false)\n            .Subscribe(x =>\n            {\n                _internalChange = true;\n                SelectedItem = Items.FirstOrDefault(i => i.Value.Equals(x));\n                _internalChange = false;\n            })\n            .DisposeItWith(Disposable);\n\n        this.ObservePropertyChanged(x => x.SelectedItem, pushCurrentValueOnSubscribe: false)\n            .Where(_ => _internalChange == false)\n            .Subscribe(x =>\n            {\n                if (x == null)\n                {\n                    return;\n                }\n\n                _internalChange = true;\n                Value.Value = x.Value;\n                _internalChange = false;\n                Write();\n            })\n            .DisposeItWith(Disposable);\n    }\n\n    public MavParamValueItem? SelectedItem\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public MavParamValueItem[] Items { get; }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/Geopoint/MavParamAltitudeTextBoxView.cs",
    "content": "using Asv.Avalonia;\n\nnamespace Asv.Drones.Api;\n\npublic class MavParamAltitudeTextBoxView : MavParamTextBoxView;\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/Geopoint/MavParamAltitudeTextBoxViewModel.cs",
    "content": "using Asv.Avalonia;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones.Api;\n\npublic class MavParamAltitudeTextBoxViewModel : MavParamTextBoxViewModel\n{\n    private readonly IUnitItem _unit;\n\n    public MavParamAltitudeTextBoxViewModel(\n        MavParamInfo param,\n        Observable<MavParamValue> update,\n        InitialReadParamDelegate initReadCallback,\n        ILoggerFactory loggerFactory,\n        IUnitService measureService\n    )\n        : base(param, update, initReadCallback, loggerFactory)\n    {\n        _unit =\n            measureService[AltitudeUnit.Id]?.CurrentUnitItem.CurrentValue\n            ?? throw new ArgumentNullException(LatitudeUnit.Id);\n    }\n\n    protected override string ValueToText(ValueType remoteValue)\n    {\n        var meter = MavlinkTypesHelper.AltFromMmToDoubleMeter((int)remoteValue);\n        return _unit.PrintFromSi(meter);\n    }\n\n    public override string? Units => _unit.Symbol;\n\n    protected override Exception? TextToValue(string valueAsString, out ValueType value)\n    {\n        var result = _unit.ValidateValue(valueAsString);\n\n        if (result.IsSuccess == false)\n        {\n            value = Info.DefaultValue;\n            return result.ValidationException;\n        }\n\n        var degE6 = _unit.ParseToSi(valueAsString);\n        value = MavlinkTypesHelper.AltFromDoubleMeterToInt32Mm(degE6);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/Geopoint/MavParamLatLonTextBoxView.cs",
    "content": "﻿using Asv.Avalonia;\n\nnamespace Asv.Drones.Api;\n\npublic class MavParamLatLonTextBoxView : MavParamTextBoxView;\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/Geopoint/MavParamLatLonTextBoxViewModel.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones.Api;\n\npublic class MavParamLatLonTextBoxViewModel : MavParamTextBoxViewModel\n{\n    private readonly IUnitItem _unit;\n\n    public MavParamLatLonTextBoxViewModel(\n        MavParamInfo param,\n        Observable<MavParamValue> update,\n        InitialReadParamDelegate initReadCallback,\n        ILoggerFactory loggerFactory,\n        IUnitService measureService,\n        bool isLatitude\n    )\n        : base(param, update, initReadCallback, loggerFactory)\n    {\n        _unit =\n            measureService[isLatitude ? LatitudeUnit.Id : LongitudeUnit.Id]\n                ?.CurrentUnitItem\n                .CurrentValue ?? throw new ArgumentNullException(LatitudeUnit.Id);\n    }\n\n    public override string? Units => null;\n\n    protected override string ValueToText(ValueType remoteValue)\n    {\n        var degree = MavlinkTypesHelper.LatLonFromInt32E7ToDegDouble((int)remoteValue);\n        return _unit.PrintFromSi(degree);\n    }\n\n    protected override Exception? TextToValue(string valueAsString, out ValueType value)\n    {\n        var result = _unit.ValidateValue(valueAsString);\n\n        if (result.IsSuccess == false)\n        {\n            value = Info.DefaultValue;\n            return result.ValidationException;\n        }\n\n        var degE6 = _unit.ParseToSi(valueAsString);\n        value = MavlinkTypesHelper.LatLonDegDoubleToFromInt32E7To(degE6);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/MavParamFactory.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones.Api;\n\npublic static class MavParamFactory\n{\n    public static MavParamViewModel Create(\n        IMavParamTypeMetadata metadata,\n        Observable<MavParamValue> update,\n        InitialReadParamDelegate initReadCallback,\n        ILoggerFactory loggerFactory,\n        IUnitService measureService\n    )\n    {\n        var info = new MavParamInfo(metadata);\n        return info.WidgetType switch\n        {\n            MavParamWidgetType.AsciiChars => new MavParamAsciiCharViewModel(\n                info,\n                update,\n                initReadCallback,\n                loggerFactory\n            ),\n            MavParamWidgetType.Altitude => new MavParamAltitudeTextBoxViewModel(\n                info,\n                update,\n                initReadCallback,\n                loggerFactory,\n                measureService\n            ),\n            MavParamWidgetType.Latitude => new MavParamLatLonTextBoxViewModel(\n                info,\n                update,\n                initReadCallback,\n                loggerFactory,\n                measureService,\n                true\n            ),\n            MavParamWidgetType.Longitude => new MavParamLatLonTextBoxViewModel(\n                info,\n                update,\n                initReadCallback,\n                loggerFactory,\n                measureService,\n                false\n            ),\n            MavParamWidgetType.Button => new MavParamButtonViewModel(\n                info,\n                update,\n                initReadCallback,\n                loggerFactory\n            ),\n            MavParamWidgetType.ComboBox => new MavParamComboBoxViewModel(\n                info,\n                update,\n                initReadCallback,\n                loggerFactory\n            ),\n            _ => new MavParamTextBoxViewModel(info, update, initReadCallback, loggerFactory),\n        };\n    }\n\n    public static MavParamViewModel Create(\n        IMavParamTypeMetadata param,\n        IParamsClientEx svc,\n        ILoggerFactory loggerFactory,\n        IUnitService measureService\n    )\n    {\n        return Create(\n            param,\n            svc.Filter(param.Name),\n            svc.GetFromCacheOrReadOnce,\n            loggerFactory,\n            measureService\n        );\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/MavParamInfo.cs",
    "content": "﻿using System.Collections.Immutable;\nusing System.Diagnostics;\nusing System.Globalization;\nusing System.Text.RegularExpressions;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Mavlink;\nusing Asv.Mavlink.Common;\nusing Material.Icons;\n\nnamespace Asv.Drones.Api;\n\npublic enum MavParamWidgetType\n{\n    TextBox,\n    ComboBox,\n    Button,\n    Latitude,\n    Longitude,\n    Altitude,\n    AsciiChars,\n}\n\npublic partial class MavParamInfo\n{\n    #region Parsing metadata long description\n\n    private const string LongDescRegexString =\n        @\"^(?<description>[^\\[\\]]*?)(?:\\s*\\[(?<metadata>[^\\]]*)\\])?$\";\n\n    [GeneratedRegex(LongDescRegexString, RegexOptions.Compiled)]\n    private static partial Regex CreateLongDescRegex();\n\n    private static readonly Regex LongDescRegex = CreateLongDescRegex();\n\n    private const string MetadataRegexString = @\"(?<key>[^\\;=]+)=(?<value>[^\\;]+)(?:;|$)\";\n\n    [GeneratedRegex(MetadataRegexString, RegexOptions.Compiled)]\n    private static partial Regex CreateMetadataRegex();\n\n    private static readonly Regex MetadataRegex = CreateMetadataRegex();\n\n    private static void ParseAdditionalInfo(\n        string? metadataLongDesc,\n        out ImmutableDictionary<string, string> additionalInfo,\n        out string description\n    )\n    {\n        description = string.Empty;\n        additionalInfo = ImmutableDictionary<string, string>.Empty;\n\n        if (string.IsNullOrWhiteSpace(metadataLongDesc))\n        {\n            return;\n        }\n\n        var match = LongDescRegex.Match(metadataLongDesc.Trim());\n        if (!match.Success)\n        {\n            return;\n        }\n\n        description = match.Groups[\"description\"].Value.Trim();\n        var metadataString = match.Groups[\"metadata\"].Value.Trim();\n        if (string.IsNullOrWhiteSpace(metadataString))\n        {\n            return;\n        }\n\n        var metadataMatches = MetadataRegex.Matches(metadataString);\n        var builder = ImmutableDictionary.CreateBuilder<string, string>();\n        foreach (Match m in metadataMatches)\n        {\n            if (m.Groups[\"key\"].Success && m.Groups[\"value\"].Success)\n            {\n                builder[m.Groups[\"key\"].Value.Trim()] = m.Groups[\"value\"].Value.Trim();\n            }\n        }\n        additionalInfo = builder.ToImmutable();\n    }\n\n    #endregion\n\n    #region Well known keys\n\n    public const string WidgetTypeKey = \"widget\";\n    public const string FormatStringKey = \"format\";\n    public const string IconKey = \"icon\";\n    public const string IconColorKey = \"icon-color\";\n    public const string OrderKey = \"order\";\n\n    #endregion\n\n    private readonly IMavParamTypeMetadata _metadata;\n    private readonly ImmutableDictionary<string, string> _additionalInfo;\n    private readonly string _description;\n\n    public MavParamInfo(IMavParamTypeMetadata metadata)\n    {\n        _metadata = metadata;\n        ParseAdditionalInfo(metadata.LongDesc, out _additionalInfo, out _description);\n    }\n\n    #region AdditionalInfo\n\n    public ImmutableDictionary<string, string> AdditionalInfo => _additionalInfo;\n\n    private static T? GetAdditionalAsEnum<T>(ImmutableDictionary<string, string> dict, string key)\n        where T : struct\n    {\n        if (dict.TryGetValue(key, out var value))\n        {\n            value = NormalizeAdditionalValue(value);\n            if (Enum.TryParse<T>(value, true, out var result))\n            {\n                return result;\n            }\n        }\n        return null;\n    }\n\n    private static int GetAdditionalAsInt(\n        ImmutableDictionary<string, string> dict,\n        string key,\n        int defaultValue = 0\n    )\n    {\n        if (dict.TryGetValue(key, out var value))\n        {\n            value = NormalizeAdditionalValue(value);\n            if (int.TryParse(value, out var result))\n            {\n                return result;\n            }\n        }\n        return defaultValue;\n    }\n\n    private static double GetAdditionalAsDouble(\n        ImmutableDictionary<string, string> dict,\n        string key,\n        double defaultValue = 0\n    )\n    {\n        if (dict.TryGetValue(key, out var value))\n        {\n            value = NormalizeAdditionalValue(value).Replace(',', Units.DecimalSeparator);\n            if (double.TryParse(value, NumberStyles.Any, null, out var result))\n            {\n                return result;\n            }\n        }\n        return defaultValue;\n    }\n\n    private static string NormalizeAdditionalValue(string value)\n    {\n        return value.Trim().Replace(\"-\", string.Empty);\n    }\n\n    #endregion\n\n    public string Description => _description;\n\n    public IMavParamTypeMetadata Metadata => _metadata;\n    public string Id => _metadata.Name;\n    public ValueType DefaultValue => Convert(Metadata.DefaultValue);\n    public ValueType Max => Convert(Metadata.MaxValue);\n    public ValueType Min => Convert(Metadata.MinValue);\n    public ValueType Increment => Convert(Metadata.Increment);\n    public string? FormatString =>\n        CollectionExtensions.GetValueOrDefault(_additionalInfo, FormatStringKey);\n    public MavParamWidgetType WidgetType =>\n        GetAdditionalAsEnum<MavParamWidgetType>(_additionalInfo, WidgetTypeKey)\n        ?? MavParamWidgetType.TextBox;\n    public MaterialIconKind? Icon =>\n        GetAdditionalAsEnum<MaterialIconKind>(_additionalInfo, IconKey);\n\n    public AsvColorKind? IconColor =>\n        GetAdditionalAsEnum<AsvColorKind>(_additionalInfo, IconColorKey);\n\n    public int Order => GetAdditionalAsInt(_additionalInfo, OrderKey);\n    public string Title => Metadata.ShortDesc ?? Metadata.Name;\n\n    public IEnumerable<MavParamValueItem> GetPredefinedValues()\n    {\n        if (Metadata.Values == null)\n        {\n            yield break;\n        }\n\n        foreach (var value in Metadata.Values)\n        {\n            ParseAdditionalInfo(value.Item2, out var dict, out var desc);\n            var icon = GetAdditionalAsEnum<MaterialIconKind>(dict, IconKey);\n            var iconColor = GetAdditionalAsEnum<AsvColorKind>(_additionalInfo, IconKey);\n\n            yield return new MavParamValueItem(\n                icon,\n                iconColor,\n                desc,\n                value.Item1,\n                Convert(value.Item1)\n            );\n        }\n    }\n\n    public MavParamValue Convert(ValueType value)\n    {\n        return Metadata.Type switch\n        {\n            MavParamType.MavParamTypeUint8 => new MavParamValue(System.Convert.ToByte(value)),\n            MavParamType.MavParamTypeInt8 => new MavParamValue(System.Convert.ToSByte(value)),\n            MavParamType.MavParamTypeUint16 => new MavParamValue(System.Convert.ToUInt16(value)),\n            MavParamType.MavParamTypeInt16 => new MavParamValue(System.Convert.ToInt16(value)),\n            MavParamType.MavParamTypeUint32 => new MavParamValue(System.Convert.ToUInt32(value)),\n            MavParamType.MavParamTypeInt32 => new MavParamValue(System.Convert.ToInt32(value)),\n            MavParamType.MavParamTypeReal32 => new MavParamValue(System.Convert.ToSingle(value)),\n            _ => throw new ArgumentOutOfRangeException(),\n        };\n    }\n\n    public ValueType Convert(MavParamValue value)\n    {\n        Debug.Assert(\n            value.Type == Metadata.Type,\n            $\"Value type {value.Type} does not match metadata type {Metadata.Type} for param {Metadata.Name}\"\n        );\n        switch (Metadata.Type)\n        {\n            case MavParamType.MavParamTypeUint8:\n                return (byte)value;\n            case MavParamType.MavParamTypeInt8:\n                return (sbyte)value;\n            case MavParamType.MavParamTypeUint16:\n                return (ushort)value;\n            case MavParamType.MavParamTypeInt16:\n                return (short)value;\n            case MavParamType.MavParamTypeUint32:\n                return (uint)value;\n            case MavParamType.MavParamTypeInt32:\n                return (int)value;\n            case MavParamType.MavParamTypeReal32:\n                return (float)value;\n            case MavParamType.MavParamTypeUint64:\n            case MavParamType.MavParamTypeInt64:\n            case MavParamType.MavParamTypeReal64:\n            default:\n                throw new ArgumentOutOfRangeException();\n        }\n    }\n\n    public string? Print(ValueType? value)\n    {\n        if (value == null)\n        {\n            return null;\n        }\n\n        if (FormatString == null)\n        {\n            return value.ToString();\n        }\n\n        switch (Metadata.Type)\n        {\n            case MavParamType.MavParamTypeUint8:\n                return ((byte)value).ToString(FormatString, CultureInfo.InvariantCulture);\n            case MavParamType.MavParamTypeInt8:\n                return ((sbyte)value).ToString(FormatString, CultureInfo.InvariantCulture);\n            case MavParamType.MavParamTypeUint16:\n                return ((ushort)value).ToString(FormatString, CultureInfo.InvariantCulture);\n            case MavParamType.MavParamTypeInt16:\n                return ((short)value).ToString(FormatString, CultureInfo.InvariantCulture);\n            case MavParamType.MavParamTypeUint32:\n                return ((uint)value).ToString(FormatString, CultureInfo.InvariantCulture);\n            case MavParamType.MavParamTypeInt32:\n                return ((int)value).ToString(FormatString, CultureInfo.InvariantCulture);\n            case MavParamType.MavParamTypeReal32:\n                return ((float)value).ToString(FormatString, CultureInfo.InvariantCulture);\n            case MavParamType.MavParamTypeUint64:\n            case MavParamType.MavParamTypeInt64:\n            case MavParamType.MavParamTypeReal64:\n            default:\n                throw new ArgumentOutOfRangeException();\n        }\n    }\n\n    public string? GetError(ValueType? value)\n    {\n        if (value is null)\n        {\n            return IsNullOrWhiteSpaceValidationException.Instance.LocalizedMessage\n                ?? IsNullOrWhiteSpaceValidationException.Instance.Message;\n        }\n        switch (Metadata.Type)\n        {\n            case MavParamType.MavParamTypeUint8:\n                var byteVal = System.Convert.ToByte(value);\n                if (byteVal > System.Convert.ToByte(Max) || byteVal < System.Convert.ToByte(Min))\n                {\n                    return ValidationResult\n                        .FailAsOutOfRange(\n                            Min.ToString() ?? string.Empty,\n                            Max.ToString() ?? string.Empty\n                        )\n                        .ValidationException?.GetExceptionWithLocalizationOrSelf()\n                        .Message;\n                }\n                break;\n            case MavParamType.MavParamTypeInt8:\n                var sbyteVal = System.Convert.ToSByte(value);\n                if (\n                    sbyteVal > System.Convert.ToSByte(Max)\n                    || sbyteVal < System.Convert.ToSByte(Min)\n                )\n                {\n                    return ValidationResult\n                        .FailAsOutOfRange(\n                            Min.ToString() ?? string.Empty,\n                            Max.ToString() ?? string.Empty\n                        )\n                        .ValidationException?.GetExceptionWithLocalizationOrSelf()\n                        .Message;\n                }\n                break;\n            case MavParamType.MavParamTypeUint16:\n                var ushortVal = System.Convert.ToUInt16(value);\n                if (\n                    ushortVal > System.Convert.ToUInt16(Max)\n                    || ushortVal < System.Convert.ToUInt16(Min)\n                )\n                {\n                    return ValidationResult\n                        .FailAsOutOfRange(\n                            Min.ToString() ?? string.Empty,\n                            Max.ToString() ?? string.Empty\n                        )\n                        .ValidationException?.GetExceptionWithLocalizationOrSelf()\n                        .Message;\n                }\n                break;\n            case MavParamType.MavParamTypeInt16:\n                var shortVal = System.Convert.ToInt16(value);\n                if (\n                    shortVal > System.Convert.ToInt16(Max)\n                    || shortVal < System.Convert.ToInt16(Min)\n                )\n                {\n                    return ValidationResult\n                        .FailAsOutOfRange(\n                            Min.ToString() ?? string.Empty,\n                            Max.ToString() ?? string.Empty\n                        )\n                        .ValidationException?.GetExceptionWithLocalizationOrSelf()\n                        .Message;\n                }\n                break;\n            case MavParamType.MavParamTypeUint32:\n                var uintVal = System.Convert.ToUInt32(value);\n                if (\n                    uintVal > System.Convert.ToUInt32(Max)\n                    || uintVal < System.Convert.ToUInt32(Min)\n                )\n                {\n                    return ValidationResult\n                        .FailAsOutOfRange(\n                            Min.ToString() ?? string.Empty,\n                            Max.ToString() ?? string.Empty\n                        )\n                        .ValidationException?.GetExceptionWithLocalizationOrSelf()\n                        .Message;\n                }\n                break;\n            case MavParamType.MavParamTypeInt32:\n                var intVal = System.Convert.ToInt32(value);\n                if (intVal > System.Convert.ToInt32(Max) || intVal < System.Convert.ToInt32(Min))\n                {\n                    return ValidationResult\n                        .FailAsOutOfRange(\n                            Min.ToString() ?? string.Empty,\n                            Max.ToString() ?? string.Empty\n                        )\n                        .ValidationException?.GetExceptionWithLocalizationOrSelf()\n                        .Message;\n                }\n                break;\n            case MavParamType.MavParamTypeReal32:\n                var floatVal = System.Convert.ToSingle(value);\n                if (\n                    floatVal > System.Convert.ToSingle(Max)\n                    || floatVal < System.Convert.ToSingle(Min)\n                )\n                {\n                    return ValidationResult\n                        .FailAsOutOfRange(\n                            Min.ToString() ?? string.Empty,\n                            Max.ToString() ?? string.Empty\n                        )\n                        .ValidationException?.GetExceptionWithLocalizationOrSelf()\n                        .Message;\n                }\n                break;\n            case MavParamType.MavParamTypeUint64:\n            case MavParamType.MavParamTypeInt64:\n            case MavParamType.MavParamTypeReal64:\n            default:\n                throw new ArgumentOutOfRangeException();\n        }\n\n        return null;\n    }\n\n    public bool IsValid(ValueType? value)\n    {\n        if (value == null)\n        {\n            return false;\n        }\n        switch (Metadata.Type)\n        {\n            case MavParamType.MavParamTypeUint8:\n                var byteVal = System.Convert.ToByte(value);\n                if (byteVal > System.Convert.ToByte(Max) || byteVal < System.Convert.ToByte(Min))\n                {\n                    return false;\n                }\n                break;\n            case MavParamType.MavParamTypeInt8:\n                var sbyteVal = System.Convert.ToSByte(value);\n                if (\n                    sbyteVal > System.Convert.ToSByte(Max)\n                    || sbyteVal < System.Convert.ToSByte(Min)\n                )\n                {\n                    return false;\n                }\n                break;\n            case MavParamType.MavParamTypeUint16:\n                var ushortVal = System.Convert.ToUInt16(value);\n                if (\n                    ushortVal > System.Convert.ToUInt16(Max)\n                    || ushortVal < System.Convert.ToUInt16(Min)\n                )\n                {\n                    return false;\n                }\n                break;\n            case MavParamType.MavParamTypeInt16:\n                var shortVal = System.Convert.ToInt16(value);\n                if (\n                    shortVal > System.Convert.ToInt16(Max)\n                    || shortVal < System.Convert.ToInt16(Min)\n                )\n                {\n                    return false;\n                }\n                break;\n            case MavParamType.MavParamTypeUint32:\n                var uintVal = System.Convert.ToUInt32(value);\n                if (\n                    uintVal > System.Convert.ToUInt32(Max)\n                    || uintVal < System.Convert.ToUInt32(Min)\n                )\n                {\n                    return false;\n                }\n                break;\n            case MavParamType.MavParamTypeInt32:\n                var intVal = System.Convert.ToInt32(value);\n                if (intVal > System.Convert.ToInt32(Max) || intVal < System.Convert.ToInt32(Min))\n                {\n                    return false;\n                }\n                break;\n            case MavParamType.MavParamTypeReal32:\n                var floatVal = System.Convert.ToSingle(value);\n                if (\n                    floatVal > System.Convert.ToSingle(Max)\n                    || floatVal < System.Convert.ToSingle(Min)\n                )\n                {\n                    return false;\n                }\n                break;\n            case MavParamType.MavParamTypeUint64:\n            case MavParamType.MavParamTypeInt64:\n            case MavParamType.MavParamTypeReal64:\n            default:\n                throw new ArgumentOutOfRangeException();\n        }\n        return true;\n    }\n\n    public Exception? ValidateString(string valueAsString, out ValueType value)\n    {\n        value = DefaultValue;\n        if (string.IsNullOrWhiteSpace(valueAsString))\n        {\n            return IsNullOrWhiteSpaceValidationException.Instance.GetExceptionWithLocalizationOrSelf();\n        }\n        valueAsString = valueAsString\n            .Replace(',', Units.DecimalSeparator)\n            .Trim(' ')\n            .Replace(\" \", string.Empty);\n\n        if (valueAsString.Length == 0)\n        {\n            return IsNullOrWhiteSpaceValidationException.Instance.GetExceptionWithLocalizationOrSelf();\n        }\n        var lastChar = valueAsString[^1];\n        int multiply;\n        switch (lastChar)\n        {\n            case 'M' or 'm' or 'М' or 'м':\n                multiply = 1_000_000;\n                valueAsString = valueAsString[..^1];\n                break;\n            case 'K' or 'k' or 'К' or 'к':\n                multiply = 1_000;\n                valueAsString = valueAsString[..^1];\n                break;\n            case 'G' or 'g' or 'Г' or 'г':\n                multiply = 1_000_000_000;\n                valueAsString = valueAsString[..^1];\n                break;\n            default:\n                multiply = 1;\n                break;\n        }\n\n        if (\n            double.TryParse(\n                valueAsString,\n                NumberStyles.Any,\n                CultureInfo.InvariantCulture,\n                out var doubleValue\n            )\n        )\n        {\n            doubleValue *= multiply;\n        }\n        else\n        {\n            return new Exception(RS.MavParamInfo_ValidationException_InvalidFormat);\n        }\n\n        switch (Metadata.Type)\n        {\n            case MavParamType.MavParamTypeUint8:\n                value = (byte)doubleValue;\n                break;\n            case MavParamType.MavParamTypeInt8:\n                value = (sbyte)doubleValue;\n                break;\n            case MavParamType.MavParamTypeUint16:\n                value = (ushort)doubleValue;\n                break;\n\n            case MavParamType.MavParamTypeInt16:\n                value = (short)doubleValue;\n                break;\n\n            case MavParamType.MavParamTypeUint32:\n                value = (uint)doubleValue;\n                break;\n            case MavParamType.MavParamTypeInt32:\n                value = (int)doubleValue;\n                break;\n            case MavParamType.MavParamTypeReal32:\n                value = (float)doubleValue;\n                break;\n            case MavParamType.MavParamTypeReal64:\n            case MavParamType.MavParamTypeUint64:\n            case MavParamType.MavParamTypeInt64:\n            default:\n                throw new ArgumentOutOfRangeException();\n        }\n\n        if (!IsValid(value))\n        {\n            return new Exception(GetError(value));\n        }\n\n        return null;\n    }\n}\n\npublic class MavParamValueItem(\n    MaterialIconKind? icon,\n    AsvColorKind? iconColor,\n    string title,\n    MavParamValue mavlinkValue,\n    ValueType value\n)\n{\n    public MaterialIconKind? Icon => icon;\n    public AsvColorKind IconColor => iconColor ?? AsvColorKind.None;\n    public string Title => title;\n    public MavParamValue MavValue => mavlinkValue;\n    public ValueType Value => value;\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/MavParamViewModel.cs",
    "content": "﻿using System.Collections.Specialized;\nusing System.Diagnostics;\nusing System.Globalization;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.Mavlink;\nusing Asv.Mavlink.Common;\nusing Material.Icons;\nusing Microsoft.Extensions.Logging;\nusing R3;\nusing ZLogger;\n\nnamespace Asv.Drones.Api;\n\npublic delegate ValueTask<MavParamValue> InitialReadParamDelegate(\n    string paramName,\n    CancellationToken cancel\n);\n\npublic class MavParamViewModel\n    : RoutableViewModel,\n        ISupportRefresh,\n        ISupportCancel,\n        IComparable<MavParamViewModel>,\n        IComparable\n{\n    protected MavParamViewModel(\n        MavParamInfo metadata,\n        Observable<MavParamValue> update,\n        InitialReadParamDelegate initReadCallback,\n        ILoggerFactory loggerFactory\n    )\n        : base(metadata.Id, loggerFactory)\n    {\n        Info = metadata;\n        update\n            .ObserveOnCurrentSynchronizationContext()\n            .Subscribe(InternalOnRemoteChanged)\n            .DisposeItWith(Disposable);\n\n        Value = new BindableReactiveProperty<ValueType>(metadata.DefaultValue).DisposeItWith(\n            Disposable\n        );\n        Value\n            .Where(_ => IsRemoteChange == false)\n            .Subscribe(_ => IsSync = false)\n            .DisposeItWith(Disposable);\n\n        // this is random delay for initial read to avoid many requests at the same time\n        Observable\n            .Timer(TimeSpan.FromMilliseconds(Random.Shared.Next(1, 1000)))\n            .Take(1)\n            .Subscribe(initReadCallback, (_, callback) => Init(callback))\n            .DisposeItWith(Disposable);\n    }\n\n    public MavParamInfo Info { get; }\n\n    private async void Init(InitialReadParamDelegate callback)\n    {\n        try\n        {\n            var value = await callback(Info.Metadata.Name, DisposeCancel);\n            InternalOnRemoteChanged(value);\n        }\n        catch (Exception e)\n        {\n            Logger.ZLogError(\n                e,\n                $\"Failed to read initial value for param {Info.Metadata.Name}:{e.Message}\"\n            );\n            IsNetworkError = true;\n            NetworkErrorMessage = e.Message;\n        }\n    }\n\n    private void InternalOnRemoteChanged(MavParamValue value)\n    {\n        if (IsInEditMode)\n        {\n            return;\n        }\n        IsRemoteChange = true;\n        Value.OnNext(Info.Convert(value));\n        IsSync = true;\n        IsNetworkError = false;\n        IsRemoteChange = false;\n    }\n\n    public void ResetToDefault()\n    {\n        Value.Value = Info.DefaultValue;\n        Write();\n    }\n\n    public async void Refresh()\n    {\n        try\n        {\n            IsBusy = true;\n            IsNetworkError = false;\n            NetworkErrorMessage = null;\n            IsInEditMode = false;\n            await Api.Commands.Mavlink.ReadParam(this, Info.Metadata.Name);\n        }\n        catch (Exception e)\n        {\n            IsNetworkError = true;\n            NetworkErrorMessage = e.Message;\n        }\n        finally\n        {\n            IsBusy = false;\n        }\n    }\n\n    public async void Write()\n    {\n        if (HasValidationErrors)\n        {\n            return;\n        }\n        var lastValue = IsInEditMode;\n        try\n        {\n            NetworkErrorMessage = null;\n            IsNetworkError = false;\n            IsBusy = true;\n            await Commands.Mavlink.WriteParam(this, Info.Metadata.Name, Info.Convert(Value.Value));\n        }\n        catch (Exception e)\n        {\n            IsNetworkError = true;\n            NetworkErrorMessage = e.Message;\n        }\n        finally\n        {\n            IsInEditMode = lastValue;\n            IsBusy = false;\n        }\n    }\n\n    public bool HasValidationErrors\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public bool IsFocused\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public bool IsNetworkError\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public string? NetworkErrorMessage\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public bool IsSync\n    {\n        get;\n        set => SetField(ref field, value);\n    } = true;\n\n    public BindableReactiveProperty<ValueType> Value { get; }\n\n    public bool IsRemoteChange\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public bool IsInEditMode\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public bool IsBusy\n    {\n        get;\n        set => SetField(ref field, value);\n    }\n\n    public bool ShowHeader\n    {\n        get;\n        set => SetField(ref field, value);\n    } = true;\n\n    public bool IsVisible\n    {\n        get;\n        set => SetField(ref field, value);\n    } = true;\n\n    public override IEnumerable<IRoutable> GetChildren()\n    {\n        yield break;\n    }\n\n    public void Cancel() { }\n\n    public int CompareTo(MavParamViewModel? other)\n    {\n        if (ReferenceEquals(this, other))\n        {\n            return 0;\n        }\n\n        if (other is null)\n        {\n            return 1;\n        }\n\n        return Info.Order.CompareTo(other.Info.Order);\n    }\n\n    public int CompareTo(object? obj)\n    {\n        if (obj is null)\n        {\n            return 1;\n        }\n\n        if (ReferenceEquals(this, obj))\n        {\n            return 0;\n        }\n\n        return obj is MavParamViewModel other\n            ? CompareTo(other)\n            : throw new ArgumentException($\"Object must be of type {nameof(MavParamViewModel)}\");\n    }\n\n    public static bool operator <(MavParamViewModel? left, MavParamViewModel? right)\n    {\n        return Comparer<MavParamViewModel>.Default.Compare(left, right) < 0;\n    }\n\n    public static bool operator >(MavParamViewModel? left, MavParamViewModel? right)\n    {\n        return Comparer<MavParamViewModel>.Default.Compare(left, right) > 0;\n    }\n\n    public static bool operator <=(MavParamViewModel? left, MavParamViewModel? right)\n    {\n        return Comparer<MavParamViewModel>.Default.Compare(left, right) <= 0;\n    }\n\n    public static bool operator >=(MavParamViewModel? left, MavParamViewModel? right)\n    {\n        return Comparer<MavParamViewModel>.Default.Compare(left, right) >= 0;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/String/MavParamAsciiCharView.cs",
    "content": "using Asv.Avalonia;\n\nnamespace Asv.Drones.Api;\n\npublic class MavParamAsciiCharView : MavParamTextBoxView;\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/String/MavParamAsciiCharViewModel.cs",
    "content": "﻿using System.Buffers.Binary;\nusing System.Text;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones.Api;\n\npublic class MavParamAsciiCharViewModel : MavParamTextBoxViewModel\n{\n    public MavParamAsciiCharViewModel(\n        MavParamInfo param,\n        Observable<MavParamValue> update,\n        InitialReadParamDelegate initReadCallback,\n        ILoggerFactory loggerFactory\n    )\n        : base(param, update, initReadCallback, loggerFactory) { }\n\n    protected override string ValueToText(ValueType remoteValue)\n    {\n        Span<byte> raw = stackalloc byte[4];\n        BinaryPrimitives.WriteInt32BigEndian(raw, (int)remoteValue);\n        var sb = new StringBuilder(4);\n        foreach (char c in raw)\n        {\n            if (!char.IsControl(c) && !char.IsWhiteSpace(c) && char.IsLetterOrDigit(c))\n            {\n                sb.Append(c);\n            }\n        }\n        return sb.ToString();\n    }\n\n    protected override Exception? TextToValue(string valueAsString, out ValueType value)\n    {\n        if (string.IsNullOrWhiteSpace(valueAsString))\n        {\n            value = 0;\n            return null;\n        }\n\n        var filtered = string.Concat(\n            valueAsString.Where(c =>\n                !char.IsControl(c) && !char.IsWhiteSpace(c) && char.IsLetterOrDigit(c)\n            )\n        );\n\n        if (filtered.Length == 0)\n        {\n            value = 0;\n            return null;\n        }\n\n        if (filtered.Length > 4)\n        {\n            filtered = filtered.Substring(0, 4);\n        }\n\n        Span<byte> buffer = stackalloc byte[4];\n        Encoding.ASCII.GetBytes(filtered, buffer);\n        value = BinaryPrimitives.ReadInt32BigEndian(buffer);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/TextBox/MavParamTextBoxView.axaml",
    "content": "﻿<UserControl\n    xmlns=\"https://github.com/avaloniaui\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:avalonia=\"clr-namespace:Asv.Avalonia;assembly=Asv.Avalonia\"\n    xmlns:avalonia1=\"clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia\"\n    xmlns:rsga=\"clr-namespace:Asv.Drones.Api\"\n    mc:Ignorable=\"d\"\n    d:DesignWidth=\"250\"\n    x:Class=\"Asv.Drones.Api.MavParamTextBoxView\"\n    x:DataType=\"rsga:MavParamTextBoxViewModel\"\n>\n    <Design.DataContext>\n        <rsga:MavParamTextBoxViewModel />\n    </Design.DataContext>\n    <UserControl.Styles>\n        <Style Selector=\"TextBox\">\n            <Style Selector=\"^:error /template/ Border#PART_BorderElement\">\n                <Setter Property=\"BorderBrush\" Value=\"{DynamicResource AsvForegroundErrorColor}\" />\n            </Style>\n        </Style>\n\n        <Style Selector=\"DataValidationErrors\">\n            <Setter Property=\"Template\">\n                <ControlTemplate>\n                    <Panel>\n                        <ContentPresenter\n                            Name=\"PART_ContentPresenter\"\n                            Background=\"{TemplateBinding Background}\"\n                            BorderBrush=\"{TemplateBinding BorderBrush}\"\n                            BorderThickness=\"{TemplateBinding BorderThickness}\"\n                            CornerRadius=\"{TemplateBinding CornerRadius}\"\n                            ContentTemplate=\"{TemplateBinding ContentTemplate}\"\n                            Content=\"{TemplateBinding Content}\"\n                            Padding=\"{TemplateBinding Padding}\"\n                        />\n                        <ContentControl\n                            HorizontalAlignment=\"Right\"\n                            Margin=\"0,0,4,0\"\n                            ContentTemplate=\"{TemplateBinding ErrorTemplate}\"\n                            DataContext=\"{TemplateBinding Owner}\"\n                            Content=\"{Binding (DataValidationErrors.Errors)}\"\n                            IsVisible=\"{Binding (DataValidationErrors.HasErrors)}\"\n                        />\n                    </Panel>\n                </ControlTemplate>\n            </Setter>\n            <Setter Property=\"ErrorTemplate\">\n                <DataTemplate x:DataType=\"{x:Type x:Object}\">\n                    <Canvas Width=\"14\" Height=\"14\" Margin=\"4 0 1 0\" Background=\"Transparent\">\n                        <Canvas.Styles>\n                            <Style Selector=\"ToolTip\">\n                                <Setter\n                                    Property=\"Background\"\n                                    Value=\"{DynamicResource AsvForegroundErrorBrush}\"\n                                />\n                                <Setter\n                                    Property=\"BorderBrush\"\n                                    Value=\"{DynamicResource AsvForegroundErrorBrush}\"\n                                />\n                            </Style>\n                        </Canvas.Styles>\n                        <ToolTip.Tip>\n                            <ItemsControl ItemsSource=\"{Binding}\" />\n                        </ToolTip.Tip>\n                        <Path\n                            Data=\"M14,7 A7,7 0 0,0 0,7 M0,7 A7,7 0 1,0 14,7 M7,3l0,5 M7,9l0,2\"\n                            Stroke=\"{DynamicResource AsvForegroundErrorBrush}\"\n                            StrokeThickness=\"2\"\n                        />\n                    </Canvas>\n                </DataTemplate>\n            </Setter>\n        </Style>\n    </UserControl.Styles>\n    <Panel VerticalAlignment=\"Top\" HorizontalAlignment=\"Stretch\">\n        <TextBox\n            MinWidth=\"200\"\n            IsReadOnly=\"{Binding IsBusy}\"\n            VerticalContentAlignment=\"Center\"\n            GotFocus=\"PART_TextBox_OnGotFocus\"\n            avalonia:NavigationHelper.IsFocused=\"{Binding IsFocused, Mode=OneWay}\"\n            Text=\"{Binding TextValue.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}\"\n            MinHeight=\"0\"\n        >\n            <TextBox.KeyBindings>\n                <KeyBinding Gesture=\"Enter\" Command=\"{Binding Write}\" />\n            </TextBox.KeyBindings>\n            <ToolTip.Tip>\n                <StackPanel Spacing=\"8\">\n                    <TextBlock Classes=\"h1\" Text=\"{Binding Info.Title}\" />\n                    <TextBlock Classes=\"description\" Text=\"{Binding Info.Description}\" />\n                </StackPanel>\n            </ToolTip.Tip>\n            <TextBox.InnerLeftContent>\n                <Panel>\n                    <Ellipse\n                        Opacity=\"0\"\n                        Margin=\"10,0,0,0\"\n                        Classes.fadeout=\"{Binding !IsRemoteChange}\"\n                        VerticalAlignment=\"Center\"\n                        HorizontalAlignment=\"Left\"\n                        Width=\"5\"\n                        Height=\"5\"\n                        Fill=\"{DynamicResource AsvForegroundInfo3Brush}\"\n                    />\n                    <Button\n                        IsEnabled=\"{Binding !IsBusy}\"\n                        TabIndex=\"0\"\n                        IsTabStop=\"True\"\n                        Padding=\"0\"\n                        VerticalAlignment=\"Stretch\"\n                        HorizontalAlignment=\"Left\"\n                        Theme=\"{DynamicResource TransparentButton}\"\n                        Command=\"{Binding Refresh}\"\n                    >\n                        <avalonia1:MaterialIcon\n                            HorizontalAlignment=\"Stretch\"\n                            Margin=\"3,0\"\n                            Width=\"20\"\n                            Height=\"20\"\n                            Foreground=\"{DynamicResource AsvForegroundUnknownBrush}\"\n                            Kind=\"Sync\"\n                        />\n                    </Button>\n                </Panel>\n            </TextBox.InnerLeftContent>\n            <TextBox.InnerRightContent>\n                <StackPanel Orientation=\"Horizontal\" Spacing=\"4\" Margin=\"0,0,25,0\">\n                    <Button\n                        Padding=\"0\"\n                        VerticalAlignment=\"Stretch\"\n                        HorizontalAlignment=\"Right\"\n                        Theme=\"{DynamicResource TransparentButton}\"\n                        Command=\"{Binding Write}\"\n                    >\n                        <Button.IsVisible>\n                            <MultiBinding Converter=\"{x:Static BoolConverters.And}\">\n                                <Binding Path=\"!IsSync\" />\n                                <Binding Path=\"!IsBusy\" />\n                                <Binding Path=\"!HasValidationErrors\" />\n                            </MultiBinding>\n                        </Button.IsVisible>\n                        <avalonia1:MaterialIcon\n                            Width=\"20\"\n                            Height=\"20\"\n                            Kind=\"Pencil\"\n                            Foreground=\"{DynamicResource AsvForegroundSuccessBrush}\"\n                        />\n                    </Button>\n                    <TextBlock\n                        Foreground=\"{DynamicResource TextFillColorDisabledBrush}\"\n                        Theme=\"{DynamicResource BodyTextBlockStyle}\"\n                        VerticalAlignment=\"Center\"\n                        Text=\"{Binding Units}\"\n                    />\n                </StackPanel>\n            </TextBox.InnerRightContent>\n        </TextBox>\n        <ProgressBar\n            MinWidth=\"0\"\n            IsVisible=\"{Binding IsBusy}\"\n            Background=\"Transparent\"\n            Margin=\"0,0,30,0\"\n            Height=\"20\"\n            Opacity=\"0.5\"\n            VerticalAlignment=\"Center\"\n            HorizontalAlignment=\"Stretch\"\n            Minimum=\"0\"\n            Maximum=\"1\"\n            IsIndeterminate=\"True\"\n        />\n        <avalonia1:MaterialIcon\n            HorizontalAlignment=\"Right\"\n            Classes=\"blink\"\n            ToolTip.Tip=\"{Binding NetworkErrorMessage}\"\n            IsVisible=\"{Binding IsNetworkError}\"\n            Kind=\"CloseNetwork\"\n            Foreground=\"{DynamicResource AsvForegroundErrorBrush}\"\n            Margin=\"0,0,4,0\"\n            Width=\"18\"\n            Height=\"18\"\n        />\n    </Panel>\n</UserControl>\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/TextBox/MavParamTextBoxView.axaml.cs",
    "content": "﻿using Asv.Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.Input;\nusing R3;\n\nnamespace Asv.Drones.Api;\n\npublic partial class MavParamTextBoxView : UserControl\n{\n    public MavParamTextBoxView()\n    {\n        InitializeComponent();\n    }\n\n    private void PART_TextBox_OnGotFocus(object? sender, GotFocusEventArgs e)\n    {\n        if (sender is TextBox textBox)\n        {\n            Observable\n                .TimerFrame(1)\n                .Subscribe(x =>\n                {\n                    textBox.SelectAll();\n                });\n        }\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Controls/MavParam/TextBox/MavParamTextBoxViewModel.cs",
    "content": "﻿using System.ComponentModel;\nusing System.Globalization;\nusing Asv.Avalonia;\nusing Asv.Common;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Asv.Mavlink.Common;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones.Api;\n\npublic class MavParamTextBoxViewModel : MavParamViewModel\n{\n    private readonly BindableReactiveProperty<string> _textValue;\n    private bool _internalChange;\n\n    public MavParamTextBoxViewModel()\n        : this(\n            new MavParamInfo(\n                new MavParamTypeMetadata(\n                    \"A\"\n                        + NavigationId\n                            .GenerateRandomAsString(15)\n                            .Replace('.', '_')\n                            .Replace('-', '_'),\n                    MavParamType.MavParamTypeInt32\n                )\n                {\n                    Units = \"MHz\",\n                    RebootRequired = false,\n                    Volatile = false,\n                    MinValue = new MavParamValue(-100),\n                    ShortDesc = \"Test param\",\n                    LongDesc = \"Long description for test param\",\n                    Group = \"System\",\n                    Category = \"System\",\n                    MaxValue = new MavParamValue(100),\n                    DefaultValue = new MavParamValue(50),\n                    Increment = new MavParamValue(1),\n                }\n            ),\n            new Subject<MavParamValue>(),\n            (_, _) => ValueTask.FromResult(new MavParamValue(100)),\n            DesignTime.LoggerFactory\n        )\n    {\n        DesignTime.ThrowIfNotDesignMode();\n\n        Task.Run(async () =>\n        {\n            while (true)\n            {\n                IsSync = false;\n                await Task.Delay(3000);\n\n                IsNetworkError = true;\n                NetworkErrorMessage = \"Network error occurred. Please try again later.\";\n                await Task.Delay(5000);\n                IsNetworkError = false;\n                IsBusy = true;\n                await Task.Delay(3000);\n                IsBusy = false;\n                IsRemoteChange = true;\n                await Task.Delay(1000);\n                IsRemoteChange = false;\n                _textValue.Value = \"asdasdasd\";\n                await Task.Delay(3000);\n                _textValue.Value = \"123450\";\n            }\n        });\n    }\n\n    public MavParamTextBoxViewModel(\n        MavParamInfo param,\n        Observable<MavParamValue> update,\n        InitialReadParamDelegate initReadCallback,\n        ILoggerFactory loggerFactory\n    )\n        : base(param, update, initReadCallback, loggerFactory)\n    {\n        _textValue = new BindableReactiveProperty<string>().DisposeItWith(Disposable);\n        _internalChange = true;\n        Value\n            .Where(_ => _internalChange == false)\n            .Subscribe(x => _textValue.Value = ValueToText(x))\n            .DisposeItWith(Disposable);\n\n        // we don't subscribe to value changes here, because we set Value at Validator\n        TextValue.EnableValidation(Validator).DisposeItWith(Disposable);\n        Observable\n            .FromEventHandler<DataErrorsChangedEventArgs>(\n                h => _textValue.ErrorsChanged += h,\n                h => _textValue.ErrorsChanged -= h\n            )\n            .Subscribe(_ => HasValidationErrors = _textValue.HasErrors)\n            .DisposeItWith(Disposable);\n\n        _textValue.DistinctUntilChanged().Subscribe(_ => IsSync = false).DisposeItWith(Disposable);\n        _internalChange = false;\n    }\n\n    public virtual string? Units => Info.Metadata.Units;\n\n    protected virtual string ValueToText(ValueType remoteValue)\n    {\n        return Info.Print(remoteValue) ?? string.Empty;\n    }\n\n    protected virtual Exception? TextToValue(string valueAsString, out ValueType value)\n    {\n        return Info.ValidateString(valueAsString, out value);\n    }\n\n    private Exception? Validator(string valueAsString)\n    {\n        var err = TextToValue(valueAsString, out var value);\n        if (err != null)\n        {\n            return err;\n        }\n        _internalChange = true;\n        Value.Value = value;\n        _internalChange = false;\n        return null;\n    }\n\n    public IReadOnlyBindableReactiveProperty<string> TextValue => _textValue;\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Services/ClientDeviceWidgetFactory/IClientDeviceWidgetCreationHandler.cs",
    "content": "﻿using Asv.IO;\n\nnamespace Asv.Drones.Api;\n\npublic interface IClientDeviceWidgetCreationHandler\n{\n    Type DeviceType { get; }\n    IFlightWidget? Create(in IClientDevice device);\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Services/ClientDeviceWidgetFactory/IClientDeviceWidgetFactory.cs",
    "content": "using Asv.IO;\n\nnamespace Asv.Drones.Api;\n\npublic interface IClientDeviceWidgetFactory\n{\n    public IFlightWidget? CreateWidget(in IClientDevice device);\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Services/Converters/IPacketConverter.cs",
    "content": "using Asv.Mavlink;\n\nnamespace Asv.Drones.Api;\n\n/// <summary>\n/// Represents the formatting options for packet data.\n/// </summary>\npublic enum PacketFormatting\n{\n    /// <summary>\n    /// One-line formatting\n    /// </summary>\n    None,\n\n    /// <summary>\n    /// Represents the formatting options for packet content.\n    /// </summary>\n    Indented,\n}\n\n/// <summary>\n/// Represents an interface for converting packet payloads to string representation.\n/// </summary>\npublic interface IPacketConverter\n{\n    /// <summary>\n    /// Gets the order of the converter in the list of all converters.\n    /// </summary>\n    int Order { get; }\n\n    /// <summary>\n    /// Checks whether the converter can convert the payload of a given packet.\n    /// </summary>\n    /// <param name=\"packet\">The packet to convert</param>\n    /// <returns>Returns true if the converter can convert the payload, false otherwise</returns>\n    bool CanConvert(MavlinkMessage packet);\n\n    /// <summary>\n    /// Converts packet's payload to string.\n    /// </summary>\n    /// <param name=\"packet\">The packet to convert.</param>\n    /// <param name=\"formatting\">\n    /// The formatting of the result string. This is optional and is used to create packets with special formatting. The default value is 'None'.\n    /// </param>\n    /// <returns>\n    /// A string representation of the packet.\n    /// </returns>\n    string Convert(MavlinkMessage packet, PacketFormatting formatting = PacketFormatting.None);\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Core/Services/Devices/Mavlink/IMavlinkHost.cs",
    "content": "using System.Collections.Immutable;\nusing Asv.IO;\nusing Asv.Mavlink;\n\nnamespace Asv.Drones.Api;\n\npublic interface IMavlinkMessagesExtension\n{\n    void Extend(ImmutableDictionary<int, Func<MavlinkMessage>>.Builder builder);\n}\n\npublic interface IMavlinkHost\n{\n    IProtocolMessageFactory<MavlinkMessage, int> MessageFactory { get; }\n    IMavlinkContext Context { get; }\n    MavlinkIdentity Identity { get; }\n    IHeartbeatServer? Heartbeat { get; }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/RS.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code was generated by a tool.\n//\n//     Changes to this file may cause incorrect behavior and will be lost if\n//     the code is regenerated.\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace Asv.Drones.Api {\n    using System;\n    \n    \n    /// <summary>\n    ///   A strongly-typed resource class, for looking up localized strings, etc.\n    /// </summary>\n    // This class was auto-generated by the StronglyTypedResourceBuilder\n    // class via a tool like ResGen or Visual Studio.\n    // To add or remove a member, edit your .ResX file then rerun ResGen\n    // with the /str option, or rebuild your VS project.\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"4.0.0.0\")]\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    internal class RS {\n        \n        private static global::System.Resources.ResourceManager resourceMan;\n        \n        private static global::System.Globalization.CultureInfo resourceCulture;\n        \n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\n        internal RS() {\n        }\n        \n        /// <summary>\n        ///   Returns the cached ResourceManager instance used by this class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Resources.ResourceManager ResourceManager {\n            get {\n                if (object.ReferenceEquals(resourceMan, null)) {\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"Asv.Drones.Api.RS\", typeof(RS).Assembly);\n                    resourceMan = temp;\n                }\n                return resourceMan;\n            }\n        }\n        \n        /// <summary>\n        ///   Overrides the current thread's CurrentUICulture property for all\n        ///   resource lookups using this strongly typed resource class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Globalization.CultureInfo Culture {\n            get {\n                return resourceCulture;\n            }\n            set {\n                resourceCulture = value;\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized string similar to Value must be a number with optional suffix (K, M, G).\n        /// </summary>\n        internal static string MavParamInfo_ValidationException_InvalidFormat {\n            get {\n                return ResourceManager.GetString(\"MavParamInfo_ValidationException_InvalidFormat\", resourceCulture);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/RS.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<root>\n    <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n        <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n            \n        </xsd:element>\n    </xsd:schema>\n    <resheader name=\"resmimetype\">\n        <value>text/microsoft-resx</value>\n    </resheader>\n    <resheader name=\"version\">\n        <value>1.3</value>\n    </resheader>\n    <resheader name=\"reader\">\n        <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n    </resheader>\n    <resheader name=\"writer\">\n        <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n    </resheader>\n    <data name=\"MavParamInfo_ValidationException_InvalidFormat\" xml:space=\"preserve\">\n        <value>Value must be a number with optional suffix (K, M, G)</value>\n    </data>\n</root>"
  },
  {
    "path": "src/Asv.Drones.Api/RS.ru.resx",
    "content": "﻿<root>\n    <resheader name=\"resmimetype\">\n        <value>text/microsoft-resx</value>\n    </resheader>\n    <resheader name=\"version\">\n        <value>1.3</value>\n    </resheader>\n    <resheader name=\"reader\">\n        <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n    </resheader>\n    <resheader name=\"writer\">\n        <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n    </resheader>\n    <data name=\"MavParamInfo_ValidationException_InvalidFormat\" xml:space=\"preserve\">\n        <value>Значение должно быть числом с опциональным суффиксом (K, M, G)</value>\n    </data>\n</root>"
  },
  {
    "path": "src/Asv.Drones.Api/Shell/Pages/FileBrowser/IFileBrowserViewModel.cs",
    "content": "using Asv.Avalonia.IO;\n\nnamespace Asv.Drones.Api;\n\npublic interface IFileBrowserViewModel : IDevicePage { }\n"
  },
  {
    "path": "src/Asv.Drones.Api/Shell/Pages/Flight/FlightMode.cs",
    "content": "using Material.Icons;\n\nnamespace Asv.Drones.Api;\n\npublic static class FlightMode\n{\n    public static MaterialIconKind PageIcon { get; set; } = MaterialIconKind.MapSearch;\n    public const string PageId = \"flight\";\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Shell/Pages/Flight/IFlightMode.cs",
    "content": "using Asv.Avalonia;\nusing Asv.Avalonia.GeoMap;\nusing ObservableCollections;\nusing R3;\n\nnamespace Asv.Drones.Api;\n\npublic interface IFlightMode : IPage\n{\n    ObservableList<IUavFlightWidget> Widgets { get; }\n    IMap MapViewModel { get; }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Shell/Pages/Flight/Widgets/UavWidget/IUavFlightWidget.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.IO;\n\nnamespace Asv.Drones.Api;\n\npublic interface IUavFlightWidget : IWorkspaceWidget\n{\n    IClientDevice Device { get; }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Shell/Pages/FlightMode/IFlightModePage.cs",
    "content": "using Asv.Avalonia;\nusing Asv.Avalonia.GeoMap;\nusing ObservableCollections;\n\nnamespace Asv.Drones.Api;\n\npublic interface IFlightModePage : IPage\n{\n    ObservableList<IFlightWidget> Widgets { get; }\n    IMap Map { get; }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Shell/Pages/FlightMode/Widgets/Device/DeviceFlightWidgetViewModelBase.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Common;\nusing Asv.IO;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones.Api;\n\npublic abstract class DeviceFlightWidgetViewModelBase<TDeviceContext, TSelf>\n    : FlightWidgetViewModel<TDeviceContext, TSelf>\n    where TDeviceContext : class, IClientDevice\n    where TSelf : class, IFlightWidget<TDeviceContext>\n{\n    private readonly IDeviceManager _deviceManager;\n\n    protected DeviceFlightWidgetViewModelBase(\n        NavigationId id,\n        IDeviceManager deviceManager,\n        ILoggerFactory loggerFactory,\n        IExtensionService ext\n    )\n        : base(id, loggerFactory, ext)\n    {\n        ArgumentNullException.ThrowIfNull(deviceManager);\n        _deviceManager = deviceManager;\n        Position = WorkspaceDock.Left;\n    }\n\n    public TDeviceContext? Device { get; private set; }\n\n    public override void InitWith(TDeviceContext device)\n    {\n        ArgumentNullException.ThrowIfNull(device);\n        InitArgs(device.Id.AsString());\n        Device = device;\n        Header = device.Id.ToString();\n        Icon = _deviceManager.GetIcon(device.Id);\n        IconColor = _deviceManager.GetDeviceColor(device.Id);\n        device\n            .Name.ObserveOnUIThreadDispatcher()\n            .Subscribe(x => Header = x)\n            .DisposeItWith(Disposable);\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Shell/Pages/FlightMode/Widgets/Device/IDeviceFlightWidget.cs",
    "content": "﻿using Asv.IO;\n\nnamespace Asv.Drones.Api;\n\npublic interface IDeviceFlightWidget<TDeviceContext> : IFlightWidget<TDeviceContext>\n    where TDeviceContext : class, IClientDevice\n{\n    public TDeviceContext? Device { get; }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/DroneFlightWidgetViewModelBase.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones.Api;\n\npublic class DroneFlightWidgetViewModelBase<TDrone, TSelf>(\n    NavigationId id,\n    IDeviceManager deviceManager,\n    ILoggerFactory loggerFactory,\n    IExtensionService ext\n) : MavlinkDeviceFlightWidgetViewModelBase<TDrone, TSelf>(id, deviceManager, loggerFactory, ext)\n    where TSelf : class, IFlightWidget<TDrone>\n    where TDrone : MavlinkClientDevice { }\n"
  },
  {
    "path": "src/Asv.Drones.Api/Shell/Pages/FlightMode/Widgets/Device/Mavlink/Drone/IDroneFlightWidget.cs",
    "content": "using Asv.Mavlink;\n\nnamespace Asv.Drones.Api;\n\npublic interface IDroneFlightWidget : IDroneFlightWidget<MavlinkClientDevice> { }\n\npublic interface IDroneFlightWidget<TDrone> : IMavlinkDeviceFlightWidget<TDrone>\n    where TDrone : MavlinkClientDevice { }\n"
  },
  {
    "path": "src/Asv.Drones.Api/Shell/Pages/FlightMode/Widgets/Device/Mavlink/IMavlinkDeviceFlightWidget.cs",
    "content": "﻿using Asv.Mavlink;\n\nnamespace Asv.Drones.Api;\n\npublic interface IMavlinkDeviceFlightWidget : IMavlinkDeviceFlightWidget<MavlinkClientDevice> { }\n\npublic interface IMavlinkDeviceFlightWidget<TMavlinkClientDevice>\n    : IDeviceFlightWidget<TMavlinkClientDevice>\n    where TMavlinkClientDevice : MavlinkClientDevice { }\n"
  },
  {
    "path": "src/Asv.Drones.Api/Shell/Pages/FlightMode/Widgets/Device/Mavlink/MavlinkDeviceFlightWidgetViewModelBase.cs",
    "content": "﻿using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Mavlink;\nusing Microsoft.Extensions.Logging;\n\nnamespace Asv.Drones.Api;\n\npublic abstract class MavlinkDeviceFlightWidgetViewModelBase<TMavlinkClientDevice, TSelf>(\n    NavigationId id,\n    IDeviceManager deviceManager,\n    ILoggerFactory loggerFactory,\n    IExtensionService ext\n)\n    : DeviceFlightWidgetViewModelBase<TMavlinkClientDevice, TSelf>(\n        id,\n        deviceManager,\n        loggerFactory,\n        ext\n    )\n    where TSelf : class, IFlightWidget<TMavlinkClientDevice>\n    where TMavlinkClientDevice : MavlinkClientDevice\n{\n    private int _order;\n    public override int Order => _order;\n\n    public override void InitWith(TMavlinkClientDevice device)\n    {\n        base.InitWith(device);\n\n        var mavlinkId =\n            device.Id as MavlinkClientDeviceId\n            ?? throw new Exception($\"Should be {typeof(MavlinkClientDeviceId)}\");\n\n        _order = CreateOrderFromId(mavlinkId);\n    }\n\n    private static int CreateOrderFromId(MavlinkClientDeviceId id)\n    {\n        return (id.Id.Target.SystemId * 1000) + id.Id.Target.ComponentId;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Shell/Pages/MavParams/IMavParamsPageViewModel.cs",
    "content": "using Asv.Avalonia.IO;\n\nnamespace Asv.Drones.Api;\n\npublic interface IMavParamsPageViewModel : IDevicePage { }\n"
  },
  {
    "path": "src/Asv.Drones.Api/Shell/Pages/Setup/ISetupPage.cs",
    "content": "using Asv.Avalonia;\nusing Asv.Avalonia.IO;\n\nnamespace Asv.Drones.Api;\n\npublic interface ISetupPage : ITreePageViewModel, IDevicePage { }\n"
  },
  {
    "path": "src/Asv.Drones.Api/Shell/Pages/Setup/Subpage/ISetupSubpage.cs",
    "content": "using Asv.Avalonia;\n\nnamespace Asv.Drones.Api;\n\npublic interface ISetupSubpage : ITreeSubpage<ISetupPage> { }\n"
  },
  {
    "path": "src/Asv.Drones.Api/Tools/Mavlink/DeviceIconMixin.cs",
    "content": "using Asv.Avalonia.GeoMap;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Material.Icons;\n\nnamespace Asv.Drones.Api;\n\npublic static class DeviceIconMixin\n{\n    public static MaterialIconKind? GetIcon(DeviceId deviceId)\n    {\n        return deviceId.DeviceClass switch\n        {\n            Vehicles.PlaneDeviceClass => MaterialIconKind.Plane,\n            Vehicles.CopterDeviceClass => MaterialIconKind.Navigation,\n            GbsClientDevice.DeviceClass => MaterialIconKind.RouterWireless,\n            _ => null,\n        };\n    }\n\n    public static HorizontalOffset GetIconCenterX(DeviceId deviceId)\n    {\n        switch (deviceId.DeviceClass)\n        {\n            case Vehicles.PlaneDeviceClass:\n                return HorizontalOffset.Default;\n            case Vehicles.CopterDeviceClass:\n                return HorizontalOffset.Default;\n            case GbsClientDevice.DeviceClass:\n                return HorizontalOffset.Default;\n            default:\n                return HorizontalOffset.Default;\n        }\n    }\n\n    public static VerticalOffset GetIconCenterY(DeviceId deviceId)\n    {\n        switch (deviceId.DeviceClass)\n        {\n            case Vehicles.PlaneDeviceClass:\n                return VerticalOffset.Default;\n            case Vehicles.CopterDeviceClass:\n                return VerticalOffset.Default;\n            case GbsClientDevice.DeviceClass:\n                return VerticalOffset.Default;\n            default:\n                return VerticalOffset.Default;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Api/Tools/Mavlink/MavlinkHost.cs",
    "content": "using Asv.Avalonia;\nusing Asv.Avalonia.IO;\nusing Asv.Cfg;\nusing Asv.IO;\nusing Asv.Mavlink;\nusing Asv.Mavlink.Ardupilotmega;\nusing Asv.Mavlink.AsvAudio;\nusing Asv.Mavlink.AsvAudio;\nusing Asv.Mavlink.AsvChart;\nusing Asv.Mavlink.AsvChart;\nusing Asv.Mavlink.AsvGbs;\nusing Asv.Mavlink.AsvRadio;\nusing Asv.Mavlink.AsvRfsa;\nusing Asv.Mavlink.AsvRsga;\nusing Asv.Mavlink.AsvSdr;\nusing Asv.Mavlink.Avssuas;\nusing Asv.Mavlink.Common;\nusing Asv.Mavlink.Csairlink;\nusing Asv.Mavlink.Cubepilot;\nusing Asv.Mavlink.Icarous;\nusing Asv.Mavlink.Minimal;\nusing Asv.Mavlink.Storm32;\nusing Asv.Mavlink.Ualberta;\nusing Asv.Mavlink.Uavionix;\nusing Material.Icons;\nusing Microsoft.Extensions.Hosting;\nusing R3;\nusing MavType = Asv.Mavlink.Minimal.MavType;\n\nnamespace Asv.Drones.Api;\n\npublic class MavlinkDeviceManagerExtensionConfig\n{\n    public byte SystemId { get; set; } = 255;\n    public byte ComponentId { get; set; } = 255;\n}\n\npublic class MavlinkHost : IDeviceManagerExtension, IMavlinkHost, IHostedService\n{\n    private readonly IConfiguration _cfgSvc;\n    private readonly IPacketSequenceCalculator _seq;\n    private readonly MavlinkDeviceManagerExtensionConfig _cfg;\n    private readonly IProtocolMessageFactory<MavlinkMessage, int> _messageFactory;\n\n    public MavlinkHost(\n        IConfiguration cfgSvc,\n        IPacketSequenceCalculator seq,\n        IEnumerable<IMavlinkMessagesExtension> extensions\n    )\n    {\n        _cfgSvc = cfgSvc;\n        _seq = seq;\n        _cfg = cfgSvc.Get<MavlinkDeviceManagerExtensionConfig>();\n        Identity = new MavlinkIdentity(_cfg.SystemId, _cfg.ComponentId);\n        _messageFactory = MavlinkV2Protocol.CreateMessageFactory(builder =>\n        {\n            // TODO: replace with extension in the future: RegisterDefault\n            builder.RegisterMinimalDialect();\n            builder.RegisterCommonDialect();\n            builder.RegisterArdupilotmegaDialect();\n            builder.RegisterIcarousDialect();\n            builder.RegisterUalbertaDialect();\n            builder.RegisterStorm32Dialect();\n            builder.RegisterAvssuasDialect();\n            builder.RegisterUavionixDialect();\n            builder.RegisterCubepilotDialect();\n            builder.RegisterCsairlinkDialect();\n            builder.RegisterAsvGbsDialect();\n            builder.RegisterAsvSdrDialect();\n            builder.RegisterAsvAudioDialect();\n            builder.RegisterAsvRadioDialect();\n            builder.RegisterAsvRfsaDialect();\n            builder.RegisterAsvChartDialect();\n            builder.RegisterAsvRsgaDialect();\n            foreach (var ext in extensions)\n            {\n                ext.Extend(builder);\n            }\n        });\n    }\n\n    public void Configure(IProtocolBuilder builder)\n    {\n        builder.RegisterMavlinkV2Protocol(_messageFactory);\n        builder.Features.RegisterMavlinkV2WrapFeature(_messageFactory);\n        builder.Features.RegisterBroadcastFeature<MavlinkMessage>();\n    }\n\n    public void Configure(IDeviceExplorerBuilder builder)\n    {\n        builder.Factories.RegisterDefaultDevices(Identity, _seq, _cfgSvc, _messageFactory);\n    }\n\n    public bool TryGetIcon(DeviceId id, out MaterialIconKind? icon)\n    {\n        icon = DeviceIconMixin.GetIcon(id);\n        return icon != null;\n    }\n\n    public bool TryGetDeviceBrush(DeviceId id, out AsvColorKind brush)\n    {\n        brush = AsvColorKind.None;\n        return false;\n    }\n\n    public void Run(IDeviceManager deviceManager)\n    {\n        var config = _cfgSvc.Get<MavlinkHeartbeatServerConfig>();\n        Context = new CoreServices(\n            _seq,\n            deviceManager.Router,\n            deviceManager.ProtocolFactory,\n            _messageFactory\n        );\n\n        Heartbeat = new HeartbeatServer(Identity, config, Context);\n        Heartbeat.Set(m =>\n        {\n            m.Autopilot = MavAutopilot.MavAutopilotInvalid;\n            m.Type = MavType.MavTypeGcs;\n            m.BaseMode = MavModeFlag.MavModeFlagCustomModeEnabled;\n        });\n        Heartbeat.Start();\n    }\n\n    public IHeartbeatServer? Heartbeat { get; private set; }\n\n    public IProtocolMessageFactory<MavlinkMessage, int> MessageFactory => _messageFactory;\n    public IMavlinkContext Context { get; private set; }\n    public MavlinkIdentity Identity { get; }\n\n    public Task StartAsync(CancellationToken cancellationToken)\n    {\n        return Task.CompletedTask;\n    }\n\n    public Task StopAsync(CancellationToken cancellationToken)\n    {\n        return Task.CompletedTask;\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Desktop/Asv.Drones.Desktop.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n    <PropertyGroup>\n        <OutputType>WinExe</OutputType>\n        <!--If you are willing to use Windows/MacOS native APIs you will need to create 3 projects.\n        One for Windows with net8.0-windows TFM, one for MacOS with net8.0-macos and one with net8.0 TFM for Linux.-->\n        <TargetFramework>$(TargetFramework)</TargetFramework>\n        <FileVersion>$(ProductVersion)</FileVersion>\n        <Version>$(ProductVersion)</Version>\n\n        <Authors>https://github.com/asv-soft</Authors>\n        <Company>https://github.com/asv-soft</Company>\n        <Copyright>https://github.com/asv-soft</Copyright>\n        \n        <Nullable>enable</Nullable>\n        <LangVersion>preview</LangVersion>\n        <BuiltInComInteropSupport>true</BuiltInComInteropSupport>\n        <CodeAnalysisRuleSet>../CodeStyle.ruleset</CodeAnalysisRuleSet>\n        <WarningsAsErrors>\n            CS0169,\n            CS0618,\n            CS1502,\n            CS1503,\n            CS8524,\n            CS8600,\n            CS8601,\n            CS8602,\n            CS8603,\n            CS8604,\n            CS8625,\n            CS8629,\n            CS8762,\n            CA1510,\n            CA1851\n        </WarningsAsErrors>\n        <ApplicationIcon>app.ico</ApplicationIcon>\n    </PropertyGroup>\n\n    <PropertyGroup>\n        <ApplicationManifest>app.manifest</ApplicationManifest>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <PackageReference Include=\"Avalonia.Desktop\" Version=\"$(AvaloniaVersion)\"/>\n        <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->\n        <PackageReference Condition=\"'$(Configuration)' == 'Debug'\" Include=\"Avalonia.Diagnostics\" Version=\"$(AvaloniaVersion)\"/>\n        <PackageReference Include=\"Roslynator.Analyzers\" Version=\"4.14.0\">\n          <PrivateAssets>all</PrivateAssets>\n          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n        </PackageReference>\n        <PackageReference Include=\"StyleCop.Analyzers\" Version=\"1.1.118\">\n          <PrivateAssets>all</PrivateAssets>\n          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n        </PackageReference>\n    </ItemGroup>\n\n    <ItemGroup>\n        <ProjectReference Include=\"..\\Asv.Drones\\Asv.Drones.csproj\"/>\n    </ItemGroup>\n\n    <ItemGroup>\n      <None Update=\"appsettings.json\">\n        <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n      </None>\n      <None Update=\"appsettings.Development.json\">\n        <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n      </None>\n      <None Update=\"appsettings.Production.json\">\n        <CopyToOutputDirectory>Always</CopyToOutputDirectory>\n      </None>\n    </ItemGroup>\n</Project>\n"
  },
  {
    "path": "src/Asv.Drones.Desktop/Program.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Reflection;\nusing System.Threading.Tasks;\nusing Asv.Avalonia;\nusing Asv.Avalonia.GeoMap;\nusing Asv.Avalonia.IO;\nusing Asv.Avalonia.Plugins;\nusing Asv.Common;\nusing Asv.Drones.Api;\nusing Avalonia;\nusing Avalonia.Controls;\nusing DotNext;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Logging;\nusing R3;\n\nnamespace Asv.Drones.Desktop;\n\nsealed class Program\n{\n    // Initialization code. Don't use any Avalonia, third-party APIs or any\n    // SynchronizationContext-reliant code before AppMain is called: things aren't initialized\n    // yet and stuff might break.\n    [STAThread]\n    public static void Main(string[] args)\n    {\n        try\n        {\n            BuildAvaloniaApp()\n                .StartWithClassicDesktopLifetime(args, ShutdownMode.OnMainWindowClose);\n            AppHost.Instance.StopAsync().GetAwaiter().GetResult();\n            Task.Run(AppHost.Instance.Dispose).GetAwaiter().GetResult();\n        }\n        catch (Exception e)\n        {\n            AppHost.HandleApplicationCrash(e);\n        }\n    }\n\n    // Avalonia configuration, don't remove; also used by visual designer.\n    public static AppBuilder BuildAvaloniaApp()\n    {\n        return AppBuilder\n            .Configure<App>()\n            .UsePlatformDetect()\n            .With(new Win32PlatformOptions { OverlayPopups = true }) // Windows\n            .With(new X11PlatformOptions { OverlayPopups = true, UseDBusFilePicker = false }) // Unix/Linux\n            .With(new AvaloniaNativePlatformOptions { OverlayPopups = true }) // Mac\n            .WithInterFont()\n            .LogToTrace()\n            .UseAsv(builder =>\n            {\n                builder\n                    .UseDefault()\n                    .UseAppInfo(configure => configure.FillFromAssembly(typeof(App).Assembly))\n                    .UseOptionalLogToFile()\n                    .UseOptionalLogViewer()\n                    .UseOptionalSoloRun(opt => opt.WithArgumentForwarding())\n                    .UsePluginManager(options =>\n                    {\n                        options.WithApiPackage(typeof(MavlinkHost).Assembly);\n                        options.WithPluginPrefix(\"Asv.Drones.Plugin.\");\n                    })\n                    .UseDesktopShell()\n                    .UseModulePlugins(configure =>\n                    {\n                        configure\n                            .WithApiPackage(typeof(MavlinkHost).Assembly)\n                            .UseOptionalInstalled() // register installed plugins page\n                            .UseOptionalMarket(); // register market plugins page\n                    })\n                    .UseModuleGeoMap()\n                    .UseModuleIo()\n                    .UseDronesApp();\n            });\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.Desktop/app.manifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n  <!-- This manifest is used on Windows only.\n       Don't remove it as it might cause problems with window transparency and embedded controls.\n       For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->\n  <assemblyIdentity version=\"1.0.0.0\" name=\"Asv.Drones.Desktop\"/>\n\n  <compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n      <!-- A list of the Windows versions that this application has been tested on\n           and is designed to work with. Uncomment the appropriate elements\n           and Windows will automatically select the most compatible environment. -->\n\n      <!-- Windows 10 -->\n      <supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\" />\n    </application>\n  </compatibility>\n</assembly>\n"
  },
  {
    "path": "src/Asv.Drones.Desktop/appsettings.Development.json",
    "content": "{\n\n}"
  },
  {
    "path": "src/Asv.Drones.Desktop/appsettings.Production.json",
    "content": "{\n\n}"
  },
  {
    "path": "src/Asv.Drones.Desktop/appsettings.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Trace\",\n      \"Microsoft\": \"Warning\",\n      \"System\": \"Warning\"\n    }\n  },\n  \"UnhandledExceptions\": {\n    \"R3\": {\n      \"PublishToShell\": true,\n      \"PublishToLogger\": true,\n      \"ForceApplicationCrash\": false\n    },\n    \"TaskScheduler\": {\n      \"PublishToShell\": true,\n      \"PublishToLogger\": true,\n      \"ForceApplicationCrash\": false\n    }\n  },\n  \"Plugins\":\n  {\n    \"PluginDataFolder\": \"plugins\",\n    \"AdditionalFolderPerPlugin\": [],\n    \"ApiPackageName\": \"Asv.Drones.Api\",\n    \"PluginAssemblyPrefix\": \"Asv.Drones.Plugin.\"\n  },\n  \"UserConfiguration\":\n  {\n    \"FilePath\": \"user_settings.json\",\n    \"AutoSaveMs\": 500\n  },\n  \"SoloRun\":\n  {\n    \"Mutex\": \"Asv.Drones.Api\",\n    \"ArgForward\": true,\n    \"Pipe\": \"Asv.Drones.Api\"\n  },\n  \"LogViewer\": {\n    \"RollingSizeKb\": 50,\n    \"Folder\": \"logs\"\n  }\n\n}"
  },
  {
    "path": "src/Asv.Drones.iOS/AppDelegate.cs",
    "content": "using Avalonia;\nusing Avalonia.Controls;\nusing Avalonia.iOS;\nusing Avalonia.Media;\nusing Foundation;\nusing UIKit;\n\nnamespace Asv.Drones.iOS;\n\n// The UIApplicationDelegate for the application. This class is responsible for launching the\n// User Interface of the application, as well as listening (and optionally responding) to\n// application events from iOS.\n[Register(\"AppDelegate\")]\n#pragma warning disable CA1711 // Identifiers should not have incorrect suffix\npublic partial class AppDelegate : AvaloniaAppDelegate<App>\n#pragma warning restore CA1711 // Identifiers should not have incorrect suffix\n{\n    protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)\n    {\n        return base.CustomizeAppBuilder(builder).WithInterFont();\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.iOS/Asv.Drones.iOS.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n    <PropertyGroup>\n        <OutputType>Exe</OutputType>\n        <TargetFramework>net9.0-ios</TargetFramework>\n        <SupportedOSPlatformVersion>13.0</SupportedOSPlatformVersion>\n        <Nullable>enable</Nullable>\n        <CodeAnalysisRuleSet>../CodeStyle.ruleset</CodeAnalysisRuleSet>\n        <WarningsAsErrors>\n            CS0169,\n            CS0618,\n            CS1502,\n            CS1503,\n            CS8524,\n            CS8600,\n            CS8601,\n            CS8602,\n            CS8603,\n            CS8604,\n            CS8625,\n            CS8629,\n            CS8762,\n            CA1510,\n            CA1851\n        </WarningsAsErrors>\n    </PropertyGroup>\n\n    <ItemGroup>\n        <PackageReference Include=\"Avalonia.iOS\" Version=\"$(AvaloniaVersion)\"/>\n        <PackageReference Include=\"Microsoft.NET.ILLink.Tasks\" Version=\"9.0.7\" />\n        <PackageReference Include=\"Roslynator.Analyzers\" Version=\"4.14.0\">\n          <PrivateAssets>all</PrivateAssets>\n          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n        </PackageReference>\n        <PackageReference Include=\"StyleCop.Analyzers\" Version=\"1.1.118\">\n          <PrivateAssets>all</PrivateAssets>\n          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n        </PackageReference>\n    </ItemGroup>\n\n    <ItemGroup>\n        <ProjectReference Include=\"..\\Asv.Drones\\Asv.Drones.csproj\"/>\n    </ItemGroup>\n</Project>\n"
  },
  {
    "path": "src/Asv.Drones.iOS/Entitlements.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</plist>\n"
  },
  {
    "path": "src/Asv.Drones.iOS/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>CFBundleDisplayName</key>\n\t<string>Asv.Drones</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>companyName.Asv.Drones</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1.0</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>MinimumOSVersion</key>\n\t<string>13.0</string>\n\t<key>UIDeviceFamily</key>\n\t<array>\n\t\t<integer>1</integer>\n\t\t<integer>2</integer>\n\t</array>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIRequiredDeviceCapabilities</key>\n\t<array>\n\t\t<string>armv7</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "src/Asv.Drones.iOS/Main.cs",
    "content": "using UIKit;\n\nnamespace Asv.Drones.iOS;\n\npublic class Application\n{\n    // This is the main entry point of the application.\n    static void Main(string[] args)\n    {\n        // if you want to use a different Application Delegate class from \"AppDelegate\"\n        // you can specify it here.\n        UIApplication.Main(args, null, typeof(AppDelegate));\n    }\n}\n"
  },
  {
    "path": "src/Asv.Drones.iOS/Resources/LaunchScreen.xib",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" version=\"3.0\" toolsVersion=\"6214\" systemVersion=\"14A314h\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" launchScreen=\"YES\" useTraitCollections=\"YES\">\n\t<dependencies>\n\t\t<plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"6207\" />\n\t\t<capability name=\"Constraints with non-1.0 multipliers\" minToolsVersion=\"5.1\" />\n\t</dependencies>\n\t<objects>\n\t\t<placeholder placeholderIdentifier=\"IBFilesOwner\" id=\"-1\" userLabel=\"File's Owner\" />\n\t\t<placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"-2\" customClass=\"UIResponder\" />\n\t\t<view contentMode=\"scaleToFill\" id=\"iN0-l3-epB\">\n\t\t\t<rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"480\" height=\"480\" />\n\t\t\t<autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\" />\n\t\t\t<subviews>\n\t\t\t\t<label opaque=\"NO\" clipsSubviews=\"YES\" userInteractionEnabled=\"NO\" contentMode=\"left\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"251\" text=\"  Copyright (c) 2022 \" textAlignment=\"center\" lineBreakMode=\"tailTruncation\" baselineAdjustment=\"alignBaselines\"\n\t\t\t\t\tminimumFontSize=\"9\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"8ie-xW-0ye\">\n\t\t\t\t\t<rect key=\"frame\" x=\"20\" y=\"439\" width=\"441\" height=\"21\" />\n\t\t\t\t\t<fontDescription key=\"fontDescription\" type=\"system\" pointSize=\"17\" />\n\t\t\t\t\t<color key=\"textColor\" cocoaTouchSystemColor=\"darkTextColor\" />\n\t\t\t\t\t<nil key=\"highlightedColor\" />\n\t\t\t\t</label>\n\t\t\t\t<label opaque=\"NO\" clipsSubviews=\"YES\" userInteractionEnabled=\"NO\" contentMode=\"left\" horizontalHuggingPriority=\"251\" verticalHuggingPriority=\"251\" text=\"Asv.Drones\" textAlignment=\"center\" lineBreakMode=\"middleTruncation\" baselineAdjustment=\"alignBaselines\"\n\t\t\t\t\tminimumFontSize=\"18\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"kId-c2-rCX\">\n\t\t\t\t\t<rect key=\"frame\" x=\"20\" y=\"140\" width=\"441\" height=\"43\" />\n\t\t\t\t\t<fontDescription key=\"fontDescription\" type=\"boldSystem\" pointSize=\"36\" />\n\t\t\t\t\t<color key=\"textColor\" cocoaTouchSystemColor=\"darkTextColor\" />\n\t\t\t\t\t<nil key=\"highlightedColor\" />\n\t\t\t\t</label>\n\t\t\t</subviews>\n\t\t\t<color key=\"backgroundColor\" white=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"calibratedWhite\" />\n\t\t\t<constraints>\n\t\t\t\t<constraint firstItem=\"kId-c2-rCX\" firstAttribute=\"centerY\" secondItem=\"iN0-l3-epB\" secondAttribute=\"bottom\" multiplier=\"1/3\" constant=\"1\" id=\"5cJ-9S-tgC\" />\n\t\t\t\t<constraint firstAttribute=\"centerX\" secondItem=\"kId-c2-rCX\" secondAttribute=\"centerX\" id=\"Koa-jz-hwk\" />\n\t\t\t\t<constraint firstAttribute=\"bottom\" secondItem=\"8ie-xW-0ye\" secondAttribute=\"bottom\" constant=\"20\" id=\"Kzo-t9-V3l\" />\n\t\t\t\t<constraint firstItem=\"8ie-xW-0ye\" firstAttribute=\"leading\" secondItem=\"iN0-l3-epB\" secondAttribute=\"leading\" constant=\"20\" symbolic=\"YES\" id=\"MfP-vx-nX0\" />\n\t\t\t\t<constraint firstAttribute=\"centerX\" secondItem=\"8ie-xW-0ye\" secondAttribute=\"centerX\" id=\"ZEH-qu-HZ9\" />\n\t\t\t\t<constraint firstItem=\"kId-c2-rCX\" firstAttribute=\"leading\" secondItem=\"iN0-l3-epB\" secondAttribute=\"leading\" constant=\"20\" symbolic=\"YES\" id=\"fvb-Df-36g\" />\n\t\t\t</constraints>\n\t\t\t<nil key=\"simulatedStatusBarMetrics\" />\n\t\t\t<freeformSimulatedSizeMetrics key=\"simulatedDestinationMetrics\" />\n\t\t\t<point key=\"canvasLocation\" x=\"548\" y=\"455\" />\n\t\t</view>\n\t</objects>\n</document>\n"
  },
  {
    "path": "src/Asv.Drones.slnx",
    "content": "<Solution>\n  <Folder Name=\"/Platforms/\">\n    <Project Path=\"Asv.Drones.Android/Asv.Drones.Android.csproj\" />\n    <Project Path=\"Asv.Drones.Desktop/Asv.Drones.Desktop.csproj\" />\n    <Project Path=\"Asv.Drones.iOS/Asv.Drones.iOS.csproj\" />\n  </Folder>\n  <Folder Name=\"/Solution Items/\">\n    <File Path=\"../.github/workflows/api-release-dev.yml\" />\n    <File Path=\"../.github/workflows/api-release.yml\" />\n    <File Path=\"../.github/workflows/drones-release-windows.yml\" />\n    <File Path=\"../README.md\" />\n    <File Path=\"../win-64-install.nsi\" />\n    <File Path=\".aiassistant/rules/comments.md\" />\n    <File Path=\"Directory.Build.props\" />\n  </Folder>\n  <Project Path=\"Asv.Drones.Api/Asv.Drones.Api.csproj\" />\n  <Project Path=\"Asv.Drones/Asv.Drones.csproj\" />\n</Solution>\n"
  },
  {
    "path": "src/CodeStyle.ruleset",
    "content": "<RuleSet Name=\"Rules for Asv project\" Description=\"Rule set for StyleCopAnalyzer\" ToolsVersion=\"14.0\">\n   <Rules AnalyzerId=\"StyleCop.Analyzers\" RuleNamespace=\"StyleCop.Analyzers\">\n       <!-->         None            <-->\n       <Rule Id=\"SA0001\" Action=\"None\" />\n       <Rule Id=\"SA1000\" Action=\"None\" />\n       <Rule Id=\"SA1009\" Action=\"None\" />\n       <Rule Id=\"SA1010\" Action=\"None\" />\n       <Rule Id=\"SA1011\" Action=\"None\" />\n       <Rule Id=\"SA1028\" Action=\"None\" />\n       <Rule Id=\"SA1101\" Action=\"None\" />\n       <Rule Id=\"SA1111\" Action=\"None\" />\n       <Rule Id=\"SA1118\" Action=\"None\" />\n       <Rule Id=\"SA1123\" Action=\"None\" />\n       <Rule Id=\"SA1124\" Action=\"None\" />\n       <Rule Id=\"SA1137\" Action=\"None\" />\n       <Rule Id=\"SA1200\" Action=\"None\" />\n       <Rule Id=\"SA1201\" Action=\"None\" />\n       <Rule Id=\"SA1202\" Action=\"None\" />\n       <Rule Id=\"SA1203\" Action=\"None\" />\n       <Rule Id=\"SA1204\" Action=\"None\" />\n       <Rule Id=\"SA1206\" Action=\"None\" />\n       <Rule Id=\"SA1210\" Action=\"None\" />\n       <Rule Id=\"SA1214\" Action=\"None\" />\n       <Rule Id=\"SA1309\" Action=\"None\" />\n       <Rule Id=\"SA1310\" Action=\"None\" />\n       <Rule Id=\"SA1400\" Action=\"None\" />\n       <Rule Id=\"SA1402\" Action=\"None\" />\n       <Rule Id=\"SA1500\" Action=\"None\" />\n       <Rule Id=\"SA1501\" Action=\"None\" />\n       <Rule Id=\"SA1502\" Action=\"None\" />\n       <Rule Id=\"SA1504\" Action=\"None\" />\n       <Rule Id=\"SA1513\" Action=\"None\" />\n       <Rule Id=\"SA1516\" Action=\"None\" />\n       <Rule Id=\"SA1600\" Action=\"None\" />\n       <Rule Id=\"SA1601\" Action=\"None\" />\n       <Rule Id=\"SA1602\" Action=\"None\" />\n       <Rule Id=\"SA1633\" Action=\"None\" />\n       <!-->         Error           <-->\n       <Rule Id=\"SA1119\" Action=\"Error\" />\n       <Rule Id=\"SA1122\" Action=\"Error\" />\n       <Rule Id=\"SA1300\" Action=\"Error\" />\n       <Rule Id=\"SA1313\" Action=\"Error\" />\n       <Rule Id=\"SA1407\" Action=\"Error\" />\n       <Rule Id=\"SA1413\" Action=\"Error\" />\n       <Rule Id=\"SA1503\" Action=\"Error\" />\n       <Rule Id=\"SA1512\" Action=\"Error\" />\n       <Rule Id=\"SA1515\" Action=\"Error\" />\n       <Rule Id=\"SA1520\" Action=\"Error\" />\n       <Rule Id=\"SA1614\" Action=\"Error\" />\n   </Rules>\n</RuleSet>"
  },
  {
    "path": "src/Directory.Build.props",
    "content": "<Project>\n  <PropertyGroup>\n    <Nullable>enable</Nullable>\n    <ProductVersion>2.5.1</ProductVersion>\n    <TargetFramework>net10.0</TargetFramework>\n    <ApiVersion>2.5.1</ApiVersion>\n    <AvaloniaVersion>11.3.12</AvaloniaVersion>\n    <AsvAvaloniaVersion>2.5.0-dev.3</AsvAvaloniaVersion>\n    <AsvMavlinkVersion>4.2.0</AsvMavlinkVersion>\n    <AsvGnssVersion>2.1.0</AsvGnssVersion>\n    <ScottPlotVersion>5.1.57</ScottPlotVersion>\n  </PropertyGroup>\n</Project>\n"
  },
  {
    "path": "src/global.json",
    "content": "{\n  \"scripts\": {\n    \"husky\": \"cd ../ & dotnet husky install\",\n    \"husky-unix\": \"cd .. && dotnet husky install\"\n  }\n}\n"
  },
  {
    "path": "win-64-install.nsi",
    "content": "﻿; Main constants - define following constants as you want them displayed in your installation wizard\n!define PRODUCT_NAME \"Asv.Drones\"\n!define PRODUCT_PUBLISHER \"Cursir\"\n\n; Following constants you should never change\n!define PRODUCT_UNINST_KEY \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${PRODUCT_NAME}\"\n!define PRODUCT_UNINST_ROOT_KEY \"HKLM\"\n\n!include \"MUI.nsh\"\n!include \"nsDialogs.nsh\"\n\n!define MUI_ABORTWARNING\n!define MUI_UNICON \"${NSISDIR}\\Contrib\\Graphics\\Icons\\modern-uninstall.ico\"\n\n; Wizard pages\n!insertmacro MUI_PAGE_WELCOME\n!insertmacro MUI_PAGE_COMPONENTS\n!insertmacro MUI_PAGE_DIRECTORY\n!insertmacro MUI_PAGE_INSTFILES\n!insertmacro MUI_PAGE_FINISH\n!insertmacro MUI_UNPAGE_INSTFILES\n!insertmacro MUI_LANGUAGE \"English\"\n\nName \"Asv Drones \"\nOutFile \"AsvDronesInstaller.exe\"\nInstallDir \"$PROGRAMFILES\\Asv\\Asv-Drones\"\nShowInstDetails show\nShowUnInstDetails show\n\n; Following lists the files you want to include, go through this list carefully!\nSection \"Core Files (required)\" SEC01\n  SetOutPath \"$INSTDIR\"\n  ; Check dir in yml where your project is stored\n  File /r \"./publish/app\\\\*.*\"\nSectionEnd\n\n; Section for shortcuts\nSection \"Start Menu and Desktop Shortcuts\" SEC02\n  ; Create a shortcut on the desktop\n  CreateShortcut \"$DESKTOP\\Asv Drones.lnk\" \"$INSTDIR\\Asv.Drones.Desktop.exe\" \"\" \"$INSTDIR\\Asv.Drones.Desktop.exe\" 0\n  ; Create a shortcut in the Start menu\n  CreateShortcut \"$SMPROGRAMS\\Asv Drones\\Asv Drones.lnk\" \"$INSTDIR\\Asv.Drones.Desktop.exe\" \"\" \"$INSTDIR\\Asv.Drones.Desktop.exe\" 0\nSectionEnd\n\nSection -Post\n  ;Following lines will make uninstaller work - do not change anything, unless you really want to.\n  WriteUninstaller \"$INSTDIR\\uninst.exe\"\n  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} \"${PRODUCT_UNINST_KEY}\" \"DisplayName\" \"$(^Name)\"\n  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} \"${PRODUCT_UNINST_KEY}\" \"UninstallString\" \"$INSTDIR\\uninst.exe\"\nSectionEnd\n\n; Replace the following strings to suite your needs\nFunction un.onUninstSuccess\n  HideWindow\n  MessageBox MB_ICONINFORMATION|MB_OK \"Application was successfully removed from your computer.\"\nFunctionEnd\n\nFunction un.onInit\n  MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 \"Are you sure you want to completely remove Asv Drones and all of its components?\" IDYES +2\n  Abort\nFunctionEnd\n\n; Remove any file that you have added above - removing uninstallation and folders last.\n; Note: if there is any file changed or added to these folders, they will not be removed. Also, parent folder (which in my example \n; is company name ZWare) will not be removed if there is any other application installed in it.\nSection Uninstall\n  ; Remove the installed files\n  Delete \"$INSTDIR\\*.*\"\n  \n  ; Remove the installation directory\n  RMDir /r \"$INSTDIR\"\n  \n  ; Remove the shortcuts\n    Delete \"$DESKTOP\\Asv Drones.lnk\"\n    Delete \"$SMPROGRAMS\\Asv Drones\\Asv Drones.lnk\"\n    RMDir \"$SMPROGRAMS\\Asv Drones\"\n\n  DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} \"${PRODUCT_UNINST_KEY}\"\n  SetAutoClose true\nSectionEnd"
  },
  {
    "path": "win-arm-install.iss",
    "content": "#define MyAppName \"Asv.Drones\"    \n#define MyAppVersion \"0.2.2\" \n#define MyAppPublisher \"Asv.Soft LLC\"    \n#define MyAppURL \"https://www.asv.me\"    \n#define MyAppExeName \"asv-drones-win-arm.exe\"    \n[Setup]    \nAppId={{AD0A5B4D-D17A-4301-A387-892193920F9D}    \nAppName={#MyAppName}    \nAppVersion={#MyAppVersion}    \nAppPublisher={#MyAppPublisher}    \nAppPublisherURL={#MyAppURL}    \nAppSupportURL={#MyAppURL}    \nAppUpdatesURL={#MyAppURL}    \nDefaultDirName={autopf}\\AsvDrones    \nDisableProgramGroupPage=yes    \nLicenseFile=License    \nOutputDir=publish\\win-arm    \nOutputBaseFilename=asv-drones-win-arm-install    \nSetupIconFile=src\\Asv.Drones.Gui\\Assets\\icon.ico    \nCompression=lzma    \nSolidCompression=yes    \nWizardStyle=modern    \n[Languages]    \nName: \"english\"; MessagesFile: \"compiler:Default.isl\"    \n[Tasks]    \nName: \"desktopicon\"; Description: \"{cm:CreateDesktopIcon}\"; GroupDescription: \"{cm:AdditionalIcons}\"; Flags: unchecked    \n[Files]    \nSource: \"publish\\win-arm\\app\\{#MyAppExeName}\"; DestDir: \"{app}\"; Flags: ignoreversion    \n[Icons]    \nName: \"{autoprograms}\\{#MyAppName}\"; Filename: \"{app}\\{#MyAppExeName}\"    \nName: \"{autodesktop}\\{#MyAppName}\"; Filename: \"{app}\\{#MyAppExeName}\"; Tasks: desktopicon    \n[Run]    \nFilename: \"{app}\\{#MyAppExeName}\"; Description: \"{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}\"; Flags: nowait postinstall skipifsilent    \n"
  },
  {
    "path": "win-arm64-install.iss",
    "content": "#define MyAppName \"Asv.Drones\"   \n#define MyAppVersion \"0.2.2\" \n#define MyAppPublisher \"Asv.Soft LLC\"   \n#define MyAppURL \"https://www.asv.me\"   \n#define MyAppExeName \"asv-drones-win-arm64.exe\"   \n[Setup]   \nAppId={{57EC272E-2E6B-4234-8FCB-2B339D3D9EE2}   \nAppName={#MyAppName}   \nAppVersion={#MyAppVersion}   \nAppPublisher={#MyAppPublisher}   \nAppPublisherURL={#MyAppURL}   \nAppSupportURL={#MyAppURL}   \nAppUpdatesURL={#MyAppURL}   \nDefaultDirName={autopf}\\{#MyAppName}   \nDisableProgramGroupPage=yes   \nLicenseFile=License   \nOutputDir=publish\\win-arm64   \nOutputBaseFilename=asv-drones-win-arm64-install   \nSetupIconFile=src\\Asv.Drones.Gui\\Assets\\icon.ico   \nCompression=lzma   \nSolidCompression=yes   \nWizardStyle=modern   \n[Languages]   \nName: \"english\"; MessagesFile: \"compiler:Default.isl\"   \n[Tasks]   \nName: \"desktopicon\"; Description: \"{cm:CreateDesktopIcon}\"; GroupDescription: \"{cm:AdditionalIcons}\"; Flags: unchecked   \n[Files]   \nSource: \"publish\\win-arm64\\app\\{#MyAppExeName}\"; DestDir: \"{app}\"; Flags: ignoreversion   \n[Icons]   \nName: \"{autoprograms}\\{#MyAppName}\"; Filename: \"{app}\\{#MyAppExeName}\"   \nName: \"{autodesktop}\\{#MyAppName}\"; Filename: \"{app}\\{#MyAppExeName}\"; Tasks: desktopicon   \n[Run]   \nFilename: \"{app}\\{#MyAppExeName}\"; Description: \"{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}\"; Flags: nowait postinstall skipifsilent   \n"
  },
  {
    "path": "win-x64-install.iss",
    "content": "#define MyAppName \"Asv.Drones\"   \n#define MyAppVersion \"0.2.2\" \n#define MyAppPublisher \"Asv.Soft LLC\"   \n#define MyAppURL \"https://www.asv.me\"   \n#define MyAppExeName \"asv-drones-win-x64.exe\"   \n[Setup]   \nAppId={{C5CE850B-61D6-417A-8855-AD02C9BEA9FD}   \nAppName={#MyAppName}   \nAppVersion={#MyAppVersion}   \nAppPublisher={#MyAppPublisher}   \nAppPublisherURL={#MyAppURL}   \nAppSupportURL={#MyAppURL}   \nAppUpdatesURL={#MyAppURL}   \nDefaultDirName={autopf}\\{#MyAppName}   \nDisableProgramGroupPage=yes   \nLicenseFile=License   \nOutputDir=publish\\win-x64   \nOutputBaseFilename=asv-drones-win-x64-install   \nSetupIconFile=src\\Asv.Drones.Gui\\Assets\\icon.ico   \nCompression=lzma   \nSolidCompression=yes   \nWizardStyle=modern   \n[Languages]   \nName: \"english\"; MessagesFile: \"compiler:Default.isl\"   \n[Tasks]   \nName: \"desktopicon\"; Description: \"{cm:CreateDesktopIcon}\"; GroupDescription: \"{cm:AdditionalIcons}\"; Flags: unchecked   \n[Files]   \nSource: \"publish\\win-x64\\app\\{#MyAppExeName}\"; DestDir: \"{app}\"; Flags: ignoreversion   \n[Icons]   \nName: \"{autoprograms}\\{#MyAppName}\"; Filename: \"{app}\\{#MyAppExeName}\"   \nName: \"{autodesktop}\\{#MyAppName}\"; Filename: \"{app}\\{#MyAppExeName}\"; Tasks: desktopicon   \n[Run]   \nFilename: \"{app}\\{#MyAppExeName}\"; Description: \"{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}\"; Flags: nowait postinstall skipifsilent   \n"
  },
  {
    "path": "win-x86-install.iss",
    "content": "#define MyAppName \"Asv.Drones\"   \n#define MyAppVersion \"0.2.2\" \n#define MyAppPublisher \"Asv.Soft LLC\"   \n#define MyAppURL \"https://www.asv.me\"   \n#define MyAppExeName \"asv-drones-win-x86.exe\"   \n[Setup]   \nAppId={{D799FAA2-F3B5-49CB-A5C3-E1DC4204E61F}   \nAppName={#MyAppName}   \nAppVersion={#MyAppVersion}   \nAppPublisher={#MyAppPublisher}   \nAppPublisherURL={#MyAppURL}   \nAppSupportURL={#MyAppURL}   \nAppUpdatesURL={#MyAppURL}   \nDefaultDirName={autopf}\\{#MyAppName}   \nDisableProgramGroupPage=yes   \nLicenseFile=License   \nOutputDir=publish\\win-x86   \nOutputBaseFilename=asv-drones-win-x86-install   \nSetupIconFile=src\\Asv.Drones.Gui\\Assets\\icon.ico   \nCompression=lzma   \nSolidCompression=yes   \nWizardStyle=modern   \n[Languages]   \nName: \"english\"; MessagesFile: \"compiler:Default.isl\"   \n[Tasks]   \nName: \"desktopicon\"; Description: \"{cm:CreateDesktopIcon}\"; GroupDescription: \"{cm:AdditionalIcons}\"; Flags: unchecked   \n[Files]   \nSource: \"publish\\win-x86\\app\\{#MyAppExeName}\"; DestDir: \"{app}\"; Flags: ignoreversion   \n[Icons]   \nName: \"{autoprograms}\\{#MyAppName}\"; Filename: \"{app}\\{#MyAppExeName}\"   \nName: \"{autodesktop}\\{#MyAppName}\"; Filename: \"{app}\\{#MyAppExeName}\"; Tasks: desktopicon   \n[Run]   \nFilename: \"{app}\\{#MyAppExeName}\"; Description: \"{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}\"; Flags: nowait postinstall skipifsilent   \n"
  }
]