├── .github └── workflows │ ├── android.yaml │ ├── arm64.yaml │ ├── ios.yaml │ ├── macos.yaml │ ├── snap.yaml │ └── windows.yaml ├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── apps4av │ │ │ │ └── avaremp │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── audio │ └── traffic_alerts │ │ ├── tr_00.mp3 │ │ ├── tr_01.mp3 │ │ ├── tr_02.mp3 │ │ ├── tr_03.mp3 │ │ ├── tr_04.mp3 │ │ ├── tr_05.mp3 │ │ ├── tr_06.mp3 │ │ ├── tr_07.mp3 │ │ ├── tr_08.mp3 │ │ ├── tr_09.mp3 │ │ ├── tr_10.mp3 │ │ ├── tr_100.mp3 │ │ ├── tr_1000.mp3 │ │ ├── tr_11.mp3 │ │ ├── tr_12.mp3 │ │ ├── tr_13.mp3 │ │ ├── tr_14.mp3 │ │ ├── tr_15.mp3 │ │ ├── tr_16.mp3 │ │ ├── tr_17.mp3 │ │ ├── tr_18.mp3 │ │ ├── tr_19.mp3 │ │ ├── tr_20.mp3 │ │ ├── tr_30.mp3 │ │ ├── tr_40.mp3 │ │ ├── tr_50.mp3 │ │ ├── tr_60.mp3 │ │ ├── tr_70.mp3 │ │ ├── tr_80.mp3 │ │ ├── tr_90.mp3 │ │ ├── tr_alpha.mp3 │ │ ├── tr_at.mp3 │ │ ├── tr_bogey.mp3 │ │ ├── tr_bravo.mp3 │ │ ├── tr_charlie.mp3 │ │ ├── tr_cl_chirp.mp3 │ │ ├── tr_cl_closingin.mp3 │ │ ├── tr_cl_over.mp3 │ │ ├── tr_climbing.mp3 │ │ ├── tr_delta.mp3 │ │ ├── tr_descending.mp3 │ │ ├── tr_echo.mp3 │ │ ├── tr_foxtrot.mp3 │ │ ├── tr_golf.mp3 │ │ ├── tr_high.mp3 │ │ ├── tr_hotel.mp3 │ │ ├── tr_india.mp3 │ │ ├── tr_juliet.mp3 │ │ ├── tr_kilo.mp3 │ │ ├── tr_level.mp3 │ │ ├── tr_lima.mp3 │ │ ├── tr_low.mp3 │ │ ├── tr_mike.mp3 │ │ ├── tr_miles.mp3 │ │ ├── tr_november.mp3 │ │ ├── tr_oclock.mp3 │ │ ├── tr_oscar.mp3 │ │ ├── tr_papa.mp3 │ │ ├── tr_point.mp3 │ │ ├── tr_quebec.mp3 │ │ ├── tr_romeo.mp3 │ │ ├── tr_same_altitude.mp3 │ │ ├── tr_seconds.mp3 │ │ ├── tr_sierra.mp3 │ │ ├── tr_tango.mp3 │ │ ├── tr_traffic.mp3 │ │ ├── tr_uniform.mp3 │ │ ├── tr_victor.mp3 │ │ ├── tr_whiskey.mp3 │ │ ├── tr_within.mp3 │ │ ├── tr_xray.mp3 │ │ ├── tr_yankee.mp3 │ │ └── tr_zulu.mp3 └── images │ ├── 512.png │ ├── black.png │ ├── dl_ak.png │ ├── dl_ec.png │ ├── dl_nc.png │ ├── dl_ne.png │ ├── dl_nw.png │ ├── dl_pac.png │ ├── dl_sc.png │ ├── dl_se.png │ ├── dl_sw.png │ ├── download.png │ ├── forum.png │ ├── intro.png │ ├── layers.png │ ├── logo.png │ ├── plane.png │ ├── regions.jpeg │ └── warning.png ├── integration_test └── app_test.dart ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── GithubActionsExportOptions.plist ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-50x50@1x.png │ │ │ ├── Icon-App-50x50@2x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── lib ├── aircraft.dart ├── aircraft_screen.dart ├── app_log.dart ├── app_settings.dart ├── area.dart ├── autopilot.dart ├── chart.dart ├── checklist.dart ├── checklist_screen.dart ├── constants.dart ├── data │ ├── altitude_profile.dart │ ├── cifp.dart │ ├── db_general.dart │ ├── main_database_helper.dart │ ├── user_database_helper.dart │ └── weather_database_helper.dart ├── destination │ ├── airport.dart │ ├── airway.dart │ ├── destination.dart │ ├── destination_calculations.dart │ └── nav.dart ├── documents_screen.dart ├── donate_screen.dart ├── download.dart ├── download_manager.dart ├── download_screen.dart ├── faa_dates.dart ├── filterable_logbook_dashboard.dart ├── find_screen.dart ├── flight_status.dart ├── flight_timer.dart ├── gdl90 │ ├── ahrs_message.dart │ ├── airmet_product.dart │ ├── dlac.dart │ ├── fis_buffer.dart │ ├── fis_graphics.dart │ ├── gdl90_buffer.dart │ ├── message.dart │ ├── message_factory.dart │ ├── nexrad_cache.dart │ ├── nexrad_high_product.dart │ ├── nexrad_medium_product.dart │ ├── nexrad_product.dart │ ├── notam_product.dart │ ├── ownship_geometric_altitude_message.dart │ ├── ownship_message.dart │ ├── product.dart │ ├── product_factory.dart │ ├── sigmet_product.dart │ ├── sua_product.dart │ ├── textual_weather_product.dart │ ├── traffic_alerts.dart │ ├── traffic_cache.dart │ ├── traffic_report_message.dart │ └── uplink_message.dart ├── geo_calculations.dart ├── geojson_parser.dart ├── gps.dart ├── gps_recorder.dart ├── image_utils.dart ├── instrument_list.dart ├── io_screen.dart ├── log_entry.dart ├── logbook_screen.dart ├── longpress_screen.dart ├── main.dart ├── main_screen.dart ├── map_screen.dart ├── nmea │ ├── bod_packet.dart │ ├── gga_message.dart │ ├── gga_packet.dart │ ├── nmea_buffer.dart │ ├── nmea_message.dart │ ├── nmea_message_factory.dart │ ├── nmea_ownship_message.dart │ ├── packet.dart │ ├── rmb_packet.dart │ ├── rmc_message.dart │ ├── rmc_packet.dart │ ├── rtm_message.dart │ └── rtm_packet.dart ├── onboarding_screen.dart ├── path_utils.dart ├── pdf_viewer.dart ├── pfd_painter.dart ├── plan │ ├── passage.dart │ ├── plan_action_screen.dart │ ├── plan_create_widget.dart │ ├── plan_file_widget.dart │ ├── plan_item_widget.dart │ ├── plan_line_widget.dart │ ├── plan_lmfs.dart │ ├── plan_load_save_widget.dart │ ├── plan_manage_widget.dart │ ├── plan_route.dart │ ├── plan_screen.dart │ └── waypoint.dart ├── plate_screen.dart ├── progress_button_message_input_widget.dart ├── progress_button_message_widget.dart ├── saa.dart ├── settings_cache_provider.dart ├── stack_with_one.dart ├── storage.dart ├── twilight_calculator.dart ├── udp_receiver.dart ├── unit_conversion.dart ├── warnings_widget.dart ├── weather │ ├── airep.dart │ ├── airep_cache.dart │ ├── airsigmet.dart │ ├── airsigmet_cache.dart │ ├── metar.dart │ ├── metar_cache.dart │ ├── notam.dart │ ├── notam_cache.dart │ ├── sounding.dart │ ├── taf.dart │ ├── taf_cache.dart │ ├── tfr.dart │ ├── tfr_cache.dart │ ├── time_segment_pie_chart.dart │ ├── weather.dart │ ├── weather_cache.dart │ ├── winds_aloft.dart │ └── winds_cache.dart ├── wnb.dart ├── wnb_screen.dart └── writing_screen.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements └── RunnerTests │ └── RunnerTests.swift ├── pubspec.lock ├── pubspec.yaml ├── snap ├── gui │ ├── avarex.desktop │ └── avarex.png └── snapcraft.yaml ├── tests ├── adsb_dallas.bin ├── adsb_daytona.bin ├── adsb_lancaster.bin ├── dynon.bin ├── gdl90_test.py ├── levil.bin ├── mmu.bin ├── nmea_bos.txt └── nmea_test.py ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html └── manifest.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake ├── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources │ └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h └── sqlite3.dll /.github/workflows/android.yaml: -------------------------------------------------------------------------------- 1 | name: Android 2 | 3 | on: push 4 | 5 | jobs: 6 | 7 | build-and-release: 8 | runs-on: ubuntu-24.04 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: subosito/flutter-action@v2 13 | with: 14 | channel: 'stable' 15 | flutter-version: '3.35.1' 16 | 17 | - name: Find and Replace OpenAIP 18 | uses: richardrigutins/replace-in-files@v2 19 | with: 20 | files: '**/*.dart' 21 | search-text: '@@___openaip_client_id__@@' 22 | replacement-text: ${{ secrets.OPENAIP_CLIENT_ID }} 23 | encoding: 'utf8' 24 | max-parallelism: 10 25 | 26 | - name: Find and Replace ASA 27 | uses: richardrigutins/replace-in-files@v2 28 | with: 29 | files: '**/*.dart' 30 | search-text: '@@___asa_password__@@' 31 | replacement-text: ${{ secrets.ASA_PASSWORD }} 32 | encoding: 'utf8' 33 | max-parallelism: 10 34 | 35 | - name: Install project dependencies 36 | run: flutter pub get 37 | 38 | - name: Enable KVM 39 | run: | 40 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 41 | sudo udevadm control --reload-rules 42 | sudo udevadm trigger --name-match=kvm 43 | 44 | - name: Integration Test 45 | uses: reactivecircus/android-emulator-runner@v2 46 | with: 47 | target: playstore 48 | api-level: 29 49 | arch: x86_64 50 | profile: Nexus 6 51 | script: flutter test integration_test/app_test.dart 52 | 53 | - name: Build APK 54 | run: flutter build apk --release 55 | 56 | - name: Sign APK with keystore 57 | uses: r0adkll/sign-android-release@v1 58 | id: sign_apk 59 | with: 60 | releaseDirectory: build/app/outputs/apk/release 61 | signingKeyBase64: ${{ secrets.KEY_STORE }} 62 | alias: ${{ secrets.KEY_STORE_ALIAS }} 63 | keyStorePassword: ${{ secrets.KEY_STORE_PASS }} 64 | keyPassword: ${{ secrets.KEY_STORE_PASS }} 65 | env: 66 | BUILD_TOOLS_VERSION: "34.0.0" 67 | 68 | - name: Build AAB 69 | run: flutter build appbundle --release 70 | 71 | - name: Sign AAB with keystore 72 | uses: r0adkll/sign-android-release@v1 73 | id: sign_aab 74 | with: 75 | releaseDirectory: build/app/outputs/bundle/release 76 | signingKeyBase64: ${{ secrets.KEY_STORE }} 77 | alias: ${{ secrets.KEY_STORE_ALIAS }} 78 | keyStorePassword: ${{ secrets.KEY_STORE_PASS }} 79 | keyPassword: ${{ secrets.KEY_STORE_PASS }} 80 | env: 81 | BUILD_TOOLS_VERSION: "34.0.0" 82 | 83 | - name: Upload release 84 | uses: actions/upload-artifact@v4 85 | with: 86 | name: Android 87 | path: | 88 | ${{steps.sign_apk.outputs.signedReleaseFile}} 89 | ${{steps.sign_aab.outputs.signedReleaseFile}} 90 | -------------------------------------------------------------------------------- /.github/workflows/arm64.yaml: -------------------------------------------------------------------------------- 1 | name: Arm64-Linux 2 | 3 | on: push 4 | 5 | jobs: 6 | 7 | build-and-release: 8 | runs-on: self-hosted 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Find and Replace OpenAIP 14 | uses: richardrigutins/replace-in-files@v2 15 | with: 16 | files: '**/*.dart' 17 | search-text: '@@___openaip_client_id__@@' 18 | replacement-text: ${{ secrets.OPENAIP_CLIENT_ID }} 19 | encoding: 'utf8' 20 | max-parallelism: 10 21 | 22 | - name: Find and Replace ASA 23 | uses: richardrigutins/replace-in-files@v2 24 | with: 25 | files: '**/*.dart' 26 | search-text: '@@___asa_password__@@' 27 | replacement-text: ${{ secrets.ASA_PASSWORD }} 28 | encoding: 'utf8' 29 | max-parallelism: 10 30 | 31 | - name: Build Flutter 32 | run: export PATH=${PATH}:/home/zkhan/flutter/bin && flutter build linux --release 33 | 34 | - name: Integration Test 35 | run: export PATH=${PATH}:/home/zkhan/flutter/bin && xvfb-run flutter test integration_test/app_test.dart -d linux 36 | 37 | - name: Upload Artifact 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: RaspberryPi 41 | path: build/linux/arm64/release/bundle/* 42 | -------------------------------------------------------------------------------- /.github/workflows/macos.yaml: -------------------------------------------------------------------------------- 1 | name: MacOS 2 | 3 | on: push 4 | 5 | jobs: 6 | 7 | build-and-release: 8 | runs-on: macos-14 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: subosito/flutter-action@v2 13 | with: 14 | channel: 'stable' 15 | flutter-version: '3.35.1' 16 | 17 | - name: Find and Replace OpenAIP 18 | uses: richardrigutins/replace-in-files@v2 19 | with: 20 | files: '**/*.dart' 21 | search-text: '@@___openaip_client_id__@@' 22 | replacement-text: ${{ secrets.OPENAIP_CLIENT_ID }} 23 | encoding: 'utf8' 24 | max-parallelism: 10 25 | 26 | - name: Find and Replace ASA 27 | uses: richardrigutins/replace-in-files@v2 28 | with: 29 | files: '**/*.dart' 30 | search-text: '@@___asa_password__@@' 31 | replacement-text: ${{ secrets.ASA_PASSWORD }} 32 | encoding: 'utf8' 33 | max-parallelism: 10 34 | 35 | - name: Install project dependencies 36 | run: | 37 | flutter pub get 38 | brew install create-dmg 39 | 40 | - name: Integration Test 41 | run: | 42 | flutter test integration_test/app_test.dart -d macos 43 | 44 | - name: Build artifacts and sign 45 | run: | 46 | flutter build macos --release 47 | echo -n ${{ secrets.MACOS_DIST_CERTIFICATE }} | base64 --decode --output certificate.p12 48 | # create temporary keychain 49 | security create-keychain -p test_password sign.keychain 50 | security default-keychain -s sign.keychain 51 | security unlock-keychain -p test_password sign.keychain 52 | security import certificate.p12 -k sign.keychain -P ${{ secrets.MACOS_DIST_CERTIFICATE_PASSWORD }} -T /usr/bin/codesign 53 | security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k test_password sign.keychain 54 | security find-identity 55 | # sign 56 | /usr/bin/codesign --force --deep -s LPHGYCR8ZX build/macos/Build/Products/Release/AvareX.app 57 | # make dmg 58 | pushd build/macos/Build/Products/Release 59 | create-dmg \ 60 | --volname "AvareX" \ 61 | --window-pos 200 200 \ 62 | --window-size 800 600 \ 63 | --icon-size 130 \ 64 | --text-size 14 \ 65 | --icon "AvareX.app" 260 250 \ 66 | --hide-extension "AvareX.app" \ 67 | --app-drop-link 540 250 \ 68 | --hdiutil-quiet \ 69 | "AvareX.dmg" \ 70 | "AvareX.app" 71 | popd 72 | # sign dmg 73 | /usr/bin/codesign --force --deep -s LPHGYCR8ZX build/macos/Build/Products/Release/AvareX.dmg 74 | # delete keychain 75 | security delete-keychain sign.keychain 76 | rm certificate.p12 77 | 78 | - name: Upload Artifact 79 | uses: actions/upload-artifact@v4 80 | with: 81 | name: MacOS 82 | path: build/macos/Build/Products/Release/AvareX.dmg 83 | -------------------------------------------------------------------------------- /.github/workflows/snap.yaml: -------------------------------------------------------------------------------- 1 | name: Snap-Linux 2 | 3 | on: push 4 | 5 | jobs: 6 | 7 | build-and-release: 8 | runs-on: ubuntu-22.04 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: subosito/flutter-action@v2 13 | with: 14 | channel: 'stable' 15 | flutter-version: '3.35.1' 16 | 17 | - name: Find and Replace OpenAIP 18 | uses: richardrigutins/replace-in-files@v2 19 | with: 20 | files: '**/*.dart' 21 | search-text: '@@___openaip_client_id__@@' 22 | replacement-text: ${{ secrets.OPENAIP_CLIENT_ID }} 23 | encoding: 'utf8' 24 | max-parallelism: 10 25 | 26 | - name: Find and Replace ASA 27 | uses: richardrigutins/replace-in-files@v2 28 | with: 29 | files: '**/*.dart' 30 | search-text: '@@___asa_password__@@' 31 | replacement-text: ${{ secrets.ASA_PASSWORD }} 32 | encoding: 'utf8' 33 | max-parallelism: 10 34 | 35 | - name: Update source repo 36 | run: sudo apt-get update 37 | - name: Pre-empt libunwind issue for gstreamer 38 | run: sudo apt-get install -y libunwind-dev 39 | - name: Install audioplayers dependencies 40 | run: sudo apt-get install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev 41 | - name: Install dependencies 42 | run: sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev 43 | 44 | - name: Install Snapcraft 45 | uses: samuelmeuli/action-snapcraft@v2 46 | 47 | - name: Integration Test 48 | run: xvfb-run flutter test integration_test/app_test.dart -d linux 49 | 50 | - name: Build Snap Image 51 | run: sudo snapcraft --destructive-mode 52 | 53 | - name: Upload Artifact 54 | uses: actions/upload-artifact@v4 55 | with: 56 | name: Snap 57 | path: avarex*.snap 58 | -------------------------------------------------------------------------------- /.github/workflows/windows.yaml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: push 4 | 5 | jobs: 6 | 7 | build-and-release: 8 | runs-on: windows-2022 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: subosito/flutter-action@v2 13 | with: 14 | channel: 'stable' 15 | flutter-version: '3.35.1' 16 | 17 | - name: Find and Replace OpenAIP 18 | uses: richardrigutins/replace-in-files@v2 19 | with: 20 | files: '**/*.dart' 21 | search-text: '@@___openaip_client_id__@@' 22 | replacement-text: ${{ secrets.OPENAIP_CLIENT_ID }} 23 | encoding: 'utf8' 24 | max-parallelism: 10 25 | 26 | - name: Find and Replace ASA 27 | uses: richardrigutins/replace-in-files@v2 28 | with: 29 | files: '**/*.dart' 30 | search-text: '@@___asa_password__@@' 31 | replacement-text: ${{ secrets.ASA_PASSWORD }} 32 | encoding: 'utf8' 33 | max-parallelism: 10 34 | 35 | - name: Install project dependencies 36 | run: flutter pub get 37 | 38 | - name: Integration Test 39 | run: flutter test integration_test/app_test.dart -d windows 40 | 41 | - name: Build artifacts 42 | run: flutter build windows --release 43 | - name: Build artifacts MSIX 44 | run: dart run msix:create 45 | - name: Archive Release 46 | uses: thedoctor0/zip-release@master 47 | with: 48 | type: 'zip' 49 | filename: ${{github.ref_name}}-windows.zip 50 | directory: build/windows/x64/runner/Release 51 | - name: Windows Release 52 | uses: softprops/action-gh-release@v1 53 | if: startsWith(github.ref, 'refs/tags/') 54 | env: 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | with: 57 | files: build/windows/x64/runner/Release/${{github.ref_name}}-windows.zip 58 | - name: Upload Artifact 59 | uses: actions/upload-artifact@v4 60 | with: 61 | name: WindowsX64 62 | path: build/windows/x64/runner/Release/${{github.ref_name}}-windows.zip 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | *.xccconfig 7 | *.cc 8 | *.lock 9 | *.cmake 10 | generated_plugin* 11 | pubspec.lock 12 | .DS_Store 13 | Podfile 14 | Podfile.lock 15 | .atom/ 16 | .buildlog/ 17 | .history 18 | .svn/ 19 | migrate_working_dir/ 20 | 21 | # IntelliJ related 22 | *.iml 23 | *.ipr 24 | *.iws 25 | .idea/ 26 | 27 | # The .vscode folder contains launch configuration and tasks you configure in 28 | # VS Code which you may wish to be included in version control, so this line 29 | # is commented out by default. 30 | #.vscode/ 31 | 32 | # Flutter/Dart/Pub related 33 | **/doc/api/ 34 | **/ios/Flutter/.last_build_id 35 | .dart_tool/ 36 | .flutter-plugins 37 | .flutter-plugins-dependencies 38 | .pub-cache/ 39 | .pub/ 40 | /build/ 41 | 42 | # Symbolication related 43 | app.*.symbols 44 | 45 | # Obfuscation related 46 | app.*.map.json 47 | 48 | # Android Studio will place build artifacts here 49 | /android/app/debug 50 | /android/app/profile 51 | /android/app/release 52 | 53 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "db7ef5bf9f59442b0e200a90587e8fa5e0c6336a" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 17 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 18 | - platform: android 19 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 20 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 21 | - platform: ios 22 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 23 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 24 | - platform: linux 25 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 26 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 27 | - platform: macos 28 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 29 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 30 | - platform: web 31 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 32 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 33 | - platform: windows 34 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 35 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "avarex", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "avarex (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | }, 18 | { 19 | "name": "avarex (release mode)", 20 | "request": "launch", 21 | "type": "dart", 22 | "flutterMode": "release" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Apps4Av Inc. 2 | 3 | Contact apps4av@gmail.com for commercial use of code. 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AvareX 2 | 3 | Avare, written in Flutter. Runs on Linux, Windows, MacOS, iOS, Android, and Raspberry Pi. 4 | 5 | AvareX is a pilot's all in one electronic flight bag solution. 6 | 7 | By Apps4Av. 8 | 9 | ## Getting Started 10 | 11 | 12 | 13 | ### Downloading 14 | 15 | 16 | ** Windows 17 | 18 | Download on Windows using Microsoft Store. 19 | 20 | ** MacOS 21 | 22 | Download on Apple App Store from your Mac with Apple Silicon. 23 | 24 | ** Linux 25 | 26 | Download on Linux using Snap Store. 27 | 28 | ** iOS 29 | 30 | Download on Apple App Store from your iPhone or iPad. 31 | 32 | ** Android 33 | 34 | Download on Google Play Store from your Android device. 35 | 36 | ** Raspberry Pi 37 | 38 | Download at https://github.com/apps4av/avarex/actions/workflows/arm64.yaml from your Pi. 39 | 40 | Tested on 64-bit Raspberry Pi OS (may run on other configurations). 41 | - Pi 5 with 8 GB memory 42 | - Pi 4 with 1 GB memory 43 | - Prerequisites: sudo apt-get install libgtk-3-0 libblkid1 liblzma5 libsqlite3-dev 44 | 45 | ## Store Consoles 46 | 47 | Google / Android: https://play.google.com/console 48 | 49 | iOS, MacOS: https://appstoreconnect.apple.com/login 50 | 51 | Linux: https://snapcraft.io 52 | 53 | Windows: https://partner.microsoft.com/en-us/dashboard/home 54 | 55 | ## Store Locations 56 | 57 | Google / Android : https://play.google.com/store/apps/details?id=com.apps4av.avaremp 58 | 59 | iOS, MacOS: https://apps.apple.com/us/app/avarex/id6502421523 60 | 61 | Linux: https://snapcraft.io/avarex 62 | 63 | Windows: https://apps.microsoft.com/detail/9mx4hkl30mww?hl=en-us&gl=US 64 | 65 | ## Building: 66 | 67 | Github Actions builds all store builds. 68 | 69 | Microsoft version scheme: pubspec.yaml (versions go like 1.0.9.0, last digit must be 0) 70 | 71 | Apple version scheme: pubspec.yaml 0.0.9+9 72 | 73 | Google version scheme: pubspec.yaml 0.0.9+9 (+9) is what shows up in the package) 74 | 75 | Snap version scheme: snap/snapcraft.yaml 0.0.9 76 | 77 | 78 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | namespace "com.apps4av.avaremp" 27 | compileSdkVersion flutter.compileSdkVersion 28 | ndkVersion flutter.ndkVersion 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_17 32 | targetCompatibility JavaVersion.VERSION_17 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = JavaVersion.VERSION_17.toString() 37 | } 38 | 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | 44 | defaultConfig { 45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 46 | applicationId "com.apps4av.avaremp" 47 | // You can update the following values to match your application needs. 48 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 49 | minSdkVersion flutter.minSdkVersion 50 | targetSdkVersion flutter.targetSdkVersion 51 | versionCode flutterVersionCode.toInteger() 52 | versionName flutterVersionName 53 | } 54 | 55 | buildTypes { 56 | release { 57 | // TODO: Add your own signing config for the release build. 58 | // Signing with the debug keys for now, so `flutter run --release` works. 59 | // signingConfig signingConfigs.debug 60 | } 61 | } 62 | } 63 | 64 | flutter { 65 | source '../..' 66 | } 67 | 68 | dependencies {} 69 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 24 | 28 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/apps4av/avaremp/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.apps4av.avaremp 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.8.22' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | mavenCentral() 17 | } 18 | } 19 | 20 | rootProject.buildDir = '../build' 21 | subprojects { 22 | project.buildDir = "${rootProject.buildDir}/${project.name}" 23 | } 24 | subprojects { 25 | project.evaluationDependsOn(':app') 26 | } 27 | 28 | tasks.register("clean", Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.defaults.buildfeatures.buildconfig=true 5 | android.nonTransitiveRClass=false 6 | android.nonFinalResIds=false 7 | android.ndk.suppressMinSdkVersionError=21 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false 21 | } 22 | } 23 | 24 | plugins { 25 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 26 | id "com.android.application" version '8.7.0' apply false 27 | id 'org.jetbrains.kotlin.android' version '1.8.22' apply false 28 | } 29 | 30 | include ":app" 31 | -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_00.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_00.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_01.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_02.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_02.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_03.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_03.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_04.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_04.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_05.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_05.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_06.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_06.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_07.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_07.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_08.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_08.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_09.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_09.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_10.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_10.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_100.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_100.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_1000.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_1000.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_11.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_11.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_12.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_12.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_13.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_13.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_14.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_14.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_15.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_15.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_16.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_16.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_17.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_17.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_18.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_18.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_19.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_19.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_20.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_20.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_30.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_30.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_40.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_40.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_50.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_50.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_60.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_70.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_70.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_80.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_80.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_90.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_90.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_alpha.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_alpha.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_at.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_at.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_bogey.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_bogey.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_bravo.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_bravo.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_charlie.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_charlie.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_cl_chirp.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_cl_chirp.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_cl_closingin.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_cl_closingin.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_cl_over.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_cl_over.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_climbing.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_climbing.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_delta.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_delta.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_descending.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_descending.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_echo.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_echo.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_foxtrot.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_foxtrot.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_golf.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_golf.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_high.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_high.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_hotel.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_hotel.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_india.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_india.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_juliet.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_juliet.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_kilo.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_kilo.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_level.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_level.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_lima.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_lima.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_low.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_low.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_mike.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_mike.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_miles.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_miles.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_november.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_november.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_oclock.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_oclock.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_oscar.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_oscar.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_papa.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_papa.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_point.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_point.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_quebec.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_quebec.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_romeo.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_romeo.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_same_altitude.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_same_altitude.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_seconds.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_seconds.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_sierra.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_sierra.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_tango.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_tango.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_traffic.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_traffic.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_uniform.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_uniform.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_victor.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_victor.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_whiskey.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_whiskey.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_within.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_within.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_xray.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_xray.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_yankee.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_yankee.mp3 -------------------------------------------------------------------------------- /assets/audio/traffic_alerts/tr_zulu.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/audio/traffic_alerts/tr_zulu.mp3 -------------------------------------------------------------------------------- /assets/images/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/512.png -------------------------------------------------------------------------------- /assets/images/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/black.png -------------------------------------------------------------------------------- /assets/images/dl_ak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/dl_ak.png -------------------------------------------------------------------------------- /assets/images/dl_ec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/dl_ec.png -------------------------------------------------------------------------------- /assets/images/dl_nc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/dl_nc.png -------------------------------------------------------------------------------- /assets/images/dl_ne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/dl_ne.png -------------------------------------------------------------------------------- /assets/images/dl_nw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/dl_nw.png -------------------------------------------------------------------------------- /assets/images/dl_pac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/dl_pac.png -------------------------------------------------------------------------------- /assets/images/dl_sc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/dl_sc.png -------------------------------------------------------------------------------- /assets/images/dl_se.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/dl_se.png -------------------------------------------------------------------------------- /assets/images/dl_sw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/dl_sw.png -------------------------------------------------------------------------------- /assets/images/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/download.png -------------------------------------------------------------------------------- /assets/images/forum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/forum.png -------------------------------------------------------------------------------- /assets/images/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/intro.png -------------------------------------------------------------------------------- /assets/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/layers.png -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/logo.png -------------------------------------------------------------------------------- /assets/images/plane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/plane.png -------------------------------------------------------------------------------- /assets/images/regions.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/regions.jpeg -------------------------------------------------------------------------------- /assets/images/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/assets/images/warning.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 13.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/GithubActionsExportOptions.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | compileBitcode 6 | 7 | method 8 | app-store 9 | provisioningProfiles 10 | 11 | com.apps4av.avaremp 12 | AvareX 13 | 14 | signingCertificate 15 | iOS Distribution 16 | signingStyle 17 | manual 18 | stripSwiftSymbols 19 | 20 | teamID 21 | LPHGYCR8ZX 22 | thinning 23 | <none> 24 | 25 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | AvareX 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | AvareX 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | NSMicrophoneUsageDescription 49 | This app does not use the microphone for any purpose, but an SDK used for developing this app may contain calls to the microphone API. 50 | NSLocationWhenInUseUsageDescription 51 | This app needs access to your GPS location to show your position on the Map and Plate screens. If you deny this permission, you will still be able to use the app, but your position on the map will not change during your flight. Your location is not shared with Apps4Av. 52 | NSLocationAlwaysAndWhenInUseUsageDescription 53 | This app needs continuous access to your GPS location to show your position on the Map and Plate screens. If you deny this permission, you will still be able to use the app, but your position on the map will not change during your flight. Your location is not shared with Apps4Av. 54 | NSPhotoLibraryUsageDescription 55 | This app needs access to photo library to show images on a map. 56 | 57 | 58 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/aircraft.dart: -------------------------------------------------------------------------------- 1 | class Aircraft { 2 | final String tail; 3 | final String type; 4 | final String wake; 5 | final String icao; 6 | final String equipment; 7 | final String cruiseTas; 8 | final String surveillance; 9 | final String fuelEndurance; 10 | final String color; 11 | final String pic; 12 | final String picInfo; 13 | final String sinkRate; 14 | final String fuelBurn; 15 | final String base; 16 | final String other; 17 | 18 | Aircraft( 19 | this.tail, 20 | this.type, 21 | this.wake, 22 | this.icao, 23 | this.equipment, 24 | this.cruiseTas, 25 | this.surveillance, 26 | this.fuelEndurance, 27 | this.color, 28 | this.pic, 29 | this.picInfo, 30 | this.sinkRate, 31 | this.fuelBurn, 32 | this.base, 33 | this.other); 34 | 35 | factory Aircraft.empty() { 36 | return Aircraft( 37 | "", 38 | "", 39 | "LIGHT", 40 | "", 41 | "S", 42 | "", 43 | "N", 44 | "", 45 | "", 46 | "", 47 | "", 48 | "", 49 | "", 50 | "", 51 | ""); 52 | } 53 | 54 | factory Aircraft.fromMap(Map map) { 55 | return Aircraft( 56 | (map['tail'] as String).toUpperCase(), 57 | (map['type'] as String).toUpperCase(), 58 | (map['wake'] as String).toUpperCase(), 59 | (map['icao'] as String).toUpperCase(), 60 | (map['equipment'] as String).toUpperCase(), 61 | (map['cruiseTas'] as String).toUpperCase(), 62 | (map['surveillance'] as String).toUpperCase(), 63 | (map['fuelEndurance'] as String).toUpperCase(), 64 | (map['color'] as String).toUpperCase(), 65 | (map['pic'] as String).toUpperCase(), 66 | (map['picInfo'] as String).toUpperCase(), 67 | (map['sinkRate'] as String).toUpperCase(), 68 | (map['fuelBurn'] as String).toUpperCase(), 69 | (map['base'] as String).toUpperCase(), 70 | (map['other'] as String).toUpperCase(), 71 | ); 72 | } 73 | 74 | Map toMap() { 75 | return { 76 | 'tail': tail.toUpperCase(), 77 | 'type': type.toUpperCase(), 78 | 'wake': wake.toUpperCase(), 79 | 'icao': icao.toUpperCase(), 80 | 'equipment': equipment.toUpperCase(), 81 | 'cruiseTas': cruiseTas.toUpperCase(), 82 | 'surveillance': surveillance.toUpperCase(), 83 | 'fuelEndurance': fuelEndurance.toUpperCase(), 84 | 'color': color.toUpperCase(), 85 | 'pic': pic.toUpperCase(), 86 | 'picInfo': picInfo.toUpperCase(), 87 | 'sinkRate': sinkRate.toUpperCase(), 88 | 'fuelBurn': fuelBurn.toUpperCase(), 89 | 'base': base.toUpperCase(), 90 | 'other': other.toUpperCase(), 91 | }; 92 | } 93 | } -------------------------------------------------------------------------------- /lib/app_log.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | import 'package:flutter/foundation.dart'; 3 | 4 | class AppLog { 5 | static void logMessage(String message) { 6 | // In a real application, you might want to log to a file or external service 7 | if(kDebugMode) log(message); 8 | } 9 | } -------------------------------------------------------------------------------- /lib/area.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/data/main_database_helper.dart'; 2 | import 'package:avaremp/destination/destination.dart'; 3 | import 'package:avaremp/geo_calculations.dart'; 4 | import 'package:avaremp/storage.dart'; 5 | import 'package:avaremp/weather/winds_aloft.dart'; 6 | import 'package:avaremp/weather/winds_cache.dart'; 7 | import 'package:flutter/cupertino.dart'; 8 | import 'package:geolocator/geolocator.dart'; 9 | import 'package:latlong2/latlong.dart'; 10 | import 'gps.dart'; 11 | 12 | class Area { 13 | 14 | double geoAltitude = 0; 15 | WindsAloft? _windsAloft; 16 | Destination? closestAirport; 17 | List obstacles = []; 18 | double variation = 0; 19 | ValueNotifier change = ValueNotifier(0); 20 | 21 | Future update(Position position) async { 22 | double geo = 0; 23 | double declination = 0; 24 | (geo, declination) = await MainDatabaseHelper.db.getGeoInfo(Gps.toLatLng(position)); 25 | geoAltitude = geo; 26 | variation = declination; 27 | List d = await MainDatabaseHelper.db.findNearestAirportsWithRunways(Gps.toLatLng(position), 1000); 28 | if(d.isNotEmpty) { 29 | closestAirport = d[0]; 30 | } 31 | 32 | final List layers = Storage().settings.getLayers(); 33 | final List layersOpacity = Storage().settings.getLayersOpacity(); 34 | int lIndex = layers.indexOf('Obstacles'); 35 | if(layersOpacity[lIndex] > 0) { 36 | obstacles = await MainDatabaseHelper.db.findObstacles(Gps.toLatLng(position), GeoCalculations.convertAltitude(position.altitude)); 37 | } 38 | // get surface wind from nearest airport 39 | String wind = WindsCache.getWind0kFromMetar(Gps.toLatLng(position)); 40 | // get aloft wind from nearest station 41 | String? station = WindsCache.locateNearestStation(Gps.toLatLng(position)); 42 | WindsAloft? wa = Storage().winds.get("${station}06H") as WindsAloft?; 43 | if(null != wa) { 44 | // combine surface and aloft wind 45 | _windsAloft = WindsAloft(wa.station, wa.expires, wa.received, wa.source, wind, wa.w3k, wa.w6k, wa.w9k, wa.w12k, wa.w18k, wa.w24k, wa.w30k, wa.w34k, wa.w39k); 46 | } 47 | change.value++; 48 | } 49 | 50 | (double?, double?) getWind(double altitude) { 51 | double? direction = 0; 52 | double? speed = 0; 53 | if(null != _windsAloft) { 54 | (direction, speed) = _windsAloft!.getWindAtAltitude(altitude); 55 | } 56 | return (direction, speed); 57 | } 58 | } -------------------------------------------------------------------------------- /lib/checklist.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class Checklist { 4 | final String name; 5 | final String aircraft; 6 | final List steps; 7 | 8 | Checklist(this.name, this.aircraft, this.steps); 9 | 10 | factory Checklist.empty() { 11 | return Checklist("", "", []); 12 | } 13 | 14 | factory Checklist.fromMap(Map map) { 15 | return Checklist( 16 | map['name'] as String, 17 | map['aircraft'] as String, 18 | List.from(jsonDecode(map['items'] as String)) 19 | ); 20 | } 21 | 22 | Map toMap() { 23 | return { 24 | 'name': name, 25 | 'aircraft': aircraft, 26 | 'items': jsonEncode(steps) 27 | }; 28 | } 29 | } -------------------------------------------------------------------------------- /lib/data/altitude_profile.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:avaremp/data/user_database_helper.dart'; 3 | import 'package:avaremp/storage.dart'; 4 | import 'package:http/http.dart' as http; 5 | import 'package:latlong2/latlong.dart'; 6 | 7 | 8 | class AltitudeProfile { 9 | 10 | // this creates a local cache 11 | static Future> getAltitudeProfile(List points) async { 12 | // return as many as points 13 | List altitudes = List.generate(points.length, (index) => -double.infinity); 14 | // find all elevations stored in the database 15 | List ee = await UserDatabaseHelper.db.getElevations(points); 16 | for(int i = 0; i < ee.length; i++) { 17 | if(ee[i] != null) { 18 | // take what we have, ask for the rest from internet 19 | altitudes[i] = ee[i]!; 20 | } 21 | } 22 | 23 | bool store = false; 24 | List> locations = []; 25 | for (int i = 0; i < ee.length; i++) { 26 | if(ee[i] != null) { 27 | continue; 28 | } 29 | store = true; 30 | locations.add({"latitude": points[i].latitude, "longitude": points[i].longitude}); 31 | } 32 | 33 | if(store) { // new data needed 34 | String query = "https://api.open-elevation.com/api/v1/lookup"; 35 | var response = await http.post(Uri.parse(query), body: jsonEncode({"locations": locations})); 36 | if (response.statusCode == 200) { 37 | var data = jsonDecode(response.body); 38 | int index = 0; 39 | for (int i = 0; i < ee.length; i++) { 40 | if(ee[i] != null) { 41 | continue; 42 | } 43 | if(index >= data['results'].length) { 44 | // something wrong. server responded with less data than requested 45 | continue; 46 | } 47 | altitudes[i] = (data['results'][index]['elevation'] * Storage().units.mToF); 48 | index++; 49 | } 50 | // new data, store 51 | await UserDatabaseHelper.db.insertElevations(points, altitudes); 52 | } 53 | } 54 | return altitudes; 55 | } 56 | } 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /lib/data/db_general.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:sqflite_common_ffi/sqflite_ffi.dart'; 4 | import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; 5 | class DbGeneral { 6 | static void set() { 7 | // Initialize FFI 8 | if(Platform.isWindows || Platform.isLinux) { 9 | sqfliteFfiInit(); 10 | databaseFactory = databaseFactoryFfi; 11 | } 12 | else if(kIsWeb) { 13 | databaseFactory = databaseFactoryFfiWeb; 14 | } 15 | else { 16 | // macos, ios, and android, default SQFlite 17 | } 18 | } 19 | 20 | static Future>> query(Database db, String sql) async { 21 | var ret = db.rawQuery(sql).onError((error, stackTrace) { 22 | return []; 23 | }); 24 | return ret; 25 | } 26 | 27 | static Future insert(Database db, String table, Map values) async { 28 | var ret = db.insert(table, values).onError((error, stackTrace) { 29 | return -1; 30 | } 31 | ); 32 | return ret; 33 | } 34 | 35 | static Future deleteAndInsertBatch(Database db, String table, List values) async { 36 | await db.transaction((txn) async { 37 | Batch batch = txn.batch(); 38 | batch.delete(table); 39 | for(dynamic v in values) { 40 | batch.insert(table, v.toMap()); 41 | } 42 | await batch.commit().onError((error, stackTrace) { 43 | return []; 44 | }); 45 | }); 46 | } 47 | } -------------------------------------------------------------------------------- /lib/destination/nav.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/destination/destination.dart'; 2 | import 'package:avaremp/geo_calculations.dart'; 3 | import 'package:latlong2/latlong.dart'; 4 | 5 | import '../gps.dart'; 6 | import '../storage.dart'; 7 | 8 | class Nav { 9 | static final GeoCalculations _geo = GeoCalculations(); 10 | static final Map _morseMap = { 11 | "A": ".-", 12 | "B": "-...", 13 | "C": "-.-.", 14 | "D": "-..", 15 | "E": ".", 16 | "F": "..-.", 17 | "G": "--.", 18 | "H": "....", 19 | "I": "..", 20 | "J": ".---", 21 | "K": "-.-", 22 | "L": ".-..", 23 | "M": "--", 24 | "N": "-.", 25 | "O": "---", 26 | "P": ".--.", 27 | "Q": "--.-", 28 | "R": ".-.", 29 | "S": "...", 30 | "T": "-", 31 | "U": "..-", 32 | "V": "...-", 33 | "W": ".--", 34 | "X": "-..-", 35 | "Y": "-.--", 36 | "Z": "--..", 37 | "0": "-----", 38 | "1": ".----", 39 | "2": "..---", 40 | "3": "...--", 41 | "4": "....-", 42 | "5": ".....", 43 | "6": "-....", 44 | "7": "--...", 45 | "8": "---..", 46 | "9": "----." 47 | }; 48 | 49 | static String? _getMorseCode(String character) { 50 | return _morseMap[character.toUpperCase()]; 51 | } 52 | 53 | static String? _getMorseCodeFromString(String text) { 54 | String result = ""; 55 | for (int i = 0; i < text.length; i++) { 56 | result += _getMorseCode(text[i]) ?? ""; 57 | } 58 | return result.replaceAll(".", "\u00B0"); 59 | } 60 | 61 | // From VOR map from database, get the VOR parameters and combine to make a string 62 | static const int columns = 4; 63 | static List getVorLine(NavDestination vor) { 64 | LatLng current = Gps.toLatLng(Storage().position); 65 | String morse = _getMorseCodeFromString(vor.locationID) ?? ""; 66 | String location = "${_geo.calculateDistance(current, vor.coordinate).round().toString().padLeft(3, "0")}" 67 | "/${GeoCalculations.getMagneticHeading(_geo.calculateBearing(current, vor.coordinate), Storage().area.variation).round().toString().padLeft(3, "0")}"; 68 | return ["${vor.locationID}(${vor.class_})", vor.facilityName, location, morse]; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/donate_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DonateScreen extends StatefulWidget { 4 | const DonateScreen({super.key}); 5 | @override 6 | DonateScreenState createState() => DonateScreenState(); 7 | } 8 | 9 | class DonateScreenState extends State { 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | appBar: AppBar( 15 | title: const Text('Donate'), 16 | ), 17 | body: Center( 18 | child: Column(children: [Container(padding: EdgeInsets.all(20), child:const Text("" 19 | "Hey there, high-flyer! This app runs on passion, caffeine, and the occasional miracle - but mostly on your support. " 20 | "If you’ve enjoyed soaring through our app, help us keep the engines running. Every donation matters.\n\n" 21 | "To support this app, please copy the following link and paste in your browser. Thank you!")), 22 | const SelectableText('apps4av.com/donate', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),]), 23 | ), 24 | ); 25 | } 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /lib/download_manager.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'chart.dart'; 5 | 6 | 7 | class DownloadManager { 8 | 9 | final List _charts = []; 10 | 11 | final ValueNotifier downloads = ValueNotifier(0); 12 | 13 | Chart? _findChart(Chart chart) { 14 | for (Chart c in _charts) { 15 | if (c.filename == chart.filename) { 16 | return c; 17 | } 18 | } 19 | return null; 20 | } 21 | 22 | void download(Chart chart, bool nextCycle, bool backupServer) { 23 | if (null != _findChart(chart)) { 24 | // already downloading 25 | return; 26 | } 27 | chart.enabled = false; 28 | chart.progress.value = 0; 29 | _charts.add(chart); 30 | chart.download.download(chart, nextCycle, backupServer, (c, progress) { 31 | Chart? c = _findChart(chart); 32 | if (null != c) { 33 | c.progress.value = progress; 34 | if (0 == progress) { 35 | c.subtitle = "Downloading"; 36 | c.enabled = false; 37 | } 38 | else if (50 == progress) { 39 | c.subtitle = "Installing"; 40 | c.enabled = false; 41 | } 42 | else if (100 == progress) { 43 | c.subtitle = "Download Success"; 44 | c.enabled = true; 45 | _charts.remove(c); 46 | downloads.value++; 47 | } 48 | else if (progress < 0) { 49 | c.subtitle = "Download Failed"; 50 | c.enabled = true; 51 | _charts.remove(c); 52 | } 53 | } 54 | }); 55 | } 56 | 57 | void cancel(Chart chart) { 58 | Chart? c = _findChart(chart); 59 | if(null != c) { 60 | c.download.cancel(); 61 | c.progress.value = 0; 62 | c.enabled = true; 63 | _charts.remove(c); 64 | } 65 | } 66 | 67 | void deleteSilent(Chart chart) { 68 | chart.download.delete(chart, null); 69 | } 70 | 71 | void delete(Chart chart) { 72 | if (null != _findChart(chart)) { 73 | // already deleting 74 | return; 75 | } 76 | chart.enabled = false; 77 | chart.progress.value = 0; 78 | _charts.add(chart); 79 | chart.download.delete(chart, (c, progress) { 80 | Chart? c = _findChart(chart); 81 | if (null != c) { 82 | c.progress.value = progress; 83 | if(0 == progress) { 84 | chart.subtitle = "Uninstalling"; 85 | chart.enabled = false; 86 | } 87 | else if(100 == progress) { 88 | chart.enabled = true; 89 | _charts.remove(c); 90 | downloads.value++; 91 | } 92 | else if(progress < 0) { 93 | chart.enabled = true; 94 | _charts.remove(c); 95 | } 96 | } 97 | }); 98 | } 99 | 100 | int total() { 101 | return _charts.length; 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /lib/flight_timer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | class FlightTimer { 5 | 6 | int _count = 0; 7 | final int _startCount; 8 | final bool _up; 9 | bool _started = false; 10 | bool _expired = false; 11 | 12 | FlightTimer(this._up, this._startCount, ValueNotifier timeChange) { 13 | if(_up) { 14 | _count = 0; 15 | } 16 | else { 17 | _count = _startCount; 18 | } 19 | timeChange.addListener(_timeListener); 20 | } 21 | 22 | void _timeListener() { 23 | if(_started) { 24 | if(_up) { 25 | _count++; 26 | } 27 | else { 28 | if(_count > 0) { 29 | _expired = false; 30 | _count--; 31 | } 32 | else { 33 | _started = false; 34 | _expired = true; 35 | } 36 | } 37 | } 38 | } 39 | 40 | void start() { 41 | _started = true; 42 | } 43 | 44 | void stop() { 45 | _started = false; 46 | } 47 | 48 | void reset() { 49 | _count = _startCount; 50 | } 51 | 52 | Duration getTime() { 53 | return Duration(seconds: _count); 54 | } 55 | 56 | bool isExpired() { 57 | return _expired; 58 | } 59 | 60 | bool isStarted() { 61 | return _started; 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /lib/gdl90/airmet_product.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/gdl90/product.dart'; 2 | 3 | class AirmetProduct extends Product { 4 | AirmetProduct(super.time, super.line, super.coordinate, super.productFileId, super.productFileLength, super.apduNumber, super.segFlag); 5 | 6 | } -------------------------------------------------------------------------------- /lib/gdl90/dlac.dart: -------------------------------------------------------------------------------- 1 | class Dlac { 2 | 3 | static final List _dlacCode = [ 4 | 0x03, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 5 | 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0X52, 0x53, 0x54, 0x55, 0x56, 0x57, 6 | 0x58, 0x59, 0x5A, 0x00, 0x09, 0x1e, 0x0a, 0x00, 0x20, 0x21, 0x22, 0x23, 7 | 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 8 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 9 | 0x3C, 0x3D, 0x3E, 0x3F 10 | ]; 11 | 12 | static String decode(int b1, int b2, int b3) { 13 | int holder = ((b1 & 0xFF) << 24) + ((b2 & 0xFF) << 16) + ((b3 & 0xFF) << 8); 14 | 15 | /* 16 | * 4 chars in 3 bytes 17 | */ 18 | int firstChar = _dlacCode[((holder & 0xFC000000) >> 26) & 0x3F]; 19 | int secondChar = _dlacCode[((holder & 0x03F00000) >> 20) & 0x3F]; 20 | int thirdChar = _dlacCode[((holder & 0x000FC000) >> 14) & 0x3F]; 21 | int fourthChar = _dlacCode[((holder & 0x00003F00) >> 8) & 0x3F]; 22 | 23 | String output = String.fromCharCodes([firstChar, secondChar, thirdChar, fourthChar]); 24 | return output; 25 | } 26 | 27 | static String format(String input) { 28 | if (input.isNotEmpty) { 29 | input = input.split("\u001E")[0]; 30 | input = input.replaceAll("\n\t[A-Z]{1}", "\n"); /* remove invalid chars after newline */ 31 | } 32 | return input; 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /lib/gdl90/fis_buffer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:avaremp/gdl90/product.dart'; 4 | import 'package:avaremp/gdl90/product_factory.dart'; 5 | import 'package:avaremp/storage.dart'; 6 | import 'package:flutter/foundation.dart'; 7 | import 'package:latlong2/latlong.dart'; 8 | 9 | class FisBuffer { 10 | 11 | Uint8List buffer; 12 | List products = []; 13 | FisBuffer(this.buffer, this.coordinate); 14 | LatLng? coordinate; 15 | 16 | //Parse products out of the Fis 17 | void makeProducts() { 18 | int size = buffer.lengthInBytes; 19 | int count = 0; 20 | while (count < (size - 1)) { 21 | 22 | int iFrameLength = ((buffer[count].toInt()) & 0xFF) << 1; 23 | iFrameLength += ((buffer[count + 1].toInt()) & 0x80) >> 7; 24 | 25 | if (0 == iFrameLength) { 26 | break; 27 | } 28 | 29 | int frameType = ((buffer[count + 1].toInt()) & 0x0F); 30 | 31 | //Bad frame, or reserved frame ! = 0 32 | if ((count + 2 + iFrameLength) > size || frameType != 0) { 33 | break; 34 | } 35 | 36 | Uint8List fis = buffer.sublist(count + 2, count + 2 + iFrameLength); 37 | 38 | try { 39 | Product? p = ProductFactory.buildProduct(fis, coordinate); 40 | if(p != null) { 41 | products.add(p); 42 | } 43 | } 44 | catch(e) { 45 | Storage().setException("Unable to parse ADS-B product: ${e.toString()}"); 46 | } 47 | 48 | count += iFrameLength + 2; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/gdl90/gdl90_buffer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | 4 | class Gdl90Buffer { 5 | 6 | static const _maxLength = 65536; 7 | final List _buffer = List.empty(growable: true); 8 | 9 | // not thread safe 10 | void put(Uint8List data) { 11 | if(_buffer.length > _maxLength) { 12 | _buffer.clear(); // this should not happen 13 | return; 14 | } 15 | _buffer.addAll(data); 16 | } 17 | 18 | Uint8List? get() { 19 | 20 | // find 0x7e to 0x7e 21 | // start 22 | int start = _buffer.indexWhere((element) => element == 0x7e); 23 | if(start == -1) { 24 | return null; 25 | } 26 | 27 | // skip all 7e that follow start 28 | while(_buffer[start] == 0x7e) { 29 | start++; 30 | if(start == _buffer.length) { 31 | return null; 32 | } 33 | } 34 | 35 | int end = _buffer.indexWhere((element) => element == 0x7e, start); 36 | if(end == -1) { 37 | return null; 38 | } 39 | 40 | Iterable range = _buffer.getRange(start, end); 41 | Uint8List data = Uint8List.fromList(range.toList()); 42 | 43 | 44 | // 0 means remove leading garbage if any 45 | _buffer.removeRange(0, end + 1); 46 | 47 | return data; 48 | } 49 | } -------------------------------------------------------------------------------- /lib/gdl90/message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | abstract class Message 4 | { 5 | int type; 6 | DateTime time = DateTime.now().toUtc(); 7 | 8 | Message(this.type); 9 | 10 | void parse(Uint8List message); 11 | } -------------------------------------------------------------------------------- /lib/gdl90/nexrad_high_product.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'nexrad_product.dart'; 3 | 4 | class NexradHighProduct extends NexradProduct { 5 | 6 | NexradHighProduct(super.time, super.data, super.coordinate, super.productFileId, super.productFileLength, super.apduNumber, super.segFlag); 7 | } -------------------------------------------------------------------------------- /lib/gdl90/nexrad_medium_product.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'nexrad_product.dart'; 3 | 4 | class NexradMediumProduct extends NexradProduct { 5 | 6 | NexradMediumProduct(super.time, super.data, super.coordinate, super.productFileId, super.productFileLength, super.apduNumber, super.segFlag); 7 | 8 | } -------------------------------------------------------------------------------- /lib/gdl90/notam_product.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/constants.dart'; 2 | import 'package:avaremp/gdl90/fis_graphics.dart'; 3 | import 'package:avaremp/gdl90/product.dart'; 4 | import 'package:avaremp/storage.dart' show Storage; 5 | import 'package:avaremp/weather/notam.dart'; 6 | import 'package:avaremp/weather/tfr.dart'; 7 | import 'package:avaremp/weather/weather.dart'; 8 | 9 | class NotamProduct extends Product { 10 | 11 | NotamProduct(super.time, super.line, super.coordinate, super.productFileId, super.productFileLength, super.apduNumber, super.segFlag); 12 | 13 | @override 14 | void parse() { 15 | super.parse(); 16 | if(fisGraphics.geometryOverlayOptions == FisGraphics.shapePolygonMSL && fisGraphics.coordinates.isNotEmpty) { 17 | // TFR 18 | DateTime endsDt; 19 | DateTime startsDt; 20 | try { 21 | endsDt = DateTime.parse(fisGraphics.endTime); 22 | } catch (e) { 23 | endsDt=DateTime(2099); // need end time 24 | } 25 | 26 | try { 27 | startsDt = DateTime.parse(fisGraphics.startTime); 28 | } catch (e) { 29 | startsDt=DateTime(2000); // need end time 30 | } 31 | 32 | Tfr t = Tfr("ADSB${fisGraphics.reportNumber}", 33 | endsDt, 34 | DateTime.now(), 35 | Weather.sourceADSB, 36 | fisGraphics.coordinates, 37 | fisGraphics.altitudeTop, 38 | fisGraphics.altitudeBottom, 39 | startsDt.millisecondsSinceEpoch, 40 | endsDt.millisecondsSinceEpoch, 41 | 0); 42 | Storage().tfr.put(t); 43 | } 44 | else if(fisGraphics.geometryOverlayOptions == FisGraphics.shapeNone) { 45 | List parts = fisGraphics.text.split(' '); 46 | if (parts.length > 1) { 47 | // put in the map so we can combine and retrieve, unique by id 48 | String id = parts[1]; 49 | // if notam does not exists, make it, otherwise add to already existing 50 | Notam? notam = Storage().notam.get(fisGraphics.location) as Notam?; 51 | if(notam == null || notam.source == Weather.sourceInternet) { 52 | // we have a non ADSB notam, or no notam overwrite 53 | notam = Notam(fisGraphics.location, DateTime.now().add( 54 | const Duration(minutes: Constants.weatherUpdateTimeMin)), 55 | DateTime.now(), Weather.sourceADSB, fisGraphics.text.replaceAll("\n", " ")); 56 | Storage().notam.put(notam); 57 | return; 58 | } 59 | notam.received = DateTime.now(); 60 | notam.expires = DateTime.now().add(const Duration(minutes: Constants.weatherUpdateTimeMin)); 61 | if(notam.text.contains(id)) { 62 | // throw away we already have it 63 | } 64 | else { 65 | notam.text += "\n${fisGraphics.text.replaceAll("\n", " ")}"; 66 | Storage().notam.put(notam); 67 | } 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /lib/gdl90/ownship_geometric_altitude_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:avaremp/storage.dart'; 3 | 4 | import 'message.dart'; 5 | 6 | class OwnShipGeometricAltitudeMessage extends Message { 7 | 8 | int altitude = -305; // 1000 ft; 9 | 10 | OwnShipGeometricAltitudeMessage(super.type); 11 | 12 | @override 13 | void parse(Uint8List message) { 14 | 15 | /* 16 | * bytes 0-1 are the altitude 17 | */ 18 | int upper = (message[0].toInt() & 0xFF) << 8; 19 | int lower = (message[1].toInt() & 0xFF); 20 | if (upper == 0xFF00 && lower >= 0xE0) { 21 | return;//invalid 22 | } 23 | else { 24 | double alt = upper.toDouble() + lower.toDouble(); 25 | alt *= 5; 26 | alt /= Storage().units.mToF; 27 | altitude = alt.round(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /lib/gdl90/ownship_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:avaremp/storage.dart'; 3 | import 'package:latlong2/latlong.dart'; 4 | 5 | import 'message.dart'; 6 | 7 | class OwnShipMessage extends Message { 8 | 9 | double altitude = -305; 10 | LatLng coordinates = const LatLng(0, 0); 11 | int icao = 0; 12 | double velocity = 0; 13 | double verticalSpeed = 0; 14 | double heading = 0; 15 | bool airborne = false; 16 | 17 | OwnShipMessage(super.type); 18 | 19 | @override 20 | void parse(Uint8List message) { 21 | 22 | icao = (((message[1]).toInt() & 0xFF) << 16) + ((((message[2].toInt()) & 0xFF) << 8)) + (((message[3].toInt()) & 0xFF)); 23 | double lat = calculateDegrees((message[4].toInt() & 0xFF), (message[5].toInt() & 0xFF), (message[6].toInt() & 0xFF)); 24 | double lon = calculateDegrees((message[7].toInt() & 0xFF), (message[8].toInt() & 0xFF), (message[9].toInt() & 0xFF)); 25 | coordinates = LatLng(lat, lon); 26 | 27 | int upper = ((message[10].toInt() & 0xFF)) << 4; 28 | int lower = ((message[11].toInt() & 0xF0)) >> 4; 29 | int alt = upper + lower; 30 | if (alt != 0xFFF) { 31 | alt *= 25; 32 | alt -= 1000; 33 | if (alt < -1000) { 34 | alt = -1000; 35 | } 36 | // meter 37 | altitude = alt.toDouble() * Storage().units.fToM; 38 | } 39 | 40 | airborne = (message[11] & 0x08) != 0; 41 | 42 | upper = ((message[13].toInt() & 0xFF)) << 4; 43 | lower = ((message[14].toInt() & 0xF0)) >> 4; 44 | 45 | if (upper == 0xFF0 && lower == 0xF) { 46 | } 47 | else { 48 | // m/s 49 | velocity = ((upper.toDouble() + lower.toDouble()) * 0.514444); 50 | } 51 | 52 | // vs 53 | if ((message[14].toInt() & 0x08) == 0) { 54 | verticalSpeed = ((message[14].toInt() & 0x0F) << 14) + ((message[15].toInt() & 0xFF) << 6).toDouble(); 55 | } 56 | else if (message[15].toInt() == 0) { 57 | verticalSpeed = 2000000000; 58 | } 59 | else { 60 | verticalSpeed = (((message[14].toInt() & 0x0F) << 14) + ((message[15].toInt() & 0xFF) << 6) - 0x40000).toDouble(); 61 | } 62 | 63 | // heading / track 64 | heading = ((message[16].toInt() & 0xFF).toDouble() * 1.40625); // heading resolution 1.40625 65 | } 66 | 67 | static double calculateDegrees(int highByte, int midByte, int lowByte) { 68 | int position = 0; 69 | double xx; 70 | 71 | position = highByte; 72 | position <<= 8; 73 | position |= midByte; 74 | position <<= 8; 75 | position |= lowByte; 76 | position &= 0xFFFFFFFF; 77 | 78 | if ((position & 0x800000) != 0) { 79 | int yy; 80 | 81 | position |= 0xFFFFFFFFFF000000; //ints are 64 bit in dart 82 | 83 | yy = position; 84 | xx = yy.toDouble(); 85 | } 86 | else { 87 | xx = (position & 0x7FFFFF).toDouble(); 88 | } 89 | 90 | xx *= 2.1457672E-5; // low lat resolution 91 | 92 | return xx; 93 | } 94 | } -------------------------------------------------------------------------------- /lib/gdl90/product.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:latlong2/latlong.dart'; 4 | 5 | import 'fis_graphics.dart'; 6 | 7 | class Product { 8 | 9 | final DateTime time; 10 | Uint8List data; 11 | final LatLng? coordinate; 12 | FisGraphics fisGraphics = FisGraphics(); 13 | final int productFileId; 14 | final int productFileLength; 15 | final int apduNumber; 16 | final bool segFlag; 17 | 18 | Product(this.time, this.data, this.coordinate, this.productFileId, this.productFileLength, this.apduNumber, this.segFlag); 19 | 20 | void parse() { 21 | fisGraphics.decode(data); 22 | } 23 | } -------------------------------------------------------------------------------- /lib/gdl90/sigmet_product.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/gdl90/product.dart'; 2 | 3 | class SigmetProduct extends Product { 4 | SigmetProduct(super.time, super.line, super.coordinate, super.productFileId, super.productFileLength, super.apduNumber, super.segFlag); 5 | 6 | } -------------------------------------------------------------------------------- /lib/gdl90/sua_product.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/gdl90/product.dart'; 2 | 3 | class SuaProduct extends Product { 4 | SuaProduct(super.time, super.line, super.coordinate, super.productFileId, super.productFileLength, super.apduNumber, super.segFlag); 5 | 6 | } -------------------------------------------------------------------------------- /lib/gdl90/uplink_message.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:latlong2/latlong.dart'; 4 | 5 | import 'fis_buffer.dart'; 6 | import 'message.dart'; 7 | 8 | class UplinkMessage extends Message { 9 | 10 | FisBuffer? fis; 11 | 12 | UplinkMessage(super.type); 13 | 14 | @override 15 | void parse(Uint8List message) { 16 | /* 17 | * First 3 bytes are Zulu time, 18 | * Next 8 is UAT header 19 | * Rest of 424 is payload 20 | * 21 | */ 22 | int skip = 3; 23 | int lat = 0; 24 | lat += (message[skip + 0].toInt()) & 0xFF; 25 | lat <<= 8; 26 | lat += (message[skip + 1].toInt()) & 0xFF; 27 | lat <<= 8; 28 | lat += (message[skip + 2].toInt()) & 0xFE; 29 | lat >>= 1; 30 | 31 | bool isSouth = (lat & 0x800000) != 0; 32 | double degLat = lat.toDouble() * 2.1457672E-5; 33 | if (isSouth) { 34 | degLat *= -1; 35 | } 36 | 37 | int lon = 0; 38 | lon += (message[skip + 3].toInt()) & 0xFF; 39 | lon <<= 8; 40 | lon += (message[skip + 4].toInt()) & 0xFF; 41 | lon <<= 8; 42 | lon += (message[skip + 5].toInt()) & 0xFE; 43 | lon >>= 1; 44 | if ((message[skip + 2].toInt() & 0x01) != 0) { 45 | lon += 0x800000; 46 | } 47 | 48 | bool isWest = (lon & 0x800000) != 0; 49 | double degLon = (lon & 0x7fffff) * 2.1457672E-5; 50 | if (isWest) { 51 | degLon = -1 * (180 - degLon); 52 | } 53 | 54 | bool applicationDataValid = (message[skip + 6].toInt() & 0x20) != 0; 55 | if (false == applicationDataValid) { 56 | return; 57 | } 58 | 59 | // byte 9-432: application data (multiple iFrames). 60 | skip = 3 + 8; 61 | 62 | Uint8List data = message.sublist(skip); 63 | FisBuffer fisBuffer = FisBuffer(data, LatLng(degLat, degLon)); // this product does not happen at a location? 64 | 65 | //Now decode all. 66 | fisBuffer.makeProducts(); 67 | fis = fisBuffer; 68 | } 69 | } -------------------------------------------------------------------------------- /lib/image_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui' as ui; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | 5 | class ImageUtils { 6 | static Future loadImageFromAssets(String imageName) async { 7 | final data = await rootBundle.load('assets/images/$imageName'); 8 | return decodeImageFromList(data.buffer.asUint8List()); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/donate_screen.dart'; 2 | import 'package:avaremp/logbook_screen.dart'; 3 | import 'package:avaremp/longpress_screen.dart'; 4 | import 'package:avaremp/plan/plan_action_screen.dart'; 5 | import 'package:avaremp/storage.dart'; 6 | import 'package:avaremp/wnb_screen.dart'; 7 | import 'package:avaremp/writing_screen.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'aircraft_screen.dart'; 10 | import 'checklist_screen.dart'; 11 | import 'constants.dart'; 12 | import 'destination/destination.dart'; 13 | import 'documents_screen.dart'; 14 | import 'download_screen.dart'; 15 | import 'io_screen.dart'; 16 | import 'main_screen.dart'; 17 | import 'onboarding_screen.dart'; 18 | 19 | class CustomWidgetsBinding extends WidgetsFlutterBinding { 20 | @override 21 | ImageCache createImageCache() => Storage().imageCache; 22 | } 23 | 24 | void main() { 25 | // this is to control cache. Nexrad needs it or image caching will make it impossible to animate weather 26 | CustomWidgetsBinding(); 27 | 28 | Storage().init().then((accentColor) { 29 | runApp(const MainApp()); 30 | }); 31 | 32 | } 33 | 34 | class MainApp extends StatelessWidget { 35 | const MainApp({super.key}); 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return ValueListenableBuilder( 40 | valueListenable: Storage().themeNotifier, 41 | builder: (context, value, child) { 42 | return SafeArea(child: MaterialApp( 43 | debugShowCheckedModeBanner: false, 44 | initialRoute: '/', 45 | routes: { 46 | '/': (context) => 47 | Storage().settings.showIntro() 48 | ? const OnBoardingScreen() 49 | : const MainScreen(), 50 | '/download': (context) => const DownloadScreen(), 51 | '/documents': (context) => const DocumentsScreen(), 52 | '/aircraft': (context) => const AircraftScreen(), 53 | '/checklists': (context) => const ChecklistScreen(), 54 | '/wnb': (context) => const WnbScreen(), 55 | '/logbook': (context) => const LogbookScreen(), 56 | '/donate': (context) => const DonateScreen(), 57 | if(Constants.shouldShowBluetoothSpp) '/io': (context) => const IoScreen(), 58 | '/notes': (context) => const WritingScreen(), 59 | '/plan_actions': (context) => const PlanActionScreen(), 60 | '/popup': (context) { 61 | final args = ModalRoute.of(context)!.settings.arguments as List; 62 | return LongPressScreen(destinations: args); 63 | } 64 | }, 65 | theme: value, 66 | )); // Safe Area so things from OS do not get in the way 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/nmea/bod_packet.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/nmea/packet.dart'; 2 | 3 | class BODPacket extends Packet { 4 | BODPacket(String idDest, String idStart, double bearingTrue, double bearingMag) { 5 | packet = '\$GPBOD,'; 6 | 7 | // Put bearingTrue 8 | packet += '${bearingTrue.toStringAsFixed(1).padLeft(5, '0')},T,'; 9 | 10 | // Put bearingMag 11 | packet += '${bearingMag.toStringAsFixed(1).padLeft(5, '0')},M,'; 12 | 13 | // Destination 14 | packet += '$idDest,'; 15 | 16 | // Start 17 | packet += idStart; 18 | 19 | assemble(); 20 | } 21 | } -------------------------------------------------------------------------------- /lib/nmea/gga_message.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/app_log.dart'; 2 | import 'package:avaremp/nmea/nmea_message.dart'; 3 | import 'package:avaremp/storage.dart'; 4 | import 'package:latlong2/latlong.dart'; 5 | 6 | class GGAMessage extends NmeaMessage { 7 | GGAMessage(super.type); 8 | 9 | LatLng coordinate = const LatLng(0, 0); 10 | int altitude = -100; 11 | 12 | @override 13 | void parse(String data) { 14 | List tokens = data.split(","); 15 | 16 | if (tokens.length < 11) { 17 | return; 18 | } 19 | 20 | double tmp; 21 | double tmp1; 22 | try { 23 | tmp = double.parse(tokens[2]); 24 | tmp1 = (tmp.toInt() ~/ 100).toDouble(); 25 | double lat = (tmp - (tmp1 * 100.0)) / 60 + tmp1; 26 | if (tokens[3] == "S") { 27 | lat = -lat; 28 | } 29 | 30 | tmp = double.parse(tokens[4]); 31 | tmp1 = (tmp.toInt() ~/ 100).toDouble(); 32 | double lon = (tmp - (tmp1 * 100.0)) / 60 + tmp1; 33 | if (tokens[5] == "W") { 34 | lon = -lon; 35 | } 36 | 37 | coordinate = LatLng(lat, lon); 38 | 39 | double alt = double.parse(tokens[9]); 40 | if(tokens[10] != "M") { 41 | altitude = (alt * Storage().units.mToF).round(); 42 | } 43 | else { 44 | altitude = alt.round(); 45 | } 46 | } 47 | catch (e) { 48 | AppLog.logMessage("GGAMessage: invalid data $data"); 49 | } 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /lib/nmea/gga_packet.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/nmea/packet.dart'; 2 | import 'package:intl/intl.dart'; 3 | 4 | class GGAPacket extends Packet { 5 | static const String _tag = '\$GPGGA'; 6 | 7 | GGAPacket(int time, double latitude, double longitude, double altitude, int satCount, double geoid, double horDil) { 8 | packet = '$_tag,'; 9 | 10 | // Convert to UTC system time, and format to hhmmss as in NMEA 11 | DateTime date = DateTime.fromMillisecondsSinceEpoch(time, isUtc: true); 12 | DateFormat sdf = DateFormat('HHmmss'); 13 | packet += '${sdf.format(date)},'; 14 | 15 | // Put latitude 16 | if (latitude > 0) { 17 | int lat = latitude.toInt(); 18 | double deg = (latitude - lat) * 60.0; 19 | 20 | packet += lat.toString().padLeft(2, '0'); 21 | packet += '${deg.toStringAsFixed(3).padLeft(6, '0')},N,'; 22 | } else { 23 | latitude = -latitude; 24 | int lat = latitude.toInt(); 25 | double deg = (latitude - lat) * 60.0; 26 | 27 | packet += lat.toString().padLeft(2, '0'); 28 | packet += '${deg.toStringAsFixed(3).padLeft(6, '0')},S,'; 29 | } 30 | 31 | // Put longitude 32 | if (longitude > 0) { 33 | int lon = longitude.toInt(); 34 | double deg = (longitude - lon) * 60.0; 35 | 36 | packet += lon.toString().padLeft(3, '0'); 37 | packet += '${deg.toStringAsFixed(3).padLeft(6, '0')},E,'; 38 | } else { 39 | longitude = -longitude; 40 | int lon = longitude.toInt(); 41 | double deg = (longitude - lon) * 60.0; 42 | 43 | packet += lon.toString().padLeft(3, '0'); 44 | packet += '${deg.toStringAsFixed(3).padLeft(6, '0')},W,'; 45 | } 46 | 47 | // A true GPS fix 48 | packet += '1,'; 49 | 50 | // How many satellites used in this fix. 51 | packet += '${satCount.toString().padLeft(2, '0')},'; 52 | 53 | // Horizontal dilution 54 | packet += '${horDil.toStringAsFixed(1)},'; 55 | 56 | // Put altitude in METERS 57 | packet += '${altitude.toStringAsFixed(1)},M,'; 58 | 59 | // GEOID and a couple of empty fields 60 | packet += '${geoid.toStringAsFixed(1)},M,,'; 61 | 62 | assemble(); 63 | } 64 | } -------------------------------------------------------------------------------- /lib/nmea/nmea_buffer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | 4 | class NmeaBuffer { 5 | 6 | static const _maxLength = 65536; 7 | final List _buffer = List.empty(growable: true); 8 | 9 | // not thread safe 10 | void put(Uint8List data) { 11 | if(_buffer.length > _maxLength) { 12 | _buffer.clear(); // this should not happen 13 | return; 14 | } 15 | _buffer.addAll(data); 16 | } 17 | 18 | Uint8List? get() { 19 | 20 | // find $ to LF 21 | // start 22 | int start = _buffer.indexWhere((element) => element == 0x24); 23 | if(start == -1) { 24 | return null; 25 | } 26 | 27 | // skip all $ that follow start 28 | while(_buffer[start] == 0x24) { 29 | start++; 30 | if(start == _buffer.length) { 31 | return null; 32 | } 33 | } 34 | start--; //keep $ 35 | 36 | int end = _buffer.indexWhere((element) => element == 0x0a, start); 37 | if(end == -1) { 38 | return null; 39 | } 40 | 41 | Iterable range = _buffer.getRange(start, end + 1); 42 | Uint8List data = Uint8List.fromList(range.toList()); 43 | 44 | // 0 means remove leading garbage if any 45 | _buffer.removeRange(0, end + 1); 46 | 47 | return data; 48 | } 49 | } -------------------------------------------------------------------------------- /lib/nmea/nmea_message.dart: -------------------------------------------------------------------------------- 1 | 2 | class NmeaMessage { 3 | 4 | String type; 5 | DateTime time; 6 | 7 | NmeaMessage(this.type) : time = DateTime.now(); 8 | 9 | void parse(String data) { 10 | 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /lib/nmea/nmea_ownship_message.dart: -------------------------------------------------------------------------------- 1 | // A class that combines RMC and GGA 2 | 3 | import 'package:avaremp/nmea/gga_message.dart'; 4 | import 'package:avaremp/nmea/nmea_message.dart'; 5 | import 'package:avaremp/nmea/rmc_message.dart'; 6 | import 'package:avaremp/nmea/rtm_message.dart'; 7 | import 'package:latlong2/latlong.dart'; 8 | 9 | import 'nmea_message_factory.dart'; 10 | 11 | class NmeaOwnShipMessage extends NmeaMessage { 12 | 13 | double altitude = -100; 14 | LatLng coordinates = const LatLng(0, 0); 15 | int icao = 0; 16 | double velocity = 0; 17 | double verticalSpeed = 0; 18 | double heading = 0; 19 | 20 | NmeaOwnShipMessage(super.type); 21 | 22 | factory NmeaOwnShipMessage.fromGgaRmc(GGAMessage? gga, RMCMessage? rmc) { 23 | NmeaOwnShipMessage m = NmeaOwnShipMessage(NmeaMessageType.ownShip); 24 | if(gga == null && rmc == null) { 25 | // bug 26 | } 27 | else if(gga == null && rmc != null) { 28 | m.velocity = rmc.speed.toDouble(); 29 | m.heading = rmc.track.toDouble(); 30 | m.coordinates = rmc.coordinate; 31 | } 32 | else if(gga != null && rmc == null) { 33 | m.coordinates = gga.coordinate; 34 | m.altitude = gga.altitude.toDouble(); 35 | } 36 | else if(gga != null && rmc != null) { 37 | Duration diff = gga.time.difference(rmc.time); 38 | if (diff.inMilliseconds > 0) { 39 | // GGA is newer 40 | m.coordinates = gga.coordinate; 41 | } 42 | else { 43 | m.coordinates = rmc.coordinate; 44 | } 45 | m.altitude = gga.altitude.toDouble(); 46 | m.velocity = rmc.speed.toDouble(); 47 | m.heading = rmc.track.toDouble(); 48 | } 49 | return (m); 50 | } 51 | 52 | factory NmeaOwnShipMessage.fromRtm(RTMMessage rtm) { 53 | NmeaOwnShipMessage m = NmeaOwnShipMessage(NmeaMessageType.ownShip); 54 | m.coordinates = rtm.coordinate; 55 | m.altitude = rtm.altitude.toDouble(); 56 | m.velocity = rtm.speed.toDouble(); 57 | m.heading = rtm.track.toDouble(); 58 | m.icao = rtm.icao; 59 | return (m); 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /lib/nmea/packet.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class Packet { 4 | String packet = ''; 5 | 6 | void assemble() { 7 | // Checksum 8 | packet += '*'; 9 | 10 | int xor = _checksum(utf8.encode(packet)); 11 | String ma = xor.toRadixString(16).toUpperCase(); 12 | if (ma.length < 2) { 13 | packet += '0'; 14 | } 15 | packet += ma; 16 | packet += '\r\n'; 17 | } 18 | 19 | String getPacket() { 20 | return packet; 21 | } 22 | 23 | int _checksum(List bytes) { 24 | int xor = 0; 25 | for (int byte in bytes) { 26 | xor ^= byte; 27 | } 28 | return xor; 29 | } 30 | } -------------------------------------------------------------------------------- /lib/nmea/rmb_packet.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/nmea/packet.dart'; 2 | 3 | class RMBPacket extends Packet { 4 | RMBPacket(double distance, double bearing, double longitude, double latitude, String idNext, String idOrig, double deviation, double speed, bool planComplete) { 5 | packet = '\$GPRMB,'; 6 | 7 | // valid 8 | packet += 'A,'; 9 | 10 | // deviation 11 | String dir = 'L'; 12 | if (deviation < 0) { 13 | dir = 'R'; 14 | deviation = -deviation; 15 | } 16 | if (deviation > 9.99) { 17 | deviation = 9.99; 18 | } 19 | packet += '${deviation.toStringAsFixed(2).padLeft(4, '0')},'; 20 | packet += '$dir,'; 21 | 22 | packet += '$idOrig,'; 23 | packet += '$idNext,'; 24 | 25 | // Put latitude 26 | if (latitude > 0) { 27 | int lat = latitude.toInt(); 28 | double deg = (latitude - lat) * 60.0; 29 | 30 | packet += lat.toString().padLeft(2, '0'); 31 | packet += '${deg.toStringAsFixed(3).padLeft(6, '0')},N,'; 32 | } else { 33 | latitude = -latitude; 34 | int lat = latitude.toInt(); 35 | double deg = (latitude - lat) * 60.0; 36 | 37 | packet += lat.toString().padLeft(2, '0'); 38 | packet += '${deg.toStringAsFixed(3).padLeft(6, '0')},S,'; 39 | } 40 | 41 | // Put longitude 42 | if (longitude > 0) { 43 | int lon = longitude.toInt(); 44 | double deg = (longitude - lon) * 60.0; 45 | 46 | packet += lon.toString().padLeft(3, '0'); 47 | packet += '${deg.toStringAsFixed(3).padLeft(6, '0')},E,'; 48 | } else { 49 | longitude = -longitude; 50 | int lon = longitude.toInt(); 51 | double deg = (longitude - lon) * 60.0; 52 | 53 | packet += lon.toString().padLeft(3, '0'); 54 | packet += '${deg.toStringAsFixed(3).padLeft(6, '0')},W,'; 55 | } 56 | 57 | // Put range 58 | if (distance >= 1000) { 59 | distance = 999.9; 60 | } 61 | packet += '${distance.toStringAsFixed(1).padLeft(5, '0')},'; 62 | 63 | // Put bearing 64 | packet += '${bearing.toStringAsFixed(1).padLeft(5, '0')},'; 65 | 66 | // Put speed 67 | packet += '${speed.toStringAsFixed(1).padLeft(5, '0')},'; 68 | 69 | // Final item is whether or not we have arrived at our final destination 70 | packet += planComplete ? 'A' : 'V'; 71 | 72 | assemble(); 73 | } 74 | } -------------------------------------------------------------------------------- /lib/nmea/rmc_message.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/app_log.dart'; 2 | import 'package:avaremp/nmea/nmea_message.dart'; 3 | import 'package:avaremp/storage.dart'; 4 | import 'package:latlong2/latlong.dart'; 5 | 6 | class RMCMessage extends NmeaMessage { 7 | RMCMessage(super.type); 8 | 9 | LatLng coordinate = const LatLng(0, 0); 10 | int speed = 0; 11 | int track = 0; 12 | 13 | @override 14 | void parse(String data) { 15 | List tokens = data.split(","); 16 | 17 | if (tokens.length < 9) { 18 | return; 19 | } 20 | 21 | double tmp; 22 | double tmp1; 23 | try { 24 | tmp = double.parse(tokens[3]); 25 | tmp1 = (tmp.toInt() ~/ 100).toDouble(); 26 | double lat = (tmp - (tmp1 * 100.0)) / 60 + tmp1; 27 | if (tokens[4] == "S") { 28 | lat = -lat; 29 | } 30 | 31 | tmp = double.parse(tokens[5]); 32 | tmp1 = (tmp.toInt() ~/ 100).toDouble(); 33 | double lon = (tmp - (tmp1 * 100.0)) / 60 + tmp1; 34 | if (tokens[6] == "W") { 35 | lon = -lon; 36 | } 37 | 38 | coordinate = LatLng(lat, lon); 39 | 40 | speed = (double.parse(tokens[7]) * Storage().units.toMps).round(); 41 | track = double.parse(tokens[8]).round(); 42 | 43 | } 44 | catch (e) { 45 | AppLog.logMessage("RMCMessage: invalid data $data"); 46 | } 47 | 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /lib/nmea/rmc_packet.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/nmea/packet.dart'; 2 | import 'package:intl/intl.dart'; 3 | 4 | class RMCPacket extends Packet { 5 | 6 | RMCPacket(int time, double latitude, double longitude, double speed, double bearing, double dec) { 7 | packet = "\$GPRMC,"; 8 | 9 | // Convert to UTC system time, and format to hhmmss as in NMEA 10 | DateTime date = DateTime.fromMillisecondsSinceEpoch(time, isUtc: true); 11 | DateFormat sdf = DateFormat("HHmmss"); 12 | packet += "${sdf.format(date)},"; 13 | 14 | packet += "A,"; 15 | 16 | // Put latitude 17 | if (latitude > 0) { 18 | int lat = latitude.toInt(); 19 | double deg = (latitude - lat) * 60.0; 20 | 21 | packet += lat.toString().padLeft(2, '0'); 22 | packet += "${deg.toStringAsFixed(3).padLeft(6, '0')},N,"; 23 | } else { 24 | latitude = -latitude; 25 | int lat = latitude.toInt(); 26 | double deg = (latitude - lat) * 60.0; 27 | 28 | packet += lat.toString().padLeft(2, '0'); 29 | packet += "${deg.toStringAsFixed(3).padLeft(6, '0')},S,"; 30 | } 31 | 32 | // Put longitude 33 | if (longitude > 0) { 34 | int lon = longitude.toInt(); 35 | double deg = (longitude - lon) * 60.0; 36 | 37 | packet += lon.toString().padLeft(3, '0'); 38 | packet += "${deg.toStringAsFixed(3).padLeft(6, '0')},E,"; 39 | } else { 40 | longitude = -longitude; 41 | int lon = longitude.toInt(); 42 | double deg = (longitude - lon) * 60.0; 43 | 44 | packet += lon.toString().padLeft(3, '0'); 45 | packet += "${deg.toStringAsFixed(3).padLeft(6, '0')},W,"; 46 | } 47 | 48 | // Put speed knots 49 | packet += "${speed.toStringAsFixed(1).padLeft(5, '0')},"; 50 | 51 | // Put bearing 52 | packet += "${bearing.toStringAsFixed(1).padLeft(5, '0')},"; 53 | 54 | // Put date 55 | sdf = DateFormat("ddMMyy"); 56 | packet += "${sdf.format(date)},"; 57 | 58 | // Put variation 59 | if (dec < 0) { 60 | dec = -dec; 61 | packet += "${dec.toStringAsFixed(1).padLeft(5, '0')},E"; 62 | } else { 63 | packet += "${dec.toStringAsFixed(1).padLeft(5, '0')},W"; 64 | } 65 | 66 | assemble(); 67 | } 68 | } -------------------------------------------------------------------------------- /lib/nmea/rtm_message.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/app_log.dart'; 2 | import 'package:avaremp/nmea/nmea_message.dart'; 3 | import 'package:avaremp/storage.dart'; 4 | import 'package:latlong2/latlong.dart'; 5 | 6 | class RTMMessage extends NmeaMessage { 7 | RTMMessage(super.type); 8 | 9 | int icao = 0; 10 | LatLng coordinate = const LatLng(0, 0); 11 | int altitude = -100; 12 | int speed = 0; 13 | int track = 0; 14 | 15 | @override 16 | void parse(String data) { 17 | 18 | List tokens = data.split(","); 19 | 20 | if (tokens.length < 10) { 21 | return; 22 | } 23 | 24 | double tmp; 25 | double tmp1; 26 | try { 27 | 28 | icao = int.parse(tokens[2]); 29 | 30 | tmp = double.parse(tokens[3]); 31 | tmp1 = (tmp.toInt() ~/ 100).toDouble(); 32 | double lat = (tmp - (tmp1 * 100.0)) / 60 + tmp1; 33 | if (tokens[4] == "S") { 34 | lat = -lat; 35 | } 36 | 37 | tmp = double.parse(tokens[5]); 38 | tmp1 = (tmp.toInt() ~/ 100).toDouble(); 39 | double lon = (tmp - (tmp1 * 100.0)) / 60 + tmp1; 40 | if (tokens[6] == "W") { 41 | lon = -lon; 42 | } 43 | 44 | coordinate = LatLng(lat, lon); 45 | 46 | altitude = double.parse(tokens[7]).round(); 47 | track = double.parse(tokens[8]).round(); 48 | speed = (double.parse(tokens[9]) * Storage().units.toMps).round(); 49 | 50 | } 51 | catch (e) { 52 | AppLog.logMessage("RTMMessage: invalid data $data"); 53 | } 54 | 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /lib/nmea/rtm_packet.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/nmea/packet.dart'; 2 | import 'package:intl/intl.dart'; 3 | 4 | class RTMPacket extends Packet { 5 | RTMPacket( 6 | int time, 7 | int icaoAddress, 8 | double latitude, 9 | double longitude, 10 | int altitude, 11 | int horizVelocity, 12 | int vertVelocity, 13 | double heading, 14 | String callSign, 15 | ) { 16 | packet = '\$GPRTM,'; 17 | 18 | // Convert to UTC system time, and format to hhmmss as in NMEA 19 | DateTime date = DateTime.fromMillisecondsSinceEpoch(time, isUtc: true); 20 | DateFormat sdf = DateFormat('HHmmss'); 21 | packet += '${sdf.format(date)},'; 22 | 23 | // icao 24 | packet += '$icaoAddress,'; 25 | 26 | // Put latitude 27 | if (latitude > 0) { 28 | int lat = latitude.toInt(); 29 | double deg = (latitude - lat) * 60.0; 30 | 31 | packet += lat.toString().padLeft(2, '0'); 32 | packet += '${deg.toStringAsFixed(3).padLeft(6, '0')},N,'; 33 | } else { 34 | latitude = -latitude; 35 | int lat = latitude.toInt(); 36 | double deg = (latitude - lat) * 60.0; 37 | 38 | packet += lat.toString().padLeft(2, '0'); 39 | packet += '${deg.toStringAsFixed(3).padLeft(6, '0')},S,'; 40 | } 41 | 42 | // Put longitude 43 | if (longitude > 0) { 44 | int lon = longitude.toInt(); 45 | double deg = (longitude - lon) * 60.0; 46 | 47 | packet += lon.toString().padLeft(3, '0'); 48 | packet += '${deg.toStringAsFixed(3).padLeft(6, '0')},E,'; 49 | } else { 50 | longitude = -longitude; 51 | int lon = longitude.toInt(); 52 | double deg = (longitude - lon) * 60.0; 53 | 54 | packet += lon.toString().padLeft(3, '0'); 55 | packet += '${deg.toStringAsFixed(3).padLeft(6, '0')},W,'; 56 | } 57 | 58 | // Put altitude 59 | packet += '${altitude.toStringAsFixed(1)},'; 60 | 61 | // Put heading 62 | packet += '${heading.toStringAsFixed(1).padLeft(5, '0')},'; 63 | 64 | // Put speed in knots 65 | packet += '${horizVelocity.toStringAsFixed(1).padLeft(5, '0')},'; 66 | 67 | // Put vert velocity in ft/min 68 | packet += '${vertVelocity.toStringAsFixed(1).padLeft(5, '0')},'; 69 | 70 | // Put callsign 71 | packet += '$callSign,'; 72 | 73 | assemble(); 74 | } 75 | } -------------------------------------------------------------------------------- /lib/plan/passage.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/geo_calculations.dart'; 2 | import 'package:latlong2/latlong.dart'; 3 | 4 | class Passage { 5 | 6 | double? _lastDistance; 7 | double? _currentDistance; 8 | final LatLng _nextDestinationCoordinates; 9 | 10 | Passage(this._nextDestinationCoordinates); 11 | 12 | static const double _passageDistanceMin = 2; // must pass within this nm distance to pass 13 | 14 | 15 | // call this on every GPS update 16 | bool update(LatLng currentCoordinates) { 17 | 18 | GeoCalculations geo = GeoCalculations(); 19 | 20 | if (_lastDistance == null) { 21 | _lastDistance = geo.calculateDistance(currentCoordinates, _nextDestinationCoordinates); 22 | //Init on first input on location 23 | return false; 24 | } 25 | 26 | _currentDistance = geo.calculateDistance(currentCoordinates, _nextDestinationCoordinates); 27 | 28 | bool ret; 29 | 30 | if (_currentDistance! > _lastDistance!) { 31 | // We are in passage zone, when exit, we have passed 32 | if (_currentDistance! < _passageDistanceMin) { 33 | ret = true; 34 | } 35 | else { 36 | ret = false; 37 | } 38 | } 39 | else { 40 | ret = false; 41 | } 42 | 43 | _lastDistance = _currentDistance; 44 | return ret; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/plan/plan_action_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/plan/plan_create_widget.dart'; 2 | import 'package:avaremp/plan/plan_file_widget.dart'; 3 | import 'package:avaremp/plan/plan_load_save_widget.dart'; 4 | import 'package:avaremp/plan/plan_manage_widget.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class PlanActionScreen extends StatefulWidget { 8 | const PlanActionScreen({super.key}); 9 | @override 10 | State createState() => PlanActionState(); 11 | } 12 | 13 | class PlanActionState extends State { 14 | 15 | int _current = 0; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | } 21 | 22 | @override 23 | void dispose() { 24 | super.dispose(); 25 | } 26 | 27 | Widget _makeContent() { 28 | 29 | Widget loadSavePage = const PlanLoadSaveWidget(); 30 | 31 | Widget createPage = const PlanCreateWidget(); 32 | 33 | Widget filePage = const PlanFileWidget(); 34 | 35 | Widget managePage = const PlanManageWidget(); 36 | 37 | List pages = []; 38 | pages.add(loadSavePage); 39 | pages.add(createPage); 40 | pages.add(filePage); 41 | pages.add(managePage); 42 | 43 | return Container( 44 | padding: const EdgeInsets.all(5), 45 | decoration: const BoxDecoration( 46 | borderRadius: BorderRadius.only( 47 | topLeft: Radius.circular(10), 48 | topRight: Radius.circular(10), 49 | ), 50 | ), 51 | child: 52 | Column(children: [ 53 | // various info 54 | Expanded(flex: 8, child: Padding(padding: const EdgeInsets.all(10), child: pages[_current])), 55 | // add various buttons that expand to diagram 56 | Expanded(flex: 1, child: SingleChildScrollView(scrollDirection: Axis.horizontal, child: Row(mainAxisAlignment: MainAxisAlignment.end, children:[ 57 | TextButton( 58 | child: const Text("Load & Save"), 59 | onPressed: () => setState(() { 60 | _current = 0; 61 | }) 62 | ), 63 | TextButton( 64 | child: const Text("Create"), 65 | onPressed: () => setState(() { 66 | _current = 1; 67 | }) 68 | ), 69 | TextButton( 70 | child: const Text("Brief & File"), 71 | onPressed: () => setState(() { 72 | _current = 2; 73 | }) 74 | ), 75 | TextButton( 76 | child: const Text("Manage"), 77 | onPressed: () => setState(() { 78 | _current = 3; 79 | }) 80 | ), 81 | ])), 82 | ), 83 | ], 84 | ) 85 | ); 86 | } 87 | 88 | 89 | @override 90 | Widget build(BuildContext context) { 91 | return Scaffold( 92 | appBar: AppBar( 93 | title: const Text("Plan Actions"), 94 | ), 95 | body: _makeContent()); 96 | } 97 | } -------------------------------------------------------------------------------- /lib/plan/plan_line_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/destination/destination_calculations.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:avaremp/constants.dart'; 5 | import 'package:avaremp/destination/destination.dart'; 6 | 7 | class PlanLineWidget extends StatefulWidget { 8 | 9 | final Destination destination; 10 | 11 | const PlanLineWidget({super.key, required this.destination, }); 12 | 13 | @override 14 | State createState() => PlanLineWidgetState(); 15 | } 16 | 17 | class PlanLineWidgetState extends State { 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Column( 22 | children: [ 23 | _getFields() 24 | ] 25 | ); 26 | } 27 | 28 | static Widget getHeading() { 29 | return const Row(children: [ 30 | Expanded(flex: 1, child: Text("Dist")), 31 | Expanded(flex: 1, child: Text("GSpd")), 32 | Expanded(flex: 1, child: Text("Crs")), 33 | Expanded(flex: 1, child: Text("Time")), 34 | Expanded(flex: 1, child: Text("Fuel")), 35 | ]); 36 | } 37 | 38 | static Widget getNullFields() { 39 | return const Row(children: [ 40 | Expanded(flex: 1, child: Text("---")), 41 | Expanded(flex: 1, child: Text("---")), 42 | Expanded(flex: 1, child: Text("---")), 43 | Expanded(flex: 1, child: Text("--:--")), 44 | Expanded(flex: 1, child: Text("---")), 45 | ]); 46 | } 47 | 48 | static Widget getFieldsFromCalculations(DestinationCalculations? calculations) { 49 | return null == calculations ? 50 | getNullFields() : 51 | Row(children: [ 52 | Expanded(flex: 1, child: Text(calculations.distance.round().toString())), 53 | Expanded(flex: 1, child: Text(calculations.groundSpeed.round().toString())), 54 | Expanded(flex: 1, child: Text(calculations.course.round().toString())), 55 | Expanded(flex: 1, child: Text(Constants.secondsToHHmm(calculations.time.round()))), 56 | Expanded(flex: 1, child: Text(calculations.fuel.toStringAsFixed(1))), 57 | ]); 58 | } 59 | 60 | Widget _getFields() { 61 | return getFieldsFromCalculations(widget.destination.calculations); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /lib/plan/waypoint.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:avaremp/destination/destination.dart'; 3 | 4 | class Waypoint { 5 | 6 | final Destination _destination; 7 | List destinationsOnRoute = []; 8 | int currentDestinationIndex = 0; 9 | 10 | Waypoint(this._destination); 11 | 12 | Destination get destination { 13 | return destinationsOnRoute.isNotEmpty ? 14 | destinationsOnRoute[currentDestinationIndex] : _destination; 15 | } 16 | 17 | // return points passed, current, next 18 | List getDestinationsNext() { 19 | if(destinationsOnRoute.isNotEmpty) { 20 | return currentDestinationIndex == (destinationsOnRoute.length - 1) ? 21 | [] : destinationsOnRoute.sublist(currentDestinationIndex + 1, destinationsOnRoute.length); 22 | } 23 | return []; 24 | } 25 | 26 | // return points passed, current, next 27 | List getDestinationsPassed() { 28 | if(destinationsOnRoute.isNotEmpty) { 29 | return currentDestinationIndex == 0 ? 30 | [] : destinationsOnRoute.sublist(0, currentDestinationIndex); 31 | } 32 | return []; 33 | } 34 | 35 | // return points passed, current, next 36 | List getDestinationsCurrent() { 37 | if(destinationsOnRoute.isNotEmpty) { 38 | return [destinationsOnRoute[currentDestinationIndex]]; 39 | } 40 | return []; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /lib/progress_button_message_input_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ProgressButtonMessageInputWidget extends StatefulWidget { 4 | 5 | final String label; 6 | final String label1; 7 | final String label2; 8 | final String init1; 9 | final String init2; 10 | final String title; 11 | final String success; 12 | final Future Function(List args) onPressed; 13 | final void Function(bool, String, String)? onComplete; 14 | 15 | const ProgressButtonMessageInputWidget(this.label, this.label1, this.init1, this.label2, this.init2, this.title, this.onPressed, this.onComplete, this.success, {super.key}); 16 | 17 | @override 18 | State createState() => ProgressButtonMessageInputWidgetState(); 19 | 20 | } 21 | 22 | class ProgressButtonMessageInputWidgetState extends State { 23 | 24 | bool progress = false; 25 | String error = ""; 26 | Color color = Colors.red; 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | String input1 = widget.init1; 31 | String input2 = widget.init2; 32 | 33 | return Padding(padding: const EdgeInsets.all(10), child: Column( 34 | crossAxisAlignment: CrossAxisAlignment.start, 35 | children: [ 36 | 37 | Text(widget.label, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600),), 38 | 39 | TextFormField( 40 | onChanged: (value) { 41 | input1 = value; 42 | }, 43 | controller: TextEditingController()..text = widget.init1, 44 | decoration: InputDecoration(border: const UnderlineInputBorder(), labelText: widget.label1) 45 | ), 46 | 47 | TextFormField( 48 | obscureText: true, 49 | onChanged: (value) { 50 | input2 = value; 51 | }, 52 | controller: TextEditingController()..text = widget.init2, 53 | decoration: InputDecoration(border: const UnderlineInputBorder(), labelText: widget.label2) 54 | ), 55 | 56 | Row( 57 | children: [ 58 | TextButton(onPressed: () { 59 | setState(() { 60 | error = ""; 61 | progress = true; 62 | widget.onPressed([input1, input2]).then((value) { 63 | setState(() { 64 | progress = false; 65 | error = value; 66 | if(error.isEmpty) { 67 | color = Colors.green; 68 | error = widget.success; 69 | } 70 | if(widget.onComplete != null) { 71 | // return success and new values 72 | widget.onComplete!(value.isEmpty, input1, input2); 73 | } 74 | }); 75 | }); 76 | }); 77 | }, child: Text(widget.title)), 78 | Visibility(visible: progress, child: const CircularProgressIndicator(),), 79 | ], 80 | ), 81 | Text(error, style: TextStyle(color: color),), 82 | ], 83 | )); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /lib/progress_button_message_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ProgressButtonMessageWidget extends StatefulWidget { 4 | 5 | final String label; 6 | final String title; 7 | final String success; 8 | final List args; 9 | final Future Function(List args) onPressed; 10 | final void Function(bool)? onComplete; 11 | 12 | const ProgressButtonMessageWidget(this.label, this.title, this.onPressed, this.args, this.onComplete, this.success, {super.key}); 13 | 14 | @override 15 | State createState() => ProgressButtonMessageWidgetState(); 16 | 17 | } 18 | 19 | 20 | class ProgressButtonMessageWidgetState extends State { 21 | bool progress = false; 22 | String error = ""; 23 | Color color = Colors.red; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | 28 | return Padding(padding: const EdgeInsets.all(10), child: Column( 29 | crossAxisAlignment: CrossAxisAlignment.start, 30 | children: [ 31 | 32 | Text(widget.label, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), 33 | Row( 34 | children: [ 35 | TextButton(onPressed: () { 36 | setState(() { 37 | error = ""; 38 | progress = true; 39 | widget.onPressed(widget.args).then((value) { 40 | setState(() { 41 | progress = false; 42 | error = value; 43 | if(error.isEmpty) { 44 | color = Colors.green; 45 | error = widget.success; 46 | } 47 | if(widget.onComplete != null) { 48 | widget.onComplete!(value.isEmpty); 49 | } 50 | }); 51 | }); 52 | }); 53 | }, child: Text(widget.title)), 54 | Visibility(visible: progress, child: const CircularProgressIndicator(),), 55 | ], 56 | ), 57 | Text(error, style: TextStyle(color: color),), 58 | ], 59 | )); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /lib/stack_with_one.dart: -------------------------------------------------------------------------------- 1 | class StackWithOne { 2 | StackWithOne(E startElement) : _storage = [startElement]; 3 | final List _storage; 4 | 5 | void push(E element) => _storage.add(element); 6 | E pop() { 7 | // pop last and clear the rest 8 | E element = _storage.removeLast(); 9 | _storage.clear(); 10 | // put the last back in queue so next pop will get this if no new value comes in. 11 | _storage.add(element); 12 | return (element); 13 | } 14 | } -------------------------------------------------------------------------------- /lib/twilight_calculator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | class TwilightCalculator { 4 | static const int day = 0; 5 | static const int night = 1; 6 | static const double _degreesToRadians = pi / 180.0; 7 | static const double _j0 = 0.0009; 8 | static const double _altitudeCorrectionCivilTwilight = 0; 9 | static const double _c1 = 0.0334196; 10 | static const double _c2 = 0.000349066; 11 | static const double _c3 = 0.000005236; 12 | static const double _obliquity = 0.40927971; 13 | static const int _utc2000ms = 946728000000; 14 | 15 | static (DateTime? sunset, DateTime? sunrise) calculateTwilight(double latitude, double longitude) { 16 | int sunset; 17 | int sunrise; 18 | 19 | 20 | final int time = DateTime.now().millisecondsSinceEpoch; 21 | final double daysSince2000 = (time - _utc2000ms) / Duration.millisecondsPerDay; 22 | final double meanAnomaly = 6.240059968 + daysSince2000 * 0.01720197; 23 | final double trueAnomaly = meanAnomaly + _c1 * sin(meanAnomaly) + _c2 * sin(2 * meanAnomaly) + _c3 * sin(3 * meanAnomaly); 24 | final double solarLng = trueAnomaly + 1.796593063 + pi; 25 | final double arcLongitude = -longitude / 360; 26 | double n = (daysSince2000 - _j0 - arcLongitude).roundToDouble(); 27 | double solarTransitJ2000 = n + _j0 + arcLongitude + 0.0053 * sin(meanAnomaly) - 0.0069 * sin(2 * solarLng); 28 | double solarDec = asin(sin(solarLng) * sin(_obliquity)); 29 | final double latRad = latitude * _degreesToRadians; 30 | double cosHourAngle = (sin(_altitudeCorrectionCivilTwilight) - sin(latRad) * sin(solarDec)) / (cos(latRad) * cos(solarDec)); 31 | 32 | if (cosHourAngle >= 1) { 33 | return (null, null); 34 | } 35 | else if (cosHourAngle <= -1) { 36 | return (null, null); 37 | } 38 | double hourAngle = acos(cosHourAngle) / (2 * pi); 39 | sunset = ((solarTransitJ2000 + hourAngle) * Duration.millisecondsPerDay).round() + _utc2000ms; 40 | sunrise = ((solarTransitJ2000 - hourAngle) * Duration.millisecondsPerDay).round() + _utc2000ms; 41 | // this returns local time, since offset was added 42 | return (DateTime.fromMillisecondsSinceEpoch(sunrise, isUtc: true), 43 | DateTime.fromMillisecondsSinceEpoch(sunset, isUtc: true)); 44 | } 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /lib/udp_receiver.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:avaremp/app_log.dart'; 5 | import 'package:flutter/foundation.dart'; 6 | 7 | // Get UDP from receivers, handle GDL90 8 | class UdpReceiver { 9 | 10 | late StreamController _controller; 11 | final List _sockets = []; 12 | 13 | void initChannel(int port, bool broadcast) async { 14 | try { 15 | RawDatagramSocket socket = await RawDatagramSocket.bind( 16 | InternetAddress.anyIPv4, port, reuseAddress: true).then(( 17 | RawDatagramSocket socket) { 18 | socket.broadcastEnabled = broadcast; 19 | socket.listen((e) { 20 | Datagram? dg = socket.receive(); 21 | if (dg != null) { 22 | _controller.add(dg.data); 23 | } 24 | }); 25 | return socket; 26 | }); 27 | _sockets.add(socket); 28 | } 29 | catch(e) { 30 | AppLog.logMessage("UDP listen error: $e"); 31 | } 32 | } 33 | 34 | StreamSubscription getStream(List ports, List isBroadcast) { 35 | _controller = StreamController(); 36 | for(int port in ports) { 37 | initChannel(port, isBroadcast[ports.indexOf(port)]); 38 | } 39 | return _controller.stream.listen((event) { }); 40 | } 41 | 42 | void finish() { 43 | for(RawDatagramSocket socket in _sockets) { 44 | socket.close(); 45 | } 46 | _controller.close(); 47 | } 48 | } -------------------------------------------------------------------------------- /lib/unit_conversion.dart: -------------------------------------------------------------------------------- 1 | class UnitConversion { 2 | 3 | UnitConversion(String unit) { 4 | // default for knots 5 | if(unit == "Imperial") { 6 | mTo = 0.000621371; 7 | toM = 1609.34; 8 | mpsTo = 2.23694; 9 | toMps = 0.44704; 10 | knotsTo = 1.15078; 11 | } 12 | } 13 | 14 | // default is for knots and feet for maritime units 15 | 16 | // for vertical distance 17 | double mToF = 3.28084; 18 | double fToM = 0.3048; 19 | 20 | // for horizontal distance 21 | double mTo = 0.000539957; 22 | double toM = 1851.9993; 23 | 24 | // for speed 25 | double mpsTo = 1.94384; // kph, knot, mile/hr 26 | double toMps = 0.514446; 27 | 28 | // knots for wind. 29 | double knotsTo = 1; 30 | 31 | static bool isLatitudeValid(double lat) { 32 | return lat >= -90 && lat <= 90; 33 | } 34 | 35 | static bool isLongitudeValid(double lon) { 36 | return lon >= -180 && lon <= 180; 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /lib/weather/airep.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:avaremp/weather/weather.dart'; 4 | import 'package:latlong2/latlong.dart'; 5 | 6 | class Airep extends Weather { 7 | String text; 8 | LatLng coordinates; 9 | 10 | Airep(super.station, super.expires, super.recieved, super.source, this.text, this.coordinates); 11 | 12 | Map toMap() { 13 | Map map = { 14 | "station": station, 15 | "utcMs": expires.millisecondsSinceEpoch, 16 | "receivedMs": received.millisecondsSinceEpoch, 17 | "source": source, 18 | "raw": text, 19 | "coordinates": jsonEncode([coordinates.latitude, coordinates.longitude]) 20 | }; 21 | return map; 22 | } 23 | 24 | factory Airep.fromMap(Map maps) { 25 | 26 | dynamic coordinates = jsonDecode(maps['coordinates'] as String); 27 | 28 | return Airep( 29 | maps['station'] as String, 30 | DateTime.fromMillisecondsSinceEpoch(maps['utcMs'] as int), 31 | DateTime.fromMillisecondsSinceEpoch(maps['receivedMs'] as int), 32 | maps['source'] as String, 33 | maps['raw'] as String, 34 | LatLng(coordinates[0], coordinates[1]) 35 | ); 36 | } 37 | 38 | @override 39 | String toString() { 40 | return "${super.toString()}$text"; 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /lib/weather/airep_cache.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | import 'package:avaremp/constants.dart'; 5 | import 'package:avaremp/data/weather_database_helper.dart'; 6 | import 'package:avaremp/storage.dart'; 7 | import 'package:avaremp/weather/weather_cache.dart'; 8 | import 'package:latlong2/latlong.dart'; 9 | import 'package:xml/xml.dart'; 10 | import 'airep.dart'; 11 | import 'weather.dart'; 12 | 13 | class AirepCache extends WeatherCache { 14 | 15 | AirepCache(super.url, super.dbCall); 16 | 17 | @override 18 | Future parse(List data, [String? argument]) async { 19 | if(data.isEmpty) { 20 | return; 21 | } 22 | final List decodedData; 23 | String decoded; 24 | List aireps = []; 25 | final XmlDocument document; 26 | final Iterable textual; 27 | 28 | try { 29 | decodedData = GZipCodec().decode(data[0]); 30 | decoded = utf8.decode(decodedData, allowMalformed: true); 31 | document = XmlDocument.parse(decoded); 32 | textual = document.findAllElements("AircraftReport"); 33 | } 34 | catch(e) { 35 | Storage().setException("AIREP: unable to decode data."); 36 | return; 37 | } 38 | 39 | if(decoded.startsWith(" coordinates; 10 | String hazard; 11 | String severity; 12 | String type; 13 | bool showShape = false; // reduce clutter on screen 14 | 15 | AirSigmet(super.station, super.expires, super.recieved, super.source, this.text, this.coordinates, this.hazard, this.severity, this.type); 16 | 17 | 18 | Map toMap() { 19 | 20 | List> ll = []; 21 | for(LatLng c in coordinates) { 22 | ll.add([c.latitude, c.longitude]); 23 | } 24 | 25 | Map map = { 26 | "station": station, 27 | "utcMs": expires.millisecondsSinceEpoch, 28 | "receivedMs": received.millisecondsSinceEpoch, 29 | "source": source, 30 | "raw": text, 31 | "coordinates" : jsonEncode(ll), 32 | "hazard": hazard, 33 | "severity": severity, 34 | "type": type 35 | }; 36 | return map; 37 | } 38 | 39 | factory AirSigmet.fromMap(Map maps) { 40 | List ll = []; 41 | List coordinates = jsonDecode(maps['coordinates'] as String); 42 | for(dynamic coordinate in coordinates) { 43 | ll.add(LatLng(coordinate[0], coordinate[1])); 44 | } 45 | 46 | return AirSigmet( 47 | maps['station'] as String, 48 | DateTime.fromMillisecondsSinceEpoch(maps['utcMs'] as int), 49 | DateTime.fromMillisecondsSinceEpoch(maps['receivedMs'] as int), 50 | maps['source'] as String, 51 | maps['raw'] as String, 52 | ll, 53 | maps['hazard'] as String, 54 | maps['severity'] as String, 55 | maps['type'] as String, 56 | ); 57 | } 58 | 59 | Color getColor() { 60 | if(hazard == "TURB" || type == "TANGO") { 61 | return Colors.deepOrangeAccent; 62 | } 63 | if(hazard == "IFR" || type == "SIERRA") { 64 | return Colors.purpleAccent; 65 | } 66 | if(hazard == "ICE" || type == "ZULU") { 67 | return Colors.blueAccent; 68 | } 69 | if(hazard == "MTN OBSCN" || type == "SIERRA") { 70 | return Colors.greenAccent; 71 | } 72 | return Colors.black; 73 | 74 | } 75 | @override 76 | String toString() { 77 | String t = text.replaceAll(RegExp(r'[^\w\s]+'),' '); 78 | return "${super.toString()}$t"; 79 | } 80 | 81 | } 82 | 83 | -------------------------------------------------------------------------------- /lib/weather/metar_cache.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | import 'package:avaremp/constants.dart'; 5 | import 'package:avaremp/data/weather_database_helper.dart'; 6 | import 'package:avaremp/geo_calculations.dart'; 7 | import 'package:avaremp/storage.dart'; 8 | import 'package:avaremp/weather/weather_cache.dart'; 9 | import 'package:latlong2/latlong.dart'; 10 | import 'package:xml/xml.dart'; 11 | import 'metar.dart'; 12 | import 'weather.dart'; 13 | 14 | 15 | class MetarCache extends WeatherCache { 16 | 17 | Uint8List? image; 18 | 19 | MetarCache(super.url, super.dbCall); 20 | 21 | @override 22 | Future parse(List data, [String? argument]) async { 23 | if(data.isEmpty) { 24 | return; 25 | } 26 | final List decodedData; 27 | String decoded; 28 | final XmlDocument document; 29 | final Iterable textual; 30 | final List metars = []; 31 | 32 | try { 33 | decodedData = GZipCodec().decode(data[0]); 34 | decoded = utf8.decode(decodedData, allowMalformed: true); 35 | document = XmlDocument.parse(decoded); 36 | textual = document.findAllElements("METAR"); 37 | } 38 | catch(e) { 39 | // not gzipped 40 | Storage().setException("METAR: unable to decode data."); 41 | return; 42 | } 43 | 44 | if(decoded.startsWith(" metars = Storage().metar.getAll().map((e) => e as Metar).toList(); 76 | for(Metar m in metars) { 77 | double d = geo.calculateDistance(m.coordinate, coordinate); 78 | if(d < distance) { 79 | selected = m; 80 | distance = d; 81 | } 82 | } 83 | return selected; 84 | } 85 | 86 | } 87 | 88 | -------------------------------------------------------------------------------- /lib/weather/notam.dart: -------------------------------------------------------------------------------- 1 | import 'package:avaremp/weather/weather.dart'; 2 | 3 | class Notam extends Weather { 4 | 5 | String text; 6 | 7 | Notam(super.station, super.expires, super.recieved, super.source, this.text); 8 | 9 | Map toMap() { 10 | Map map = { 11 | "station": station, 12 | "utcMs": expires.millisecondsSinceEpoch, 13 | "receivedMs": received.millisecondsSinceEpoch, 14 | "source": source, 15 | "raw": text, 16 | }; 17 | return map; 18 | } 19 | 20 | factory Notam.fromMap(Map maps) { 21 | 22 | return Notam( 23 | maps['station'] as String, 24 | DateTime.fromMillisecondsSinceEpoch(maps['utcMs'] as int), 25 | DateTime.fromMillisecondsSinceEpoch(maps['receivedMs'] as int), 26 | maps['source'] as String, 27 | maps['raw'] as String, 28 | ); 29 | } 30 | 31 | @override 32 | String toString() { 33 | return "${super.toString()}$text"; 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /lib/weather/taf_cache.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | import 'package:avaremp/constants.dart'; 5 | import 'package:avaremp/data/weather_database_helper.dart'; 6 | import 'package:avaremp/storage.dart'; 7 | import 'package:avaremp/weather/taf.dart'; 8 | import 'package:avaremp/weather/weather_cache.dart'; 9 | import 'package:latlong2/latlong.dart'; 10 | import 'package:xml/xml.dart'; 11 | 12 | import 'weather.dart'; 13 | 14 | class TafCache extends WeatherCache { 15 | 16 | TafCache(super.url, super.dbCall); 17 | 18 | @override 19 | Future parse(List data, [String? argument]) async { 20 | if(data.isEmpty) { 21 | return; 22 | } 23 | final List decodedData; 24 | String decoded; 25 | final XmlDocument document; 26 | final Iterable textual; 27 | final List tafs = []; 28 | 29 | try { 30 | decodedData = GZipCodec().decode(data[0]); 31 | decoded = utf8.decode(decodedData, allowMalformed: true); 32 | document = XmlDocument.parse(decoded); 33 | textual = document.findAllElements("TAF"); 34 | } 35 | catch(e) { 36 | // not gzipped 37 | Storage().setException("TAF: unable to decode data."); 38 | return; 39 | } 40 | 41 | if(decoded.startsWith(" coordinates; 8 | final String upperAltitude; 9 | final String lowerAltitude; 10 | final int msEffective; 11 | final int msExpires; 12 | final int labelCoordinate; 13 | 14 | Tfr(super.station, super.expires, super.recieved, super.source, this.coordinates, this.upperAltitude, this.lowerAltitude, this.msEffective, this.msExpires, this.labelCoordinate); 15 | 16 | @override 17 | String toString() { 18 | return 19 | "${super.toString()}Top $upperAltitude\nLow $lowerAltitude\n${DateTime.fromMillisecondsSinceEpoch(msEffective).toString().replaceAll(":00.000", "Z")} to\n${DateTime.fromMillisecondsSinceEpoch(msExpires).toString().replaceAll(":00.000", "Z")}"; 20 | } 21 | 22 | bool isInEffect() { 23 | int now = DateTime.now().toUtc().millisecondsSinceEpoch; 24 | return (now >= msEffective && now <= msExpires); 25 | } 26 | 27 | 28 | bool isRelevant() { 29 | return DateTime.now().toUtc().millisecondsSinceEpoch < msExpires; 30 | } 31 | 32 | int getLabelCoordinate() { 33 | return labelCoordinate; 34 | } 35 | 36 | 37 | Map toMap() { 38 | 39 | List> ll = []; 40 | for(LatLng c in coordinates) { 41 | ll.add([c.latitude, c.longitude]); 42 | } 43 | 44 | Map map = { 45 | "station": station, 46 | "utcMs": expires.millisecondsSinceEpoch, 47 | "receivedMs": received.millisecondsSinceEpoch, 48 | "source": source, 49 | "coordinates": jsonEncode(ll), 50 | "upperAltitude": upperAltitude, 51 | "lowerAltitude": lowerAltitude, 52 | "msEffective": msEffective, 53 | "msExpires": msExpires, 54 | "labelCoordinate": labelCoordinate 55 | }; 56 | return map; 57 | } 58 | 59 | factory Tfr.fromMap(Map maps) { 60 | 61 | List ll = []; 62 | List coordinates = jsonDecode(maps['coordinates'] as String); 63 | for(dynamic coordinate in coordinates) { 64 | ll.add(LatLng(coordinate[0], coordinate[1])); 65 | } 66 | 67 | return Tfr( 68 | maps['station'] as String, 69 | DateTime.fromMillisecondsSinceEpoch(maps['utcMs'] as int), 70 | DateTime.fromMillisecondsSinceEpoch(maps['receivedMs'] as int), 71 | maps['source'] as String, 72 | ll, 73 | maps['upperAltitude'] as String, 74 | maps['lowerAltitude'] as String, 75 | maps['msEffective'] as int, 76 | maps['msExpires'] as int, 77 | maps['labelCoordinate'] as int 78 | ); 79 | } 80 | 81 | } 82 | 83 | -------------------------------------------------------------------------------- /lib/weather/weather.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Weather { 4 | 5 | String station; 6 | DateTime expires; 7 | DateTime received; 8 | String source; 9 | 10 | static const String sourceInternet = "Internet"; 11 | static const String sourceADSB = "ADS-B"; 12 | 13 | Weather(this.station, this.expires, this.received, this.source); 14 | 15 | bool isExpired() { 16 | Duration diff = expires.difference(DateTime.now().toUtc()); 17 | return (diff.inSeconds < 0); 18 | } 19 | 20 | bool isVeryOld() { 21 | Duration diff = expires.difference(DateTime.now().toUtc()); 22 | return (diff.inHours < -12); 23 | } 24 | 25 | @override 26 | String toString() { 27 | // do not show milliseconds in the time 28 | String r = received.toUtc().toString().substring(5, 16); 29 | return "*$source received ${r}Z*\n"; 30 | } 31 | 32 | } 33 | 34 | 35 | -------------------------------------------------------------------------------- /lib/wnb.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:ui'; 3 | 4 | class Wnb { 5 | String name; 6 | String aircraft; 7 | List items; 8 | double minX; 9 | double minY; 10 | double maxX; 11 | double maxY; 12 | List points; 13 | 14 | Wnb(this.name, this.aircraft, this.items, this.minX, this.minY, this.maxX, this.maxY, this.points); 15 | 16 | static List getPoints(List points) { 17 | List result = []; 18 | for (String point in points) { 19 | List parts = point.split(','); 20 | result.add(Offset(double.parse(parts[0]), double.parse(parts[1]))); 21 | } 22 | return result; 23 | } 24 | 25 | static List getPointsAsString(List points) { 26 | List result = []; 27 | for (Offset point in points) { 28 | result.add('${point.dx},${point.dy}'); 29 | } 30 | return result; 31 | } 32 | 33 | factory Wnb.empty() { 34 | return Wnb('New', '', List.generate(20, (index) => ""), 30, 1500, 50, 3000, []); 35 | } 36 | 37 | factory Wnb.fromMap(Map map) { 38 | return Wnb( 39 | map['name'] as String, 40 | map['aircraft'] as String, 41 | List.from(jsonDecode(map['items'] as String)), 42 | map['minX'] as double, 43 | map['minY'] as double, 44 | map['maxX'] as double, 45 | map['maxY'] as double, 46 | List.from(jsonDecode(map['points'] as String)) 47 | ); 48 | } 49 | 50 | Map toMap() { 51 | return { 52 | 'name': name, 53 | 'aircraft': aircraft, 54 | 'items': jsonEncode(items), 55 | 'minX': minX, 56 | 'minY': minY, 57 | 'maxX': maxX, 58 | 'maxY': maxY, 59 | 'points': jsonEncode(points) 60 | }; 61 | } 62 | 63 | } 64 | 65 | 66 | class WnbItem { 67 | String description; 68 | double weight; 69 | double arm; 70 | 71 | WnbItem(this.description, this.weight, this.arm); 72 | 73 | String toJson() { 74 | Map map = { 75 | 'description': description, 76 | 'weight': weight, 77 | 'arm': arm 78 | }; 79 | return jsonEncode(map); 80 | } 81 | 82 | factory WnbItem.fromJson(String json) { 83 | try { 84 | final Map map = jsonDecode(json); 85 | return WnbItem(map['description'] as String, map['weight'] as double, 86 | map['arm'] as double); 87 | } 88 | catch(e) { 89 | return WnbItem('', 0, 0); 90 | } 91 | } 92 | 93 | } 94 | 95 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | 12 | void fl_register_plugins(FlPluginRegistry* registry) { 13 | g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = 14 | fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); 15 | audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); 16 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 17 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 18 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 19 | } 20 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | audioplayers_linux 7 | url_launcher_linux 8 | ) 9 | 10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 11 | ) 12 | 13 | set(PLUGIN_BUNDLED_LIBRARIES) 14 | 15 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 20 | endforeach(plugin) 21 | 22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 25 | endforeach(ffi_plugin) 26 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import audioplayers_darwin 9 | import device_info_plus 10 | import geolocator_apple 11 | import in_app_review 12 | import package_info_plus 13 | import path_provider_foundation 14 | import share_plus 15 | import sqflite 16 | import syncfusion_pdfviewer_macos 17 | import url_launcher_macos 18 | import wakelock_plus 19 | 20 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 21 | AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) 22 | DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) 23 | GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) 24 | InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) 25 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 26 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 27 | SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) 28 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 29 | SyncfusionFlutterPdfViewerPlugin.register(with: registry.registrar(forPlugin: "SyncfusionFlutterPdfViewerPlugin")) 30 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 31 | WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) 32 | } 33 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | 10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "xcode" 5 | }, 6 | "images": [ 7 | { 8 | "size": "16x16", 9 | "idiom": "mac", 10 | "filename": "app_icon_16.png", 11 | "scale": "1x" 12 | }, 13 | { 14 | "size": "16x16", 15 | "idiom": "mac", 16 | "filename": "app_icon_32.png", 17 | "scale": "2x" 18 | }, 19 | { 20 | "size": "32x32", 21 | "idiom": "mac", 22 | "filename": "app_icon_32.png", 23 | "scale": "1x" 24 | }, 25 | { 26 | "size": "32x32", 27 | "idiom": "mac", 28 | "filename": "app_icon_64.png", 29 | "scale": "2x" 30 | }, 31 | { 32 | "size": "128x128", 33 | "idiom": "mac", 34 | "filename": "app_icon_128.png", 35 | "scale": "1x" 36 | }, 37 | { 38 | "size": "128x128", 39 | "idiom": "mac", 40 | "filename": "app_icon_256.png", 41 | "scale": "2x" 42 | }, 43 | { 44 | "size": "256x256", 45 | "idiom": "mac", 46 | "filename": "app_icon_256.png", 47 | "scale": "1x" 48 | }, 49 | { 50 | "size": "256x256", 51 | "idiom": "mac", 52 | "filename": "app_icon_512.png", 53 | "scale": "2x" 54 | }, 55 | { 56 | "size": "512x512", 57 | "idiom": "mac", 58 | "filename": "app_icon_512.png", 59 | "scale": "1x" 60 | }, 61 | { 62 | "size": "512x512", 63 | "idiom": "mac", 64 | "filename": "app_icon_1024.png", 65 | "scale": "2x" 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = AvareX 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.apps4av.avaremp 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2023 com.apps4av. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | com.apple.security.network.client 12 | 13 | com.apple.security.personal-information.location 14 | 15 | com.apple.security.files.user-selected.read-write 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | LSApplicationCategoryType 32 | public.app-category.navigation 33 | NSLocationUsageDescription 34 | This app needs access to your GPS location to show your position on the Map and Plate screens. If you deny this permission, you will still be able to use the app, but your position on the map will not change during your flight. Your location is not shared with Apps4Av. 35 | 36 | 37 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.personal-information.location 10 | 11 | com.apple.security.files.user-selected.read-write 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import FlutterMacOS 2 | import Cocoa 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /snap/gui/avarex.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=avarex 3 | Comment=AvareX Flight Support 4 | Exec=avarex 5 | Icon=${SNAP}/meta/gui/avarex.png 6 | Terminal=false 7 | Type=Application 8 | Categories=Utilities; -------------------------------------------------------------------------------- /snap/gui/avarex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/snap/gui/avarex.png -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: avarex 2 | version: 0.0.67 3 | icon: assets/images/logo.png 4 | summary: AvareX Flight Support 5 | description: AvareX provide FAA charts, plates, airport data, weather, air traffic over ADS-B, and lets you create and file flight plans. 6 | 7 | confinement: strict 8 | base: core22 9 | grade: stable 10 | 11 | apps: 12 | avarex: 13 | command: AvareX 14 | extensions: [gnome] # gnome includes the libraries required by flutter 15 | plugs: 16 | - network 17 | - location-observe 18 | - network-bind 19 | - home 20 | - audio-playback 21 | 22 | parts: 23 | 24 | # for file import 25 | zenity-integration: 26 | plugin: nil 27 | stage-snaps: 28 | - zenity-integration 29 | 30 | avarex: 31 | 32 | override-pull: | 33 | set -eux 34 | rm -rf $SNAPCRAFT_PROJECT_DIR/build 35 | snapcraftctl pull 36 | 37 | source: . 38 | plugin: flutter 39 | flutter-target: lib/main.dart # The main entry-point file of the application 40 | 41 | build-packages: 42 | - libgstreamer1.0-dev 43 | - libgstreamer-plugins-base1.0-dev 44 | stage-packages: 45 | - libsqlite3-dev 46 | -------------------------------------------------------------------------------- /tests/adsb_dallas.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/tests/adsb_dallas.bin -------------------------------------------------------------------------------- /tests/adsb_daytona.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/tests/adsb_daytona.bin -------------------------------------------------------------------------------- /tests/adsb_lancaster.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/tests/adsb_lancaster.bin -------------------------------------------------------------------------------- /tests/dynon.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/tests/dynon.bin -------------------------------------------------------------------------------- /tests/gdl90_test.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import time 3 | import sys 4 | 5 | if len(sys.argv) > 1: 6 | infile = sys.argv[1] 7 | else: 8 | infile = input("Enter file name:") 9 | print("Reading ADSB data from file: " + infile) 10 | while True: 11 | file = open(infile, "rb") 12 | 13 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 14 | 15 | while True: 16 | data = file.read(256) 17 | if len(data) == 0: 18 | break; 19 | sock.sendto(data, ("127.0.0.1", 43211)) 20 | time.sleep(0.01) 21 | 22 | file.close() 23 | sock.close() 24 | -------------------------------------------------------------------------------- /tests/levil.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/tests/levil.bin -------------------------------------------------------------------------------- /tests/mmu.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/tests/mmu.bin -------------------------------------------------------------------------------- /tests/nmea_test.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import time 3 | 4 | infile = input("Enter file name:") 5 | while True: 6 | file = open(infile, "rt") 7 | 8 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 9 | 10 | while True: 11 | input = file.readline() 12 | if len(input) == 0: 13 | break; 14 | data = bytes(input, 'UTF-8') 15 | sock.sendto(data, ("127.0.0.1", 49002)) 16 | time.sleep(0.01) 17 | 18 | file.close() 19 | sock.close() 20 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | avaremp 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "avaremp", 3 | "short_name": "avaremp", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#hexcode", 7 | "theme_color": "#hexcode", 8 | "description": "Multi Platform Avare", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | void RegisterPlugins(flutter::PluginRegistry* registry) { 16 | AudioplayersWindowsPluginRegisterWithRegistrar( 17 | registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); 18 | GeolocatorWindowsRegisterWithRegistrar( 19 | registry->GetRegistrarForPlugin("GeolocatorWindows")); 20 | MsvcredistPluginCApiRegisterWithRegistrar( 21 | registry->GetRegistrarForPlugin("MsvcredistPluginCApi")); 22 | SharePlusWindowsPluginCApiRegisterWithRegistrar( 23 | registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); 24 | UrlLauncherWindowsRegisterWithRegistrar( 25 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 26 | } 27 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | audioplayers_windows 7 | geolocator_windows 8 | msvcredist 9 | share_plus 10 | url_launcher_windows 11 | ) 12 | 13 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 14 | ) 15 | 16 | set(PLUGIN_BUNDLED_LIBRARIES) 17 | 18 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 19 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 20 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 23 | endforeach(plugin) 24 | 25 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 26 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 28 | endforeach(ffi_plugin) 29 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"AvareX", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length <= 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /windows/sqlite3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apps4av/avarex/c2e8ef8c0fe8c7bae022905f6d627a90c20b9382/windows/sqlite3.dll --------------------------------------------------------------------------------