├── 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 | [Download from Google Play](https://play.google.com/store/apps/details?id=com.vovagorodok.ble_ota_app)    15 | [Download from F-Droid](https://f-droid.org/packages/com.vovagorodok.ble_ota_app/)    18 | [Download from App Store](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 | image/svg+xml 46 | 51 | OPEN IT ONWEB Page 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 | --------------------------------------------------------------------------------