From 3d258f0e3b7b19a2cf5b1a187af95529e61169f1 Mon Sep 17 00:00:00 2001 From: Bram Verhulst Date: Thu, 29 Aug 2024 21:16:30 +0200 Subject: [PATCH] Init --- .gitignore | 43 ++ .metadata | 30 ++ README.md | 16 + analysis_options.yaml | 28 ++ android/.gitignore | 13 + android/app/build.gradle | 58 +++ android/app/src/debug/AndroidManifest.xml | 7 + android/app/src/main/AndroidManifest.xml | 48 ++ .../brammie15/xapk_installer/MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + android/app/src/main/res/values/styles.xml | 18 + android/app/src/profile/AndroidManifest.xml | 7 + android/build.gradle | 18 + android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + android/settings.gradle | 25 ++ lib/AppStateModel.dart | 47 ++ lib/BluetoothUtils.dart | 10 + lib/ConnectionScreenDialogs.dart | 18 + lib/CustomBluetoothService.dart | 25 ++ lib/MainControlScreen.dart | 119 +++++ lib/NoDeviceScreen.dart | 34 ++ lib/connectPage.dart | 368 +++++++++++++++ lib/main.dart | 39 ++ lib/pickers/block_picker.dart | 419 ++++++++++++++++++ lib/widgets.dart | 0 pubspec.lock | 282 ++++++++++++ pubspec.yaml | 95 ++++ test/widget_test.dart | 30 ++ 36 files changed, 1852 insertions(+) create mode 100644 .gitignore create mode 100644 .metadata create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 android/.gitignore create mode 100644 android/app/build.gradle create mode 100644 android/app/src/debug/AndroidManifest.xml create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/kotlin/dev/brammie15/xapk_installer/MainActivity.kt create mode 100644 android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 android/app/src/main/res/drawable/launch_background.xml create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/values-night/styles.xml create mode 100644 android/app/src/main/res/values/styles.xml create mode 100644 android/app/src/profile/AndroidManifest.xml create mode 100644 android/build.gradle create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/settings.gradle create mode 100644 lib/AppStateModel.dart create mode 100644 lib/BluetoothUtils.dart create mode 100644 lib/ConnectionScreenDialogs.dart create mode 100644 lib/CustomBluetoothService.dart create mode 100644 lib/MainControlScreen.dart create mode 100644 lib/NoDeviceScreen.dart create mode 100644 lib/connectPage.dart create mode 100644 lib/main.dart create mode 100644 lib/pickers/block_picker.dart create mode 100644 lib/widgets.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml create mode 100644 test/widget_test.dart diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6503b5e --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..e720147 --- /dev/null +++ b/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "b0850beeb25f6d5b10426284f506557f66181b36" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: b0850beeb25f6d5b10426284f506557f66181b36 + base_revision: b0850beeb25f6d5b10426284f506557f66181b36 + - platform: android + create_revision: b0850beeb25f6d5b10426284f506557f66181b36 + base_revision: b0850beeb25f6d5b10426284f506557f66181b36 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/README.md b/README.md new file mode 100644 index 0000000..22aadc8 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# xapk_installer + +A quick app to install XAPK files + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..d4e0f0c --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..5d99765 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..dd808a6 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,58 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file("local.properties") +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader("UTF-8") { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty("flutter.versionCode") +if (flutterVersionCode == null) { + flutterVersionCode = "1" +} + +def flutterVersionName = localProperties.getProperty("flutter.versionName") +if (flutterVersionName == null) { + flutterVersionName = "1.0" +} + +android { + namespace = "dev.brammie15.xapk_installer" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "dev.brammie15.xapk_installer" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdk = 19 + targetSdk = flutter.targetSdkVersion + versionCode = flutterVersionCode.toInteger() + versionName = flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..8ffe024 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..82609bb --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/dev/brammie15/xapk_installer/MainActivity.kt b/android/app/src/main/kotlin/dev/brammie15/xapk_installer/MainActivity.kt new file mode 100644 index 0000000..39328f9 --- /dev/null +++ b/android/app/src/main/kotlin/dev/brammie15/xapk_installer/MainActivity.kt @@ -0,0 +1,5 @@ +package dev.brammie15.xapk_installer + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..1cb7aa2 --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..8403758 --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..360a160 --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..5fac679 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..8ffe024 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..0066644 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..0d7fbd5 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..8854897 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..0f10e04 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" diff --git a/lib/AppStateModel.dart b/lib/AppStateModel.dart new file mode 100644 index 0000000..fcc0791 --- /dev/null +++ b/lib/AppStateModel.dart @@ -0,0 +1,47 @@ +import 'dart:async'; + +import 'package:bluetooth_classic/models/device.dart'; +import 'package:flutter/material.dart'; + +class AppStateModel extends ChangeNotifier { + bool _scanning = false; + List _discoveredDevices = []; + int _deviceStatus = 0; + + + bool get scanning => _scanning; + List get discoveredDevices => _discoveredDevices; + int get deviceStatus => _deviceStatus; + + void setScanning(bool value) { + _scanning = value; + notifyListeners(); + } + + void setDiscoveredDevices(List value) { + _discoveredDevices = value; + notifyListeners(); + } + + void addDiscoveredDevice(Device value) { + _discoveredDevices.add(value); + notifyListeners(); + } + + void clearDiscoveredDevices() { + _discoveredDevices.clear(); + notifyListeners(); + } + + void setDeviceStatus(int value) { + _deviceStatus = value; + notifyListeners(); + } + + + @override + void dispose() { + // listen?.cancel(); + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/BluetoothUtils.dart b/lib/BluetoothUtils.dart new file mode 100644 index 0000000..d5eacdb --- /dev/null +++ b/lib/BluetoothUtils.dart @@ -0,0 +1,10 @@ + +// +// bool isAlreadyConnectedToDevice(Device device, List connectedDevices) { +// for (final connectedDevice in connectedDevices) { +// if (connectedDevice.address == device.address) { +// return true; +// } +// } +// return false; +// } \ No newline at end of file diff --git a/lib/ConnectionScreenDialogs.dart b/lib/ConnectionScreenDialogs.dart new file mode 100644 index 0000000..d94f7b6 --- /dev/null +++ b/lib/ConnectionScreenDialogs.dart @@ -0,0 +1,18 @@ + +import 'package:flutter/material.dart'; + +void showAlreadyConnectedDialog(BuildContext context) { + showDialog(context: context, builder: (context) { + return AlertDialog( + title: const Text('Already connected'), + content: const Text('This device is already connected'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('OK')) + ], + ); + }); +} diff --git a/lib/CustomBluetoothService.dart b/lib/CustomBluetoothService.dart new file mode 100644 index 0000000..17eeb80 --- /dev/null +++ b/lib/CustomBluetoothService.dart @@ -0,0 +1,25 @@ + +import 'package:bluetooth_classic/bluetooth_classic.dart'; +import 'package:bluetooth_classic/models/device.dart'; + +class Custombluetoothservice extends BluetoothClassic { + // @override + // Stream onDeviceDataReceived() { + // return _onDeviceDataReceived ??= + // super.onDeviceDataReceived().asBroadcastStream(); + // } + + // Stream? _onDeviceDataReceived; + + @override + Stream onDeviceDiscovered() => + _onDeviceDiscovered ??= super.onDeviceDiscovered().asBroadcastStream(); + Stream? _onDeviceDiscovered; + + @override + Stream onDeviceStatusChanged() => + _onDeviceStatusChanged ??= + super.onDeviceStatusChanged().asBroadcastStream(); + Stream? _onDeviceStatusChanged; + +} \ No newline at end of file diff --git a/lib/MainControlScreen.dart b/lib/MainControlScreen.dart new file mode 100644 index 0000000..9a4ae13 --- /dev/null +++ b/lib/MainControlScreen.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_colorpicker/flutter_colorpicker.dart'; +import 'package:provider/provider.dart'; +import 'package:xapk_installer/AppStateModel.dart'; +import 'package:xapk_installer/connectPage.dart'; +import 'package:xapk_installer/pickers/block_picker.dart'; + +class Maincontrolscreen extends StatefulWidget { + const Maincontrolscreen({super.key}); + + @override + State createState() => _MaincontrolscreenState(); +} + +class _MaincontrolscreenState extends State { + // final _bluetoothClassicPlugin = BluetoothClassic(); + + List> gridColors = List.generate( + 5, + (i) => List.generate(5, (j) => Colors.grey), // Initialize with grey color + ); + + // Method to toggle the color of a button + void toggleButtonColor(int i, int j) { + setState(() { + gridColors[i][j] = currentColor; + }); + } + + bool isConnected = false; + + Color currentColor = Colors.grey; + + // Widget buildStatusIcon(){ + // if(Provider.of(context).name == Device.connected){ + // return Icon(Icons.bluetooth_connected); + // } + // else if( == Device.connecting){ + // return Icon(Icons.bluetooth_searching); + // } + // else{ + // return Icon(Icons.bluetooth_disabled); + // } + // } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Connect Page'), + actions: [ + IconButton(onPressed: (){ + Navigator.push(context, MaterialPageRoute(builder: (context) => ConnectPage())); + }, icon: Icon(Icons.bluetooth)), + IconButton(onPressed: (){ + + }, icon: Icon(Icons.upload)), + ], + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: GridView.builder( + shrinkWrap: true, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 5, + childAspectRatio: 1.0, + crossAxisSpacing: 8.0, + mainAxisSpacing: 8.0, + ), + itemCount: 25, // 5x5 grid + itemBuilder: (context, index) { + int row = index ~/ 5; + int col = index % 5; + return GestureDetector( + onTap: () => toggleButtonColor(row, col), + child: Container( + color: gridColors[row][col], + ), + ); + }, + ), + ), + SizedBox(height: 15), + // ColorPicker( + // hexInputBar: false, + // pickerAreaBorderRadius: const BorderRadius.all( + // Radius.circular(10)), + // portraitOnly: true, + // pickerColor: currentColor, + // onColorChanged: (color) { + // currentColor = color; + // }, + // enableAlpha: false, + // colorPickerWidth: 100, + // displayThumbColor: true, + // + // + // ) + Center( + child: BlockPicker( + pickerColor: currentColor, + onColorChanged: (color) { + currentColor = color; + }, + + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/NoDeviceScreen.dart b/lib/NoDeviceScreen.dart new file mode 100644 index 0000000..e507614 --- /dev/null +++ b/lib/NoDeviceScreen.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +class NoDevicesScreen extends StatelessWidget { + const NoDevicesScreen({super.key, required this.onScanPressed}); + + final VoidCallback onScanPressed; + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.bluetooth_disabled, + size: 100, + color: Colors.grey, + ), + const SizedBox(height: 10), + const Text('No devices found', style: TextStyle(fontSize: 20)), + const SizedBox(height: 10), + // Button to scan for devices + ElevatedButton( + onPressed: onScanPressed, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: const Text('Scan for devices', style: TextStyle(fontSize: 15)), + ), + ), + ], + ), + ); + } +} diff --git a/lib/connectPage.dart b/lib/connectPage.dart new file mode 100644 index 0000000..964942d --- /dev/null +++ b/lib/connectPage.dart @@ -0,0 +1,368 @@ +import 'dart:async'; + +import 'package:bluetooth_classic/bluetooth_classic.dart'; +import 'package:bluetooth_classic/models/device.dart'; +import 'package:flutter/foundation.dart'; + +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:provider/provider.dart'; +import 'package:xapk_installer/AppStateModel.dart'; +import 'package:xapk_installer/BluetoothUtils.dart'; +import 'package:xapk_installer/ConnectionScreenDialogs.dart'; +import 'package:xapk_installer/CustomBluetoothService.dart'; + +import 'NoDeviceScreen.dart'; + +class ConnectPage extends StatefulWidget { + const ConnectPage({super.key}); + + @override + State createState() => _ConnectPageState(); +} + +class _ConnectPageState extends State { + final _bluetoothClassicPlugin = Custombluetoothservice(); + + //a bool to hide the no devices found screen when the user presses the scan button + bool _hideNoDevicesScreen = false; + + bool _scanning = false; + + Icon _statusIcon = const Icon(Icons.restart_alt); + + int _deviceStatus = Device.disconnected; + + Device _connectedDevice = Device(name: 'Unknown', address: 'Unknown'); + + StreamSubscription? deviceListen; + StreamSubscription? deviceChangeListen; + + + + //Init + @override + void initState() { + super.initState(); + _bluetoothClassicPlugin.initPermissions(); + _bluetoothClassicPlugin.onDeviceStatusChanged().listen((event) { + setState(() { + Provider.of(context, listen: false).setDeviceStatus(event); + _deviceStatus = event; + }); + }); + _bluetoothClassicPlugin.onDeviceDiscovered().listen( + (event) { + if (kDebugMode) { + print('Device discovered: ${event.name}'); + } + if (_scanning) { + Provider.of(context, listen: false) + .addDiscoveredDevice(event); + //Sort list put Unknowns at the end + Provider.of(context, listen: false) + .discoveredDevices + .sort((a, b) { + if (a.name == null) { + return 1; + } + if (b.name == null) { + return -1; + } + return a.name!.compareTo(b.name!); + }); + } + }, + ); + } + + //Dispose + @override + void dispose() { + super.dispose(); + // Provider.of(context, listen: false).listen?.cancel(); + + } + + Future _scan() async { + if (_scanning) { + await _bluetoothClassicPlugin.stopScan(); + if (kDebugMode) { + print('Scanning stopped'); + } + setState(() { + _scanning = false; + _statusIcon = const Icon(Icons.restart_alt); + }); + } else { + setState(() { + Provider.of(context, listen: false).clearDiscoveredDevices(); + _hideNoDevicesScreen = true; + _statusIcon = const Icon(Icons.cancel); + }); + await _bluetoothClassicPlugin.startScan(); + if (kDebugMode) { + print('Scanning started'); + } + setState(() { + _scanning = true; + }); + } + } + + Future _stopScan() async { + await _bluetoothClassicPlugin.stopScan(); + if (kDebugMode) { + print('Scanning stopped'); + } + await _bluetoothClassicPlugin.disconnect(); + setState(() { + _scanning = false; + _statusIcon = const Icon(Icons.restart_alt); + }); + } + + Future _showConnectConfirmDialog( + BuildContext context, String deviceName, String deviceAddress) { + return showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Connect'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Do you want to connect to this device?'), + Container(height: 10), + Text(deviceName, + style: const TextStyle( + fontWeight: FontWeight.bold, + decoration: TextDecoration.underline, + fontSize: 20)), + Text(deviceAddress, style: const TextStyle(fontSize: 12)), + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: const Text('No')), + TextButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + child: const Text('Yes')), + ], + ); + }); + } + + Future _connect(Device device) async { + final connect = await _showConnectConfirmDialog( + context, device.name ?? 'Unknown', device.address); + if (connect == null || !connect) { + return; + } + bool connected = false; + bool error = false; + List alreadyConnectedDevices = []; + try { + alreadyConnectedDevices = + await _bluetoothClassicPlugin.getPairedDevices(); + } catch (e) { + error = true; + } + if(error){ + if (context.mounted) { + if (kDebugMode) { + print('Failed to get paired devices'); + } + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Failed to get paired devices'), + content: const Text('Failed to get paired devices'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('OK')) + ], + ); + }); + return; + } + } + // //If the device is already connected, tell the user + // if (alreadyConnectedDevices + // .any((element) => element.address == device.address)) { + // if (context.mounted) { + // if (kDebugMode) { + // print('Device already connected'); + // } + // showAlreadyConnectedDialog(context); + // return; + // } + // } + + if(_deviceStatus == Device.connected){ + if (context.mounted) { + if (kDebugMode) { + print('Device already connected'); + } + showAlreadyConnectedDialog(context); + return; + } + } + + try { + connected = await _bluetoothClassicPlugin.connect( + device.address, "00001101-0000-1000-8000-00805f9b34fb"); + } catch (e) { + error = true; + } + if (error || !connected) { + if (context.mounted) { + if (kDebugMode) { + print('Failed to connect'); + } + } + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Failed to connect'), + content: const Text('Failed to connect to the device'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('OK')) + ], + ); + }); + return; + } + setState(() { + _statusIcon = const Icon(Icons.restart_alt); + _connectedDevice = device; + }); + + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Connected'), + content: const Text('You are now connected to the device'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('OK')) + ], + ); + }); + + await _bluetoothClassicPlugin.write("setPix 1 1 255 0 0\n"); //TODO: remove this + } + + + Future _disconnect() async { + await _bluetoothClassicPlugin.disconnect(); + setState(() { + _statusIcon = const Icon(Icons.restart_alt); + _connectedDevice = Device(name: 'Unknown', address: 'Unknown'); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Connect Page'), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + actions: [ + IconButton( + icon: _statusIcon, + onPressed: () { + _scan(); + }, + ), + IconButton( + onPressed: () { + _stopScan(); + Provider.of(context, listen: false) + .clearDiscoveredDevices(); + }, + icon: const Icon(Icons.star_purple500_outlined)) + ], + ), + body: !_hideNoDevicesScreen + ? NoDevicesScreen(onScanPressed: () { + setState(() { + _hideNoDevicesScreen = true; + }); + _scan(); + }) + : Padding( + padding: const EdgeInsets.all(4.0), + child: Consumer( + builder: (context, appstate, child) { + final List _discoveredDevices = + appstate.discoveredDevices; + return ListView.builder(itemBuilder: (context, index) { + if (index >= _discoveredDevices.length) { + return null; + } + return ListTile( + title: Text(_discoveredDevices[index].name ?? 'Unknown', style: TextStyle( + fontWeight: _discoveredDevices[index].name == _connectedDevice.name ? FontWeight.bold : FontWeight.normal, + color: _discoveredDevices[index].name == _connectedDevice.name ? Colors.green : Colors.white + ),), + subtitle: Text(_discoveredDevices[index].address), + leading: _discoveredDevices[index].name == null ? const Icon(Icons.question_mark) : const Icon( + Icons.bluetooth, + color: Colors.blueAccent, + ), + style: ListTileStyle.list, + onTap: () async { + if(_discoveredDevices[index].name == _connectedDevice.name){ + await _disconnect(); + } else { + await _connect(_discoveredDevices[index]); + } + }, + onLongPress: () async { + // if (isAlreadyConnectedToDevice(_discoveredDevices[index], + // await _bluetoothClassicPlugin.getPairedDevices())) { + // _bluetoothClassicPlugin.disconnect(); + // } + Fluttertoast.showToast( + msg: Provider.of(context, listen: false).deviceStatus.toString(), + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + timeInSecForIosWeb: 1, + backgroundColor: Colors.red, + textColor: Colors.white, + fontSize: 16.0 + ); + + }, + ); + }); + } + ), + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..4b20f49 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:provider/provider.dart'; +import 'package:xapk_installer/MainControlScreen.dart'; +import 'package:xapk_installer/connectPage.dart'; + +import 'AppStateModel.dart'; + +void main() { + runApp(ChangeNotifierProvider( + create: (context) => AppStateModel(), + child: const MyApp(), + )); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + darkTheme: ThemeData.dark(), + debugShowCheckedModeBanner: false, + home: Maincontrolscreen(), + + ); + } +} diff --git a/lib/pickers/block_picker.dart b/lib/pickers/block_picker.dart new file mode 100644 index 0000000..1e4796d --- /dev/null +++ b/lib/pickers/block_picker.dart @@ -0,0 +1,419 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_colorpicker/flutter_colorpicker.dart'; + +const List colors = [ + Colors.red, + Colors.pink, + Colors.purple, + Colors.deepPurple, + Colors.indigo, + Colors.blue, + Colors.lightBlue, + Colors.cyan, + Colors.teal, + Colors.green, + Colors.lightGreen, + Colors.lime, + Colors.yellow, + Colors.amber, + Colors.orange, + Colors.deepOrange, + Colors.brown, + Colors.grey, + Colors.blueGrey, + Colors.black, +]; + +class BlockColorPickerExample extends StatefulWidget { + const BlockColorPickerExample({ + Key? key, + required this.pickerColor, + required this.onColorChanged, + required this.pickerColors, + required this.onColorsChanged, + required this.colorHistory, + }) : super(key: key); + + final Color pickerColor; + final ValueChanged onColorChanged; + final List pickerColors; + final ValueChanged> onColorsChanged; + final List colorHistory; + + @override + State createState() => _BlockColorPickerExampleState(); +} + +class _BlockColorPickerExampleState extends State { + int _portraitCrossAxisCount = 4; + int _landscapeCrossAxisCount = 5; + double _borderRadius = 30; + double _blurRadius = 5; + double _iconSize = 24; + + Widget pickerLayoutBuilder(BuildContext context, List colors, PickerItem child) { + Orientation orientation = MediaQuery.of(context).orientation; + + return SizedBox( + width: 300, + height: orientation == Orientation.portrait ? 360 : 240, + child: GridView.count( + crossAxisCount: orientation == Orientation.portrait ? _portraitCrossAxisCount : _landscapeCrossAxisCount, + crossAxisSpacing: 5, + mainAxisSpacing: 5, + children: [for (Color color in colors) child(color)], + ), + ); + } + + Widget pickerItemBuilder(Color color, bool isCurrentColor, void Function() changeColor) { + return Container( + margin: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(_borderRadius), + color: color, + boxShadow: [BoxShadow(color: color.withOpacity(0.8), offset: const Offset(1, 2), blurRadius: _blurRadius)], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: changeColor, + borderRadius: BorderRadius.circular(_borderRadius), + child: AnimatedOpacity( + duration: const Duration(milliseconds: 250), + opacity: isCurrentColor ? 1 : 0, + child: Icon( + Icons.done, + size: _iconSize, + color: useWhiteForeground(color) ? Colors.white : Colors.black, + ), + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return ListView( + children: [ + const SizedBox(height: 20), + Center( + child: ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + titlePadding: const EdgeInsets.all(0), + contentPadding: const EdgeInsets.all(25), + content: SingleChildScrollView( + child: Text( + ''' +Widget pickerLayoutBuilder(BuildContext context, List colors, PickerItem child) { + Orientation orientation = MediaQuery.of(context).orientation; + + return SizedBox( + width: 300, + height: orientation == Orientation.portrait ? 360 : 240, + child: GridView.count( + crossAxisCount: orientation == Orientation.portrait ? $_portraitCrossAxisCount : $_landscapeCrossAxisCount, + crossAxisSpacing: 5, + mainAxisSpacing: 5, + children: [for (Color color in colors) child(color)], + ), + ); +} + ''', + ), + ), + ); + }, + ); + }, + child: Icon(Icons.code, color: useWhiteForeground(widget.pickerColor) ? Colors.white : Colors.black), + style: ElevatedButton.styleFrom( + backgroundColor: widget.pickerColor, + shadowColor: widget.pickerColor.withOpacity(1), + elevation: 10, + ), + ), + ), + ListTile( + title: const Text('Portrait Cross Axis Count'), + subtitle: Text(_portraitCrossAxisCount.toString()), + trailing: SizedBox( + width: 200, + child: Slider( + value: _portraitCrossAxisCount.toDouble(), + min: 1, + max: 10, + divisions: 9, + label: _portraitCrossAxisCount.toString(), + onChanged: (double value) => setState(() => _portraitCrossAxisCount = value.round()), + ), + ), + ), + ListTile( + title: const Text('Landscape Cross Axis Count'), + subtitle: Text(_landscapeCrossAxisCount.toString()), + trailing: SizedBox( + width: 200, + child: Slider( + value: _landscapeCrossAxisCount.toDouble(), + min: 1, + max: 10, + divisions: 9, + label: _landscapeCrossAxisCount.toString(), + onChanged: (double value) => setState(() => _landscapeCrossAxisCount = value.round()), + ), + ), + ), + const Divider(), + const SizedBox(height: 20), + Center( + child: ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + titlePadding: const EdgeInsets.all(0), + contentPadding: const EdgeInsets.all(25), + content: SingleChildScrollView( + child: Text( + ''' +Widget pickerItemBuilder(Color color, bool isCurrentColor, void Function() changeColor) { + return Container( + margin: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular($_borderRadius), + color: color, + boxShadow: [BoxShadow(color: color.withOpacity(0.8), offset: const Offset(1, 2), blurRadius: $_blurRadius)], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: changeColor, + borderRadius: BorderRadius.circular(_borderRadius), + child: AnimatedOpacity( + duration: const Duration(milliseconds: 250), + opacity: isCurrentColor ? 1 : 0, + child: Icon( + Icons.done, + size: $_iconSize, + color: useWhiteForeground(color) ? Colors.white : Colors.black, + ), + ), + ), + ), + ); +} + ''', + ), + ), + ); + }, + ); + }, + child: Icon(Icons.code, color: useWhiteForeground(widget.pickerColor) ? Colors.white : Colors.black), + style: ElevatedButton.styleFrom( + backgroundColor: widget.pickerColor, + shadowColor: widget.pickerColor.withOpacity(1), + elevation: 10, + ), + ), + ), + ListTile( + title: const Text('Border Radius'), + subtitle: Text(_borderRadius.toString()), + trailing: SizedBox( + width: 200, + child: Slider( + value: _borderRadius, + min: 0, + max: 30, + divisions: 30, + label: _borderRadius.toString(), + onChanged: (double value) => setState(() => _borderRadius = value.round().toDouble()), + ), + ), + ), + ListTile( + title: const Text('Blur Radius'), + subtitle: Text(_blurRadius.toString()), + trailing: SizedBox( + width: 200, + child: Slider( + value: _blurRadius, + min: 0, + max: 5, + divisions: 5, + label: _blurRadius.toString(), + onChanged: (double value) => setState(() => _blurRadius = value.round().toDouble()), + ), + ), + ), + ListTile( + title: const Text('Icon Size'), + subtitle: Text(_iconSize.toString()), + trailing: SizedBox( + width: 200, + child: Slider( + value: _iconSize, + min: 1, + max: 50, + divisions: 49, + label: _iconSize.toString(), + onChanged: (double value) => setState(() => _iconSize = value.round().toDouble()), + ), + ), + ), + const Divider(), + const SizedBox(height: 20), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Select a color'), + content: SingleChildScrollView( + child: BlockPicker( + pickerColor: widget.pickerColor, + onColorChanged: widget.onColorChanged, + availableColors: widget.colorHistory.isNotEmpty ? widget.colorHistory : colors, + layoutBuilder: pickerLayoutBuilder, + itemBuilder: pickerItemBuilder, + ), + ), + ); + }, + ); + }, + child: Text( + 'Blocky Color Picker', + style: TextStyle(color: useWhiteForeground(widget.pickerColor) ? Colors.white : Colors.black), + ), + style: ElevatedButton.styleFrom( + backgroundColor: widget.pickerColor, + shadowColor: widget.pickerColor.withOpacity(1), + elevation: 10, + ), + ), + const SizedBox(width: 20), + ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return const AlertDialog( + titlePadding: EdgeInsets.all(0), + contentPadding: EdgeInsets.all(25), + content: SingleChildScrollView( + child: Text( + ''' +BlockPicker( + pickerColor: color, + onColorChanged: changeColor, + availableColors: colors, + layoutBuilder: pickerLayoutBuilder, + itemBuilder: pickerItemBuilder, +) + ''', + ), + ), + ); + }, + ); + }, + child: Icon(Icons.code, color: useWhiteForeground(widget.pickerColor) ? Colors.white : Colors.black), + style: ElevatedButton.styleFrom( + backgroundColor: widget.pickerColor, + shadowColor: widget.pickerColor.withOpacity(1), + elevation: 10, + ), + ), + ], + ), + const SizedBox(height: 20), + const Divider(), + const SizedBox(height: 20), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Select colors'), + content: SingleChildScrollView( + child: MultipleChoiceBlockPicker( + pickerColors: widget.pickerColors, + onColorsChanged: widget.onColorsChanged, + availableColors: widget.colorHistory.isNotEmpty ? widget.colorHistory : colors, + layoutBuilder: pickerLayoutBuilder, + itemBuilder: pickerItemBuilder, + ), + ), + ); + }, + ); + }, + child: Text( + 'Multiple selection Blocky Color Picker', + style: TextStyle(color: useWhiteForeground(widget.pickerColor) ? Colors.white : Colors.black), + ), + style: ElevatedButton.styleFrom( + backgroundColor: widget.pickerColor, + shadowColor: widget.pickerColor.withOpacity(1), + elevation: 10, + ), + ), + const SizedBox(width: 20), + ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return const AlertDialog( + titlePadding: EdgeInsets.all(0), + contentPadding: EdgeInsets.all(25), + content: SingleChildScrollView( + child: Text( + ''' +MultipleChoiceBlockPicker( + pickerColors: colors, + onColorsChanged: changeColors, + availableColors: colors, + layoutBuilder: pickerLayoutBuilder, + itemBuilder: pickerItemBuilder, +) + ''', + ), + ), + ); + }, + ); + }, + child: Icon(Icons.code, color: useWhiteForeground(widget.pickerColor) ? Colors.white : Colors.black), + style: ElevatedButton.styleFrom( + backgroundColor: widget.pickerColor, + shadowColor: widget.pickerColor.withOpacity(1), + elevation: 10, + ), + ), + ], + ), + const SizedBox(height: 80), + ], + ); + } +} \ No newline at end of file diff --git a/lib/widgets.dart b/lib/widgets.dart new file mode 100644 index 0000000..e69de29 diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..06547d6 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,282 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + bluetooth_classic: + dependency: "direct main" + description: + name: bluetooth_classic + sha256: "0c73a487517b69acb90e2f1c9a21b563fb11771aa1a41ad0fe7d5563b5fb2877" + url: "https://pub.dev" + source: hosted + version: "0.0.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bluetooth_serial: + dependency: "direct main" + description: + name: flutter_bluetooth_serial + sha256: "248608f777e92e867b642c88327c030fd5eacafb9841d4aa3e34d04ae314de20" + url: "https://pub.dev" + source: hosted + version: "0.3.2" + flutter_colorpicker: + dependency: "direct main" + description: + name: flutter_colorpicker + sha256: "969de5f6f9e2a570ac660fb7b501551451ea2a1ab9e2097e89475f60e07816ea" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "95f349437aeebe524ef7d6c9bde3e6b4772717cf46a0eb6a3ceaddc740b297cc" + url: "https://pub.dev" + source: hosted + version: "8.2.8" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + provider: + dependency: "direct main" + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" + source: hosted + version: "14.2.1" + web: + dependency: transitive + description: + name: web + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + url: "https://pub.dev" + source: hosted + version: "1.0.0" +sdks: + dart: ">=3.4.4 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..2108f51 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,95 @@ +name: xapk_installer +description: "A quick app to install XAPK files" +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=3.4.4 <4.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.6 + bluetooth_classic: ^0.0.1 + flutter_colorpicker: ^1.1.0 + provider: ^6.1.2 + fluttertoast: ^8.2.8 + flutter_bluetooth_serial: ^0.3.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^3.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..faf0136 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:xapk_installer/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}