├── linux
├── .gitignore
├── runner
│ ├── main.cc
│ ├── my_application.h
│ ├── CMakeLists.txt
│ └── my_application.cc
├── flutter
│ ├── generated_plugin_registrant.h
│ ├── generated_plugin_registrant.cc
│ ├── generated_plugins.cmake
│ └── CMakeLists.txt
└── CMakeLists.txt
├── ios
├── Flutter
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── AppFrameworkInfo.plist
├── Runner
│ ├── Runner-Bridging-Header.h
│ ├── Assets.xcassets
│ │ ├── LaunchImage.imageset
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ ├── README.md
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ ├── 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-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ └── Contents.json
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ └── Info.plist
├── Runner.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
└── .gitignore
├── android
├── app
│ ├── src
│ │ ├── main
│ │ │ ├── play
│ │ │ │ ├── contact-email.txt
│ │ │ │ └── listings
│ │ │ │ │ ├── en-US
│ │ │ │ │ ├── short-description.txt
│ │ │ │ │ ├── video.txt
│ │ │ │ │ ├── graphics
│ │ │ │ │ │ └── icon
│ │ │ │ │ │ │ └── icon.png
│ │ │ │ │ └── full-description.txt
│ │ │ │ │ ├── ru
│ │ │ │ │ ├── short-description.txt
│ │ │ │ │ └── full-description.txt
│ │ │ │ │ ├── ua
│ │ │ │ │ ├── short-description.txt
│ │ │ │ │ └── full-description.txt
│ │ │ │ │ └── pl
│ │ │ │ │ ├── short-description.txt
│ │ │ │ │ └── full-description.txt
│ │ │ ├── ic_launcher-playstore.png
│ │ │ ├── res
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ ├── values
│ │ │ │ │ ├── colors.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ └── ic_launcher.xml
│ │ │ │ ├── drawable
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-v21
│ │ │ │ │ └── launch_background.xml
│ │ │ │ └── values-night
│ │ │ │ │ └── styles.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── ble_ota_app
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ └── build.gradle.kts
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── .gitignore
├── build.gradle.kts
└── settings.gradle.kts
├── macos
├── Flutter
│ ├── Flutter-Debug.xcconfig
│ ├── Flutter-Release.xcconfig
│ └── GeneratedPluginRegistrant.swift
├── Runner
│ ├── Configs
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ ├── Warnings.xcconfig
│ │ └── AppInfo.xcconfig
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ ├── app_icon_128.png
│ │ │ ├── app_icon_16.png
│ │ │ ├── app_icon_256.png
│ │ │ ├── app_icon_32.png
│ │ │ ├── app_icon_512.png
│ │ │ ├── app_icon_64.png
│ │ │ ├── app_icon_1024.png
│ │ │ └── Contents.json
│ ├── AppDelegate.swift
│ ├── Release.entitlements
│ ├── DebugProfile.entitlements
│ ├── MainFlutterWindow.swift
│ └── Info.plist
├── .gitignore
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── Runner.xcodeproj
│ ├── project.xcworkspace
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ └── xcschemes
│ └── Runner.xcscheme
├── web
├── favicon.png
├── icons
│ ├── Icon-192.png
│ ├── Icon-512.png
│ ├── Icon-maskable-192.png
│ └── Icon-maskable-512.png
├── manifest.json
└── index.html
├── assets
├── images
│ ├── icon.png
│ ├── icon_color_foreground.png
│ └── icon_mono_foreground.png
└── translations
│ ├── en-US.json
│ ├── ru-RU.json
│ ├── uk-UA.json
│ └── pl-PL.json
├── windows
├── runner
│ ├── resources
│ │ └── app_icon.ico
│ ├── resource.h
│ ├── utils.h
│ ├── runner.exe.manifest
│ ├── flutter_window.h
│ ├── main.cpp
│ ├── CMakeLists.txt
│ ├── utils.cpp
│ ├── flutter_window.cpp
│ ├── Runner.rc
│ ├── win32_window.h
│ └── win32_window.cpp
├── .gitignore
├── flutter
│ ├── generated_plugin_registrant.h
│ ├── generated_plugin_registrant.cc
│ ├── generated_plugins.cmake
│ └── CMakeLists.txt
└── CMakeLists.txt
├── resources
├── manufactures.yaml
├── manufactures.json
├── privacy-policy.md
└── Web_badge.svg
├── doc
├── RELEASE.md
├── USING.md
├── SETUP.md
└── REGISTER_HARDWARE.md
├── lib
├── src
│ ├── ui
│ │ ├── ui_consts.dart
│ │ └── jumping_dots.dart
│ ├── settings
│ │ └── settings.dart
│ ├── screens
│ │ ├── info_screen.dart
│ │ ├── settings_screen.dart
│ │ ├── status_screen.dart
│ │ ├── pin_screen.dart
│ │ ├── scanner_screen.dart
│ │ └── upload_screen.dart
│ └── utils
│ │ └── string_forms.dart
└── main.dart
├── .github
├── FUNDING.yml
└── workflows
│ └── dart.yml
├── .gitignore
├── .metadata
├── LICENSE
├── test
└── widget_test.dart
├── analysis_options.yaml
├── pubspec.yaml
└── README.md
/linux/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/android/app/src/main/play/contact-email.txt:
--------------------------------------------------------------------------------
1 | vovagorodok2@gmail.com
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "ephemeral/Flutter-Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/short-description.txt:
--------------------------------------------------------------------------------
1 | Upload firmware over Bluetooth
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/ru/short-description.txt:
--------------------------------------------------------------------------------
1 | Загрузи прошивку через Bluetooth
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/ua/short-description.txt:
--------------------------------------------------------------------------------
1 | Завантаж прошивку через Bluetooth
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "ephemeral/Flutter-Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/web/favicon.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/video.txt:
--------------------------------------------------------------------------------
1 | https://youtu.be/ubBchke7kzI?si=8R8ZIttgYH4hzCOz
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/pl/short-description.txt:
--------------------------------------------------------------------------------
1 | Wgraj oprogramowanie przez Bluetooth
--------------------------------------------------------------------------------
/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/assets/images/icon.png
--------------------------------------------------------------------------------
/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/windows/runner/resources/app_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/windows/runner/resources/app_icon.ico
--------------------------------------------------------------------------------
/assets/images/icon_color_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/assets/images/icon_color_foreground.png
--------------------------------------------------------------------------------
/assets/images/icon_mono_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/assets/images/icon_mono_foreground.png
--------------------------------------------------------------------------------
/resources/manufactures.yaml:
--------------------------------------------------------------------------------
1 | Example MF: https://raw.githubusercontent.com/vovagorodok/ArduinoBleOTA/main/tools/release_builder/example_hardwares.yaml
--------------------------------------------------------------------------------
/android/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/resources/manufactures.json:
--------------------------------------------------------------------------------
1 | {
2 | "Example MF": "https://raw.githubusercontent.com/vovagorodok/ArduinoBleOTA/main/tools/release_builder/example_hardwares.json"
3 | }
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/doc/RELEASE.md:
--------------------------------------------------------------------------------
1 | # Release guide
2 |
3 | Build and release: https://docs.flutter.dev/deployment/android
4 |
5 | Remember to update if needed: `.github/workflows/dart.yml`
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFFFF
4 |
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/graphics/icon/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/play/listings/en-US/graphics/icon/icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/en-US/full-description.txt:
--------------------------------------------------------------------------------
1 | Open source application for upload firmware over Bluetooth.
2 | Additionally supports update functionality for specific hardwares.
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/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/vovagorodok/ble_ota_app/HEAD/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/vovagorodok/ble_ota_app/HEAD/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/vovagorodok/ble_ota_app/HEAD/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/vovagorodok/ble_ota_app/HEAD/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/vovagorodok/ble_ota_app/HEAD/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/vovagorodok/ble_ota_app/HEAD/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/vovagorodok/ble_ota_app/HEAD/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/vovagorodok/ble_ota_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/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/vovagorodok/ble_ota_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/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/vovagorodok/ble_ota_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/pl/full-description.txt:
--------------------------------------------------------------------------------
1 | Otwartoźródłowa aplikacja do wgrywania oprogramowania przez Bluetooth.
2 | Dodatkowo wspiera funkcjonalność aktualizacji dla niektórych urządzeń.
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/ua/full-description.txt:
--------------------------------------------------------------------------------
1 | Програма з відкритим кодом для завантаження мікропрограми через Bluetooth.
2 | Крім того, підтримується функція оновлення для певного обладнання.
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vovagorodok/ble_ota_app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/android/app/src/main/play/listings/ru/full-description.txt:
--------------------------------------------------------------------------------
1 | Приложение с открытым исходным кодом для загрузки прошивки через Bluetooth.
2 | Дополнительно поддерживает функцию обновления для конкретного оборудования.
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/ble_ota_app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.vovagorodok.ble_ota_app
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity : FlutterActivity()
6 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/linux/runner/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 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lib/src/ui/ui_consts.dart:
--------------------------------------------------------------------------------
1 | const screenPadding = 16.0;
2 | const screenPortraitSplitter = screenPadding / 2;
3 | const screenLandscapeSplitter = screenPadding;
4 | const buttonHeight = 50.0;
5 | const buttonsSplitter = screenPadding;
6 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: vovagorodok
4 | custom: [revolut.me/vovagorodok, paypal.me/vovagorodok]
5 | patreon: vovagorodok
6 | ko_fi: vovagorodok
7 | buy_me_a_coffee: vovagorodok
8 | liberapay: vovagorodok
9 |
--------------------------------------------------------------------------------
/lib/src/ui/jumping_dots.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:jumping_dot/jumping_dot.dart';
3 |
4 | Widget createJumpingDots() => JumpingDots(
5 | color: Colors.grey,
6 | radius: 6,
7 | innerPadding: 5,
8 | );
9 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
6 |
--------------------------------------------------------------------------------
/macos/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | @NSApplicationMain
5 | class AppDelegate: FlutterAppDelegate {
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 | return true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/doc/USING.md:
--------------------------------------------------------------------------------
1 | # Using guide
2 |
3 | ## Web
4 | Bluetooth support is experimental for web and can be enabled at `chrome://flags`.
5 | Run flutter with experimental flags:
6 | ```
7 | flutter run -d chrome --release --web-browser-flag "--enable-experimental-web-platform-features" --web-browser-flag "--disable-web-security"
8 | ```
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 | .cxx/
9 |
10 | # Remember to never publicly share your keystore.
11 | # See https://flutter.dev/to/reference-keystore
12 | key.properties
13 | **/*.keystore
14 | **/*.jks
15 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
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 |
--------------------------------------------------------------------------------
/linux/runner/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/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController.init()
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/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 |
11 | void fl_register_plugins(FlPluginRegistry* registry) {
12 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
13 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
14 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
15 | }
16 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | val newBuildDir: Directory =
9 | rootProject.layout.buildDirectory
10 | .dir("../../build")
11 | .get()
12 | rootProject.layout.buildDirectory.value(newBuildDir)
13 |
14 | subprojects {
15 | val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
16 | project.layout.buildDirectory.value(newSubprojectBuildDir)
17 | }
18 | subprojects {
19 | project.evaluationDependsOn(":app")
20 | }
21 |
22 | tasks.register("clean") {
23 | delete(rootProject.layout.buildDirectory)
24 | }
25 |
--------------------------------------------------------------------------------
/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/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 = BleOta
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.vovagorodok.ble_ota_app
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2023 com.vovagorodok. All rights reserved.
15 |
--------------------------------------------------------------------------------
/doc/SETUP.md:
--------------------------------------------------------------------------------
1 | # Setup guide
2 |
3 | ## Setup environment using snap
4 | ### Android Studio:
5 | ```
6 | sudo snap install android-studio --classic
7 | android-studio
8 | ```
9 | In SDK manager install `Android SDK Command-line Tools`.
10 | And `Android Emulator`/`Android SDK Build-Tools`/`Android SDK Platform-Tools` if needed.
11 | Create virtual device if needed.
12 |
13 | ### Flutter:
14 | ```
15 | sudo snap install flutter --classic
16 | flutter config --android-studio-dir /snap/android-studio/current
17 | flutter doctor --android-licenses
18 | flutter doctor
19 | ```
20 |
21 | ### VS Code:
22 | Install Flutter extension
23 |
24 | ## Links
25 | https://docs.flutter.dev/get-started/install/linux
26 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
13 | void RegisterPlugins(flutter::PluginRegistry* registry) {
14 | PermissionHandlerWindowsPluginRegisterWithRegistrar(
15 | registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
16 | UniversalBlePluginCApiRegisterWithRegistrar(
17 | registry->GetRegistrarForPlugin("UniversalBlePluginCApi"));
18 | UrlLauncherWindowsRegisterWithRegistrar(
19 | registry->GetRegistrarForPlugin("UrlLauncherWindows"));
20 | }
21 |
--------------------------------------------------------------------------------
/linux/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | url_launcher_linux
7 | )
8 |
9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
10 | )
11 |
12 | set(PLUGIN_BUNDLED_LIBRARIES)
13 |
14 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
19 | endforeach(plugin)
20 |
21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
24 | endforeach(ffi_plugin)
25 |
--------------------------------------------------------------------------------
/android/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | val flutterSdkPath =
3 | run {
4 | val properties = java.util.Properties()
5 | file("local.properties").inputStream().use { properties.load(it) }
6 | val flutterSdkPath = properties.getProperty("flutter.sdk")
7 | require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
8 | flutterSdkPath
9 | }
10 |
11 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
12 |
13 | repositories {
14 | google()
15 | mavenCentral()
16 | gradlePluginPortal()
17 | }
18 | }
19 |
20 | plugins {
21 | id("dev.flutter.flutter-plugin-loader") version "1.0.0"
22 | id("com.android.application") version "8.9.1" apply false
23 | id("org.jetbrains.kotlin.android") version "2.1.0" apply false
24 | }
25 |
26 | include(":app")
27 |
--------------------------------------------------------------------------------
/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 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | .vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Symbolication related
36 | app.*.symbols
37 |
38 | # Obfuscation related
39 | app.*.map.json
40 |
41 | # Android Studio will place build artifacts here
42 | /android/app/debug
43 | /android/app/profile
44 | /android/app/release
45 |
46 | # Upload keystore
47 | upload-keystore.jks
--------------------------------------------------------------------------------
/windows/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | permission_handler_windows
7 | universal_ble
8 | url_launcher_windows
9 | )
10 |
11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
12 | )
13 |
14 | set(PLUGIN_BUNDLED_LIBRARIES)
15 |
16 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
17 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
18 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
21 | endforeach(plugin)
22 |
23 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
26 | endforeach(ffi_plugin)
27 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "BleOta",
3 | "short_name": "BleOta",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A new Flutter project.",
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 | }
36 |
--------------------------------------------------------------------------------
/.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: "d693b4b9dbac2acd4477aea4555ca6dcbea44ba2"
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: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
17 | base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
18 | - platform: android
19 | create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
20 | base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
21 |
22 | # User provided section
23 |
24 | # List of Local paths (relative to this file) that should be
25 | # ignored by the migrate tool.
26 | #
27 | # Files that are not part of the templates will be ignored by default.
28 | unmanaged_files:
29 | - 'lib/main.dart'
30 | - 'ios/Runner.xcodeproj/project.pbxproj'
31 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/linux/runner/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.13)
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}
10 | "main.cc"
11 | "my_application.cc"
12 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
13 | )
14 |
15 | # Apply the standard set of build settings. This can be removed for applications
16 | # that need different build settings.
17 | apply_standard_settings(${BINARY_NAME})
18 |
19 | # Add preprocessor definitions for the application ID.
20 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
21 |
22 | # Add dependency libraries. Add any application-specific dependencies here.
23 | target_link_libraries(${BINARY_NAME} PRIVATE flutter)
24 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
25 |
26 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
27 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Włodzimierz Ciesielski
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:ble_ota_app/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const BleOtaApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/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 |
32 |
33 |
--------------------------------------------------------------------------------
/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 | import device_info_plus
9 | import file_picker
10 | import package_info_plus
11 | import path_provider_foundation
12 | import reactive_ble_mobile
13 | import shared_preferences_foundation
14 | import universal_ble
15 | import url_launcher_macos
16 | import wakelock_plus
17 |
18 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
19 | DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
20 | FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
21 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
22 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
23 | ReactiveBlePlugin.register(with: registry.registrar(forPlugin: "ReactiveBlePlugin"))
24 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
25 | UniversalBlePlugin.register(with: registry.registrar(forPlugin: "UniversalBlePlugin"))
26 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
27 | WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
28 | }
29 |
--------------------------------------------------------------------------------
/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.CreateAndShow(L"BleOta", 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 |
--------------------------------------------------------------------------------
/lib/src/settings/settings.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io' show Platform;
2 |
3 | import 'package:flutter_settings_screens/flutter_settings_screens.dart';
4 | import 'package:flutter/foundation.dart' show kIsWeb;
5 | import 'package:meta/meta.dart';
6 |
7 | @immutable
8 | class Setting {
9 | const Setting({
10 | required this.key,
11 | required this.defaultValue,
12 | });
13 |
14 | T get value => Settings.getValue(key, defaultValue: defaultValue) as T;
15 |
16 | final String key;
17 | final T defaultValue;
18 | }
19 |
20 | const infiniteScan = Setting(
21 | key: 'key-infinite-scan',
22 | defaultValue: false,
23 | );
24 | const skipInfoReading = Setting(
25 | key: 'key-skip-info-reading',
26 | defaultValue: false,
27 | );
28 | const alwaysAllowLocalFilesUpload = Setting(
29 | key: 'key-allow-local-upload',
30 | defaultValue: false,
31 | );
32 | final disableBuffer = Setting(
33 | key: 'key-disable-buffer',
34 | defaultValue: false,
35 | );
36 | final sequentialUpload = Setting(
37 | key: 'key-sequential-upload',
38 | defaultValue: _isSequentialUploadRequired,
39 | );
40 | const manufacturesDictUrl = Setting(
41 | key: 'key-dict-url',
42 | defaultValue:
43 | "https://raw.githubusercontent.com/vovagorodok/ble_ota_app/main/resources/manufactures.yaml",
44 | );
45 | const maxMtuSize = Setting(
46 | key: 'key-max-mtu-size',
47 | defaultValue: 515,
48 | );
49 |
50 | final _isSequentialUploadRequired =
51 | !kIsWeb && !Platform.isAndroid && !Platform.isIOS;
52 |
--------------------------------------------------------------------------------
/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
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "filename": "app_icon_16.png",
5 | "idiom": "mac",
6 | "scale": "1x",
7 | "size": "16x16"
8 | },
9 | {
10 | "filename": "app_icon_32.png",
11 | "idiom": "mac",
12 | "scale": "2x",
13 | "size": "16x16"
14 | },
15 | {
16 | "filename": "app_icon_32.png",
17 | "idiom": "mac",
18 | "scale": "1x",
19 | "size": "32x32"
20 | },
21 | {
22 | "filename": "app_icon_64.png",
23 | "idiom": "mac",
24 | "scale": "2x",
25 | "size": "32x32"
26 | },
27 | {
28 | "filename": "app_icon_128.png",
29 | "idiom": "mac",
30 | "scale": "1x",
31 | "size": "128x128"
32 | },
33 | {
34 | "filename": "app_icon_256.png",
35 | "idiom": "mac",
36 | "scale": "2x",
37 | "size": "128x128"
38 | },
39 | {
40 | "filename": "app_icon_256.png",
41 | "idiom": "mac",
42 | "scale": "1x",
43 | "size": "256x256"
44 | },
45 | {
46 | "filename": "app_icon_512.png",
47 | "idiom": "mac",
48 | "scale": "2x",
49 | "size": "256x256"
50 | },
51 | {
52 | "filename": "app_icon_512.png",
53 | "idiom": "mac",
54 | "scale": "1x",
55 | "size": "512x512"
56 | },
57 | {
58 | "filename": "app_icon_1024.png",
59 | "idiom": "mac",
60 | "scale": "2x",
61 | "size": "512x512"
62 | }
63 | ],
64 | "info": {
65 | "author": "icons_launcher",
66 | "version": 1
67 | }
68 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: ble_ota_app
2 | description: Upload firmware over bluetooth.
3 | publish_to: 'none'
4 | version: 2.0.1+14
5 |
6 | environment:
7 | sdk: '>=3.0.0 <4.0.0'
8 |
9 | dependencies:
10 | flutter:
11 | sdk: flutter
12 | ble_backend: ^1.4.0
13 | ble_backend_factory: ^1.4.2
14 | ble_ota: ^2.0.1
15 | cupertino_icons: ^1.0.8
16 | file_picker: ^10.3.3
17 | http: ^1.5.0
18 | permission_handler: ^12.0.1
19 | device_info_plus: ^12.1.0
20 | wakelock_plus: ^1.4.0
21 | expandable: ^5.0.1
22 | meta: ^1.16.0
23 | jumping_dot: ^0.0.7
24 | easy_localization: ^3.0.8
25 | flutter_markdown: ^0.7.7
26 | flutter_pin_code_fields: ^2.2.0
27 | flutter_settings_screens: ^0.3.4
28 | url_launcher: ^6.3.2
29 |
30 | dev_dependencies:
31 | flutter_test:
32 | sdk: flutter
33 | flutter_lints: ^6.0.0
34 | icons_launcher: ^3.0.3
35 |
36 | flutter:
37 | uses-material-design: true
38 | assets:
39 | - assets/translations/
40 |
41 | icons_launcher:
42 | image_path: "assets/images/icon.png"
43 | platforms:
44 | android:
45 | enable: true
46 | image_path: "assets/images/icon.png"
47 | adaptive_background_color: '#ffffff'
48 | adaptive_foreground_image: "assets/images/icon_color_foreground.png"
49 | adaptive_monochrome_image: "assets/images/icon_mono_foreground.png"
50 | ios:
51 | enable: true
52 | image_path: "assets/images/icon.png"
53 | web:
54 | enable: true
55 | image_path: "assets/images/icon.png"
56 | favicon_path: "assets/images/icon.png"
57 | macos:
58 | enable: true
59 | image_path: "assets/images/icon.png"
60 | windows:
61 | enable: true
62 | image_path: "assets/images/icon.png"
63 | linux:
64 | enable: false
65 | image_path: "assets/images/icon.png"
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BleOta
2 | Graphical application for upload firmware over Bluetooth.
3 |
4 | Local files upload is disabled by default to prevent unknown firmware upload by end users, enable it by changing `Always allow local files upload` in `Settings`.
5 | Additionally update functionality for specific hardwares is supported.
6 | If you want end users have ability to update your hardware check `doc/REGISTER_HARDWARE.md`.
7 |
8 | Fully works on `Android` and `iOS`.
9 | On desktop `Web` can require additional browser flags, see `doc/USING.md`.
10 | For other OS it depends on internal libraries.
11 |
12 | [
](https://play.google.com/store/apps/details?id=com.vovagorodok.ble_ota_app)
15 | [
](https://f-droid.org/packages/com.vovagorodok.ble_ota_app/)
18 | [
](https://itunes.apple.com/us/app/ble_ota_app/id0000000000)
21 |
22 | > **REMARK**: Application not released on `App Store` yet.
23 | > Apple corporation require `100$/year` developer fees even for free and open source applications.
24 | > If you want to support project, fill free to send me small amout or help with idea how to release app in `iOS` without developer fees.
25 |
26 | ## Peripheral device side
27 | Arduino library: https://github.com/vovagorodok/ArduinoBleOTA
28 |
--------------------------------------------------------------------------------
/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_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
37 |
38 | # Run the Flutter tool portions of the build. This must not be removed.
39 | add_dependencies(${BINARY_NAME} flutter_assemble)
40 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:ble_backend_factory/ble_central.dart';
2 | import 'package:ble_ota_app/src/screens/scanner_screen.dart';
3 | import 'package:ble_ota/ble/uuids.dart';
4 | import 'package:easy_localization/easy_localization.dart';
5 | import 'package:flutter_settings_screens/flutter_settings_screens.dart';
6 | import 'package:flutter/material.dart';
7 |
8 | void main() async {
9 | await Settings.init();
10 | WidgetsFlutterBinding.ensureInitialized();
11 | await EasyLocalization.ensureInitialized();
12 | runApp(
13 | EasyLocalization(
14 | supportedLocales: const [
15 | Locale('en', 'US'),
16 | Locale('pl', 'PL'),
17 | Locale('ru', 'RU'),
18 | Locale('uk', 'UA'),
19 | ],
20 | path: 'assets/translations',
21 | fallbackLocale: const Locale('en', 'US'),
22 | child: const BleOtaApp()),
23 | );
24 | }
25 |
26 | class BleOtaApp extends StatelessWidget {
27 | const BleOtaApp({super.key});
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | return MaterialApp(
32 | title: 'Ble Ota',
33 | theme: ThemeData(
34 | useMaterial3: true,
35 | colorScheme: ColorScheme.fromSeed(
36 | seedColor: Colors.indigo,
37 | brightness: Brightness.light,
38 | ),
39 | ),
40 | darkTheme: ThemeData(
41 | useMaterial3: true,
42 | colorScheme: ColorScheme.fromSeed(
43 | seedColor: Colors.indigo,
44 | brightness: Brightness.dark,
45 | ),
46 | ),
47 | localizationsDelegates: context.localizationDelegates,
48 | supportedLocales: context.supportedLocales,
49 | locale: context.locale,
50 | home: ScannerScreen(
51 | bleCentral: bleCentral,
52 | bleScanner: bleCentral.createScanner(serviceIds: [serviceUuid])),
53 | debugShowCheckedModeBanner: false,
54 | );
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/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 | std::string utf8_string;
52 | if (target_length == 0 || target_length > utf8_string.max_size()) {
53 | return utf8_string;
54 | }
55 | utf8_string.resize(target_length);
56 | int converted_length = ::WideCharToMultiByte(
57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
58 | -1, utf8_string.data(),
59 | target_length, nullptr, nullptr);
60 | if (converted_length == 0) {
61 | return std::string();
62 | }
63 | return utf8_string;
64 | }
65 |
--------------------------------------------------------------------------------
/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 | return true;
30 | }
31 |
32 | void FlutterWindow::OnDestroy() {
33 | if (flutter_controller_) {
34 | flutter_controller_ = nullptr;
35 | }
36 |
37 | Win32Window::OnDestroy();
38 | }
39 |
40 | LRESULT
41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
42 | WPARAM const wparam,
43 | LPARAM const lparam) noexcept {
44 | // Give Flutter, including plugins, an opportunity to handle window messages.
45 | if (flutter_controller_) {
46 | std::optional result =
47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
48 | lparam);
49 | if (result) {
50 | return *result;
51 | }
52 | }
53 |
54 | switch (message) {
55 | case WM_FONTCHANGE:
56 | flutter_controller_->engine()->ReloadSystemFonts();
57 | break;
58 | }
59 |
60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
61 | }
62 |
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | BleOta
33 |
34 |
35 |
39 |
40 |
41 |
42 |
43 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/.github/workflows/dart.yml:
--------------------------------------------------------------------------------
1 | name: "Build & Release"
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | build:
8 | name: Build & Release
9 | runs-on: macos-latest
10 |
11 | steps:
12 | #1 Checkout repository
13 | - name: Checkout Repository
14 | uses: actions/checkout@v3
15 |
16 | #2 setup java
17 | - name: Set Up Java
18 | uses: actions/setup-java@v3.12.0
19 | with:
20 | distribution: 'oracle'
21 | java-version: '17'
22 |
23 | #3 setup Flutter
24 | - name: Set Up Flutter
25 | uses: subosito/flutter-action@v2
26 | with:
27 | flutter-version: '3.35.5'
28 | channel: 'stable'
29 |
30 | #4 install dependencies
31 | - name: Install Dependencies
32 | run: flutter pub get
33 |
34 | #5 run test
35 | # - name: Test flutter app
36 | # run: flutter test
37 |
38 | #6 build apk
39 | - name: Build APK
40 | run: flutter build apk --release
41 |
42 | #7 build aab
43 | - name: Build appBundle
44 | run: flutter build appbundle
45 |
46 | #8 build ipa
47 | - name: Build IPA
48 | run:
49 | flutter build ipa --no-codesign
50 |
51 | #9 build ipa
52 | - name: Rename packages
53 | run: |
54 | mv build/app/outputs/flutter-apk/app-release.apk build/ble_ota_app_v1.0.0.apk
55 | mv build/app/outputs/bundle/release/app-release.aab build/ble_ota_app_v1.0.0.aab
56 | mv build/ios/iphoneos/app.ipa build/ble_ota_app_v1.0.0.ipa
57 |
58 | #10 get those build to be available to download
59 | - name: Upload Artifacts
60 | uses: actions/upload-artifact@v2
61 | with:
62 | name: Releases
63 | path: |
64 | build/ble_ota_app_v1.0.0.apk
65 | build/ble_ota_app_v1.0.0.aab
66 | ble_ota_app_v1.0.0.ipa
67 |
68 | #11 create release with those builds
69 | - name: Create Release
70 | uses: ncipollo/release-action@v1
71 | with:
72 | artifacts: "build/ble_ota_app_v1.0.0.apk,build/ble_ota_app_v1.0.0.aab,ble_ota_app_v1.0.0.ipa"
73 | tag: v1.0.0
74 | token: ${{ secrets.TOKEN }}
75 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSBluetoothAlwaysUsageDescription
6 | Need BLE permission
7 | NSBluetoothPeripheralUsageDescription
8 | Need BLE permission
9 | CFBundleDevelopmentRegion
10 | $(DEVELOPMENT_LANGUAGE)
11 | CFBundleDisplayName
12 | BleOta
13 | CFBundleExecutable
14 | $(EXECUTABLE_NAME)
15 | CFBundleIdentifier
16 | $(PRODUCT_BUNDLE_IDENTIFIER)
17 | CFBundleInfoDictionaryVersion
18 | 6.0
19 | CFBundleName
20 | BleOta
21 | CFBundlePackageType
22 | APPL
23 | CFBundleShortVersionString
24 | $(FLUTTER_BUILD_NAME)
25 | CFBundleSignature
26 | ????
27 | CFBundleVersion
28 | $(FLUTTER_BUILD_NUMBER)
29 | LSRequiresIPhoneOS
30 |
31 | UILaunchStoryboardName
32 | LaunchScreen
33 | UIMainStoryboardFile
34 | Main
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationLandscapeLeft
39 | UIInterfaceOrientationLandscapeRight
40 |
41 | UISupportedInterfaceOrientations~ipad
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationPortraitUpsideDown
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 |
48 | UIViewControllerBasedStatusBarAppearance
49 |
50 | CADisableMinimumFrameDurationOnPhone
51 |
52 | UIApplicationSupportsIndirectInputEvents
53 |
54 | CFBundleLocalizations
55 |
56 | en
57 | pl
58 | ru
59 | uk
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/android/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import java.util.Properties
2 | import java.io.FileInputStream
3 |
4 | plugins {
5 | id("com.android.application")
6 | id("kotlin-android")
7 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
8 | id("dev.flutter.flutter-gradle-plugin")
9 | }
10 |
11 | val keystoreProperties = Properties()
12 | val keystorePropertiesFile = rootProject.file("key.properties")
13 | if (keystorePropertiesFile.exists()) {
14 | keystoreProperties.load(FileInputStream(keystorePropertiesFile))
15 | }
16 |
17 | android {
18 | namespace = "com.vovagorodok.ble_ota_app"
19 | compileSdk = flutter.compileSdkVersion
20 | ndkVersion = flutter.ndkVersion
21 |
22 | compileOptions {
23 | sourceCompatibility = JavaVersion.VERSION_11
24 | targetCompatibility = JavaVersion.VERSION_11
25 | }
26 |
27 | kotlinOptions {
28 | jvmTarget = JavaVersion.VERSION_11.toString()
29 | }
30 |
31 | defaultConfig {
32 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
33 | applicationId = "com.vovagorodok.ble_ota_app"
34 | // You can update the following values to match your application needs.
35 | // For more information, see: https://flutter.dev/to/review-gradle-config.
36 | minSdk = flutter.minSdkVersion
37 | targetSdk = flutter.targetSdkVersion
38 | versionCode = flutter.versionCode
39 | versionName = flutter.versionName
40 | }
41 |
42 | signingConfigs {
43 | create("release") {
44 | keyAlias = keystoreProperties["keyAlias"] as String
45 | keyPassword = keystoreProperties["keyPassword"] as String
46 | storeFile = keystoreProperties["storeFile"]?.let { file(it) }
47 | storePassword = keystoreProperties["storePassword"] as String
48 | }
49 | }
50 |
51 | buildTypes {
52 | release {
53 | // TODO: Add your own signing config for the release build.
54 | // Signing with the debug keys for now, so `flutter run --release` works.
55 | signingConfig = signingConfigs.getByName("release")
56 | }
57 | }
58 | }
59 |
60 | flutter {
61 | source = "../.."
62 | }
63 |
--------------------------------------------------------------------------------
/doc/REGISTER_HARDWARE.md:
--------------------------------------------------------------------------------
1 | # Register hardware
2 |
3 | ## Staps
4 | 1. Create your hardware dicts (yaml or json) for each device
5 | 2. Create your hardwares dict (yaml or json) that contain links to your hardware dicts
6 | 3. Add link to your hardwares dict in `resources/manufactures.yaml`
7 | 4. Push change in `resources/manufactures.yaml` to this repo
8 |
9 | ## Hardware dict
10 | Required fields:
11 | ```yaml
12 | hardware_name: ...
13 | ...
14 | softwares:
15 | - software_name: ...
16 | software_version: ...
17 | software_path: ...
18 | ...
19 | ...
20 | ```
21 |
22 | General fields:
23 | - required `hardware_name` - string
24 | - required `softwares` - list
25 | - optional `hardware_icon` - string contains url to icon
26 | - optional `hardware_text` - string contains url to text about hardware in markdown
27 | - optional `hardware_page` - string contains url to hardware web page
28 |
29 | Software fields:
30 | - required `software_name` - string
31 | - required `software_version` - list of ints contains \[major, minor, patch\]
32 | - required `software_path` - string contains url to bin file
33 | - optional `software_icon` - string contains url to icon
34 | - optional `software_text` - string contains url to text about software in markdown
35 | - optional `software_page` - string contains url to software web page
36 | - optional `software_size` - int contains original size that is required if software compressed
37 | - optional `hardware_version` - specific version of hardware that software is for
38 | - optional `min_hardware_version` - min version of hardware that software is for
39 | - optional `max_hardware_version` - max version of hardware that software is for
40 |
41 | ## Examples
42 | ### ArduinoBleOTA
43 | Files:
44 | - hardwares dict: `example_hardwares.yaml/json`
45 | - hardware dicts: `example_hardware_esp32.yaml/json` and `example_hardware_samd.yaml/json`
46 |
47 | Link: https://github.com/vovagorodok/ArduinoBleOTA/tree/main/tools/release_builder.
48 |
49 | ## Signature and compression
50 | Generate signature only on original `firmware.bin`:
51 | ```
52 | openssl dgst -sign priv_key.pem -keyform PEM -sha256 -out signature.sig -binary firmware.bin
53 | pigz -kzc firmware.bin > firmware.bin.zlib
54 | cat firmware.bin.zlib signature.sig > firmware.sig.bin.zlib
55 | ```
56 | More in `SECURITY.md` and `COMPRESSION.md` at https://github.com/vovagorodok/ArduinoBleOTA/tree/main/doc
57 | Remember to fill `software_size` field with original `firmware.bin` size if software is compressed.
58 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
18 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
33 |
36 |
37 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/lib/src/screens/info_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:ble_ota_app/src/ui/jumping_dots.dart';
2 | import 'package:ble_ota_app/src/ui/ui_consts.dart';
3 | import 'package:flutter_markdown/flutter_markdown.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:http/http.dart' as http;
6 | import 'package:url_launcher/url_launcher.dart';
7 |
8 | class InfoScreen extends StatefulWidget {
9 | const InfoScreen({
10 | required this.title,
11 | required this.textUrl,
12 | this.pageUrl,
13 | super.key,
14 | });
15 |
16 | final String title;
17 | final String textUrl;
18 | final String? pageUrl;
19 |
20 | @override
21 | State createState() => InfoScreenState();
22 | }
23 |
24 | class InfoScreenState extends State {
25 | String? _text;
26 |
27 | Future _fetchHttpText(String url) async {
28 | try {
29 | final response = await http.get(Uri.parse(url));
30 | if (response.statusCode != 200) {
31 | setState(() {
32 | _text = '';
33 | });
34 | return;
35 | }
36 |
37 | setState(() {
38 | _text = response.body;
39 | });
40 | } catch (_) {
41 | setState(() {
42 | _text = '';
43 | });
44 | }
45 | }
46 |
47 | @override
48 | void initState() {
49 | super.initState();
50 | _fetchHttpText(widget.textUrl);
51 | }
52 |
53 | @override
54 | Widget build(BuildContext context) {
55 | return Scaffold(
56 | primary: MediaQuery.of(context).orientation == Orientation.portrait,
57 | appBar: AppBar(
58 | title: Text(widget.title),
59 | centerTitle: true,
60 | leading: IconButton(
61 | icon: const Icon(Icons.arrow_back_rounded),
62 | onPressed: () {
63 | Navigator.pop(context);
64 | }),
65 | actions: [
66 | if (widget.pageUrl != null)
67 | IconButton(
68 | icon: const Icon(Icons.language_rounded),
69 | onPressed: () async =>
70 | await launchUrl(Uri.parse(widget.pageUrl!)),
71 | )
72 | ],
73 | ),
74 | body: SafeArea(
75 | child: _text != null
76 | ? Markdown(
77 | data: _text!,
78 | onTapLink: (String text, String? href, String title) async =>
79 | await launchUrl(Uri.parse(href!)),
80 | padding: const EdgeInsets.all(screenPadding),
81 | )
82 | : Padding(
83 | padding: const EdgeInsets.all(screenPadding),
84 | child: createJumpingDots(),
85 | ),
86 | ),
87 | );
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "filename": "Icon-App-20x20@2x.png",
5 | "idiom": "iphone",
6 | "scale": "2x",
7 | "size": "20x20"
8 | },
9 | {
10 | "filename": "Icon-App-20x20@3x.png",
11 | "idiom": "iphone",
12 | "scale": "3x",
13 | "size": "20x20"
14 | },
15 | {
16 | "filename": "Icon-App-29x29@1x.png",
17 | "idiom": "iphone",
18 | "scale": "1x",
19 | "size": "29x29"
20 | },
21 | {
22 | "filename": "Icon-App-29x29@2x.png",
23 | "idiom": "iphone",
24 | "scale": "2x",
25 | "size": "29x29"
26 | },
27 | {
28 | "filename": "Icon-App-29x29@3x.png",
29 | "idiom": "iphone",
30 | "scale": "3x",
31 | "size": "29x29"
32 | },
33 | {
34 | "filename": "Icon-App-40x40@2x.png",
35 | "idiom": "iphone",
36 | "scale": "2x",
37 | "size": "40x40"
38 | },
39 | {
40 | "filename": "Icon-App-40x40@3x.png",
41 | "idiom": "iphone",
42 | "scale": "3x",
43 | "size": "40x40"
44 | },
45 | {
46 | "filename": "Icon-App-60x60@2x.png",
47 | "idiom": "iphone",
48 | "scale": "2x",
49 | "size": "60x60"
50 | },
51 | {
52 | "filename": "Icon-App-60x60@3x.png",
53 | "idiom": "iphone",
54 | "scale": "3x",
55 | "size": "60x60"
56 | },
57 | {
58 | "filename": "Icon-App-20x20@1x.png",
59 | "idiom": "ipad",
60 | "scale": "1x",
61 | "size": "20x20"
62 | },
63 | {
64 | "filename": "Icon-App-20x20@2x.png",
65 | "idiom": "ipad",
66 | "scale": "2x",
67 | "size": "20x20"
68 | },
69 | {
70 | "filename": "Icon-App-29x29@1x.png",
71 | "idiom": "ipad",
72 | "scale": "1x",
73 | "size": "29x29"
74 | },
75 | {
76 | "filename": "Icon-App-29x29@2x.png",
77 | "idiom": "ipad",
78 | "scale": "2x",
79 | "size": "29x29"
80 | },
81 | {
82 | "filename": "Icon-App-40x40@1x.png",
83 | "idiom": "ipad",
84 | "scale": "1x",
85 | "size": "40x40"
86 | },
87 | {
88 | "filename": "Icon-App-40x40@2x.png",
89 | "idiom": "ipad",
90 | "scale": "2x",
91 | "size": "40x40"
92 | },
93 | {
94 | "filename": "Icon-App-76x76@1x.png",
95 | "idiom": "ipad",
96 | "scale": "1x",
97 | "size": "76x76"
98 | },
99 | {
100 | "filename": "Icon-App-76x76@2x.png",
101 | "idiom": "ipad",
102 | "scale": "2x",
103 | "size": "76x76"
104 | },
105 | {
106 | "filename": "Icon-App-83.5x83.5@2x.png",
107 | "idiom": "ipad",
108 | "scale": "2x",
109 | "size": "83.5x83.5"
110 | },
111 | {
112 | "filename": "Icon-App-1024x1024@1x.png",
113 | "idiom": "ios-marketing",
114 | "scale": "1x",
115 | "size": "1024x1024"
116 | }
117 | ],
118 | "info": {
119 | "author": "icons_launcher",
120 | "version": 1
121 | }
122 | }
--------------------------------------------------------------------------------
/linux/flutter/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # This file controls Flutter-level build steps. It should not be edited.
2 | cmake_minimum_required(VERSION 3.10)
3 |
4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
5 |
6 | # Configuration provided via flutter tool.
7 | include(${EPHEMERAL_DIR}/generated_config.cmake)
8 |
9 | # TODO: Move the rest of this into files in ephemeral. See
10 | # https://github.com/flutter/flutter/issues/57146.
11 |
12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...),
13 | # which isn't available in 3.10.
14 | function(list_prepend LIST_NAME PREFIX)
15 | set(NEW_LIST "")
16 | foreach(element ${${LIST_NAME}})
17 | list(APPEND NEW_LIST "${PREFIX}${element}")
18 | endforeach(element)
19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
20 | endfunction()
21 |
22 | # === Flutter Library ===
23 | # System-level dependencies.
24 | find_package(PkgConfig REQUIRED)
25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
28 |
29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
30 |
31 | # Published to parent scope for install step.
32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
36 |
37 | list(APPEND FLUTTER_LIBRARY_HEADERS
38 | "fl_basic_message_channel.h"
39 | "fl_binary_codec.h"
40 | "fl_binary_messenger.h"
41 | "fl_dart_project.h"
42 | "fl_engine.h"
43 | "fl_json_message_codec.h"
44 | "fl_json_method_codec.h"
45 | "fl_message_codec.h"
46 | "fl_method_call.h"
47 | "fl_method_channel.h"
48 | "fl_method_codec.h"
49 | "fl_method_response.h"
50 | "fl_plugin_registrar.h"
51 | "fl_plugin_registry.h"
52 | "fl_standard_message_codec.h"
53 | "fl_standard_method_codec.h"
54 | "fl_string_codec.h"
55 | "fl_value.h"
56 | "fl_view.h"
57 | "flutter_linux.h"
58 | )
59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
60 | add_library(flutter INTERFACE)
61 | target_include_directories(flutter INTERFACE
62 | "${EPHEMERAL_DIR}"
63 | )
64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
65 | target_link_libraries(flutter INTERFACE
66 | PkgConfig::GTK
67 | PkgConfig::GLIB
68 | PkgConfig::GIO
69 | )
70 | add_dependencies(flutter flutter_assemble)
71 |
72 | # === Flutter tool backend ===
73 | # _phony_ is a non-existent file to force this command to run every time,
74 | # since currently there's no way to get a full input/output list from the
75 | # flutter tool.
76 | add_custom_command(
77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_
79 | COMMAND ${CMAKE_COMMAND} -E env
80 | ${FLUTTER_TOOL_ENVIRONMENT}
81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
83 | VERBATIM
84 | )
85 | add_custom_target(flutter_assemble DEPENDS
86 | "${FLUTTER_LIBRARY}"
87 | ${FLUTTER_LIBRARY_HEADERS}
88 | )
89 |
--------------------------------------------------------------------------------
/lib/src/screens/settings_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:ble_ota_app/src/settings/settings.dart';
2 | import 'package:easy_localization/easy_localization.dart';
3 | import 'package:flutter_settings_screens/flutter_settings_screens.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | class SettingsScreen extends StatefulWidget {
7 | const SettingsScreen({super.key});
8 |
9 | @override
10 | State createState() => SettingsScreenState();
11 | }
12 |
13 | class SettingsScreenState extends State {
14 | @override
15 | Widget build(BuildContext context) {
16 | return Scaffold(
17 | primary: MediaQuery.of(context).orientation == Orientation.portrait,
18 | appBar: AppBar(
19 | title: Text(tr('Settings')),
20 | centerTitle: true,
21 | leading: IconButton(
22 | icon: const Icon(Icons.arrow_back_rounded),
23 | onPressed: () {
24 | Navigator.pop(context);
25 | }),
26 | actions: [
27 | IconButton(
28 | icon: const Icon(Icons.cached_rounded),
29 | onPressed: () => setState(Settings.clearCache),
30 | ),
31 | ],
32 | ),
33 | body: SafeArea(
34 | child: ListView(children: [
35 | SettingsGroup(
36 | title: tr('General'),
37 | children: [
38 | SwitchSettingsTile(
39 | title: tr('InfiniteScan'),
40 | settingKey: infiniteScan.key,
41 | defaultValue: infiniteScan.defaultValue,
42 | showDivider: true,
43 | ),
44 | ],
45 | ),
46 | SettingsGroup(
47 | title: tr('DeveloperOptions'),
48 | children: [
49 | SwitchSettingsTile(
50 | title: tr('SkipInfoReading'),
51 | settingKey: skipInfoReading.key,
52 | defaultValue: skipInfoReading.defaultValue,
53 | showDivider: false,
54 | ),
55 | SwitchSettingsTile(
56 | title: tr('AlwaysAllowLocalFilesUpload'),
57 | settingKey: alwaysAllowLocalFilesUpload.key,
58 | defaultValue: alwaysAllowLocalFilesUpload.defaultValue,
59 | showDivider: false,
60 | ),
61 | SwitchSettingsTile(
62 | title: tr('DisableBuffer'),
63 | settingKey: disableBuffer.key,
64 | defaultValue: disableBuffer.defaultValue,
65 | showDivider: false,
66 | ),
67 | SwitchSettingsTile(
68 | title: tr('SequentialUpload'),
69 | settingKey: sequentialUpload.key,
70 | defaultValue: sequentialUpload.defaultValue,
71 | showDivider: false,
72 | ),
73 | TextInputSettingsTile(
74 | title: tr('ManufacturesDictionaryLink'),
75 | settingKey: manufacturesDictUrl.key,
76 | initialValue: manufacturesDictUrl.defaultValue,
77 | ),
78 | SliderSettingsTile(
79 | title: tr('MaxMtuSize'),
80 | settingKey: maxMtuSize.key,
81 | min: 23,
82 | max: 515,
83 | decimalPrecision: 0,
84 | defaultValue: maxMtuSize.defaultValue,
85 | ),
86 | ],
87 | ),
88 | ]),
89 | ),
90 | );
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/windows/runner/Runner.rc:
--------------------------------------------------------------------------------
1 | // Microsoft Visual C++ generated resource script.
2 | //
3 | #pragma code_page(65001)
4 | #include "resource.h"
5 |
6 | #define APSTUDIO_READONLY_SYMBOLS
7 | /////////////////////////////////////////////////////////////////////////////
8 | //
9 | // Generated from the TEXTINCLUDE 2 resource.
10 | //
11 | #include "winres.h"
12 |
13 | /////////////////////////////////////////////////////////////////////////////
14 | #undef APSTUDIO_READONLY_SYMBOLS
15 |
16 | /////////////////////////////////////////////////////////////////////////////
17 | // English (United States) resources
18 |
19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
21 |
22 | #ifdef APSTUDIO_INVOKED
23 | /////////////////////////////////////////////////////////////////////////////
24 | //
25 | // TEXTINCLUDE
26 | //
27 |
28 | 1 TEXTINCLUDE
29 | BEGIN
30 | "resource.h\0"
31 | END
32 |
33 | 2 TEXTINCLUDE
34 | BEGIN
35 | "#include ""winres.h""\r\n"
36 | "\0"
37 | END
38 |
39 | 3 TEXTINCLUDE
40 | BEGIN
41 | "\r\n"
42 | "\0"
43 | END
44 |
45 | #endif // APSTUDIO_INVOKED
46 |
47 |
48 | /////////////////////////////////////////////////////////////////////////////
49 | //
50 | // Icon
51 | //
52 |
53 | // Icon with lowest ID value placed first to ensure application icon
54 | // remains consistent on all systems.
55 | IDI_APP_ICON ICON "resources\\app_icon.ico"
56 |
57 |
58 | /////////////////////////////////////////////////////////////////////////////
59 | //
60 | // Version
61 | //
62 |
63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
65 | #else
66 | #define VERSION_AS_NUMBER 1,0,0,0
67 | #endif
68 |
69 | #if defined(FLUTTER_VERSION)
70 | #define VERSION_AS_STRING FLUTTER_VERSION
71 | #else
72 | #define VERSION_AS_STRING "1.0.0"
73 | #endif
74 |
75 | VS_VERSION_INFO VERSIONINFO
76 | FILEVERSION VERSION_AS_NUMBER
77 | PRODUCTVERSION VERSION_AS_NUMBER
78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
79 | #ifdef _DEBUG
80 | FILEFLAGS VS_FF_DEBUG
81 | #else
82 | FILEFLAGS 0x0L
83 | #endif
84 | FILEOS VOS__WINDOWS32
85 | FILETYPE VFT_APP
86 | FILESUBTYPE 0x0L
87 | BEGIN
88 | BLOCK "StringFileInfo"
89 | BEGIN
90 | BLOCK "040904e4"
91 | BEGIN
92 | VALUE "CompanyName", "com.vovagorodok" "\0"
93 | VALUE "FileDescription", "BleOta" "\0"
94 | VALUE "FileVersion", VERSION_AS_STRING "\0"
95 | VALUE "InternalName", "BleOta" "\0"
96 | VALUE "LegalCopyright", "Copyright (C) 2023 com.vovagorodok. All rights reserved." "\0"
97 | VALUE "OriginalFilename", "BleOta.exe" "\0"
98 | VALUE "ProductName", "BleOta" "\0"
99 | VALUE "ProductVersion", VERSION_AS_STRING "\0"
100 | END
101 | END
102 | BLOCK "VarFileInfo"
103 | BEGIN
104 | VALUE "Translation", 0x409, 1252
105 | END
106 | END
107 |
108 | #endif // English (United States) resources
109 | /////////////////////////////////////////////////////////////////////////////
110 |
111 |
112 |
113 | #ifndef APSTUDIO_INVOKED
114 | /////////////////////////////////////////////////////////////////////////////
115 | //
116 | // Generated from the TEXTINCLUDE 3 resource.
117 | //
118 |
119 |
120 | /////////////////////////////////////////////////////////////////////////////
121 | #endif // not APSTUDIO_INVOKED
122 |
--------------------------------------------------------------------------------
/lib/src/utils/string_forms.dart:
--------------------------------------------------------------------------------
1 | import 'package:ble_ota/ble_ota.dart';
2 | import 'package:ble_ota/core/errors.dart';
3 | import 'package:ble_ota/core/version.dart';
4 | import 'package:easy_localization/easy_localization.dart';
5 |
6 | String determineError(BleOtaState state) {
7 | switch (state.error) {
8 | case Error.deviceError:
9 | return tr('DeviceError');
10 | case Error.noDeviceResponse:
11 | return tr('NoDeviceResponse');
12 | case Error.unexpectedDeviceResponse:
13 | return tr('UnexpectedDeviceResponse', args: ['${state.errorCode}']);
14 | case Error.incorrectDeviceResponse:
15 | return tr('IncorrectDeviceResponse');
16 |
17 | case Error.networkError:
18 | return tr('NetworkError');
19 | case Error.unexpectedNetworkResponse:
20 | return tr('UnexpectedNetworkResponse', args: ['${state.errorCode}']);
21 | case Error.incorrectNetworkFile:
22 | return tr('IncorrectNetworkFile');
23 |
24 | case Error.incorrectMessageSize:
25 | return tr('IncorrectMessageSize');
26 | case Error.incorrectMessageHeader:
27 | return tr('IncorrectMessageHeader');
28 | case Error.incorrectFirmwareSize:
29 | return tr('IncorrectFirmwareSize');
30 | case Error.internalStorageError:
31 | return tr('InternalStorageError');
32 | case Error.uploadDisabled:
33 | return tr('UploadDisabled');
34 | case Error.uploadRunning:
35 | return tr('UploadRunning');
36 | case Error.uploadStopped:
37 | return tr('UploadStopped');
38 | case Error.installRunning:
39 | return tr('InstallRunning');
40 | case Error.bufferDisabled:
41 | return tr('BufferDisabled');
42 | case Error.bufferOverflow:
43 | return tr('BufferOverflow');
44 | case Error.compressionNotSupported:
45 | return tr('CompressionNotSupported');
46 | case Error.incorrectCompression:
47 | return tr('IncorrectCompression');
48 | case Error.incorrectCompressedSize:
49 | return tr('IncorrectCompressedSize');
50 | case Error.incorrectCompressionChecksum:
51 | return tr('IncorrectCompressionChecksum');
52 | case Error.incorrectCompressionParam:
53 | return tr('IncorrectCompressionParam');
54 | case Error.incorrectCompressionEnd:
55 | return tr('IncorrectCompressionEnd');
56 | case Error.checksumNotSupported:
57 | return tr('ChecksumNotSupported');
58 | case Error.incorrectChecksum:
59 | return tr('IncorrectChecksum');
60 | case Error.signatureNotSupported:
61 | return tr('SignatureNotSupported');
62 | case Error.incorrectSignature:
63 | return tr('IncorrectSignature');
64 | case Error.incorrectSignatureSize:
65 | return tr('IncorrectSignatureSize');
66 | case Error.pinNotSupported:
67 | return tr('PinNotSupported');
68 | case Error.pinChangeError:
69 | return tr('PinChangeError');
70 |
71 | default:
72 | return tr('UnknownError', args: ['${state.errorCode}']);
73 | }
74 | }
75 |
76 | String createDeviceString(
77 | BleOtaState bleOtaState, String name, Version version) {
78 | if (bleOtaState.deviceInfo.isAvailable) {
79 | return "$name v$version";
80 | } else if (bleOtaState.status == BleOtaStatus.init) {
81 | return tr('Loading..');
82 | } else {
83 | return tr('NoInformation');
84 | }
85 | }
86 |
87 | String createHardwareString(BleOtaState bleOtaState) => createDeviceString(
88 | bleOtaState,
89 | bleOtaState.deviceInfo.hardwareName,
90 | bleOtaState.deviceInfo.hardwareVersion);
91 | String createSoftwareString(BleOtaState bleOtaState) => createDeviceString(
92 | bleOtaState,
93 | bleOtaState.deviceInfo.softwareName,
94 | bleOtaState.deviceInfo.softwareVersion);
95 |
--------------------------------------------------------------------------------
/assets/translations/en-US.json:
--------------------------------------------------------------------------------
1 | {
2 | "Devices": "Devices",
3 | "Scan": "Scan",
4 | "Stop": "Stop",
5 | "Settings": "Settings",
6 | "General": "General",
7 | "InfiniteScan": "Infinite scan",
8 | "DeveloperOptions": "Developer options",
9 | "SkipInfoReading": "Skip info reading",
10 | "AlwaysAllowLocalFilesUpload": "Always allow local files upload",
11 | "DisableBuffer" : "Disable buffer",
12 | "SequentialUpload" : "Sequential upload",
13 | "ManufacturesDictionaryLink": "Manufactures dictionary link",
14 | "MaxMtuSize": "Maximum MTU size",
15 | "ThisDeviceDoesNotSupportBluetooth": "This device does not support Bluetooth",
16 | "ThisBrowserDoesNotSupportBluetooth": "This browser does not support Bluetooth",
17 | "AuthorizeApplicationToUseBluetoothAndLocation": "Authorize application to use Bluetooth and location",
18 | "BluetoothIsDisabledTurnItOn": "Bluetooth is disabled, turn it on",
19 | "LocationServicesAreDisabledEnableThem": "Location services are disabled, enable them",
20 | "BluetoothIsUpAndRunning": "Bluetooth is up and running",
21 | "WaitingToFetchBluetoothStatus": "Waiting to fetch Bluetooth status: {}",
22 | "DeviceError": "Device error",
23 | "NoDeviceResponse": "No device response",
24 | "UnexpectedDeviceResponse": "Unexpected device response: {}",
25 | "IncorrectDeviceResponse": "Incorrect device response",
26 | "NetworkError": "Network error",
27 | "UnexpectedNetworkResponse": "Unexpected network response: {}",
28 | "IncorrectNetworkFile": "Incorrect network file",
29 | "IncorrectMessageSize": "Incorrect message size",
30 | "IncorrectMessageHeader": "Incorrect message header",
31 | "IncorrectFirmwareSize": "Incorrect firmware size",
32 | "InternalStorageError": "Internal storage error",
33 | "UploadDisabled": "Upload disabled",
34 | "UploadRunning": "Upload running",
35 | "UploadStopped": "Upload stopped",
36 | "InstallRunning": "Install running",
37 | "BufferDisabled": "Buffer disabled",
38 | "BufferOverflow": "Buffer overflow",
39 | "CompressionNotSupported": "Compression not supported",
40 | "IncorrectCompression": "Incorrect compression",
41 | "IncorrectCompressedSize": "Incorrect compressed size",
42 | "IncorrectCompressionChecksum": "Incorrect compression checksum",
43 | "IncorrectCompressionParam": "Incorrect compression param",
44 | "IncorrectCompressionEnd": "Incorrect compression end",
45 | "ChecksumNotSupported": "Checksum not supported",
46 | "IncorrectChecksum": "Incorrect checksum",
47 | "SignatureNotSupported": "Signature not supported",
48 | "IncorrectSignature": "Incorrect signature",
49 | "IncorrectSignatureSize": "Incorrect signature size",
50 | "PinNotSupported": "Pin not supported",
51 | "PinChangeError": "Pin change error",
52 | "UnknownError": "Unknown error: {}",
53 | "NoInformation": "No information",
54 | "Loading..": "Loading..",
55 | "Connecting..": "Connecting..",
56 | "Disconnecting..": "Disconnecting..",
57 | "Scanning..": "Scanning..",
58 | "Uploading..": "Uploading..",
59 | "Connected": "Connected",
60 | "Disconnected": "Disconnected",
61 | "NoAvailableSoftwares": "No available softwares",
62 | "NewestSoftwareAlreadyInstalled": "Newest software already installed",
63 | "NewSoftwareAvailable:": "New software available:",
64 | "AllAvailableSoftwares:" : "All available softwares:",
65 | "UploadFile": "Upload file",
66 | "Hardware:": "Hardware: {}",
67 | "Software:": "Software: {}",
68 | "Changing..": "Changing..",
69 | "Changed": "Changed",
70 | "ChangePinCode:": "Change pin code:",
71 | "Set": "Set",
72 | "Remove": "Remove"
73 | }
--------------------------------------------------------------------------------
/windows/runner/win32_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_WIN32_WINDOW_H_
2 | #define RUNNER_WIN32_WINDOW_H_
3 |
4 | #include
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be
11 | // inherited from by classes that wish to specialize with custom
12 | // rendering and input handling
13 | class Win32Window {
14 | public:
15 | struct Point {
16 | unsigned int x;
17 | unsigned int y;
18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {}
19 | };
20 |
21 | struct Size {
22 | unsigned int width;
23 | unsigned int height;
24 | Size(unsigned int width, unsigned int height)
25 | : width(width), height(height) {}
26 | };
27 |
28 | Win32Window();
29 | virtual ~Win32Window();
30 |
31 | // Creates and shows a win32 window with |title| and position and size using
32 | // |origin| and |size|. New windows are created on the default monitor. Window
33 | // sizes are specified to the OS in physical pixels, hence to ensure a
34 | // consistent size to will treat the width height passed in to this function
35 | // as logical pixels and scale to appropriate for the default monitor. Returns
36 | // true if the window was created successfully.
37 | bool CreateAndShow(const std::wstring& title,
38 | const Point& origin,
39 | const Size& size);
40 |
41 | // Release OS resources associated with window.
42 | void Destroy();
43 |
44 | // Inserts |content| into the window tree.
45 | void SetChildContent(HWND content);
46 |
47 | // Returns the backing Window handle to enable clients to set icon and other
48 | // window properties. Returns nullptr if the window has been destroyed.
49 | HWND GetHandle();
50 |
51 | // If true, closing this window will quit the application.
52 | void SetQuitOnClose(bool quit_on_close);
53 |
54 | // Return a RECT representing the bounds of the current client area.
55 | RECT GetClientArea();
56 |
57 | protected:
58 | // Processes and route salient window messages for mouse handling,
59 | // size change and DPI. Delegates handling of these to member overloads that
60 | // inheriting classes can handle.
61 | virtual LRESULT MessageHandler(HWND window,
62 | UINT const message,
63 | WPARAM const wparam,
64 | LPARAM const lparam) noexcept;
65 |
66 | // Called when CreateAndShow is called, allowing subclass window-related
67 | // setup. Subclasses should return false if setup fails.
68 | virtual bool OnCreate();
69 |
70 | // Called when Destroy is called.
71 | virtual void OnDestroy();
72 |
73 | private:
74 | friend class WindowClassRegistrar;
75 |
76 | // OS callback called by message pump. Handles the WM_NCCREATE message which
77 | // is passed when the non-client area is being created and enables automatic
78 | // non-client DPI scaling so that the non-client area automatically
79 | // responsponds to changes in DPI. All other messages are handled by
80 | // MessageHandler.
81 | static LRESULT CALLBACK WndProc(HWND const window,
82 | UINT const message,
83 | WPARAM const wparam,
84 | LPARAM const lparam) noexcept;
85 |
86 | // Retrieves a class instance pointer for |window|
87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept;
88 |
89 | bool quit_on_close_ = false;
90 |
91 | // window handle for top level window.
92 | HWND window_handle_ = nullptr;
93 |
94 | // window handle for hosted content.
95 | HWND child_content_ = nullptr;
96 | };
97 |
98 | #endif // RUNNER_WIN32_WINDOW_H_
99 |
--------------------------------------------------------------------------------
/assets/translations/ru-RU.json:
--------------------------------------------------------------------------------
1 | {
2 | "Devices": "Устройства",
3 | "Scan": "Сканируй",
4 | "Stop": "Стоп",
5 | "Settings": "Настройки",
6 | "General": "Общие",
7 | "InfiniteScan": "Бесконечное сканирование",
8 | "DeveloperOptions": "Параметры разработчика",
9 | "SkipInfoReading": "Пропустить чтение информации",
10 | "AlwaysAllowLocalFilesUpload": "Всегда разрешать локальную загрузку файлов",
11 | "DisableBuffer" : "Отключить буфер",
12 | "SequentialUpload" : "Последовательная загрузка",
13 | "ManufacturesDictionaryLink": "Ссылка на словарь производителей",
14 | "MaxMtuSize": "Максимальный размер MTU",
15 | "ThisDeviceDoesNotSupportBluetooth": "Это устройство не поддерживает Bluetooth",
16 | "ThisBrowserDoesNotSupportBluetooth": "Этот браузер не поддерживает Bluetooth",
17 | "AuthorizeApplicationToUseBluetoothAndLocation": "Разрешите приложению использовать Bluetooth и геолокацию",
18 | "BluetoothIsDisabledTurnItOn": "Bluetooth отключен, включите его",
19 | "LocationServicesAreDisabledEnableThem": "Службы геолокации отключены, включите их",
20 | "BluetoothIsUpAndRunning": "Bluetooth включен и работает",
21 | "WaitingToFetchBluetoothStatus": "Ожидание получения статуса Bluetooth: {}",
22 | "DeviceError": "Ошибка устройства",
23 | "NoDeviceResponse": "Устройство не отвечает",
24 | "UnexpectedDeviceResponse": "Неожиданный ответ устройства: {}",
25 | "IncorrectDeviceResponse": "Неправильный ответ устройства",
26 | "NetworkError": "Ошибка сети",
27 | "UnexpectedNetworkResponse": "Неожиданный ответ сети: {}",
28 | "IncorrectNetworkFile": "Неправильный сетевой файл",
29 | "IncorrectMessageSize": "Неправильный размер сообщения",
30 | "IncorrectMessageHeader": "Неправильный заголовок сообщения",
31 | "IncorrectFirmwareSize": "Неправильный размер прошивки",
32 | "InternalStorageError": "Ошибка внутренней памяти",
33 | "UploadDisabled": "Загрузка отключена",
34 | "UploadRunning": "Загрузка работает",
35 | "UploadStopped": "Загрузка остановлена",
36 | "InstallRunning": "Установка работает",
37 | "BufferDisabled": "Буфер отключен",
38 | "BufferOverflow": "Переполнение буфера",
39 | "CompressionNotSupported": "Сжатие не поддерживается",
40 | "IncorrectCompression": "Неправильное сжатие",
41 | "IncorrectCompressedSize": "Неправильный размер сжатия",
42 | "IncorrectCompressionChecksum": "Неправильная контрольная сумма сжатия",
43 | "IncorrectCompressionParam": "Неправильный параметр сжатия",
44 | "IncorrectCompressionEnd": "Неправильный конец сжатия",
45 | "ChecksumNotSupported": "Контрольная сумма не поддерживается",
46 | "IncorrectChecksum": "Неверная контрольная сумма",
47 | "SignatureNotSupported": "Подпись не поддерживается",
48 | "IncorrectSignature": "Неправильная подпись",
49 | "IncorrectSignatureSize": "Неправильный размер подписи",
50 | "PinNotSupported": "Пин не поддерживается",
51 | "PinChangeError": "Ошибка смены пина",
52 | "UnknownError": "Неизвестная ошибка: {}",
53 | "NoInformation": "Нет информации",
54 | "Loading..": "Загрузка..",
55 | "Connecting..": "Подключение..",
56 | "Disconnecting..": "Отключение..",
57 | "Scanning..": "Сканирование..",
58 | "Uploading..": "Загрузка..",
59 | "Connected": "Подключено",
60 | "Disconnected": "Отключено",
61 | "NoAvailableSoftwares": "Нет доступной прошивки",
62 | "NewestSoftwareAlreadyInstalled": "Самая новая прошивка уже установлена",
63 | "NewSoftwareAvailable:": "Доступная новая прошивка:",
64 | "AllAvailableSoftwares:" : "Все доступные прошивки:",
65 | "UploadFile": "Загрузить файл",
66 | "Hardware:": "Оборудование: {}",
67 | "Software:": "Прошивка: {}",
68 | "Changing..": "Изменение..",
69 | "Changed": "Изменено",
70 | "ChangePinCode:": "Смените пин код:",
71 | "Set": "Установить",
72 | "Remove": "Удалить"
73 | }
--------------------------------------------------------------------------------
/assets/translations/uk-UA.json:
--------------------------------------------------------------------------------
1 | {
2 | "Devices": "Пристрої",
3 | "Scan": "Скануй",
4 | "Stop": "Стоп",
5 | "Settings": "Налаштування",
6 | "General": "Загальні",
7 | "InfiniteScan": "Нескінченне сканування",
8 | "DeveloperOptions": "Параметри розробника",
9 | "SkipInfoReading": "Пропустити читання інформації",
10 | "AlwaysAllowLocalFilesUpload": "Завжди дозволяти локальне завантаження файлів",
11 | "DisableBuffer" : "Вимкнути буфер",
12 | "SequentialUpload" : "Послідовне завантаження",
13 | "ManufacturesDictionaryLink": "Посилання на словник виробників",
14 | "MaxMtuSize": "Максимальний розмір MTU",
15 | "ThisDeviceDoesNotSupportBluetooth": "Цей пристрій не підтримує Bluetooth",
16 | "ThisBrowserDoesNotSupportBluetooth": "Цей браузер не підтримує Bluetooth",
17 | "AuthorizeApplicationToUseBluetoothAndLocation": "Авторизуйте програму для використання Bluetooth і розташування",
18 | "BluetoothIsDisabledTurnItOn": "Bluetooth вимкнено, увімкніть його",
19 | "LocationServicesAreDisabledEnableThem": "Служби локації вимкнено, увімкніть їх",
20 | "BluetoothIsUpAndRunning": "Bluetooth запущено та працює",
21 | "WaitingToFetchBluetoothStatus": "Очікування отримання статусу Bluetooth: {}",
22 | "DeviceError": "Помилка пристрою",
23 | "NoDeviceResponse": "Пристрій не відповідає",
24 | "UnexpectedDeviceResponse": "Неочікувана відповідь пристрою: {}",
25 | "IncorrectDeviceResponse": "Неправильна відповідь пристрою",
26 | "NetworkError": "Помилка мережі",
27 | "UnexpectedNetworkResponse": "Неочікувана відповідь мережі: {}",
28 | "IncorrectNetworkFile": "Неправильний мережевий файл",
29 | "IncorrectMessageSize": "Неправильний розмір повідомлення",
30 | "IncorrectMessageHeader": "Неправильний заголовок повідомлення",
31 | "IncorrectFirmwareSize": "Неправильний розмір прошивки",
32 | "InternalStorageError": "Помилка внутрішньої пам'яті",
33 | "UploadDisabled": "Завантаження вимкнено",
34 | "UploadRunning": "Завантаження виконується",
35 | "UploadStopped": "Завантаження зупинено",
36 | "InstallRunning": "Інсталяція виконується",
37 | "BufferDisabled": "Буфер вимкнено",
38 | "BufferOverflow": "Переповнення буфера",
39 | "CompressionNotSupported": "Компресія не підтримується",
40 | "IncorrectCompression": "Неправильна компресія",
41 | "IncorrectCompressedSize": "Неправильний розмір компресії",
42 | "IncorrectCompressionChecksum": "Неправильна контрольна сума компресії",
43 | "IncorrectCompressionParam": "Неправильний параметр компресії",
44 | "IncorrectCompressionEnd": "Неправильний кінець компресії",
45 | "ChecksumNotSupported": "Контрольна сума не підтримується",
46 | "IncorrectChecksum": "Неправильна контрольна сума",
47 | "SignatureNotSupported": "Підпис не підтримується",
48 | "IncorrectSignature": "Неправильний підпис",
49 | "IncorrectSignatureSize": "Неправильний розмір підпису",
50 | "PinNotSupported": "Пін не підтримується",
51 | "PinChangeError": "Помилка зміни піну",
52 | "UnknownError": "Невідома помилка: {}",
53 | "NoInformation": "Немає інформації",
54 | "Loading..": "Завантаження..",
55 | "Connecting..": "Підключення..",
56 | "Disconnecting..": "Відключення..",
57 | "Scanning..": "Сканування..",
58 | "Uploading..": "Завантаження..",
59 | "Connected": "Підключено",
60 | "Disconnected": "Відключено",
61 | "NoAvailableSoftwares": "Немає доступних прошивок",
62 | "NewestSoftwareAlreadyInstalled": "Найновіша прошивка вже встановлена",
63 | "NewSoftwareAvailable:": "Доступнa нова прошивка:",
64 | "AllAvailableSoftwares:" : "Всі доступні прошивки:",
65 | "UploadFile": "Завантажити файл",
66 | "Hardware:": "Обладнання: {}",
67 | "Software:": "Прошивка: {}",
68 | "Changing..": "Зміна..",
69 | "Changed": "Змінено",
70 | "ChangePinCode:": "Зміни пін код:",
71 | "Set": "Встановити",
72 | "Remove": "Видалити"
73 | }
--------------------------------------------------------------------------------
/assets/translations/pl-PL.json:
--------------------------------------------------------------------------------
1 | {
2 | "Devices": "Urządzenia",
3 | "Scan": "Skanuj",
4 | "Stop": "Zatrzymaj",
5 | "Settings": "Ustawienia",
6 | "General": "Ogólne",
7 | "InfiniteScan": "Nieskończone skanowanie",
8 | "DeveloperOptions": "Opcje programistyczne",
9 | "SkipInfoReading": "Pomiń czytanie informacji",
10 | "AlwaysAllowLocalFilesUpload": "Zawsze zezwalaj na lokalne wgrywanie plików",
11 | "DisableBuffer" : "Wyłącz bufor",
12 | "SequentialUpload" : "Sekwencyjne wgrywanie",
13 | "ManufacturesDictionaryLink": "Link do słownika producentów",
14 | "MaxMtuSize": "Maksymalny rozmiar MTU",
15 | "ThisDeviceDoesNotSupportBluetooth": "To urządzenie nie obsługuje Bluetooth",
16 | "ThisBrowserDoesNotSupportBluetooth": "Ta przeglądarka nie obsługuje Bluetooth",
17 | "AuthorizeApplicationToUseBluetoothAndLocation": "Autoryzuj aplikację do korzystania z Bluetooth i lokalizacji",
18 | "BluetoothIsDisabledTurnItOn": "Bluetooth jest wyłączony, włącz go",
19 | "LocationServicesAreDisabledEnableThem": "Usługi lokalizacyjne są wyłączone, włącz je",
20 | "BluetoothIsUpAndRunning": "Bluetooth jest włączony i działa",
21 | "WaitingToFetchBluetoothStatus": "Oczekiwanie na pobranie stanu Bluetooth: {}",
22 | "DeviceError": "Błąd urządzenia",
23 | "NoDeviceResponse": "Brak odpowiedzi urządzenia",
24 | "UnexpectedDeviceResponse": "Nieoczekiwana odpowiedź urządzenia: {}",
25 | "IncorrectDeviceResponse": "Nieprawidłowa odpowiedź urządzenia",
26 | "NetworkError": "Błąd sieci",
27 | "UnexpectedNetworkResponse": "Nieoczekiwana odpowiedź sieciowa: {}",
28 | "IncorrectNetworkFile": "Nieprawidłowy plik sieciowy",
29 | "IncorrectMessageSize": "Nieprawidłowy rozmiar wiadomości",
30 | "IncorrectMessageHeader": "Nieprawidłowy nagłówek wiadomości",
31 | "IncorrectFirmwareSize": "Nieprawidłowy rozmiar oprogramowania",
32 | "InternalStorageError": "Błąd pamięci wewnętrznej",
33 | "UploadDisabled": "Wgrywanie wyłączone",
34 | "UploadRunning": "Wgrywanie uruchomione",
35 | "UploadStopped": "Wgrywanie zatrzymane",
36 | "InstallRunning": "Instalacja uruchomiona",
37 | "BufferDisabled": "Bufor wyłączony",
38 | "BufferOverflow": "Przepełnienie bufora",
39 | "CompressionNotSupported": "Kompresja nie jest obsługiwana",
40 | "IncorrectCompression": "Nieprawidłowa kompresja",
41 | "IncorrectCompressedSize": "Nieprawidłowy rozmiar kompresji",
42 | "IncorrectCompressionChecksum": "Nieprawidłowa suma kontrolna kompresji",
43 | "IncorrectCompressionParam": "Nieprawidłowy parametr kompresji",
44 | "IncorrectCompressionEnd": "Nieprawidłowy koniec kompresji",
45 | "ChecksumNotSupported": "Suma kontrolna nie jest obsługiwana",
46 | "IncorrectChecksum": "Nieprawidłowa suma kontrolna",
47 | "SignatureNotSupported": "Podpis nie jest obsługiwany",
48 | "IncorrectSignature": "Nieprawidłowy podpis",
49 | "IncorrectSignatureSize": "Nieprawidłowy rozmiar podpisu",
50 | "PinNotSupported": "Pin nie jest obsługiwany",
51 | "PinChangeError": "Błąd zmiany pinu",
52 | "UnknownError": "Nieznany błąd: {}",
53 | "NoInformation": "Brak informacji",
54 | "Loading..": "Ładowanie..",
55 | "Connecting..": "Łączenie..",
56 | "Disconnecting..": "Rozłączanie..",
57 | "Scanning..": "Skanowanie..",
58 | "Uploading..": "Wgrywanie..",
59 | "Connected": "Połączono",
60 | "Disconnected": "Rozłączono",
61 | "NoAvailableSoftwares": "Brak dostępnego oprogramowania",
62 | "NewestSoftwareAlreadyInstalled": "Najnowsze oprogramowanie już zainstalowane",
63 | "NewSoftwareAvailable:": "Dostępne nowe oprogramowanie:",
64 | "AllAvailableSoftwares:" : "Wszystkie dostępne oprogramowania:",
65 | "UploadFile": "Wgraj plik",
66 | "Hardware:": "Sprzęt: {}",
67 | "Software:": "Oprogramowanie: {}",
68 | "Changing..": "Zmiana..",
69 | "Changed": "Zmieniono",
70 | "ChangePinCode:": "Zmień pin kod:",
71 | "Set": "Ustaw",
72 | "Remove": "Usuń"
73 | }
--------------------------------------------------------------------------------
/windows/flutter/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # This file controls Flutter-level build steps. It should not be edited.
2 | cmake_minimum_required(VERSION 3.14)
3 |
4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
5 |
6 | # Configuration provided via flutter tool.
7 | include(${EPHEMERAL_DIR}/generated_config.cmake)
8 |
9 | # TODO: Move the rest of this into files in ephemeral. See
10 | # https://github.com/flutter/flutter/issues/57146.
11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
12 |
13 | # === Flutter Library ===
14 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
15 |
16 | # Published to parent scope for install step.
17 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
18 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
19 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
20 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
21 |
22 | list(APPEND FLUTTER_LIBRARY_HEADERS
23 | "flutter_export.h"
24 | "flutter_windows.h"
25 | "flutter_messenger.h"
26 | "flutter_plugin_registrar.h"
27 | "flutter_texture_registrar.h"
28 | )
29 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
30 | add_library(flutter INTERFACE)
31 | target_include_directories(flutter INTERFACE
32 | "${EPHEMERAL_DIR}"
33 | )
34 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
35 | add_dependencies(flutter flutter_assemble)
36 |
37 | # === Wrapper ===
38 | list(APPEND CPP_WRAPPER_SOURCES_CORE
39 | "core_implementations.cc"
40 | "standard_codec.cc"
41 | )
42 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
43 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
44 | "plugin_registrar.cc"
45 | )
46 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
47 | list(APPEND CPP_WRAPPER_SOURCES_APP
48 | "flutter_engine.cc"
49 | "flutter_view_controller.cc"
50 | )
51 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
52 |
53 | # Wrapper sources needed for a plugin.
54 | add_library(flutter_wrapper_plugin STATIC
55 | ${CPP_WRAPPER_SOURCES_CORE}
56 | ${CPP_WRAPPER_SOURCES_PLUGIN}
57 | )
58 | apply_standard_settings(flutter_wrapper_plugin)
59 | set_target_properties(flutter_wrapper_plugin PROPERTIES
60 | POSITION_INDEPENDENT_CODE ON)
61 | set_target_properties(flutter_wrapper_plugin PROPERTIES
62 | CXX_VISIBILITY_PRESET hidden)
63 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
64 | target_include_directories(flutter_wrapper_plugin PUBLIC
65 | "${WRAPPER_ROOT}/include"
66 | )
67 | add_dependencies(flutter_wrapper_plugin flutter_assemble)
68 |
69 | # Wrapper sources needed for the runner.
70 | add_library(flutter_wrapper_app STATIC
71 | ${CPP_WRAPPER_SOURCES_CORE}
72 | ${CPP_WRAPPER_SOURCES_APP}
73 | )
74 | apply_standard_settings(flutter_wrapper_app)
75 | target_link_libraries(flutter_wrapper_app PUBLIC flutter)
76 | target_include_directories(flutter_wrapper_app PUBLIC
77 | "${WRAPPER_ROOT}/include"
78 | )
79 | add_dependencies(flutter_wrapper_app flutter_assemble)
80 |
81 | # === Flutter tool backend ===
82 | # _phony_ is a non-existent file to force this command to run every time,
83 | # since currently there's no way to get a full input/output list from the
84 | # flutter tool.
85 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
86 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
87 | add_custom_command(
88 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
89 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
90 | ${CPP_WRAPPER_SOURCES_APP}
91 | ${PHONY_OUTPUT}
92 | COMMAND ${CMAKE_COMMAND} -E env
93 | ${FLUTTER_TOOL_ENVIRONMENT}
94 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
95 | windows-x64 $
96 | VERBATIM
97 | )
98 | add_custom_target(flutter_assemble DEPENDS
99 | "${FLUTTER_LIBRARY}"
100 | ${FLUTTER_LIBRARY_HEADERS}
101 | ${CPP_WRAPPER_SOURCES_CORE}
102 | ${CPP_WRAPPER_SOURCES_PLUGIN}
103 | ${CPP_WRAPPER_SOURCES_APP}
104 | )
105 |
--------------------------------------------------------------------------------
/resources/privacy-policy.md:
--------------------------------------------------------------------------------
1 | Privacy Policy
2 | ----------------
3 |
4 | ### Introduction
5 | Our privacy policy will help you understand what information we collect at *vovagorodok/BleOta*, how *vovagorodok/BleOta* uses it, and what choices you have.
6 | *vovagorodok/BleOta* built the *BleOta* app as a free app. This SERVICE is provided by *vovagorodok/BleOta* at no cost and is intended for use as is.
7 | If you choose to use our Service, then you agree to the collection and use of information in relation with this policy. The Personal Information that we collect are used for providing and improving the Service. We will not use or share your information with anyone except as described in this Privacy Policy.
8 | The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible in our website, unless otherwise defined in this Privacy Policy.
9 |
10 | ### Information Collection and Use
11 | For a better experience while using our Service, we may require you to provide us with certain personally identifiable information, including but not limited to users name, email address, gender, location, pictures. The information that we request will be retained by us and used as described in this privacy policy.
12 | The app does use third party services that may collect information used to identify you.
13 |
14 | ### Cookies
15 | Cookies are files with small amount of data that is commonly used an anonymous unique identifier. These are sent to your browser from the website that you visit and are stored on your devices’s internal memory.
16 |
17 | This Services does not uses these “cookies” explicitly. However, the app may use third party code and libraries that use “cookies” to collection information and to improve their services. You have the option to either accept or refuse these cookies, and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.
18 |
19 | ### Location Information
20 | Some of the services may use location information transmitted from users' mobile phones. We only use this information within the scope necessary for the designated service.
21 |
22 | ### Device Information
23 | We collect information from your device in some cases. The information will be utilized for the provision of better service and to prevent fraudulent acts. Additionally, such information will not include that which will identify the individual user.
24 |
25 | ### Service Providers
26 | We may employ third-party companies and individuals due to the following reasons:
27 | * To facilitate our Service;
28 | * To provide the Service on our behalf;
29 | * To perform Service-related services; or
30 | * To assist us in analyzing how our Service is used.
31 |
32 | We want to inform users of this Service that these third parties have access to your Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.
33 |
34 | ### Security
35 | We value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and we cannot guarantee its absolute security.
36 |
37 | ### Children’s Privacy
38 | This Services do not address anyone under the age of 13. We do not knowingly collect personal identifiable information from children under 13. In the case we discover that a child under 13 has provided us with personal information, we immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact us so that we will be able to do necessary actions.
39 |
40 | ### Changes to This Privacy Policy
41 | We may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. We will notify you of any changes by posting the new Privacy Policy on this page. These changes are effective immediately, after they are posted on this page.
42 |
43 | ### Contact Us
44 | If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact us.
45 | Contact Information:
46 | Email: *vovagorodok2@gmail.com*
--------------------------------------------------------------------------------
/windows/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Project-level configuration.
2 | cmake_minimum_required(VERSION 3.14)
3 | project(ble_ota_app LANGUAGES CXX)
4 |
5 | # The name of the executable created for the application. Change this to change
6 | # the on-disk name of your application.
7 | set(BINARY_NAME "BleOta")
8 |
9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
10 | # versions of CMake.
11 | cmake_policy(SET CMP0063 NEW)
12 |
13 | # Define build configuration option.
14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
15 | if(IS_MULTICONFIG)
16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
17 | CACHE STRING "" FORCE)
18 | else()
19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
20 | set(CMAKE_BUILD_TYPE "Debug" CACHE
21 | STRING "Flutter build mode" FORCE)
22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
23 | "Debug" "Profile" "Release")
24 | endif()
25 | endif()
26 | # Define settings for the Profile build mode.
27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
31 |
32 | # Use Unicode for all projects.
33 | add_definitions(-DUNICODE -D_UNICODE)
34 |
35 | # Compilation settings that should be applied to most targets.
36 | #
37 | # Be cautious about adding new options here, as plugins use this function by
38 | # default. In most cases, you should add new options to specific targets instead
39 | # of modifying this function.
40 | function(APPLY_STANDARD_SETTINGS TARGET)
41 | target_compile_features(${TARGET} PUBLIC cxx_std_17)
42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
43 | target_compile_options(${TARGET} PRIVATE /EHsc)
44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>")
46 | endfunction()
47 |
48 | # Flutter library and tool build rules.
49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
50 | add_subdirectory(${FLUTTER_MANAGED_DIR})
51 |
52 | # Application build; see runner/CMakeLists.txt.
53 | add_subdirectory("runner")
54 |
55 | # Generated plugin build rules, which manage building the plugins and adding
56 | # them to the application.
57 | include(flutter/generated_plugins.cmake)
58 |
59 |
60 | # === Installation ===
61 | # Support files are copied into place next to the executable, so that it can
62 | # run in place. This is done instead of making a separate bundle (as on Linux)
63 | # so that building and running from within Visual Studio will work.
64 | set(BUILD_BUNDLE_DIR "$")
65 | # Make the "install" step default, as it's required to run.
66 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
67 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
68 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
69 | endif()
70 |
71 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
72 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
73 |
74 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
75 | COMPONENT Runtime)
76 |
77 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
78 | COMPONENT Runtime)
79 |
80 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
81 | COMPONENT Runtime)
82 |
83 | if(PLUGIN_BUNDLED_LIBRARIES)
84 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
85 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
86 | COMPONENT Runtime)
87 | endif()
88 |
89 | # Fully re-copy the assets directory on each build to avoid having stale files
90 | # from a previous install.
91 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
92 | install(CODE "
93 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
94 | " COMPONENT Runtime)
95 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
96 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
97 |
98 | # Install the AOT library on non-Debug builds only.
99 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
100 | CONFIGURATIONS Profile;Release
101 | COMPONENT Runtime)
102 |
--------------------------------------------------------------------------------
/lib/src/screens/status_screen.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:ble_backend/ble_central.dart';
4 | import 'package:ble_ota_app/src/ui/ui_consts.dart';
5 | import 'package:device_info_plus/device_info_plus.dart';
6 | import 'package:easy_localization/easy_localization.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:permission_handler/permission_handler.dart';
9 |
10 | class StatusScreen extends StatefulWidget {
11 | const StatusScreen({
12 | required this.bleCentral,
13 | super.key,
14 | });
15 |
16 | final BleCentral bleCentral;
17 |
18 | @override
19 | State createState() => StatusScreenState();
20 | }
21 |
22 | class StatusScreenState extends State {
23 | BleCentral get bleCentral => widget.bleCentral;
24 |
25 | String _determineText(BleCentralStatus status) {
26 | switch (status) {
27 | case BleCentralStatus.unsupported:
28 | return tr('ThisDeviceDoesNotSupportBluetooth');
29 | case BleCentralStatus.unsupportedBrowser:
30 | return tr('ThisBrowserDoesNotSupportBluetooth');
31 | case BleCentralStatus.unauthorized:
32 | return tr('AuthorizeApplicationToUseBluetoothAndLocation');
33 | case BleCentralStatus.poweredOff:
34 | return tr('BluetoothIsDisabledTurnItOn');
35 | case BleCentralStatus.locationServicesDisabled:
36 | return tr('LocationServicesAreDisabledEnableThem');
37 | case BleCentralStatus.ready:
38 | return tr('BluetoothIsUpAndRunning');
39 | default:
40 | return tr('WaitingToFetchBluetoothStatus', args: ['$status']);
41 | }
42 | }
43 |
44 | IconData _determineIcon(BleCentralStatus status) {
45 | switch (status) {
46 | case BleCentralStatus.unsupported:
47 | return Icons.bluetooth_disabled_rounded;
48 | case BleCentralStatus.unsupportedBrowser:
49 | return Icons.browser_not_supported_rounded;
50 | case BleCentralStatus.unauthorized:
51 | return Icons.person_off_rounded;
52 | case BleCentralStatus.poweredOff:
53 | return Icons.bluetooth_disabled_rounded;
54 | case BleCentralStatus.locationServicesDisabled:
55 | return Icons.location_off_rounded;
56 | case BleCentralStatus.ready:
57 | return Icons.bluetooth_rounded;
58 | default:
59 | return Icons.autorenew_rounded;
60 | }
61 | }
62 |
63 | void _evaluateBleCentralStatus(BleCentralStatus status) {
64 | setState(() {
65 | if (status == BleCentralStatus.ready) {
66 | Navigator.pop(context);
67 | }
68 | });
69 | }
70 |
71 | @override
72 | void initState() {
73 | super.initState();
74 | bleCentral.stateStream.listen(_evaluateBleCentralStatus);
75 | () async {
76 | if (!Platform.isAndroid && !Platform.isIOS && !Platform.isWindows) return;
77 |
78 | if (Platform.isAndroid) {
79 | final deviceInfo = DeviceInfoPlugin();
80 | final androidInfo = await deviceInfo.androidInfo;
81 | final sdkVersion = androidInfo.version.sdkInt;
82 | if (sdkVersion < 31) {
83 | await Permission.location.request();
84 | }
85 | }
86 | await Permission.bluetooth.request();
87 | await Permission.bluetoothScan.request();
88 | await Permission.bluetoothAdvertise.request();
89 | await Permission.bluetoothConnect.request();
90 | }.call();
91 | _evaluateBleCentralStatus(bleCentral.state);
92 | }
93 |
94 | @override
95 | Widget build(BuildContext context) => PopScope(
96 | canPop: false,
97 | child: Scaffold(
98 | body: SafeArea(
99 | minimum: const EdgeInsets.all(screenPadding),
100 | child: Column(
101 | crossAxisAlignment: CrossAxisAlignment.stretch,
102 | mainAxisAlignment: MainAxisAlignment.center,
103 | children: [
104 | Text(
105 | _determineText(bleCentral.state),
106 | textAlign: TextAlign.center,
107 | style: const TextStyle(
108 | fontWeight: FontWeight.bold,
109 | fontSize: 30.0,
110 | ),
111 | ),
112 | const SizedBox(height: 20),
113 | Icon(
114 | _determineIcon(bleCentral.state),
115 | size: 100,
116 | ),
117 | ],
118 | ),
119 | ),
120 | ),
121 | );
122 | }
123 |
--------------------------------------------------------------------------------
/linux/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Project-level configuration.
2 | cmake_minimum_required(VERSION 3.13)
3 | project(runner LANGUAGES CXX)
4 |
5 | # The name of the executable created for the application. Change this to change
6 | # the on-disk name of your application.
7 | set(BINARY_NAME "BleOta")
8 | # The unique GTK application identifier for this application. See:
9 | # https://wiki.gnome.org/HowDoI/ChooseApplicationID
10 | set(APPLICATION_ID "com.vovagorodok.ble_ota_app")
11 |
12 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
13 | # versions of CMake.
14 | cmake_policy(SET CMP0063 NEW)
15 |
16 | # Load bundled libraries from the lib/ directory relative to the binary.
17 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
18 |
19 | # Root filesystem for cross-building.
20 | if(FLUTTER_TARGET_PLATFORM_SYSROOT)
21 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
22 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
23 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
24 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
25 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
26 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
27 | endif()
28 |
29 | # Define build configuration options.
30 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
31 | set(CMAKE_BUILD_TYPE "Debug" CACHE
32 | STRING "Flutter build mode" FORCE)
33 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
34 | "Debug" "Profile" "Release")
35 | endif()
36 |
37 | # Compilation settings that should be applied to most targets.
38 | #
39 | # Be cautious about adding new options here, as plugins use this function by
40 | # default. In most cases, you should add new options to specific targets instead
41 | # of modifying this function.
42 | function(APPLY_STANDARD_SETTINGS TARGET)
43 | target_compile_features(${TARGET} PUBLIC cxx_std_14)
44 | target_compile_options(${TARGET} PRIVATE -Wall -Werror)
45 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>")
46 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>")
47 | endfunction()
48 |
49 | # Flutter library and tool build rules.
50 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
51 | add_subdirectory(${FLUTTER_MANAGED_DIR})
52 |
53 | # System-level dependencies.
54 | find_package(PkgConfig REQUIRED)
55 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
56 |
57 | # Application build; see runner/CMakeLists.txt.
58 | add_subdirectory("runner")
59 |
60 | # Run the Flutter tool portions of the build. This must not be removed.
61 | add_dependencies(${BINARY_NAME} flutter_assemble)
62 |
63 | # Only the install-generated bundle's copy of the executable will launch
64 | # correctly, since the resources must in the right relative locations. To avoid
65 | # people trying to run the unbundled copy, put it in a subdirectory instead of
66 | # the default top-level location.
67 | set_target_properties(${BINARY_NAME}
68 | PROPERTIES
69 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
70 | )
71 |
72 |
73 | # Generated plugin build rules, which manage building the plugins and adding
74 | # them to the application.
75 | include(flutter/generated_plugins.cmake)
76 |
77 |
78 | # === Installation ===
79 | # By default, "installing" just makes a relocatable bundle in the build
80 | # directory.
81 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
82 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
83 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
84 | endif()
85 |
86 | # Start with a clean build bundle directory every time.
87 | install(CODE "
88 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
89 | " COMPONENT Runtime)
90 |
91 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
92 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
93 |
94 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
95 | COMPONENT Runtime)
96 |
97 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
98 | COMPONENT Runtime)
99 |
100 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
101 | COMPONENT Runtime)
102 |
103 | foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
104 | install(FILES "${bundled_library}"
105 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
106 | COMPONENT Runtime)
107 | endforeach(bundled_library)
108 |
109 | # Copy the native assets provided by the build.dart from all packages.
110 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
111 | install(DIRECTORY "${NATIVE_ASSETS_DIR}"
112 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
113 | COMPONENT Runtime)
114 |
115 | # Fully re-copy the assets directory on each build to avoid having stale files
116 | # from a previous install.
117 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
118 | install(CODE "
119 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
120 | " COMPONENT Runtime)
121 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
122 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
123 |
124 | # Install the AOT library on non-Debug builds only.
125 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
126 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
127 | COMPONENT Runtime)
128 | endif()
129 |
--------------------------------------------------------------------------------
/resources/Web_badge.svg:
--------------------------------------------------------------------------------
1 |
2 |
76 |
--------------------------------------------------------------------------------
/linux/runner/my_application.cc:
--------------------------------------------------------------------------------
1 | #include "my_application.h"
2 |
3 | #include
4 | #ifdef GDK_WINDOWING_X11
5 | #include
6 | #endif
7 |
8 | #include "flutter/generated_plugin_registrant.h"
9 |
10 | struct _MyApplication {
11 | GtkApplication parent_instance;
12 | char** dart_entrypoint_arguments;
13 | };
14 |
15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
16 |
17 | // Called when first Flutter frame received.
18 | static void first_frame_cb(MyApplication* self, FlView *view)
19 | {
20 | gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
21 | }
22 |
23 | // Implements GApplication::activate.
24 | static void my_application_activate(GApplication* application) {
25 | MyApplication* self = MY_APPLICATION(application);
26 | GtkWindow* window =
27 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
28 |
29 | // Use a header bar when running in GNOME as this is the common style used
30 | // by applications and is the setup most users will be using (e.g. Ubuntu
31 | // desktop).
32 | // If running on X and not using GNOME then just use a traditional title bar
33 | // in case the window manager does more exotic layout, e.g. tiling.
34 | // If running on Wayland assume the header bar will work (may need changing
35 | // if future cases occur).
36 | gboolean use_header_bar = TRUE;
37 | #ifdef GDK_WINDOWING_X11
38 | GdkScreen* screen = gtk_window_get_screen(window);
39 | if (GDK_IS_X11_SCREEN(screen)) {
40 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
41 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
42 | use_header_bar = FALSE;
43 | }
44 | }
45 | #endif
46 | if (use_header_bar) {
47 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
48 | gtk_widget_show(GTK_WIDGET(header_bar));
49 | gtk_header_bar_set_title(header_bar, "BleOta");
50 | gtk_header_bar_set_show_close_button(header_bar, TRUE);
51 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
52 | } else {
53 | gtk_window_set_title(window, "BleOta");
54 | }
55 |
56 | gtk_window_set_default_size(window, 1280, 720);
57 |
58 | g_autoptr(FlDartProject) project = fl_dart_project_new();
59 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
60 |
61 | FlView* view = fl_view_new(project);
62 | GdkRGBA background_color;
63 | // Background defaults to black, override it here if necessary, e.g. #00000000 for transparent.
64 | gdk_rgba_parse(&background_color, "#000000");
65 | fl_view_set_background_color(view, &background_color);
66 | gtk_widget_show(GTK_WIDGET(view));
67 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
68 |
69 | // Show the window when Flutter renders.
70 | // Requires the view to be realized so we can start rendering.
71 | g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), self);
72 | gtk_widget_realize(GTK_WIDGET(view));
73 |
74 | fl_register_plugins(FL_PLUGIN_REGISTRY(view));
75 |
76 | gtk_widget_grab_focus(GTK_WIDGET(view));
77 | }
78 |
79 | // Implements GApplication::local_command_line.
80 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
81 | MyApplication* self = MY_APPLICATION(application);
82 | // Strip out the first argument as it is the binary name.
83 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
84 |
85 | g_autoptr(GError) error = nullptr;
86 | if (!g_application_register(application, nullptr, &error)) {
87 | g_warning("Failed to register: %s", error->message);
88 | *exit_status = 1;
89 | return TRUE;
90 | }
91 |
92 | g_application_activate(application);
93 | *exit_status = 0;
94 |
95 | return TRUE;
96 | }
97 |
98 | // Implements GApplication::startup.
99 | static void my_application_startup(GApplication* application) {
100 | //MyApplication* self = MY_APPLICATION(object);
101 |
102 | // Perform any actions required at application startup.
103 |
104 | G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
105 | }
106 |
107 | // Implements GApplication::shutdown.
108 | static void my_application_shutdown(GApplication* application) {
109 | //MyApplication* self = MY_APPLICATION(object);
110 |
111 | // Perform any actions required at application shutdown.
112 |
113 | G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
114 | }
115 |
116 | // Implements GObject::dispose.
117 | static void my_application_dispose(GObject* object) {
118 | MyApplication* self = MY_APPLICATION(object);
119 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
120 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
121 | }
122 |
123 | static void my_application_class_init(MyApplicationClass* klass) {
124 | G_APPLICATION_CLASS(klass)->activate = my_application_activate;
125 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
126 | G_APPLICATION_CLASS(klass)->startup = my_application_startup;
127 | G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
128 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
129 | }
130 |
131 | static void my_application_init(MyApplication* self) {}
132 |
133 | MyApplication* my_application_new() {
134 | // Set the program name to the application ID, which helps various systems
135 | // like GTK and desktop environments map this running application to its
136 | // corresponding .desktop file. This ensures better integration by allowing
137 | // the application to be recognized beyond its binary name.
138 | g_set_prgname(APPLICATION_ID);
139 |
140 | return MY_APPLICATION(g_object_new(my_application_get_type(),
141 | "application-id", APPLICATION_ID,
142 | "flags", G_APPLICATION_NON_UNIQUE,
143 | nullptr));
144 | }
145 |
--------------------------------------------------------------------------------
/lib/src/screens/pin_screen.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:ble_backend/ble_peripheral.dart';
4 | import 'package:ble_ota_app/src/ui/ui_consts.dart';
5 | import 'package:ble_ota_app/src/utils/string_forms.dart';
6 | import 'package:ble_ota/ble_ota.dart';
7 | import 'package:easy_localization/easy_localization.dart';
8 | import 'package:flutter_pin_code_fields/flutter_pin_code_fields.dart';
9 | import 'package:flutter/material.dart';
10 |
11 | class PinScreen extends StatefulWidget {
12 | const PinScreen({
13 | required this.blePeripheral,
14 | required this.bleOta,
15 | super.key,
16 | });
17 |
18 | final BlePeripheral blePeripheral;
19 | final BleOta bleOta;
20 |
21 | @override
22 | State createState() => PinScreenState();
23 | }
24 |
25 | class PinScreenState extends State {
26 | int? _pin;
27 | StreamSubscription? _subscription;
28 |
29 | BleOta get bleOta => widget.bleOta;
30 | BleOtaState get bleOtaState => bleOta.state;
31 | BleOtaStatus get bleOtaStatus => bleOtaState.status;
32 |
33 | void _onBleOtaStateChanged(BleOtaState state) {
34 | setState(() {});
35 | }
36 |
37 | @override
38 | void initState() {
39 | super.initState();
40 | _subscription = bleOta.stateStream.listen(_onBleOtaStateChanged);
41 | }
42 |
43 | @override
44 | void dispose() {
45 | _subscription?.cancel();
46 | super.dispose();
47 | }
48 |
49 | void _onChange(String value) {
50 | setState(() {
51 | _pin = value.length == 6 ? int.tryParse(value) : null;
52 | });
53 | }
54 |
55 | bool _canChange() {
56 | return bleOtaStatus != BleOtaStatus.pinChange;
57 | }
58 |
59 | bool _canSetPin() {
60 | return _canChange() && _pin != null;
61 | }
62 |
63 | void _setPin() {
64 | bleOta.setPin(pin: _pin!);
65 | }
66 |
67 | void _removePin() {
68 | bleOta.removePin();
69 | }
70 |
71 | String _determinateStatusText() {
72 | if (bleOtaStatus == BleOtaStatus.pinChange) {
73 | return tr('Changing..');
74 | } else if (bleOtaStatus == BleOtaStatus.error) {
75 | return determineError(bleOtaState);
76 | } else if (bleOtaStatus == BleOtaStatus.pinChanged) {
77 | return tr('Changed');
78 | } else {
79 | return tr('ChangePinCode:');
80 | }
81 | }
82 |
83 | Color? _determinateStatusColor() {
84 | if (bleOtaStatus == BleOtaStatus.error) {
85 | return Colors.red;
86 | } else if (bleOtaStatus == BleOtaStatus.pinChanged) {
87 | return Colors.green;
88 | } else {
89 | return null;
90 | }
91 | }
92 |
93 | Widget _buildStatusWidget() => Text(
94 | _determinateStatusText(),
95 | style: TextStyle(
96 | fontWeight: FontWeight.bold,
97 | fontSize: 20,
98 | color: _determinateStatusColor(),
99 | ),
100 | );
101 |
102 | Widget _buildPinCodeWidget() => PinCodeFields(
103 | length: 6,
104 | keyboardType: TextInputType.number,
105 | margin: const EdgeInsets.symmetric(
106 | vertical: 0,
107 | horizontal: 5,
108 | ),
109 | padding: const EdgeInsets.all(0),
110 | onChange: _onChange,
111 | onComplete: _onChange,
112 | );
113 |
114 | Widget _buildPinCodeWithStatusWidget() => Column(
115 | children: [
116 | _buildStatusWidget(),
117 | _buildPinCodeWidget(),
118 | ],
119 | );
120 |
121 | Widget _buildSetButton() => FilledButton.icon(
122 | icon: const Icon(Icons.upload_rounded),
123 | label: Text(tr('Set')),
124 | onPressed: _canSetPin() ? _setPin : null,
125 | );
126 |
127 | Widget _buildRemoveButton() => FilledButton.icon(
128 | icon: const Icon(Icons.delete_rounded),
129 | label: Text(tr('Remove')),
130 | onPressed: _canChange() ? _removePin : null,
131 | );
132 |
133 | Widget _buildControlButtons() => SizedBox(
134 | height: buttonHeight,
135 | child: Row(
136 | crossAxisAlignment: CrossAxisAlignment.stretch,
137 | children: [
138 | Expanded(
139 | child: _buildSetButton(),
140 | ),
141 | const SizedBox(width: buttonsSplitter),
142 | Expanded(
143 | child: _buildRemoveButton(),
144 | ),
145 | ],
146 | ),
147 | );
148 |
149 | Widget _buildPortrait() => Column(
150 | children: [
151 | Expanded(
152 | child: _buildPinCodeWithStatusWidget(),
153 | ),
154 | _buildControlButtons(),
155 | ],
156 | );
157 |
158 | Widget _buildLandscape() => Row(
159 | crossAxisAlignment: CrossAxisAlignment.end,
160 | children: [
161 | Expanded(
162 | child: _buildPinCodeWithStatusWidget(),
163 | ),
164 | const SizedBox(width: screenLandscapeSplitter),
165 | Expanded(
166 | child: _buildControlButtons(),
167 | ),
168 | ],
169 | );
170 |
171 | @override
172 | Widget build(BuildContext context) => Scaffold(
173 | primary: MediaQuery.of(context).orientation == Orientation.portrait,
174 | appBar: AppBar(
175 | title: Text(widget.blePeripheral.name ?? ''),
176 | centerTitle: true,
177 | leading: IconButton(
178 | icon: const Icon(Icons.arrow_back_rounded),
179 | onPressed: _canChange()
180 | ? () {
181 | Navigator.pop(context);
182 | }
183 | : null,
184 | ),
185 | ),
186 | body: SafeArea(
187 | minimum: const EdgeInsets.all(screenPadding),
188 | child: OrientationBuilder(
189 | builder: (context, orientation) =>
190 | orientation == Orientation.portrait
191 | ? _buildPortrait()
192 | : _buildLandscape(),
193 | ),
194 | ),
195 | );
196 | }
197 |
--------------------------------------------------------------------------------
/lib/src/screens/scanner_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:ble_backend/ble_central.dart';
2 | import 'package:ble_backend/ble_peripheral.dart';
3 | import 'package:ble_backend/ble_scanner.dart';
4 | import 'package:ble_backend/utils/timer_wrapper.dart';
5 | import 'package:ble_ota_app/src/screens/settings_screen.dart';
6 | import 'package:ble_ota_app/src/screens/status_screen.dart';
7 | import 'package:ble_ota_app/src/screens/upload_screen.dart';
8 | import 'package:ble_ota_app/src/settings/settings.dart';
9 | import 'package:ble_ota_app/src/ui/jumping_dots.dart';
10 | import 'package:ble_ota_app/src/ui/ui_consts.dart';
11 | import 'package:easy_localization/easy_localization.dart';
12 | import 'package:flutter/foundation.dart' show kIsWeb;
13 | import 'package:flutter/material.dart';
14 | import 'package:wakelock_plus/wakelock_plus.dart';
15 |
16 | class ScannerScreen extends StatefulWidget {
17 | const ScannerScreen({
18 | required this.bleCentral,
19 | required this.bleScanner,
20 | super.key,
21 | });
22 |
23 | final BleCentral bleCentral;
24 | final BleScanner bleScanner;
25 |
26 | @override
27 | State createState() => ScannerScreenState();
28 | }
29 |
30 | class ScannerScreenState extends State {
31 | final scanTimer = TimerWrapper();
32 |
33 | BleCentral get bleCentral => widget.bleCentral;
34 | BleScanner get bleScanner => widget.bleScanner;
35 |
36 | void _evaluateBleCentralStatus(BleCentralStatus status) {
37 | setState(() {
38 | if (kIsWeb) {
39 | } else if (status == BleCentralStatus.ready) {
40 | _startScan();
41 | } else if (status != BleCentralStatus.unknown) {
42 | _stopScan();
43 | }
44 |
45 | if (status != BleCentralStatus.ready &&
46 | status != BleCentralStatus.unknown) {
47 | Navigator.push(
48 | context,
49 | MaterialPageRoute(
50 | builder: (context) => StatusScreen(
51 | bleCentral: bleCentral,
52 | )),
53 | );
54 | }
55 | });
56 | }
57 |
58 | void _startScan() {
59 | WakelockPlus.enable();
60 | bleScanner.scan();
61 |
62 | if (!infiniteScan.value) {
63 | scanTimer.start(const Duration(seconds: 10), _stopScan);
64 | }
65 | }
66 |
67 | void _stopScan() {
68 | scanTimer.stop();
69 | WakelockPlus.disable();
70 | bleScanner.stop();
71 | }
72 |
73 | Widget _buildDeviceCard(BlePeripheral device) => Card(
74 | child: ListTile(
75 | title: Text(device.name ?? ''),
76 | subtitle: Text("${device.id}\nRSSI: ${device.rssi ?? ''}"),
77 | leading: const Icon(Icons.bluetooth_rounded),
78 | onTap: () async {
79 | _stopScan();
80 | await Navigator.push(
81 | context,
82 | MaterialPageRoute(
83 | builder: (context) => UploadScreen(
84 | blePeripheral: device,
85 | bleConnector: device.createConnector(),
86 | ),
87 | ),
88 | );
89 | },
90 | ),
91 | );
92 |
93 | Widget _buildDevicesList() {
94 | final devices = bleScanner.state.devices;
95 | final additionalElement = bleScanner.state.isScanInProgress ? 1 : 0;
96 |
97 | return ListView.builder(
98 | itemCount: devices.length + additionalElement,
99 | itemBuilder: (context, index) => index != devices.length
100 | ? _buildDeviceCard(devices[index])
101 | : Padding(
102 | padding: const EdgeInsets.all(25.0),
103 | child: createJumpingDots(),
104 | ),
105 | );
106 | }
107 |
108 | Widget _buildScanButton() => FilledButton.icon(
109 | icon: const Icon(Icons.search_rounded),
110 | label: Text(tr('Scan')),
111 | onPressed: !bleScanner.state.isScanInProgress ? _startScan : null,
112 | );
113 |
114 | Widget _buildStopButton() => FilledButton.icon(
115 | icon: const Icon(Icons.search_off_rounded),
116 | label: Text(tr('Stop')),
117 | onPressed: bleScanner.state.isScanInProgress ? _stopScan : null,
118 | );
119 |
120 | Widget _buildControlButtons() => SizedBox(
121 | height: buttonHeight,
122 | child: Row(
123 | crossAxisAlignment: CrossAxisAlignment.stretch,
124 | children: [
125 | Expanded(
126 | child: _buildScanButton(),
127 | ),
128 | if (!kIsWeb) const SizedBox(width: buttonsSplitter),
129 | if (!kIsWeb)
130 | Expanded(
131 | child: _buildStopButton(),
132 | ),
133 | ],
134 | ),
135 | );
136 |
137 | Widget _buildPortrait() => Column(
138 | children: [
139 | Expanded(
140 | child: _buildDevicesList(),
141 | ),
142 | const SizedBox(height: screenPortraitSplitter),
143 | _buildControlButtons(),
144 | ],
145 | );
146 |
147 | Widget _buildLandscape() => Row(
148 | crossAxisAlignment: CrossAxisAlignment.end,
149 | children: [
150 | Expanded(
151 | child: _buildDevicesList(),
152 | ),
153 | const SizedBox(width: screenLandscapeSplitter),
154 | Expanded(
155 | child: _buildControlButtons(),
156 | ),
157 | ],
158 | );
159 |
160 | @override
161 | void initState() {
162 | super.initState();
163 | bleCentral.stateStream.listen(_evaluateBleCentralStatus);
164 | _evaluateBleCentralStatus(bleCentral.state);
165 | }
166 |
167 | @override
168 | Widget build(BuildContext context) {
169 | return Scaffold(
170 | primary: MediaQuery.of(context).orientation == Orientation.portrait,
171 | appBar: AppBar(
172 | title: Text(tr('Devices')),
173 | centerTitle: true,
174 | actions: [
175 | IconButton(
176 | icon: const Icon(Icons.settings_rounded),
177 | onPressed: () async {
178 | _stopScan();
179 | await Navigator.push(
180 | context,
181 | MaterialPageRoute(
182 | builder: (context) => const SettingsScreen(),
183 | ),
184 | );
185 | },
186 | ),
187 | ],
188 | ),
189 | body: SafeArea(
190 | minimum: const EdgeInsets.all(screenPadding),
191 | child: StreamBuilder(
192 | stream: bleScanner.stateStream,
193 | builder: (context, snapshot) => OrientationBuilder(
194 | builder: (context, orientation) =>
195 | orientation == Orientation.portrait
196 | ? _buildPortrait()
197 | : _buildLandscape(),
198 | ),
199 | ),
200 | ),
201 | );
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/windows/runner/win32_window.cpp:
--------------------------------------------------------------------------------
1 | #include "win32_window.h"
2 |
3 | #include
4 |
5 | #include "resource.h"
6 |
7 | namespace {
8 |
9 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
10 |
11 | // The number of Win32Window objects that currently exist.
12 | static int g_active_window_count = 0;
13 |
14 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
15 |
16 | // Scale helper to convert logical scaler values to physical using passed in
17 | // scale factor
18 | int Scale(int source, double scale_factor) {
19 | return static_cast(source * scale_factor);
20 | }
21 |
22 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
23 | // This API is only needed for PerMonitor V1 awareness mode.
24 | void EnableFullDpiSupportIfAvailable(HWND hwnd) {
25 | HMODULE user32_module = LoadLibraryA("User32.dll");
26 | if (!user32_module) {
27 | return;
28 | }
29 | auto enable_non_client_dpi_scaling =
30 | reinterpret_cast(
31 | GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
32 | if (enable_non_client_dpi_scaling != nullptr) {
33 | enable_non_client_dpi_scaling(hwnd);
34 | FreeLibrary(user32_module);
35 | }
36 | }
37 |
38 | } // namespace
39 |
40 | // Manages the Win32Window's window class registration.
41 | class WindowClassRegistrar {
42 | public:
43 | ~WindowClassRegistrar() = default;
44 |
45 | // Returns the singleton registar instance.
46 | static WindowClassRegistrar* GetInstance() {
47 | if (!instance_) {
48 | instance_ = new WindowClassRegistrar();
49 | }
50 | return instance_;
51 | }
52 |
53 | // Returns the name of the window class, registering the class if it hasn't
54 | // previously been registered.
55 | const wchar_t* GetWindowClass();
56 |
57 | // Unregisters the window class. Should only be called if there are no
58 | // instances of the window.
59 | void UnregisterWindowClass();
60 |
61 | private:
62 | WindowClassRegistrar() = default;
63 |
64 | static WindowClassRegistrar* instance_;
65 |
66 | bool class_registered_ = false;
67 | };
68 |
69 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
70 |
71 | const wchar_t* WindowClassRegistrar::GetWindowClass() {
72 | if (!class_registered_) {
73 | WNDCLASS window_class{};
74 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
75 | window_class.lpszClassName = kWindowClassName;
76 | window_class.style = CS_HREDRAW | CS_VREDRAW;
77 | window_class.cbClsExtra = 0;
78 | window_class.cbWndExtra = 0;
79 | window_class.hInstance = GetModuleHandle(nullptr);
80 | window_class.hIcon =
81 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
82 | window_class.hbrBackground = 0;
83 | window_class.lpszMenuName = nullptr;
84 | window_class.lpfnWndProc = Win32Window::WndProc;
85 | RegisterClass(&window_class);
86 | class_registered_ = true;
87 | }
88 | return kWindowClassName;
89 | }
90 |
91 | void WindowClassRegistrar::UnregisterWindowClass() {
92 | UnregisterClass(kWindowClassName, nullptr);
93 | class_registered_ = false;
94 | }
95 |
96 | Win32Window::Win32Window() {
97 | ++g_active_window_count;
98 | }
99 |
100 | Win32Window::~Win32Window() {
101 | --g_active_window_count;
102 | Destroy();
103 | }
104 |
105 | bool Win32Window::CreateAndShow(const std::wstring& title,
106 | const Point& origin,
107 | const Size& size) {
108 | Destroy();
109 |
110 | const wchar_t* window_class =
111 | WindowClassRegistrar::GetInstance()->GetWindowClass();
112 |
113 | const POINT target_point = {static_cast(origin.x),
114 | static_cast(origin.y)};
115 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
116 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
117 | double scale_factor = dpi / 96.0;
118 |
119 | HWND window = CreateWindow(
120 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
121 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
122 | Scale(size.width, scale_factor), Scale(size.height, scale_factor),
123 | nullptr, nullptr, GetModuleHandle(nullptr), this);
124 |
125 | if (!window) {
126 | return false;
127 | }
128 |
129 | return OnCreate();
130 | }
131 |
132 | // static
133 | LRESULT CALLBACK Win32Window::WndProc(HWND const window,
134 | UINT const message,
135 | WPARAM const wparam,
136 | LPARAM const lparam) noexcept {
137 | if (message == WM_NCCREATE) {
138 | auto window_struct = reinterpret_cast(lparam);
139 | SetWindowLongPtr(window, GWLP_USERDATA,
140 | reinterpret_cast(window_struct->lpCreateParams));
141 |
142 | auto that = static_cast(window_struct->lpCreateParams);
143 | EnableFullDpiSupportIfAvailable(window);
144 | that->window_handle_ = window;
145 | } else if (Win32Window* that = GetThisFromHandle(window)) {
146 | return that->MessageHandler(window, message, wparam, lparam);
147 | }
148 |
149 | return DefWindowProc(window, message, wparam, lparam);
150 | }
151 |
152 | LRESULT
153 | Win32Window::MessageHandler(HWND hwnd,
154 | UINT const message,
155 | WPARAM const wparam,
156 | LPARAM const lparam) noexcept {
157 | switch (message) {
158 | case WM_DESTROY:
159 | window_handle_ = nullptr;
160 | Destroy();
161 | if (quit_on_close_) {
162 | PostQuitMessage(0);
163 | }
164 | return 0;
165 |
166 | case WM_DPICHANGED: {
167 | auto newRectSize = reinterpret_cast(lparam);
168 | LONG newWidth = newRectSize->right - newRectSize->left;
169 | LONG newHeight = newRectSize->bottom - newRectSize->top;
170 |
171 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
172 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
173 |
174 | return 0;
175 | }
176 | case WM_SIZE: {
177 | RECT rect = GetClientArea();
178 | if (child_content_ != nullptr) {
179 | // Size and position the child window.
180 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
181 | rect.bottom - rect.top, TRUE);
182 | }
183 | return 0;
184 | }
185 |
186 | case WM_ACTIVATE:
187 | if (child_content_ != nullptr) {
188 | SetFocus(child_content_);
189 | }
190 | return 0;
191 | }
192 |
193 | return DefWindowProc(window_handle_, message, wparam, lparam);
194 | }
195 |
196 | void Win32Window::Destroy() {
197 | OnDestroy();
198 |
199 | if (window_handle_) {
200 | DestroyWindow(window_handle_);
201 | window_handle_ = nullptr;
202 | }
203 | if (g_active_window_count == 0) {
204 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
205 | }
206 | }
207 |
208 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
209 | return reinterpret_cast(
210 | GetWindowLongPtr(window, GWLP_USERDATA));
211 | }
212 |
213 | void Win32Window::SetChildContent(HWND content) {
214 | child_content_ = content;
215 | SetParent(content, window_handle_);
216 | RECT frame = GetClientArea();
217 |
218 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
219 | frame.bottom - frame.top, true);
220 |
221 | SetFocus(child_content_);
222 | }
223 |
224 | RECT Win32Window::GetClientArea() {
225 | RECT frame;
226 | GetClientRect(window_handle_, &frame);
227 | return frame;
228 | }
229 |
230 | HWND Win32Window::GetHandle() {
231 | return window_handle_;
232 | }
233 |
234 | void Win32Window::SetQuitOnClose(bool quit_on_close) {
235 | quit_on_close_ = quit_on_close;
236 | }
237 |
238 | bool Win32Window::OnCreate() {
239 | // No-op; provided for subclasses.
240 | return true;
241 | }
242 |
243 | void Win32Window::OnDestroy() {
244 | // No-op; provided for subclasses.
245 | }
246 |
--------------------------------------------------------------------------------
/lib/src/screens/upload_screen.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:ble_backend/ble_connector.dart';
4 | import 'package:ble_backend/ble_peripheral.dart';
5 | import 'package:ble_ota_app/src/screens/info_screen.dart';
6 | import 'package:ble_ota_app/src/screens/pin_screen.dart';
7 | import 'package:ble_ota_app/src/settings/settings.dart';
8 | import 'package:ble_ota_app/src/ui/ui_consts.dart';
9 | import 'package:ble_ota_app/src/utils/string_forms.dart';
10 | import 'package:ble_ota/ble_ota.dart';
11 | import 'package:ble_ota/core/software.dart';
12 | import 'package:easy_localization/easy_localization.dart';
13 | import 'package:expandable/expandable.dart';
14 | import 'package:file_picker/file_picker.dart';
15 | import 'package:flutter/material.dart';
16 | import 'package:url_launcher/url_launcher.dart';
17 | import 'package:wakelock_plus/wakelock_plus.dart';
18 |
19 | class UploadScreen extends StatefulWidget {
20 | UploadScreen({
21 | required this.blePeripheral,
22 | required this.bleConnector,
23 | super.key,
24 | }) : bleOta = BleOta(
25 | bleConnector: bleConnector,
26 | manufacturesDictUrl: manufacturesDictUrl.value,
27 | maxMtuSize: maxMtuSize.value.toInt(),
28 | maxBufferSize: disableBuffer.value ? 0 : null,
29 | skipInfoReading: skipInfoReading.value,
30 | sequentialUpload: sequentialUpload.value);
31 |
32 | final BlePeripheral blePeripheral;
33 | final BleConnector bleConnector;
34 | final BleOta bleOta;
35 |
36 | @override
37 | State createState() => UploadScreenState();
38 | }
39 |
40 | class UploadScreenState extends State {
41 | List _subscriptions = [];
42 |
43 | BlePeripheral get blePeripheral => widget.blePeripheral;
44 | BleConnector get bleConnector => widget.bleConnector;
45 | BleOta get bleOta => widget.bleOta;
46 | BleConnectorStatus get connectionStatus => bleConnector.state;
47 | BleOtaState get bleOtaState => bleOta.state;
48 | BleOtaStatus get bleOtaStatus => bleOtaState.status;
49 |
50 | void _onConnectionStateChanged(BleConnectorStatus state) {
51 | setState(() {
52 | [
53 | BleConnectorStatus.connecting,
54 | BleConnectorStatus.disconnecting,
55 | BleConnectorStatus.scanning
56 | ].contains(state)
57 | ? WakelockPlus.enable()
58 | : WakelockPlus.disable();
59 |
60 | if (state == BleConnectorStatus.connected) {
61 | bleOta.init();
62 | }
63 | });
64 | }
65 |
66 | void _onBleOtaStateChanged(BleOtaState state) {
67 | setState(() {
68 | if (state.status == BleOtaStatus.uploaded) {
69 | () async {
70 | await bleConnector.disconnect();
71 | if (bleConnector.isConnectToKnownDeviceSupported) {
72 | await bleConnector.connectToKnownDevice();
73 | }
74 | }.call();
75 | } else if (state.status == BleOtaStatus.init) {
76 | WakelockPlus.enable();
77 | } else if (state.status == BleOtaStatus.initialized ||
78 | state.status == BleOtaStatus.error) {
79 | WakelockPlus.disable();
80 | }
81 | });
82 | }
83 |
84 | @override
85 | void initState() {
86 | super.initState();
87 | _subscriptions = [
88 | bleOta.stateStream.listen(_onBleOtaStateChanged),
89 | bleConnector.stateStream.listen(_onConnectionStateChanged),
90 | ];
91 | bleConnector.connect();
92 | }
93 |
94 | @override
95 | void dispose() {
96 | () async {
97 | await bleConnector.disconnect();
98 | for (var subscription in _subscriptions) {
99 | await subscription.cancel();
100 | }
101 | }.call();
102 | WakelockPlus.disable();
103 |
104 | bleOta.dispose();
105 | bleConnector.dispose();
106 | super.dispose();
107 | }
108 |
109 | bool _isBleOtaActive() {
110 | return bleOtaStatus == BleOtaStatus.idle ||
111 | bleOtaStatus == BleOtaStatus.init ||
112 | bleOtaStatus == BleOtaStatus.upload ||
113 | bleOtaStatus == BleOtaStatus.pinChange;
114 | }
115 |
116 | bool _isBleOtaInitialized() {
117 | return bleOtaStatus != BleOtaStatus.idle &&
118 | bleOtaStatus != BleOtaStatus.init;
119 | }
120 |
121 | bool _isRemoteInfoNotAvailable() {
122 | return _isBleOtaInitialized() && !bleOtaState.remoteInfo.isAvailable;
123 | }
124 |
125 | bool _canPop() {
126 | return !_isBleOtaActive();
127 | }
128 |
129 | bool _canUpload() {
130 | return connectionStatus == BleConnectorStatus.connected &&
131 | !_isBleOtaActive() &&
132 | bleOtaState.deviceCapabilities.uploadEnabled;
133 | }
134 |
135 | bool _canUploadLocalFile() {
136 | return skipInfoReading.value ||
137 | alwaysAllowLocalFilesUpload.value ||
138 | _isRemoteInfoNotAvailable();
139 | }
140 |
141 | Future _pickFile() async {
142 | FilePickerResult? result = await FilePicker.platform.pickFiles(
143 | type: FileType.custom,
144 | allowedExtensions: ['bin'],
145 | );
146 |
147 | if (result != null) {
148 | await WakelockPlus.enable();
149 | result.files.single.bytes == null
150 | ? await bleOta.uploadLocalFile(localPath: result.files.single.path!)
151 | : await bleOta.uploadBytes(bytes: result.files.single.bytes!);
152 | }
153 | }
154 |
155 | Future _uploadHttpFile(Software sw) async {
156 | await WakelockPlus.enable();
157 | await bleOta.uploadHttpFile(url: sw.path, size: sw.size);
158 | }
159 |
160 | MaterialColor _determinateStatusColor() {
161 | if (bleOtaStatus == BleOtaStatus.upload) {
162 | return Colors.blue;
163 | } else if (bleOtaStatus == BleOtaStatus.error) {
164 | return Colors.red;
165 | } else if (bleOtaStatus == BleOtaStatus.uploaded) {
166 | return Colors.green;
167 | } else {
168 | return Colors.blue;
169 | }
170 | }
171 |
172 | Widget _buildPripheralInfoWidget() => Card.outlined(
173 | child: ListTile(
174 | subtitle: Column(
175 | crossAxisAlignment: CrossAxisAlignment.start,
176 | children: [
177 | Text(
178 | tr('Hardware:', args: [createHardwareString(bleOtaState)]),
179 | ),
180 | const Divider(),
181 | Text(
182 | tr('Software:', args: [createSoftwareString(bleOtaState)]),
183 | ),
184 | ],
185 | ),
186 | onTap: bleOtaState.remoteInfo.isAvailable &&
187 | (bleOtaState.remoteInfo.hardwareText != null ||
188 | bleOtaState.remoteInfo.hardwarePage != null)
189 | ? bleOtaState.remoteInfo.hardwareText == null
190 | ? () async => await launchUrl(
191 | Uri.parse(bleOtaState.remoteInfo.hardwarePage!))
192 | : () async => await Navigator.push(
193 | context,
194 | MaterialPageRoute(
195 | builder: (context) => InfoScreen(
196 | title: bleOtaState.remoteInfo.hardwareName,
197 | textUrl: bleOtaState.remoteInfo.hardwareText!,
198 | pageUrl: bleOtaState.remoteInfo.hardwarePage,
199 | ),
200 | ),
201 | )
202 | : null,
203 | enabled: _canUpload(),
204 | ),
205 | );
206 |
207 | Widget _buildProgressInside() {
208 | if (bleOtaStatus == BleOtaStatus.upload) {
209 | return Text(
210 | (bleOtaState.uploadProgress * 100).toStringAsFixed(1),
211 | style: const TextStyle(
212 | fontWeight: FontWeight.bold,
213 | color: Colors.blue,
214 | fontSize: 24,
215 | ),
216 | );
217 | } else if (bleOtaStatus == BleOtaStatus.error) {
218 | return const Icon(
219 | Icons.error_rounded,
220 | color: Colors.red,
221 | size: 56,
222 | );
223 | } else if (bleOtaStatus == BleOtaStatus.uploaded) {
224 | return const Icon(
225 | Icons.done_rounded,
226 | color: Colors.green,
227 | size: 56,
228 | );
229 | } else {
230 | return CircleAvatar(
231 | radius: 55,
232 | backgroundColor: Colors.transparent,
233 | backgroundImage: bleOtaState.remoteInfo.hardwareIcon != null
234 | ? NetworkImage(bleOtaState.remoteInfo.hardwareIcon!)
235 | : null,
236 | );
237 | }
238 | }
239 |
240 | Widget _buildProgressWidget() {
241 | return SizedBox(
242 | width: 120,
243 | height: 120,
244 | child: Stack(
245 | fit: StackFit.expand,
246 | children: [
247 | CircularProgressIndicator(
248 | value: bleOtaState.uploadProgress,
249 | color: _determinateStatusColor(),
250 | strokeWidth: 10,
251 | backgroundColor: _determinateStatusColor().shade200,
252 | ),
253 | Center(child: _buildProgressInside()),
254 | ],
255 | ),
256 | );
257 | }
258 |
259 | Widget _buildSoftwareCard(Software sw) => Card(
260 | child: ListTile(
261 | leading: CircleAvatar(
262 | radius: 28,
263 | backgroundColor: Colors.grey,
264 | backgroundImage: sw.icon != null ? NetworkImage(sw.icon!) : null,
265 | ),
266 | title: Text(sw.name),
267 | subtitle: Text("v${sw.version}"),
268 | trailing: sw.text != null || sw.page != null
269 | ? IconButton(
270 | icon: Icon(sw.text == null
271 | ? Icons.language_rounded
272 | : Icons.info_rounded),
273 | onPressed: sw.text == null
274 | ? () async => await launchUrl(Uri.parse(sw.page!))
275 | : () async => await Navigator.push(
276 | context,
277 | MaterialPageRoute(
278 | builder: (context) => InfoScreen(
279 | title: sw.toString(),
280 | textUrl: sw.text!,
281 | pageUrl: sw.page,
282 | ),
283 | ),
284 | ),
285 | )
286 | : null,
287 | onTap: () => _uploadHttpFile(sw),
288 | enabled: _canUpload(),
289 | ),
290 | );
291 |
292 | Widget _buildSoftwareList() => Column(
293 | children: [
294 | for (var sw in bleOtaState.remoteInfo.softwareList)
295 | _buildSoftwareCard(sw)
296 | ],
297 | );
298 |
299 | Widget _buildStatusText(String text) {
300 | return Padding(
301 | padding: const EdgeInsets.all(10),
302 | child: Text(text,
303 | textAlign: TextAlign.center,
304 | style: const TextStyle(
305 | fontSize: 24,
306 | )),
307 | );
308 | }
309 |
310 | Widget _buildStatusWidget() {
311 | if (connectionStatus == BleConnectorStatus.connecting) {
312 | return _buildStatusText(tr('Connecting..'));
313 | } else if (connectionStatus == BleConnectorStatus.disconnecting) {
314 | return _buildStatusText(tr('Disconnecting..'));
315 | } else if (connectionStatus == BleConnectorStatus.disconnected) {
316 | return _buildStatusText(tr('Disconnected'));
317 | } else if (connectionStatus == BleConnectorStatus.scanning) {
318 | return _buildStatusText(tr('Scanning..'));
319 | } else if (bleOtaStatus == BleOtaStatus.init) {
320 | return _buildStatusText(tr('Loading..'));
321 | } else if (bleOtaStatus == BleOtaStatus.upload) {
322 | return _buildStatusText(tr('Uploading..'));
323 | } else if (bleOtaStatus == BleOtaStatus.error) {
324 | return _buildStatusText(determineError(bleOtaState));
325 | } else if (!bleOtaState.remoteInfo.isAvailable) {
326 | return _buildStatusText(tr('Connected'));
327 | } else if (bleOtaState.remoteInfo.softwareList.isEmpty) {
328 | return _buildStatusText(tr('NoAvailableSoftwares'));
329 | } else if (bleOtaState.remoteInfo.newestSoftware == null) {
330 | return _buildStatusText(tr('NewestSoftwareAlreadyInstalled'));
331 | } else {
332 | return Column(
333 | crossAxisAlignment: CrossAxisAlignment.start,
334 | mainAxisAlignment: MainAxisAlignment.start,
335 | children: [
336 | Padding(
337 | padding: const EdgeInsets.all(10),
338 | child: Text(
339 | tr('NewSoftwareAvailable:'),
340 | textAlign: TextAlign.left,
341 | ),
342 | ),
343 | _buildSoftwareCard(bleOtaState.remoteInfo.newestSoftware!),
344 | ],
345 | );
346 | }
347 | }
348 |
349 | Widget _buildStatusWithSoftwareList() => ExpandableNotifier(
350 | child: Column(children: [
351 | _buildStatusWidget(),
352 | ScrollOnExpand(
353 | scrollOnExpand: true,
354 | scrollOnCollapse: false,
355 | child: ExpandablePanel(
356 | header: Padding(
357 | padding: const EdgeInsets.all(10),
358 | child: Text(tr('AllAvailableSoftwares:')),
359 | ),
360 | collapsed: const SizedBox(),
361 | expanded: _buildSoftwareList(),
362 | ),
363 | ),
364 | ]),
365 | );
366 |
367 | Widget _buildStatusWithOptionallySoftwareList() {
368 | final buildSoftwareList =
369 | connectionStatus == BleConnectorStatus.connected &&
370 | !_isBleOtaActive() &&
371 | bleOtaState.remoteInfo.isAvailable &&
372 | bleOtaState.remoteInfo.softwareList.isNotEmpty;
373 | return buildSoftwareList
374 | ? _buildStatusWithSoftwareList()
375 | : _buildStatusWidget();
376 | }
377 |
378 | Widget _buildSoftwareStatusWidget() => Expanded(
379 | child: ListView(
380 | children: [
381 | _buildStatusWithOptionallySoftwareList(),
382 | ],
383 | ),
384 | );
385 |
386 | Widget _buildUploadFileButton() => SizedBox(
387 | height: buttonHeight,
388 | child: Row(
389 | crossAxisAlignment: CrossAxisAlignment.stretch,
390 | children: [
391 | Expanded(
392 | child: FilledButton.icon(
393 | icon: const Icon(Icons.file_open_rounded),
394 | label: Text(tr('UploadFile')),
395 | onPressed: _canUpload() ? _pickFile : null,
396 | ),
397 | ),
398 | ],
399 | ),
400 | );
401 |
402 | Widget _buildPortrait() => Column(
403 | crossAxisAlignment: CrossAxisAlignment.stretch,
404 | children: [
405 | _buildPripheralInfoWidget(),
406 | const SizedBox(height: 20),
407 | Row(
408 | mainAxisAlignment: MainAxisAlignment.center,
409 | children: [_buildProgressWidget()],
410 | ),
411 | const SizedBox(height: 20),
412 | _buildSoftwareStatusWidget(),
413 | if (_canUploadLocalFile())
414 | const SizedBox(height: screenPortraitSplitter),
415 | if (_canUploadLocalFile()) _buildUploadFileButton(),
416 | ],
417 | );
418 |
419 | Widget _buildLandscape() => Row(
420 | children: [
421 | Expanded(
422 | child: Column(
423 | children: [
424 | _buildPripheralInfoWidget(),
425 | _buildSoftwareStatusWidget(),
426 | ],
427 | ),
428 | ),
429 | const SizedBox(width: screenLandscapeSplitter),
430 | Expanded(
431 | child: Column(
432 | crossAxisAlignment: CrossAxisAlignment.stretch,
433 | mainAxisAlignment: _canUploadLocalFile()
434 | ? MainAxisAlignment.spaceBetween
435 | : MainAxisAlignment.spaceEvenly,
436 | children: [
437 | Row(
438 | mainAxisAlignment: MainAxisAlignment.center,
439 | children: [_buildProgressWidget()],
440 | ),
441 | const SizedBox(height: 20),
442 | if (_canUploadLocalFile()) _buildUploadFileButton(),
443 | ],
444 | ),
445 | ),
446 | ],
447 | );
448 |
449 | @override
450 | Widget build(BuildContext context) => Scaffold(
451 | primary: MediaQuery.of(context).orientation == Orientation.portrait,
452 | appBar: AppBar(
453 | title: Text(blePeripheral.name ?? ''),
454 | centerTitle: true,
455 | leading: IconButton(
456 | icon: const Icon(Icons.arrow_back_rounded),
457 | onPressed: _canPop()
458 | ? () {
459 | Navigator.pop(context);
460 | }
461 | : null,
462 | ),
463 | actions: [
464 | if (bleOtaState.deviceCapabilities.pinChangeSupported)
465 | IconButton(
466 | icon: const Icon(Icons.pin_rounded),
467 | onPressed: _canUpload()
468 | ? () async => await Navigator.push(
469 | context,
470 | MaterialPageRoute(
471 | builder: (context) => PinScreen(
472 | blePeripheral: blePeripheral,
473 | bleOta: bleOta,
474 | ),
475 | ),
476 | )
477 | : null,
478 | ),
479 | ],
480 | ),
481 | body: SafeArea(
482 | minimum: const EdgeInsets.all(screenPadding),
483 | child: OrientationBuilder(
484 | builder: (context, orientation) =>
485 | orientation == Orientation.portrait
486 | ? _buildPortrait()
487 | : _buildLandscape(),
488 | ),
489 | ),
490 | );
491 | }
492 |
--------------------------------------------------------------------------------