├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── LICENSE ├── POLICY.md ├── README.md ├── TERMS.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── ic_launcher-playstore.png │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── duino │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ └── launch_image.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ └── launch_image.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ └── launch_image.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ └── launch_image.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ └── launch_image.png │ │ │ ├── values-night │ │ │ └── colors.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── ic_launcher_background.xml │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── settings_aar.gradle ├── assets ├── about.png ├── bluetooth.png ├── git │ ├── app-store-badge.png │ ├── circuit.png │ ├── circuit.xd │ ├── examples │ │ ├── Duino │ │ │ └── Duino.ino │ │ └── LED │ │ │ └── LED.ino │ ├── featured.png │ ├── google-play-badge.png │ ├── logo.xd │ ├── mockup.png │ └── mockup.xd ├── gyroscope.png ├── joystick.png ├── remote.png └── resource.png ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 180.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── README.md │ │ ├── logo.png │ │ ├── logo@2x.png │ │ └── logo@3x.png │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── components │ ├── adaptive-components │ │ ├── adaptive-actionsheet.dart │ │ ├── adaptive-activityindicator.dart │ │ ├── adaptive-alertdialog.dart │ │ ├── adaptive-button.dart │ │ ├── adaptive-customscrollview.dart │ │ ├── adaptive-iconbutton.dart │ │ ├── adaptive-material.dart │ │ ├── adaptive-navbar.dart │ │ ├── adaptive-scaffold.dart │ │ ├── adaptive-switch.dart │ │ ├── adaptive-theme.dart │ │ └── adaptive-widget.dart │ ├── joystick-component.dart │ │ ├── circle-component.dart │ │ └── joystick-component.dart │ ├── pageroute-component.dart │ ├── state-component.dart │ └── util-components │ │ └── math-util.dart ├── main.dart ├── models │ └── bledevice-model.dart ├── providers │ └── bluetooth-provider.dart ├── routes.dart ├── styles.dart └── views │ ├── about-view │ └── about-view.dart │ ├── connect-vieiw │ ├── components │ │ ├── device-component.dart │ │ ├── dialog-component.dart │ │ ├── nodevice-component.dart │ │ └── status-component.dart │ ├── connect-view.dart │ └── providers │ │ └── connect-provider.dart │ ├── home-view │ ├── components │ │ └── action-component.dart │ └── home-view.dart │ ├── joystick-view │ └── joystick-view.dart │ ├── remote-view │ ├── components │ │ └── button-component.dart │ ├── providers │ │ └── remote-provider.dart │ └── remote-view.dart │ └── tilt-view │ ├── components │ └── ring-component.dart │ └── tilt-view.dart ├── pubspec.lock └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | android/app/src/key.properties 39 | android/key.properties 40 | -------------------------------------------------------------------------------- /.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: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Flutter", 9 | "request": "launch", 10 | "type": "dart" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /POLICY.md: -------------------------------------------------------------------------------- 1 | **Privacy Policy** 2 | 3 | davebaraka built the Duino app as an Open Source app. This SERVICE is provided by davebaraka at no cost and is intended for use as is. 4 | 5 | This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service. 6 | 7 | If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy. 8 | 9 | The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at Duino unless otherwise defined in this Privacy Policy. 10 | 11 | **Information Collection and Use** 12 | 13 | For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information. The information that I request will be retained on your device and is not collected by me in any way. 14 | 15 | **Log Data** 16 | 17 | I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics. 18 | 19 | **Cookies** 20 | 21 | Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory. 22 | 23 | This Service does not use these “cookies” explicitly. However, the app may use third party code and libraries that use “cookies” to collect information and 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. 24 | 25 | **Service Providers** 26 | 27 | I may employ third-party companies and individuals due to the following reasons: 28 | 29 | * To facilitate our Service; 30 | * To provide the Service on our behalf; 31 | * To perform Service-related services; or 32 | * To assist us in analyzing how our Service is used. 33 | 34 | I 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. 35 | 36 | **Security** 37 | 38 | I 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 I cannot guarantee its absolute security. 39 | 40 | **Links to Other Sites** 41 | 42 | This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services. 43 | 44 | **Children’s Privacy** 45 | 46 | These Services do not address anyone under the age of 13. I do not knowingly collect personally identifiable information from children under 13\. In the case I discover that a child under 13 has provided me with personal information, I 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 me so that I will be able to do necessary actions. 47 | 48 | **Changes to This Privacy Policy** 49 | 50 | I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page. 51 | 52 | This policy is effective as of 2020-04-29 53 | 54 | **Contact Us** 55 | 56 | If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me at https://davebaraka.dev. 57 | 58 | This privacy policy page was created at [privacypolicytemplate.net](https://privacypolicytemplate.net) and modified/generated by [App Privacy Policy Generator](https://app-privacy-policy-generator.firebaseapp.com/) 59 | -------------------------------------------------------------------------------- /TERMS.md: -------------------------------------------------------------------------------- 1 | **Terms & Conditions** 2 | 3 | By downloading or using the app, these terms will automatically apply to you – you should make sure therefore that you read them carefully before using the app. You’re not allowed to copy, or modify the app, any part of the app, or our trademarks in any way. You’re not allowed to attempt to extract the source code of the app, and you also shouldn’t try to translate the app into other languages, or make derivative versions. The app itself, and all the trade marks, copyright, database rights and other intellectual property rights related to it, still belong to davebaraka. 4 | 5 | davebaraka is committed to ensuring that the app is as useful and efficient as possible. For that reason, we reserve the right to make changes to the app or to charge for its services, at any time and for any reason. We will never charge you for the app or its services without making it very clear to you exactly what you’re paying for. 6 | 7 | The Duino app stores and processes personal data that you have provided to us, in order to provide my Service. It’s your responsibility to keep your phone and access to the app secure. We therefore recommend that you do not jailbreak or root your phone, which is the process of removing software restrictions and limitations imposed by the official operating system of your device. It could make your phone vulnerable to malware/viruses/malicious programs, compromise your phone’s security features and it could mean that the Duino app won’t work properly or at all. 8 | 9 | You should be aware that there are certain things that davebaraka will not take responsibility for. Certain functions of the app will require the app to have an active internet connection. The connection can be Wi-Fi, or provided by your mobile network provider, but davebaraka cannot take responsibility for the app not working at full functionality if you don’t have access to Wi-Fi, and you don’t have any of your data allowance left. 10 | 11 | If you’re using the app outside of an area with Wi-Fi, you should remember that your terms of the agreement with your mobile network provider will still apply. As a result, you may be charged by your mobile provider for the cost of data for the duration of the connection while accessing the app, or other third party charges. In using the app, you’re accepting responsibility for any such charges, including roaming data charges if you use the app outside of your home territory (i.e. region or country) without turning off data roaming. If you are not the bill payer for the device on which you’re using the app, please be aware that we assume that you have received permission from the bill payer for using the app. 12 | 13 | Along the same lines, davebaraka cannot always take responsibility for the way you use the app i.e. You need to make sure that your device stays charged – if it runs out of battery and you can’t turn it on to avail the Service, davebaraka cannot accept responsibility. 14 | 15 | With respect to davebaraka’s responsibility for your use of the app, when you’re using the app, it’s important to bear in mind that although we endeavour to ensure that it is updated and correct at all times, we do rely on third parties to provide information to us so that we can make it available to you. davebaraka accepts no liability for any loss, direct or indirect, you experience as a result of relying wholly on this functionality of the app. 16 | 17 | At some point, we may wish to update the app. The app is currently available on Android & iOS – the requirements for both systems(and for any additional systems we decide to extend the availability of the app to) may change, and you’ll need to download the updates if you want to keep using the app. davebaraka does not promise that it will always update the app so that it is relevant to you and/or works with the Android & iOS version that you have installed on your device. However, you promise to always accept updates to the application when offered to you, We may also wish to stop providing the app, and may terminate use of it at any time without giving notice of termination to you. Unless we tell you otherwise, upon any termination, (a) the rights and licenses granted to you in these terms will end; (b) you must stop using the app, and (if needed) delete it from your device. 18 | 19 | **Changes to This Terms and Conditions** 20 | 21 | I may update our Terms and Conditions from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Terms and Conditions on this page. 22 | 23 | These terms and conditions are effective as of 2020-04-29 24 | 25 | **Contact Us** 26 | 27 | If you have any questions or suggestions about my Terms and Conditions, do not hesitate to contact me at https://davebaraka.dev. 28 | 29 | This Terms and Conditions page was generated by [App Privacy Policy Generator](https://app-privacy-policy-generator.firebaseapp.com/) 30 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | def keystoreProperties = new Properties() 29 | def keystorePropertiesFile = rootProject.file('key.properties') 30 | if (keystorePropertiesFile.exists()) { 31 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 32 | } 33 | 34 | android { 35 | compileSdkVersion 29 36 | 37 | sourceSets { 38 | main.java.srcDirs += 'src/main/kotlin' 39 | } 40 | 41 | lintOptions { 42 | disable 'InvalidPackage' 43 | } 44 | 45 | defaultConfig { 46 | applicationId "dev.duino" 47 | minSdkVersion 19 48 | targetSdkVersion 29 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 52 | } 53 | 54 | signingConfigs { 55 | release { 56 | keyAlias keystoreProperties['keyAlias'] 57 | keyPassword keystoreProperties['keyPassword'] 58 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 59 | storePassword keystoreProperties['storePassword'] 60 | } 61 | } 62 | buildTypes { 63 | release { 64 | signingConfig signingConfigs.release 65 | } 66 | } 67 | } 68 | 69 | flutter { 70 | source '../..' 71 | } 72 | 73 | dependencies { 74 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 75 | testImplementation 'junit:junit:4.12' 76 | androidTestImplementation 'androidx.test:runner:1.1.1' 77 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 78 | } 79 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | 15 | 19 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/duino/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.duino 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-hdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-mdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-xhdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-xxhdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/android/app/src/main/res/mipmap-xxxhdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/black 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/white 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.6.3' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | android.bundle.enableUncompressedNativeLibs=false 6 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 29 16:26:19 EDT 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /assets/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/assets/about.png -------------------------------------------------------------------------------- /assets/bluetooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/assets/bluetooth.png -------------------------------------------------------------------------------- /assets/git/app-store-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/assets/git/app-store-badge.png -------------------------------------------------------------------------------- /assets/git/circuit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/assets/git/circuit.png -------------------------------------------------------------------------------- /assets/git/circuit.xd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/assets/git/circuit.xd -------------------------------------------------------------------------------- /assets/git/examples/Duino/Duino.ino: -------------------------------------------------------------------------------- 1 | /// Requried to set pins to receive data. 2 | #include 3 | 4 | /// Define your RX & TX arduino pins. 5 | SoftwareSerial BTSerial(10, 11); // RX | TX 6 | 7 | void setup() { 8 | Serial.begin(9600); 9 | BTSerial.begin(9600); 10 | while (!Serial); 11 | Serial.println("DUINO: "); 12 | } 13 | 14 | void loop() { 15 | //parseKeypad(); 16 | //parseDpad(); 17 | //parseJoystick(); 18 | //parseTiltPad(); 19 | } 20 | 21 | /* 22 | Parses keypad inputs from Duino. 23 | 24 | The variable 'var' contains the desired value when newData is true. 25 | var = 26 | 27 | For keypad inputs, Duino registers keypad releases as a valid input. 28 | Long presses or canceled presses will be unregistered. 29 | See D-pad for long press inputs. 30 | */ 31 | void parseKeypad() { 32 | boolean debug = true; // set this to false to hide debug output 33 | boolean newData = false; // true when newData is ready 34 | 35 | const byte numChars = 2; // number of characters for accepted data 36 | const char delimeter = '#'; // delimeter that separates unit of data 37 | char c; // current character 38 | 39 | char receivedChars[numChars]; // an array to store the received data 40 | static byte i = 0; // current index of array 41 | 42 | int var; // final value 43 | 44 | 45 | while (BTSerial.available() > 0 && newData == false) { 46 | 47 | c = BTSerial.read(); 48 | 49 | if (c != delimeter) { 50 | if (isDigit(c)) { 51 | receivedChars[i] = c; 52 | i++; 53 | if (i >= numChars) { 54 | if (debug)Serial.println("DUINO: EXCEPTION - INVALID INPUT / DATA MISSED"); 55 | if (debug)Serial.println("DUINO: MSG - WAITING FOR DELIMITER"); 56 | while (BTSerial.available() > 0) { 57 | if ( BTSerial.read() == delimeter) { 58 | break; 59 | } 60 | } 61 | if (debug) Serial.println("DUINO: "); 62 | i = 0; // reset static value 63 | break; 64 | } 65 | } else { 66 | if (debug)Serial.println("DUINO: EXCEPTION - INVALID INPUT / DATA MISSED"); 67 | if (debug)Serial.println("DUINO: MSG - WAITING FOR DELIMITER"); 68 | while (BTSerial.available() > 0) { 69 | if ( BTSerial.read() == delimeter) { 70 | break; 71 | } 72 | } 73 | if (debug) Serial.println("DUINO: "); 74 | i = 0; // reset static value 75 | break; 76 | } 77 | } else if (i < numChars - 1) { 78 | if (debug)Serial.println("DUINO: EXCEPTION - INVALID INPUT / DATA MISSED"); 79 | if (debug)Serial.println("DUINO: MSG - WAITING FOR DELIMITER"); 80 | while (BTSerial.available() > 0) { 81 | if ( BTSerial.read() == delimeter) { 82 | break; 83 | } 84 | } 85 | if (debug) Serial.println("DUINO: "); 86 | i = 0; // reset static value 87 | break; 88 | } 89 | else { 90 | receivedChars[i] = '\0'; // terminate the string (array of characters) 91 | var = strtol(receivedChars, NULL, 10); // convert array to int 92 | if (debug) { 93 | Serial.print("DUINO: Keypad Digit Pressed --> "); 94 | Serial.println(var); 95 | } 96 | i = 0; // reset static value 97 | newData = true; 98 | } 99 | } 100 | } 101 | 102 | 103 | /* 104 | Parses d-pad inputs from Duino. 105 | 106 | The variable 'var' contains the direction value when newData is true. 107 | var = < char, either 'N', 'S', 'E', or 'W'> 108 | 109 | The variable 'state' - pressed state of the button when newData is true. 110 | state = 111 | */ 112 | void parseDpad() { 113 | boolean debug = true; // set this to false to hide debug output 114 | boolean newData = false; // true when newData is ready 115 | 116 | const byte numChars = 2; // number of characters for accepted data 117 | const char delimeter = '#'; // delimeter that separates unit of data 118 | char c; // current character 119 | 120 | static char receivedChars[numChars]; // an array to store the received data 121 | static byte i = 0; // current index of array 122 | 123 | static char var; // direction value 124 | static boolean state = false; // pressing state value 125 | 126 | 127 | while (BTSerial.available() > 0 && newData == false) { 128 | 129 | c = BTSerial.read(); 130 | 131 | if (c != delimeter) { 132 | if (isAlpha(c)) { 133 | receivedChars[i] = c; 134 | i++; 135 | state = true; 136 | var = c; 137 | newData = true; 138 | if (debug) { 139 | Serial.print("DUINO: D-pad Pressing --> "); 140 | Serial.println(var); 141 | } 142 | break; 143 | } else { 144 | if (debug)Serial.println("DUINO: EXCEPTION - INVALID INPUT / DATA MISSED"); 145 | if (debug)Serial.println("DUINO: MSG - WAITING FOR DELIMITER"); 146 | while (BTSerial.available() > 0) { 147 | if ( BTSerial.read() == delimeter) { 148 | break; 149 | } 150 | } 151 | if (debug) Serial.println("DUINO: "); 152 | state = false; 153 | i = 0; // reset static value 154 | break; 155 | 156 | } 157 | } else if (i < numChars - 1) { 158 | if (debug)Serial.println("DUINO: EXCEPTION - INVALID INPUT / DATA MISSED"); 159 | if (debug)Serial.println("DUINO: MSG - WAITING FOR DELIMITER"); 160 | while (BTSerial.available() > 0) { 161 | if ( BTSerial.read() == delimeter) { 162 | break; 163 | } 164 | } 165 | if (debug) Serial.println("DUINO: "); 166 | state = false; 167 | i = 0; // reset static value 168 | break; 169 | 170 | } else { 171 | receivedChars[i] = '\0'; // terminate the string (array of characters) 172 | if (debug) { 173 | Serial.print("DUINO: D-pad Released --> "); 174 | Serial.println(var); 175 | } 176 | state = false; 177 | i = 0; // reset static value 178 | newData = true; 179 | } 180 | 181 | } 182 | } 183 | 184 | /* 185 | Parses joystick inputs from Duino. 186 | 187 | The variable 'degree', joystick angle degree when newData is true. 188 | degree = 189 | 190 | The variable 'distance', joystick distance from center when newData is true. 191 | distance = 192 | */ 193 | void parseJoystick() { 194 | boolean debug = true; // set this to false to hide debug output 195 | boolean newData = false; // true when newData is ready 196 | 197 | const byte numChars = 7; // number of characters for accepted data 198 | const char delimeter = '#'; // delimeter that separates unit of data 199 | char c; // current character 200 | 201 | char receivedChars[numChars]; // an array to store the received data 202 | static byte i = 0; // current index of array 203 | 204 | int degree; // angle degree 205 | int distance; // distance from center 206 | 207 | 208 | while (BTSerial.available() > 0 && newData == false) { 209 | 210 | c = BTSerial.read(); 211 | 212 | if (c != delimeter) { 213 | if (isDigit(c)) { 214 | receivedChars[i] = c; 215 | i++; 216 | if (i >= numChars) { 217 | if (debug)Serial.println("DUINO: EXCEPTION - INVALID INPUT / DATA MISSED"); 218 | if (debug)Serial.println("DUINO: MSG - WAITING FOR DELIMITER"); 219 | while (BTSerial.available() > 0) { 220 | if ( BTSerial.read() == delimeter) { 221 | break; 222 | } 223 | } 224 | if (debug) Serial.println("DUINO: "); 225 | i = 0; // reset static value 226 | break; 227 | } 228 | } else { 229 | if (debug)Serial.println("DUINO: EXCEPTION - INVALID INPUT / DATA MISSED"); 230 | if (debug)Serial.println("DUINO: MSG - WAITING FOR DELIMITER"); 231 | while (BTSerial.available() > 0) { 232 | if ( BTSerial.read() == delimeter) { 233 | break; 234 | } 235 | } 236 | if (debug) Serial.println("DUINO: "); 237 | i = 0; // reset static value 238 | break; 239 | } 240 | } else if (i < numChars - 1) { 241 | if (debug)Serial.println("DUINO: EXCEPTION - INVALID INPUT / DATA MISSED"); 242 | if (debug)Serial.println("DUINO: MSG - WAITING FOR DELIMITER"); 243 | while (BTSerial.available() > 0) { 244 | if ( BTSerial.read() == delimeter) { 245 | break; 246 | } 247 | } 248 | if (debug) Serial.println("DUINO: "); 249 | i = 0; // reset static value 250 | break; 251 | } 252 | else { 253 | receivedChars[i] = '\0'; // terminate the string (array of characters) 254 | 255 | char tmp[4]; 256 | 257 | tmp[0] = receivedChars[0]; 258 | tmp[1] = receivedChars[1]; 259 | tmp[2] = receivedChars[2]; 260 | tmp[3] = '\0'; 261 | 262 | degree = strtol(tmp, NULL, 10); // convert array to int 263 | 264 | 265 | tmp[0] = receivedChars[3]; 266 | tmp[1] = receivedChars[4]; 267 | tmp[2] = receivedChars[5]; 268 | tmp[3] = '\0'; 269 | 270 | distance = strtol(tmp, NULL, 10); // convert array to int 271 | 272 | if (debug) { 273 | Serial.print("DUINO: Angle (Degrees): "); 274 | Serial.print(degree); 275 | Serial.print(" Distance (Pixels): "); 276 | Serial.println(distance); 277 | } 278 | i = 0; // reset static value 279 | newData = true; 280 | } 281 | } 282 | } 283 | 284 | /* 285 | Parses tilt pad inputs from Duino. 286 | 287 | The variable 'roll', device rotation around the front-to-back axis in degrees. 288 | roll = 289 | 290 | The variable 'pitch', device rotation around the side-to-side axis in degrees. 291 | pitch = 292 | */ 293 | void parseTiltPad() { 294 | boolean debug = true; // set this to false to hide debug output 295 | boolean newData = false; // true when newData is ready 296 | 297 | const byte numChars = 9; // number of characters for accepted data 298 | const char delimeter = '#'; // delimeter that separates unit of data 299 | char c; // current character 300 | 301 | char receivedChars[numChars]; // an array to store the received data 302 | static byte i = 0; // current index of array 303 | 304 | int roll; // angle degree 305 | int pitch; // distance from center 306 | 307 | 308 | while (BTSerial.available() > 0 && newData == false) { 309 | 310 | c = BTSerial.read(); 311 | 312 | if (c != delimeter) { 313 | if (isDigit(c) or c == '-') { 314 | receivedChars[i] = c; 315 | i++; 316 | if (i >= numChars) { 317 | if (debug)Serial.println("DUINO: EXCEPTION - INVALID INPUT / DATA MISSED"); 318 | if (debug)Serial.println("DUINO: MSG - WAITING FOR DELIMITER"); 319 | while (BTSerial.available() > 0) { 320 | if ( BTSerial.read() == delimeter) { 321 | break; 322 | } 323 | } 324 | if (debug) Serial.println("DUINO: "); 325 | i = 0; // reset static value 326 | break; 327 | } 328 | } else { 329 | if (debug)Serial.println("DUINO: EXCEPTION - INVALID INPUT / DATA MISSED"); 330 | if (debug)Serial.println("DUINO: MSG - WAITING FOR DELIMITER"); 331 | while (BTSerial.available() > 0) { 332 | if ( BTSerial.read() == delimeter) { 333 | break; 334 | } 335 | } 336 | if (debug) Serial.println("DUINO: "); 337 | i = 0; // reset static value 338 | break; 339 | } 340 | } else if (i < numChars - 1) { 341 | if (debug)Serial.println("DUINO: EXCEPTION - INVALID INPUT / DATA MISSED"); 342 | if (debug)Serial.println("DUINO: MSG - WAITING FOR DELIMITER"); 343 | while (BTSerial.available() > 0) { 344 | if ( BTSerial.read() == delimeter) { 345 | break; 346 | } 347 | } 348 | if (debug) Serial.println("DUINO: "); 349 | i = 0; // reset static value 350 | break; 351 | } 352 | else { 353 | receivedChars[i] = '\0'; // terminate the string (array of characters) 354 | 355 | char tmp[5]; 356 | 357 | tmp[0] = receivedChars[0]; 358 | tmp[1] = receivedChars[1]; 359 | tmp[2] = receivedChars[2]; 360 | tmp[3] = receivedChars[3]; 361 | tmp[4] = '\0'; 362 | 363 | roll = strtol(tmp, NULL, 10); // convert array to int 364 | 365 | 366 | tmp[0] = receivedChars[4]; 367 | tmp[1] = receivedChars[5]; 368 | tmp[2] = receivedChars[6]; 369 | tmp[3] = receivedChars[7]; 370 | tmp[4] = '\0'; 371 | 372 | pitch = strtol(tmp, NULL, 10); // convert array to int 373 | 374 | if (debug) { 375 | Serial.print("DUINO: Roll (Degrees): "); 376 | Serial.print(roll); 377 | Serial.print(" Pitch (Degrees): "); 378 | Serial.println(pitch); 379 | } 380 | i = 0; // reset static value 381 | newData = true; 382 | } 383 | } 384 | } 385 | 386 | 387 | /* 388 | Send data to Duino. 389 | 390 | Currently unsupported by Duinio. 391 | */ 392 | void writeToBTSerial() { 393 | /// read from serial and write to the bluetooth module 394 | if (Serial.available()) { 395 | BTSerial.write(Serial.read()); 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /assets/git/examples/LED/LED.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | SoftwareSerial BTSerial(10, 11); // RX | TX 4 | 5 | void setup() { 6 | pinMode(LED_BUILTIN, OUTPUT); 7 | Serial.begin(9600); 8 | BTSerial.begin(9600); 9 | while (!Serial); 10 | Serial.println("DUINO: "); 11 | } 12 | 13 | void loop() { 14 | controlLED(); 15 | } 16 | 17 | void controlLED() { 18 | boolean debug = true; // set this to false to hide debug output 19 | boolean newData = false; // true when newData is ready 20 | 21 | const byte numChars = 2; // number of characters for accepted data 22 | const char delimeter = '#'; // delimeter that separates unit of data 23 | char c; // current character 24 | 25 | char receivedChars[numChars]; // an array to store the received data 26 | static byte i = 0; // current index of array 27 | 28 | int var; // final value 29 | 30 | 31 | while (BTSerial.available() > 0 && newData == false) { 32 | 33 | c = BTSerial.read(); 34 | 35 | if (c != delimeter) { 36 | if (isDigit(c)) { 37 | receivedChars[i] = c; 38 | i++; 39 | } 40 | } 41 | else { 42 | receivedChars[i] = '\0'; 43 | var = strtol(receivedChars, NULL, 10); 44 | if (debug) { 45 | Serial.print("DUINO: Keypad Digit Pressed --> "); 46 | Serial.println(var); 47 | } 48 | i = 0; 49 | newData = true; 50 | } 51 | } 52 | 53 | if (newData) { 54 | if(var == 0) { 55 | digitalWrite(LED_BUILTIN, LOW); 56 | } else if (var == 1) { 57 | digitalWrite(LED_BUILTIN, HIGH); 58 | } 59 | newData = false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /assets/git/featured.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/assets/git/featured.png -------------------------------------------------------------------------------- /assets/git/google-play-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/assets/git/google-play-badge.png -------------------------------------------------------------------------------- /assets/git/logo.xd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/assets/git/logo.xd -------------------------------------------------------------------------------- /assets/git/mockup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/assets/git/mockup.png -------------------------------------------------------------------------------- /assets/git/mockup.xd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/assets/git/mockup.xd -------------------------------------------------------------------------------- /assets/gyroscope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/assets/gyroscope.png -------------------------------------------------------------------------------- /assets/joystick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/assets/joystick.png -------------------------------------------------------------------------------- /assets/remote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/assets/remote.png -------------------------------------------------------------------------------- /assets/resource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/assets/resource.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | use_modular_headers! 38 | 39 | # Flutter Pod 40 | 41 | copied_flutter_dir = File.join(__dir__, 'Flutter') 42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 48 | 49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 50 | unless File.exist?(generated_xcode_build_settings_path) 51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 52 | end 53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 55 | 56 | unless File.exist?(copied_framework_path) 57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 58 | end 59 | unless File.exist?(copied_podspec_path) 60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 61 | end 62 | end 63 | 64 | # Keep pod path relative so it can be checked into Podfile.lock. 65 | pod 'Flutter', :path => 'Flutter' 66 | 67 | # Plugin Pods 68 | 69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 70 | # referring to absolute paths on developers' machines. 71 | system('rm -rf .symlinks') 72 | system('mkdir -p .symlinks/plugins') 73 | plugin_pods = parse_KV_file('../.flutter-plugins') 74 | plugin_pods.each do |name, path| 75 | symlink = File.join('.symlinks', 'plugins', name) 76 | File.symlink(path, symlink) 77 | pod name, :path => File.join(symlink, 'ios') 78 | end 79 | end 80 | 81 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 82 | install! 'cocoapods', :disable_input_output_paths => true 83 | 84 | post_install do |installer| 85 | installer.pods_project.targets.each do |target| 86 | target.build_configurations.each do |config| 87 | config.build_settings['ENABLE_BITCODE'] = 'NO' 88 | 89 | # You can remove unused permissions here 90 | # for more infomation: https://github.com/BaseflowIT/flutter-permission-handler/blob/develop/permission_handler/ios/Classes/PermissionHandlerEnums.h 91 | # e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0' 92 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ 93 | '$(inherited)', 94 | 95 | ## dart: PermissionGroup.calendar 96 | 'PERMISSION_EVENTS=0', 97 | 98 | ## dart: PermissionGroup.reminders 99 | 'PERMISSION_REMINDERS=0', 100 | 101 | ## dart: PermissionGroup.contacts 102 | 'PERMISSION_CONTACTS=0', 103 | 104 | ## dart: PermissionGroup.camera 105 | 'PERMISSION_CAMERA=0', 106 | 107 | ## dart: PermissionGroup.microphone 108 | 'PERMISSION_MICROPHONE=0', 109 | 110 | ## dart: PermissionGroup.speech 111 | 'PERMISSION_SPEECH_RECOGNIZER=0', 112 | 113 | ## dart: PermissionGroup.photos 114 | 'PERMISSION_PHOTOS=0', 115 | 116 | ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse] 117 | 'PERMISSION_LOCATION=0', 118 | 119 | ## dart: PermissionGroup.notification 120 | 'PERMISSION_NOTIFICATIONS=0', 121 | 122 | ## dart: PermissionGroup.mediaLibrary 123 | 'PERMISSION_MEDIA_LIBRARY=0', 124 | 125 | ## dart: PermissionGroup.sensors 126 | 'PERMISSION_SENSORS=0' 127 | ] 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_ble_lib (2.2.4): 4 | - Flutter 5 | - MultiplatformBleAdapter (~> 0.1.5) 6 | - MultiplatformBleAdapter (0.1.5) 7 | - "permission_handler (5.0.0+hotfix.5)": 8 | - Flutter 9 | - sensors (0.0.1): 10 | - Flutter 11 | - url_launcher (0.0.1): 12 | - Flutter 13 | - url_launcher_macos (0.0.1): 14 | - Flutter 15 | - url_launcher_web (0.0.1): 16 | - Flutter 17 | 18 | DEPENDENCIES: 19 | - Flutter (from `Flutter`) 20 | - flutter_ble_lib (from `.symlinks/plugins/flutter_ble_lib/ios`) 21 | - permission_handler (from `.symlinks/plugins/permission_handler/ios`) 22 | - sensors (from `.symlinks/plugins/sensors/ios`) 23 | - url_launcher (from `.symlinks/plugins/url_launcher/ios`) 24 | - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) 25 | - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) 26 | 27 | SPEC REPOS: 28 | trunk: 29 | - MultiplatformBleAdapter 30 | 31 | EXTERNAL SOURCES: 32 | Flutter: 33 | :path: Flutter 34 | flutter_ble_lib: 35 | :path: ".symlinks/plugins/flutter_ble_lib/ios" 36 | permission_handler: 37 | :path: ".symlinks/plugins/permission_handler/ios" 38 | sensors: 39 | :path: ".symlinks/plugins/sensors/ios" 40 | url_launcher: 41 | :path: ".symlinks/plugins/url_launcher/ios" 42 | url_launcher_macos: 43 | :path: ".symlinks/plugins/url_launcher_macos/ios" 44 | url_launcher_web: 45 | :path: ".symlinks/plugins/url_launcher_web/ios" 46 | 47 | SPEC CHECKSUMS: 48 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 49 | flutter_ble_lib: ff02e3a782af6431ed0a2b5bef325ec041604b2e 50 | MultiplatformBleAdapter: 3c4391d428382738a47662ae1f665a29ce78ff39 51 | permission_handler: 6226fcb78b97c7c7458a95c7346a11d5184fec12 52 | sensors: 84eb7a30e47a649e4172b71d6e81be614c280336 53 | url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef 54 | url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 55 | url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c 56 | 57 | PODFILE CHECKSUM: b54c4234742931eafb12773a8bfe2421a5bdcafe 58 | 59 | COCOAPODS: 1.8.4 60 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"}]} -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "logo.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "logo@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "logo@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/ios/Runner/Assets.xcassets/LaunchImage.imageset/logo.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/ios/Runner/Assets.xcassets/LaunchImage.imageset/logo@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davebaraka/duino/fa8493e6c785bba71987579af988d77526723f1a/ios/Runner/Assets.xcassets/LaunchImage.imageset/logo@3x.png -------------------------------------------------------------------------------- /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 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | Duino 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | NSBluetoothAlwaysUsageDescription 26 | Duino uses bluetooth to find, connect, and transfer data between devices. 27 | NSBluetoothPeripheralUsageDescription 28 | Duino uses bluetooth to find, connect, and transfer data between devices. 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lib/components/adaptive-components/adaptive-actionsheet.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Platform; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class AdaptiveActionSheet extends StatelessWidget{ 6 | final List actions; 7 | final Widget cancelButton; 8 | final Widget title; 9 | final Widget message; 10 | 11 | AdaptiveActionSheet({@required this.actions, this.cancelButton, this.title, this.message}); 12 | 13 | Widget _buildiOS(context) { 14 | return CupertinoActionSheet( 15 | title: title, 16 | message: message, 17 | actions: actions, 18 | cancelButton: cancelButton); 19 | } 20 | 21 | Widget _buildAndroid(context) { 22 | return null; 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Platform.isIOS ? _buildiOS(context) : _buildAndroid(context); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/components/adaptive-components/adaptive-activityindicator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Platform; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class AdaptiveActivityIndicator extends StatelessWidget { 6 | final Color color; 7 | AdaptiveActivityIndicator({this.color}); 8 | Widget _buildiOS() { 9 | return CupertinoActivityIndicator( 10 | radius: 9.25, 11 | ); 12 | } 13 | 14 | Widget _buildAndroid() { 15 | return SizedBox( 16 | child: CircularProgressIndicator( 17 | backgroundColor: color, 18 | strokeWidth: 2, 19 | ), 20 | height: 18.5, 21 | width: 18.5, 22 | ); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Platform.isIOS ? _buildiOS() : _buildAndroid(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/components/adaptive-components/adaptive-alertdialog.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Platform; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class AdaptiveAlertDialog extends StatelessWidget { 6 | final List actions; 7 | final Widget cancelButton; 8 | final Widget title; 9 | final Widget content; 10 | 11 | AdaptiveAlertDialog( 12 | {@required this.actions, this.cancelButton, this.title, this.content}); 13 | 14 | Widget _buildiOS(context) { 15 | return CupertinoAlertDialog( 16 | title: title, 17 | content: content, 18 | actions: actions, 19 | ); 20 | } 21 | 22 | Widget _buildAndroid(context) { 23 | return AlertDialog( 24 | title: title, 25 | content: content, 26 | actions: actions, 27 | ); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return Platform.isIOS ? _buildiOS(context) : _buildAndroid(context); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/components/adaptive-components/adaptive-button.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Platform; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class AdaptiveButton extends StatelessWidget { 6 | final VoidCallback onPressed; 7 | final Widget child; 8 | final Color color; 9 | final BorderRadius borderRadius; 10 | final EdgeInsets padding; 11 | 12 | AdaptiveButton( 13 | {@required this.onPressed, 14 | @required this.child, 15 | this.color, 16 | this.padding, 17 | this.borderRadius}); 18 | 19 | Widget _buildiOS(BuildContext context) { 20 | return CupertinoButton( 21 | borderRadius: borderRadius ?? BorderRadius.circular(8.0), 22 | color: color, 23 | minSize: 0, 24 | padding: padding ?? EdgeInsets.only(), 25 | child: child, 26 | onPressed: onPressed, 27 | ); 28 | } 29 | 30 | Widget _buildAndroid(BuildContext context) { 31 | return FlatButton( 32 | shape: RoundedRectangleBorder( 33 | borderRadius: borderRadius ?? BorderRadius.circular(8.0), 34 | ), 35 | color: color, 36 | onPressed: onPressed, 37 | child: child, 38 | ); 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return Platform.isIOS ? _buildiOS(context) : _buildAndroid(context); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/components/adaptive-components/adaptive-customscrollview.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Platform; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class AdaptiveCustomScrollView extends StatelessWidget { 6 | final Widget child; 7 | final ScrollController controller; 8 | final Widget navBar; 9 | final VoidCallback onRefresh; 10 | final Key key; 11 | final double absorber; //Sliveroverlapinjecotr yadiya 12 | 13 | AdaptiveCustomScrollView( 14 | {@required this.child, 15 | this.controller, 16 | this.navBar, 17 | this.absorber, 18 | this.key, 19 | this.onRefresh}); 20 | 21 | Widget _buildiOS(BuildContext context) { 22 | return CustomScrollView( 23 | key: key, 24 | controller: controller, 25 | cacheExtent: MediaQuery.of(context).size.height, 26 | slivers: [ 27 | if (navBar != null) navBar, 28 | if (absorber != null) 29 | SliverPersistentHeader( 30 | delegate: _SliverPersistentHeaderDelegate(absorber: absorber)), 31 | if (onRefresh != null) 32 | CupertinoSliverRefreshControl( 33 | onRefresh: onRefresh, 34 | ), 35 | child, 36 | ], 37 | physics: child is SliverFillRemaining 38 | ? NeverScrollableScrollPhysics() 39 | : AlwaysScrollableScrollPhysics(), 40 | ); 41 | } 42 | 43 | Widget _buildAndroid(BuildContext context) { 44 | return onRefresh != null 45 | ? RefreshIndicator( 46 | onRefresh: onRefresh, 47 | child: CustomScrollView( 48 | cacheExtent: MediaQuery.of(context).size.height, 49 | slivers: [ 50 | if (navBar != null) navBar, 51 | if (absorber != null) 52 | SliverPersistentHeader( 53 | delegate: _SliverPersistentHeaderDelegate( 54 | absorber: absorber)), 55 | child 56 | ])) 57 | : CustomScrollView( 58 | cacheExtent: MediaQuery.of(context).size.height, 59 | slivers: [ 60 | if (navBar != null) navBar, 61 | if (absorber != null) 62 | SliverPersistentHeader( 63 | delegate: _SliverPersistentHeaderDelegate( 64 | absorber: absorber)), 65 | child 66 | ]); 67 | } 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | return Platform.isIOS ? _buildiOS(context) : _buildAndroid(context); 72 | } 73 | } 74 | 75 | class _SliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate { 76 | final double absorber; 77 | _SliverPersistentHeaderDelegate({this.absorber}); 78 | @override 79 | double get minExtent => absorber; 80 | @override 81 | double get maxExtent => absorber; 82 | @override 83 | Widget build( 84 | BuildContext context, double shrinkOffset, bool overlapsContent) { 85 | return Container( 86 | height: absorber, 87 | ); 88 | } 89 | 90 | @override 91 | bool shouldRebuild(_SliverPersistentHeaderDelegate oldDelegate) { 92 | return false; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/components/adaptive-components/adaptive-iconbutton.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class AdaptiveIconButton extends StatelessWidget { 7 | final VoidCallback onPressed; 8 | final Widget child; 9 | final Color color; 10 | final BorderRadius borderRadius; 11 | final EdgeInsets padding; 12 | 13 | AdaptiveIconButton( 14 | {@required this.onPressed, 15 | @required this.child, 16 | this.padding, 17 | this.borderRadius, 18 | this.color}); 19 | 20 | Widget _buildiOS(BuildContext context) { 21 | return CupertinoButton( 22 | borderRadius: borderRadius ?? BorderRadius.circular(8.0), 23 | color: color, 24 | minSize: 0, 25 | padding: padding ?? EdgeInsets.only(), 26 | child: child, 27 | onPressed: onPressed, 28 | ); 29 | } 30 | 31 | Widget _buildAndroid(BuildContext context) { 32 | return SizedBox( 33 | height: 56, 34 | width: 56, 35 | child: ClipOval( 36 | child: Material( 37 | color: Colors.transparent, // button color 38 | child: InkWell( 39 | highlightColor: Colors.transparent, 40 | splashColor: Theme.of(context).splashColor, // splash color 41 | onTap: onPressed, // button pressed 42 | child: child, 43 | ), 44 | ))); 45 | } 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | return Platform.isIOS ? _buildiOS(context) : _buildAndroid(context); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/components/adaptive-components/adaptive-material.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class AdaptiveMaterial extends StatelessWidget{ 5 | final Widget child; 6 | 7 | AdaptiveMaterial({@required this.child}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Material( 12 | type: MaterialType.transparency, 13 | child: child, 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/components/adaptive-components/adaptive-navbar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Platform; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class AdaptiveNavBar extends StatelessWidget 6 | implements ObstructingPreferredSizeWidget, PreferredSizeWidget { 7 | final Widget middle; 8 | final Widget largeTitle; 9 | final Widget leading; 10 | final Widget trailing; 11 | final bool implyLeading; 12 | final dynamic navbar; 13 | final Color backgroundColor; 14 | final double elevation; 15 | 16 | AdaptiveNavBar( 17 | {this.middle, 18 | this.elevation, 19 | this.backgroundColor, 20 | this.implyLeading = true, 21 | this.leading, 22 | this.largeTitle, 23 | this.trailing}) 24 | : navbar = Platform.isIOS 25 | ? largeTitle != null 26 | ? CupertinoSliverNavigationBar( 27 | leading: leading, 28 | largeTitle: largeTitle, 29 | middle: middle, 30 | backgroundColor: backgroundColor, 31 | trailing: trailing, 32 | automaticallyImplyLeading: implyLeading, 33 | border: null, 34 | ) 35 | : CupertinoNavigationBar( 36 | padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 0), 37 | leading: leading, 38 | middle: middle, 39 | backgroundColor: backgroundColor, 40 | trailing: trailing, 41 | automaticallyImplyLeading: implyLeading, 42 | border: null, 43 | ) 44 | : largeTitle != null 45 | ? SliverAppBar( 46 | pinned: true, 47 | forceElevated: true, 48 | leading: leading, 49 | title: largeTitle, 50 | backgroundColor: backgroundColor, 51 | actions: trailing != null ? [trailing] : [], 52 | centerTitle: true, 53 | automaticallyImplyLeading: implyLeading, 54 | ) 55 | : AppBar( 56 | elevation: elevation ?? 4, 57 | leading: leading, 58 | title: middle, 59 | backgroundColor: backgroundColor, 60 | actions: trailing != null ? [trailing] : [], 61 | centerTitle: true, 62 | automaticallyImplyLeading: implyLeading, 63 | ); 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | return navbar; 68 | } 69 | 70 | @override 71 | Size get preferredSize => navbar.preferredSize; 72 | 73 | @override 74 | bool shouldFullyObstruct(BuildContext context) { 75 | return navbar.shouldFullyObstruct(context); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/components/adaptive-components/adaptive-scaffold.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Platform; 2 | import 'package:duino/components/adaptive-components/adaptive-material.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class AdaptiveScaffold extends StatelessWidget { 7 | final Widget navBar; 8 | final Widget child; 9 | final Color backgroundColor; 10 | final bool safeAreaBottom; 11 | final bool safeAreaTop; 12 | 13 | AdaptiveScaffold( 14 | {this.navBar, 15 | @required this.child, 16 | this.backgroundColor, 17 | this.safeAreaBottom = false, 18 | this.safeAreaTop = false}); 19 | 20 | Widget _buildiOS(context) { 21 | return CupertinoPageScaffold( 22 | backgroundColor: backgroundColor, 23 | navigationBar: navBar, 24 | child: SafeArea( 25 | top: safeAreaTop, 26 | bottom: safeAreaBottom, 27 | child: AdaptiveMaterial(child: child), 28 | ), 29 | ); 30 | } 31 | 32 | Widget _buildAndroid(context) { 33 | return Scaffold( 34 | backgroundColor: backgroundColor, 35 | appBar: navBar, 36 | body: SafeArea(bottom: safeAreaBottom, top: safeAreaTop, child: child)); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return Platform.isIOS ? _buildiOS(context) : _buildAndroid(context); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/components/adaptive-components/adaptive-switch.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Platform; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class AdaptiveSwitch extends StatelessWidget{ 6 | final ValueChanged onChanged; 7 | final bool value; 8 | 9 | AdaptiveSwitch({this.onChanged, @required this.value}); 10 | 11 | Widget _buildiOS(context) { 12 | return CupertinoSwitch(value: value, onChanged: onChanged); 13 | } 14 | 15 | Widget _buildAndroid(context) { 16 | return Switch( 17 | value: value, 18 | onChanged: onChanged, 19 | ); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Platform.isIOS ? _buildiOS(context) : _buildAndroid(context); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/components/adaptive-components/adaptive-theme.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Platform; 2 | import 'package:flutter/material.dart'; 3 | 4 | class AdaptiveTheme extends StatelessWidget { 5 | 6 | final ThemeData themeData; 7 | final Widget child; 8 | 9 | AdaptiveTheme({@required this.themeData, @required this.child}); 10 | 11 | Widget _buildiOS(BuildContext context) { 12 | return Theme( 13 | data: themeData, 14 | child: child, 15 | ); 16 | } 17 | 18 | Widget _buildAndroid(BuildContext context) { 19 | return child; 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Platform.isIOS ? _buildiOS(context) : _buildAndroid(context); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/components/adaptive-components/adaptive-widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Platform; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class AdaptiveWidget extends StatelessWidget { 6 | final Widget iOS; 7 | final Widget android; 8 | 9 | AdaptiveWidget({@required this.iOS, @required this.android}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Platform.isIOS ? iOS : android; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/components/joystick-component.dart/circle-component.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | class CircleView extends StatelessWidget { 5 | final double size; 6 | 7 | final Color color; 8 | 9 | final List boxShadow; 10 | 11 | final Border border; 12 | 13 | final double opacity; 14 | 15 | final Image buttonImage; 16 | 17 | final Icon buttonIcon; 18 | 19 | final String buttonText; 20 | 21 | CircleView({ 22 | this.size, 23 | this.color = Colors.transparent, 24 | this.boxShadow, 25 | this.border, 26 | this.opacity, 27 | this.buttonImage, 28 | this.buttonIcon, 29 | this.buttonText, 30 | }); 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Container( 35 | width: size, 36 | height: size, 37 | child: Center( 38 | child: buttonIcon != null 39 | ? buttonIcon 40 | : (buttonImage != null) 41 | ? buttonImage 42 | : (buttonText != null) ? Text(buttonText) : null, 43 | ), 44 | decoration: BoxDecoration( 45 | color: color, 46 | shape: BoxShape.circle, 47 | border: border, 48 | boxShadow: boxShadow, 49 | ), 50 | ); 51 | } 52 | 53 | factory CircleView.joystickCircle(double size, Color color) => CircleView( 54 | size: size, 55 | color: color, 56 | border: Border.all( 57 | color: Colors.transparent, 58 | width: 4.0, 59 | style: BorderStyle.solid, 60 | ), 61 | ); 62 | 63 | factory CircleView.joystickInnerCircle(double size, Color color) => 64 | CircleView( 65 | size: size, 66 | color: color, 67 | boxShadow: [ 68 | BoxShadow( 69 | color: Colors.black12, 70 | spreadRadius: 4.0, 71 | blurRadius: 16.0, 72 | ) 73 | ], 74 | ); 75 | 76 | factory CircleView.padBackgroundCircle( 77 | double size, Color backgroundColour, borderColor, Color shadowColor, 78 | {double opacity}) => 79 | CircleView( 80 | size: size, 81 | color: backgroundColour, 82 | opacity: opacity, 83 | border: Border.all( 84 | color: borderColor, 85 | width: 4.0, 86 | style: BorderStyle.solid, 87 | ), 88 | boxShadow: [ 89 | BoxShadow( 90 | color: shadowColor, 91 | spreadRadius: 8.0, 92 | blurRadius: 8.0, 93 | ) 94 | ], 95 | ); 96 | 97 | factory CircleView.padButtonCircle( 98 | double size, 99 | Color color, 100 | Image image, 101 | Icon icon, 102 | String text, 103 | ) => 104 | CircleView( 105 | size: size, 106 | color: color, 107 | buttonImage: image, 108 | buttonIcon: icon, 109 | buttonText: text, 110 | border: Border.all( 111 | color: Colors.black26, 112 | width: 2.0, 113 | style: BorderStyle.solid, 114 | ), 115 | boxShadow: [ 116 | BoxShadow( 117 | color: Colors.black12, 118 | spreadRadius: 8.0, 119 | blurRadius: 8.0, 120 | ) 121 | ], 122 | ); 123 | } 124 | -------------------------------------------------------------------------------- /lib/components/joystick-component.dart/joystick-component.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as _math; 2 | 3 | import 'package:duino/components/joystick-component.dart/circle-component.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/widgets.dart'; 6 | 7 | 8 | typedef JoystickDirectionCallback = void Function( 9 | double degrees, double distance); 10 | 11 | class JoystickComponent extends StatelessWidget { 12 | /// The size of the joystick. 13 | /// 14 | /// Defaults to half of the width in the portrait 15 | /// or half of the height in the landscape mode 16 | final double size; 17 | 18 | /// Color of the icons 19 | /// 20 | /// Defaults to [Colors.white54] 21 | final Color iconsColor; 22 | 23 | /// Color of the joystick background 24 | /// 25 | /// Defaults to [Colors.blueGrey] 26 | final Color backgroundColor; 27 | 28 | /// Color of the inner (smaller) circle background 29 | /// 30 | /// Defaults to [Colors.blueGrey] 31 | final Color innerCircleColor; 32 | 33 | /// Opacity of the joystick 34 | /// 35 | /// The opacity applies to the whole joystick including icons 36 | /// 37 | /// Defaults to [null] which means there will be no [Opacity] widget used 38 | final double opacity; 39 | 40 | /// Callback to be called when user pans the joystick 41 | /// 42 | /// Defaults to [null] 43 | final JoystickDirectionCallback onDirectionChanged; 44 | 45 | /// Indicates how often the [onDirectionChanged] should be called. 46 | /// 47 | /// Defaults to [null] which means there will be no lower limit. 48 | /// Setting it to ie. 1 second will cause the callback to be not called more often 49 | /// than once per second. 50 | /// 51 | /// The exception is the [onDirectionChanged] callback being called 52 | /// on the [onPanStart] and [onPanEnd] callbacks. It will be called immediately. 53 | final Duration interval; 54 | 55 | /// Shows top/right/bottom/left arrows on top of Joystick 56 | /// 57 | /// Defaults to [true] 58 | final bool showArrows; 59 | 60 | JoystickComponent( 61 | {this.size, 62 | this.iconsColor = Colors.white54, 63 | this.backgroundColor = Colors.blueGrey, 64 | this.innerCircleColor = Colors.blueGrey, 65 | this.opacity, 66 | this.onDirectionChanged, 67 | this.interval, 68 | this.showArrows = true}); 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | double actualSize = size != null 73 | ? size 74 | : _math.min(MediaQuery.of(context).size.width, 75 | MediaQuery.of(context).size.height) * 76 | 0.5; 77 | double innerCircleSize = actualSize / 2; 78 | Offset lastPosition = Offset(innerCircleSize, innerCircleSize); 79 | Offset joystickInnerPosition = _calculatePositionOfInnerCircle( 80 | lastPosition, innerCircleSize, actualSize, Offset(0, 0)); 81 | 82 | DateTime _callbackTimestamp; 83 | 84 | return Center( 85 | child: StatefulBuilder( 86 | builder: (context, setState) { 87 | Widget joystick = Stack( 88 | children: [ 89 | CircleView.joystickCircle( 90 | actualSize, 91 | backgroundColor, 92 | ), 93 | Positioned( 94 | child: CircleView.joystickInnerCircle( 95 | actualSize / 2, 96 | innerCircleColor, 97 | ), 98 | top: joystickInnerPosition.dy, 99 | left: joystickInnerPosition.dx, 100 | ), 101 | if (showArrows) ...createArrows(), 102 | ], 103 | ); 104 | 105 | return GestureDetector( 106 | onPanStart: (details) { 107 | _callbackTimestamp = _processGesture(actualSize, actualSize / 2, 108 | details.localPosition, _callbackTimestamp); 109 | setState(() => lastPosition = details.localPosition); 110 | }, 111 | onPanEnd: (details) { 112 | _callbackTimestamp = null; 113 | if (onDirectionChanged != null) { 114 | onDirectionChanged(0, 0); 115 | } 116 | joystickInnerPosition = _calculatePositionOfInnerCircle( 117 | Offset(innerCircleSize, innerCircleSize), 118 | innerCircleSize, 119 | actualSize, 120 | Offset(0, 0)); 121 | setState(() => 122 | lastPosition = Offset(innerCircleSize, innerCircleSize)); 123 | }, 124 | onPanUpdate: (details) { 125 | _callbackTimestamp = _processGesture(actualSize, actualSize / 2, 126 | details.localPosition, _callbackTimestamp); 127 | joystickInnerPosition = _calculatePositionOfInnerCircle( 128 | lastPosition, 129 | innerCircleSize, 130 | actualSize, 131 | details.localPosition); 132 | 133 | setState(() => lastPosition = details.localPosition); 134 | }, 135 | child: (opacity != null) 136 | ? Opacity(opacity: opacity, child: joystick) 137 | : joystick, 138 | ); 139 | }, 140 | ), 141 | ); 142 | } 143 | 144 | List createArrows() { 145 | return [ 146 | Positioned( 147 | child: Icon( 148 | Icons.arrow_upward, 149 | color: iconsColor, 150 | ), 151 | top: 16.0, 152 | left: 0.0, 153 | right: 0.0, 154 | ), 155 | Positioned( 156 | child: Icon( 157 | Icons.arrow_back, 158 | color: iconsColor, 159 | ), 160 | top: 0.0, 161 | bottom: 0.0, 162 | left: 16.0, 163 | ), 164 | Positioned( 165 | child: Icon( 166 | Icons.arrow_forward, 167 | color: iconsColor, 168 | ), 169 | top: 0.0, 170 | bottom: 0.0, 171 | right: 16.0, 172 | ), 173 | Positioned( 174 | child: Icon( 175 | Icons.arrow_downward, 176 | color: iconsColor, 177 | ), 178 | bottom: 16.0, 179 | left: 0.0, 180 | right: 0.0, 181 | ), 182 | ]; 183 | } 184 | 185 | DateTime _processGesture(double size, double ignoreSize, Offset offset, 186 | DateTime callbackTimestamp) { 187 | double middle = size / 2.0; 188 | 189 | double angle = _math.atan2(offset.dy - middle, offset.dx - middle); 190 | double degrees = angle * 180 / _math.pi + 90; 191 | if (offset.dx < middle && offset.dy < middle) { 192 | degrees = 360 + degrees; 193 | } 194 | 195 | double dx = _math.max(0, _math.min(offset.dx, size)); 196 | double dy = _math.max(0, _math.min(offset.dy, size)); 197 | 198 | double distance = 199 | _math.sqrt(_math.pow(middle - dx, 2) + _math.pow(middle - dy, 2)); 200 | 201 | double normalizedDistance = _math.min(distance / (size / 2), 1.0); 202 | 203 | DateTime _callbackTimestamp = callbackTimestamp; 204 | if (onDirectionChanged != null && 205 | _canCallOnDirectionChanged(callbackTimestamp)) { 206 | _callbackTimestamp = DateTime.now(); 207 | onDirectionChanged(degrees, normalizedDistance); 208 | } 209 | 210 | return _callbackTimestamp; 211 | } 212 | 213 | /// Checks if the [onDirectionChanged] can be called. 214 | /// 215 | /// Returns true if enough time has passed since last time it was called 216 | /// or when there is no [interval] set. 217 | bool _canCallOnDirectionChanged(DateTime callbackTimestamp) { 218 | if (interval != null && callbackTimestamp != null) { 219 | int intervalMilliseconds = interval.inMilliseconds; 220 | int timestampMilliseconds = callbackTimestamp.millisecondsSinceEpoch; 221 | int currentTimeMilliseconds = DateTime.now().millisecondsSinceEpoch; 222 | 223 | if (currentTimeMilliseconds - timestampMilliseconds <= 224 | intervalMilliseconds) { 225 | return false; 226 | } 227 | } 228 | 229 | return true; 230 | } 231 | 232 | Offset _calculatePositionOfInnerCircle( 233 | Offset lastPosition, double innerCircleSize, double size, Offset offset) { 234 | double middle = size / 2.0; 235 | 236 | double angle = _math.atan2(offset.dy - middle, offset.dx - middle); 237 | double degrees = angle * 180 / _math.pi; 238 | if (offset.dx < middle && offset.dy < middle) { 239 | degrees = 360 + degrees; 240 | } 241 | bool isStartPosition = lastPosition.dx == innerCircleSize && 242 | lastPosition.dy == innerCircleSize; 243 | double lastAngleRadians = 244 | (isStartPosition) ? 0 : (degrees) * (_math.pi / 180.0); 245 | 246 | var rBig = size / 2; 247 | var rSmall = innerCircleSize / 2; 248 | 249 | var x = (lastAngleRadians == -1) 250 | ? rBig - rSmall 251 | : (rBig - rSmall) + (rBig - rSmall) * _math.cos(lastAngleRadians); 252 | var y = (lastAngleRadians == -1) 253 | ? rBig - rSmall 254 | : (rBig - rSmall) + (rBig - rSmall) * _math.sin(lastAngleRadians); 255 | 256 | var xPosition = lastPosition.dx - rSmall; 257 | var yPosition = lastPosition.dy - rSmall; 258 | 259 | var angleRadianPlus = lastAngleRadians + _math.pi / 2; 260 | if (angleRadianPlus < _math.pi / 2) { 261 | if (xPosition > x) { 262 | xPosition = x; 263 | } 264 | if (yPosition < y) { 265 | yPosition = y; 266 | } 267 | } else if (angleRadianPlus < _math.pi) { 268 | if (xPosition > x) { 269 | xPosition = x; 270 | } 271 | if (yPosition > y) { 272 | yPosition = y; 273 | } 274 | } else if (angleRadianPlus < 3 * _math.pi / 2) { 275 | if (xPosition < x) { 276 | xPosition = x; 277 | } 278 | if (yPosition > y) { 279 | yPosition = y; 280 | } 281 | } else { 282 | if (xPosition < x) { 283 | xPosition = x; 284 | } 285 | if (yPosition < y) { 286 | yPosition = y; 287 | } 288 | } 289 | return Offset(xPosition, yPosition); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /lib/components/pageroute-component.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | Route pageRoute({Widget page, String animation}) { 5 | switch (animation) { 6 | case 'PLATFORM-D': //Default platform animation 7 | return MaterialPageRoute(builder: (context) => page); 8 | case 'PLATFORM-F': //Default platform fullscreen animation 9 | return MaterialPageRoute( 10 | fullscreenDialog: true, builder: (context) => page); 11 | case 'FADE': 12 | return PageTransition(page: page); 13 | default: //By default, no animation 14 | return PageRouteBuilder( 15 | pageBuilder: (context, animation, secondaryAnimation) => page, 16 | transitionsBuilder: (context, animation, secondaryAnimation, child) { 17 | return child; 18 | }, 19 | ); 20 | } 21 | } 22 | 23 | class PageTransition extends PageRouteBuilder { 24 | final Widget page; 25 | PageTransition({this.page}) 26 | : super( 27 | pageBuilder: ( 28 | BuildContext context, 29 | Animation animation, 30 | Animation secondaryAnimation, 31 | ) => 32 | page, 33 | transitionsBuilder: ( 34 | BuildContext context, 35 | Animation animation, 36 | Animation secondaryAnimation, 37 | Widget child, 38 | ) => 39 | FadeTransition( 40 | opacity: animation, 41 | child: child, 42 | ), 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /lib/components/state-component.dart: -------------------------------------------------------------------------------- 1 | import 'package:duino/components/adaptive-components/adaptive-iconbutton.dart'; 2 | import 'package:duino/providers/bluetooth-provider.dart'; 3 | import 'package:duino/styles.dart'; 4 | import 'package:eva_icons_flutter/eva_icons_flutter.dart'; 5 | import 'package:flutter/cupertino.dart'; 6 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | /// Icon displays current bluetooth/device state. 10 | class StateComponent extends StatelessWidget { 11 | void onPressed(context) => Navigator.of(context) 12 | .pushNamed('/ConnectView', arguments: {'ANIM': 'PLATFORM-D', 'DATA': {}}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | BluetoothProvider bluetoothProvider = 17 | Provider.of(context); 18 | PeripheralConnectionState deviceState = bluetoothProvider.bleDeviceState; 19 | BluetoothState bluetoothState = bluetoothProvider.bluetoothState; 20 | if (bluetoothState == BluetoothState.POWERED_ON || bluetoothState == BluetoothState.UNKNOWN) { 21 | switch (deviceState) { 22 | case PeripheralConnectionState.connected: 23 | return AdaptiveIconButton( 24 | onPressed: () => onPressed(context), 25 | child: Icon( 26 | EvaIcons.checkmarkCircleOutline, 27 | color: Styles.adaptiveGreenColor, 28 | ), 29 | ); 30 | case PeripheralConnectionState.connecting: 31 | return AdaptiveIconButton( 32 | onPressed: () => onPressed(context), 33 | child: Icon( 34 | EvaIcons.activity, 35 | color: Styles.adaptiveOrangeColor, 36 | ), 37 | ); 38 | case PeripheralConnectionState.disconnecting: 39 | return AdaptiveIconButton( 40 | onPressed: () => onPressed(context), 41 | child: Icon( 42 | EvaIcons.activity, 43 | color: Styles.adaptiveOrangeColor, 44 | ), 45 | ); 46 | case PeripheralConnectionState.disconnected: 47 | return AdaptiveIconButton( 48 | onPressed: () => onPressed(context), 49 | child: Icon( 50 | EvaIcons.minusCircleOutline, 51 | color: Styles.of(context).textStyle.color, 52 | ), 53 | ); 54 | default: 55 | return AdaptiveIconButton( 56 | onPressed: () => onPressed(context), 57 | child: Icon( 58 | EvaIcons.minusCircleOutline, 59 | color: Styles.of(context).textStyle.color, 60 | ), 61 | ); 62 | } 63 | } else { 64 | return AdaptiveIconButton( 65 | onPressed: () => onPressed(context), 66 | child: Icon( 67 | EvaIcons.minusCircleOutline, 68 | color: Styles.of(context).textStyle.color, 69 | ), 70 | ); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/components/util-components/math-util.dart: -------------------------------------------------------------------------------- 1 | class MathUtil { 2 | static double map( 3 | double x, double inMin, double inMax, double outMin, double outMax) { 4 | return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:duino/components/pageroute-component.dart'; 4 | import 'package:duino/providers/bluetooth-provider.dart'; 5 | import 'package:duino/routes.dart'; 6 | import 'package:duino/styles.dart'; 7 | import 'package:duino/views/home-view/home-view.dart'; 8 | import 'package:flutter/cupertino.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:provider/provider.dart'; 11 | 12 | void main() async { 13 | WidgetsFlutterBinding.ensureInitialized(); 14 | BluetoothProvider bluetoothProvider = BluetoothProvider(); 15 | await bluetoothProvider.startBluetooth(); 16 | runApp(ChangeNotifierProvider.value(value: bluetoothProvider, child: App())); 17 | } 18 | 19 | class App extends StatelessWidget { 20 | Widget _buildiOS(BuildContext context) { 21 | return CupertinoApp( 22 | debugShowCheckedModeBanner: false, 23 | title: 'Duino', 24 | theme: Styles.cupertinoTheme, 25 | initialRoute: '/HomeView', 26 | onGenerateInitialRoutes: (String initalRoute) => 27 | [pageRoute(page: HomeView(), animation: null)], 28 | onGenerateRoute: RouteGenerator.generateRoute, 29 | ); 30 | } 31 | 32 | Widget _buildAndroid(BuildContext context) { 33 | return MaterialApp( 34 | debugShowCheckedModeBanner: false, 35 | title: 'Duino', 36 | theme: Styles.themeDataLight, 37 | darkTheme: Styles.themeDataDark, 38 | initialRoute: '/HomeView', 39 | onGenerateInitialRoutes: (String initalRoute) => 40 | [pageRoute(page: HomeView(), animation: null)], 41 | onGenerateRoute: RouteGenerator.generateRoute, 42 | ); 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return (Platform.isIOS) ? _buildiOS(context) : _buildAndroid(context); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/models/bledevice-model.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 3 | 4 | /// Based on https://github.com/Polidea/FlutterBleLib/blob/develop/example/lib/model/ble_device.dart. 5 | class BleDevice { 6 | final Peripheral peripheral; 7 | final String name; 8 | final DeviceCategory category; 9 | Characteristic characteristic; 10 | 11 | String get id => peripheral.identifier; 12 | 13 | BleDevice(ScanResult scanResult) 14 | : peripheral = scanResult.peripheral, 15 | name = scanResult.name != "" ? scanResult.name : '(Unknown)', 16 | category = scanResult.category; 17 | 18 | /// Gets the Service with FFE0 and characteristic with FFE1 19 | /// This is defined in DSD Tech User Guide for HM-10 20 | Future setCharacteristic() async { 21 | await peripheral.discoverAllServicesAndCharacteristics(); 22 | List services = await peripheral.services(); 23 | Service service = services.firstWhere( 24 | (element) => element.uuid.toLowerCase().startsWith(RegExp(r'0*ffe0'))); 25 | List characteristics = await service.characteristics(); 26 | characteristic = characteristics.firstWhere( 27 | (element) => element.uuid.toLowerCase().startsWith(RegExp(r'0*ffe1'))); 28 | } 29 | 30 | @override 31 | int get hashCode => id.hashCode; 32 | 33 | @override 34 | bool operator ==(other) => 35 | other is BleDevice && 36 | this.name != null && 37 | other.name != null && 38 | compareAsciiLowerCase(this.name, other.name) == 0 && 39 | this.id == other.id; 40 | 41 | @override 42 | String toString() { 43 | return 'BleDevice{name: $name}'; 44 | } 45 | } 46 | 47 | enum DeviceCategory { sensorTag, hex, other } 48 | 49 | extension on ScanResult { 50 | String get name => 51 | peripheral.name ?? advertisementData.localName ?? "Unknown"; 52 | 53 | DeviceCategory get category { 54 | if (name == "SensorTag") { 55 | return DeviceCategory.sensorTag; 56 | } else if (name != null && name.startsWith("Hex")) { 57 | return DeviceCategory.hex; 58 | } else { 59 | return DeviceCategory.other; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/providers/bluetooth-provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:duino/models/bledevice-model.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 7 | 8 | /// Entry point for all bluetooth functionality. 9 | class BluetoothProvider with ChangeNotifier { 10 | final BleManager bleManager = BleManager(); 11 | 12 | BleDevice bleDevice; 13 | StreamSubscription deviceStateSubscription; 14 | StreamSubscription bluetoothStateSubscription; 15 | BluetoothState bluetoothState = BluetoothState.UNKNOWN; 16 | PeripheralConnectionState bleDeviceState = 17 | PeripheralConnectionState.disconnected; 18 | bool _mounted = true; 19 | 20 | /// Starts bluetooth services. 21 | Future startBluetooth() async { 22 | try { 23 | await bleManager.createClient(); 24 | bluetoothStateSubscription = 25 | bleManager.observeBluetoothState().listen((btState) { 26 | bluetoothState = btState; 27 | switch (btState) { 28 | case BluetoothState.POWERED_OFF: 29 | bleDevice = null; 30 | break; 31 | case BluetoothState.RESETTING: 32 | bleDevice = null; 33 | break; 34 | case BluetoothState.UNAUTHORIZED: 35 | bleDevice = null; 36 | break; 37 | case BluetoothState.UNSUPPORTED: 38 | bleDevice = null; 39 | break; 40 | default: 41 | } 42 | notify(); 43 | }, onError: (e) { 44 | print(e); 45 | bluetoothState = BluetoothState.UNKNOWN; 46 | notify(); 47 | }); 48 | } catch (e) { 49 | print(e); 50 | bluetoothState = BluetoothState.UNKNOWN; 51 | } 52 | } 53 | 54 | /// Connects to a bluetooth device 55 | Future connect(BleDevice device) async { 56 | try { 57 | await disconnect(); 58 | bleDevice = device; 59 | bleDeviceState = PeripheralConnectionState.connecting; 60 | notify(); 61 | await device.peripheral.connect(timeout: Duration(seconds: 8)); 62 | await device.peripheral 63 | .discoverAllServicesAndCharacteristics() 64 | .timeout(Duration(seconds: 8)); 65 | await device.setCharacteristic(); 66 | bleDeviceState = PeripheralConnectionState.connected; 67 | deviceStateSubscription = device.peripheral 68 | .observeConnectionState( 69 | completeOnDisconnect: true, emitCurrentValue: true) 70 | .listen((connectionState) { 71 | bleDeviceState = connectionState; 72 | notify(); 73 | }, onError: (e) { 74 | print(e); 75 | }, onDone: () => bleDevice = null); 76 | notify(); 77 | } catch (e) { 78 | print(e); 79 | disconnect(); 80 | bleDeviceState = PeripheralConnectionState.disconnected; 81 | notify(); 82 | } 83 | } 84 | 85 | /// Disconnects a bluetooth device. 86 | Future disconnect() async { 87 | bleDeviceState = PeripheralConnectionState.disconnecting; 88 | notify(); 89 | try { 90 | if (bleDevice != null) 91 | await bleDevice.peripheral 92 | .disconnectOrCancelConnection() 93 | .timeout(Duration(seconds: 8)); 94 | if (deviceStateSubscription != null) 95 | await deviceStateSubscription.cancel().timeout(Duration(seconds: 4)); 96 | } catch (e) { 97 | print(e); 98 | } 99 | bleDevice = null; 100 | bleDeviceState = PeripheralConnectionState.disconnected; 101 | notify(); 102 | } 103 | 104 | /// Write to bluetooth characteristic. 105 | void write(String data) { 106 | if ((bluetoothState == BluetoothState.POWERED_ON || 107 | bluetoothState == BluetoothState.UNKNOWN) && 108 | bleDeviceState == PeripheralConnectionState.connected) { 109 | if (bleDevice.characteristic.isWritableWithoutResponse || 110 | bleDevice.characteristic.isWritableWithResponse) { 111 | bleDevice.characteristic 112 | .write(utf8.encode(data), false) 113 | .catchError((e) => print(e)); 114 | } 115 | } 116 | } 117 | 118 | /// Notify Listeners 119 | void notify() => _mounted ? notifyListeners() : null; 120 | 121 | @override 122 | void dispose() async { 123 | _mounted = false; 124 | await bluetoothStateSubscription.cancel(); 125 | await bleManager.destroyClient(); 126 | super.dispose(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lib/routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:duino/components/pageroute-component.dart'; 2 | import 'package:duino/views/about-view/about-view.dart'; 3 | import 'package:duino/views/connect-vieiw/connect-view.dart'; 4 | import 'package:duino/views/connect-vieiw/providers/connect-provider.dart'; 5 | import 'package:duino/views/home-view/home-view.dart'; 6 | import 'package:duino/views/joystick-view/joystick-view.dart'; 7 | import 'package:duino/views/remote-view/providers/remote-provider.dart'; 8 | import 'package:duino/views/remote-view/remote-view.dart'; 9 | import 'package:duino/views/tilt-view/tilt-view.dart'; 10 | import 'package:flutter/cupertino.dart'; 11 | import 'package:flutter/material.dart'; 12 | import 'package:flutter/widgets.dart'; 13 | import 'package:provider/provider.dart'; 14 | 15 | class RouteGenerator { 16 | static Route generateRoute(RouteSettings settings) { 17 | final Map args = settings.arguments; 18 | final String animation = args['ANIM']; 19 | 20 | switch (settings.name) { 21 | case '/HomeView': 22 | return pageRoute(page: HomeView(), animation: animation); 23 | case '/ConnectView': 24 | return pageRoute( 25 | page: ChangeNotifierProvider( 26 | create: (_) => ConnectProvider(), child: ConnectView()), 27 | animation: animation); 28 | case '/RemoteView': 29 | return pageRoute( 30 | page: ChangeNotifierProvider( 31 | create: (_) => RemoteProvider(), child: RemoteView()), 32 | animation: animation); 33 | case '/JoystickView': 34 | return pageRoute(page: JoystickView(), animation: animation); 35 | case '/TiltView': 36 | return pageRoute(page: TiltView(), animation: animation); 37 | case '/AboutView': 38 | return pageRoute(page: AboutView(), animation: animation); 39 | default: 40 | return null; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'dart:io' show Platform; 4 | 5 | class Styles { 6 | /* 7 | Snackbar colors 8 | */ 9 | 10 | static Color successTextColor = Colors.green[700]; 11 | static Color successBackgroundColor = Colors.green[100]; 12 | 13 | static Color dangerTextColor = Colors.red[700]; 14 | static Color dangerBackgroundColor = Colors.red[100]; 15 | 16 | static Color warningTextColor = Colors.yellow[700]; 17 | static Color warningBackgroundColor = Colors.yellow[100]; 18 | 19 | static Color alertTextColor = Colors.blue[700]; 20 | static Color alertBackgroundColor = Colors.blue[100]; 21 | 22 | static Color inactiveTextColor = Colors.grey[700]; 23 | static Color inactiveBackgroundColor = Colors.grey[100]; 24 | 25 | /* 26 | Default Themes 27 | */ 28 | 29 | static ThemeData _themeData = ThemeData(); 30 | static ThemeData _themeDataDark = ThemeData.dark(); 31 | //static CupertinoThemeData _cupertinoThemeData = CupertinoThemeData(); 32 | 33 | /* 34 | Adaptive colors 35 | */ 36 | 37 | // Opaque black color. Used for texts against light backgrounds. 38 | static Color adaptiveBlackColor = 39 | Platform.isIOS ? CupertinoColors.black : Colors.black; 40 | 41 | // Opaque white color. Used for backgrounds and fonts against dark backgrounds. 42 | static Color adaptiveWhiteColor = 43 | Platform.isIOS ? CupertinoColors.white : Colors.white; 44 | 45 | // Used for iOS 13 for destructive actions such as the delete actions in table view cells and dialogs. 46 | static Color adaptiveRedColor = 47 | Platform.isIOS ? CupertinoColors.destructiveRed : Colors.red[700]; 48 | 49 | // iOS 13's default green color. Used to indicate active accents such as the switch in its on state and some accent buttons such as the call button and Apple Map's 'Go' button. 50 | static Color adaptiveGreenColor = 51 | Platform.isIOS ? CupertinoColors.activeGreen : Colors.green; 52 | 53 | // iOS 13's default blue color. Used to indicate active elements such as buttons, selected tabs and your own chat bubbles. 54 | static Color adaptiveBlueColor = 55 | Platform.isIOS ? CupertinoColors.activeBlue : Colors.lightBlue; 56 | 57 | // iOS 13's default blue color. Used to indicate active elements such as buttons, selected tabs and your own chat bubbles. 58 | static Color adaptiveOrangeColor = 59 | Platform.isIOS ? CupertinoColors.activeOrange : Colors.orangeAccent; 60 | 61 | // The color for thin borders or divider lines that allows some underlying content to be visible 62 | static Color adaptiveSeparatorColor = Platform.isIOS 63 | ? CupertinoColors.opaqueSeparator 64 | : _themeData.dividerColor; 65 | 66 | // Used in iOS 13 for unselected selectables such as tab bar items in their inactive state or de-emphasized subtitles and details text. 67 | static Color adaptiveGrayColor = 68 | Platform.isIOS ? CupertinoColors.inactiveGray : _themeData.disabledColor; 69 | 70 | // The color for placeholder text in controls or text views 71 | static Color adaptivePlaceholderColor = 72 | Platform.isIOS ? CupertinoColors.placeholderText : _themeData.hintColor; 73 | 74 | /* 75 | Platform Themes 76 | */ 77 | 78 | // iOS Dark/Light Theme 79 | static CupertinoThemeData cupertinoTheme = CupertinoThemeData( 80 | scaffoldBackgroundColor: CupertinoColors.systemBackground, 81 | barBackgroundColor: CupertinoColors.systemBackground, 82 | primaryColor: CupertinoColors.tertiarySystemBackground, 83 | primaryContrastingColor: CupertinoColors.secondarySystemBackground); 84 | 85 | // Android Light Theme 86 | static ThemeData themeDataLight = ThemeData( 87 | primaryColor: Colors.white, 88 | textTheme: _themeData.textTheme, 89 | scaffoldBackgroundColor: Colors.white, 90 | accentColor: Colors.white, 91 | primaryColorLight: Colors.white, 92 | primaryColorDark: Colors.grey[100], 93 | ); 94 | 95 | // Android Dark Theme 96 | static ThemeData themeDataDark = ThemeData( 97 | primaryColor: Colors.black, 98 | textTheme: _themeDataDark.textTheme, 99 | scaffoldBackgroundColor: Colors.black, 100 | accentColor: Colors.black, 101 | primaryColorLight: Colors.grey[800], 102 | primaryColorDark: Colors.grey[900], 103 | ); 104 | 105 | // Get Platform Theme Style 106 | static _Data of(context) => _Data(context: context); 107 | } 108 | 109 | class _Data { 110 | final Color primaryColor; 111 | final Color primaryContrastingColor; 112 | final Brightness brightness; 113 | final Color barBackgroundColor; 114 | final Color scaffoldBackgroundColor; 115 | final TextStyle textStyle; 116 | final TextStyle navTitleTextStyle; 117 | final TextStyle navLargeTitleTextStyle; 118 | 119 | _Data({context}) 120 | : primaryColor = Platform.isIOS 121 | ? CupertinoTheme.of(context).primaryColor 122 | : Theme.of(context).primaryColorLight, 123 | primaryContrastingColor = Platform.isIOS 124 | ? CupertinoTheme.of(context).primaryContrastingColor 125 | : Theme.of(context).primaryColorDark, 126 | brightness = Platform.isIOS 127 | ? CupertinoTheme.of(context).brightness 128 | : Theme.of(context).brightness, 129 | barBackgroundColor = Platform.isIOS 130 | ? CupertinoTheme.of(context).barBackgroundColor 131 | : Theme.of(context).accentColor, 132 | scaffoldBackgroundColor = Platform.isIOS 133 | ? CupertinoTheme.of(context).scaffoldBackgroundColor 134 | : Theme.of(context).scaffoldBackgroundColor, 135 | textStyle = Platform.isIOS 136 | ? CupertinoTheme.of(context).textTheme.textStyle 137 | : Theme.of(context).textTheme.bodyText1, 138 | navTitleTextStyle = Platform.isIOS 139 | ? CupertinoTheme.of(context).textTheme.navTitleTextStyle 140 | : Theme.of(context).textTheme.headline6, 141 | navLargeTitleTextStyle = 142 | Platform.isIOS ? null : Theme.of(context).textTheme.headline6; 143 | } 144 | -------------------------------------------------------------------------------- /lib/views/about-view/about-view.dart: -------------------------------------------------------------------------------- 1 | import 'package:duino/components/adaptive-components/adaptive-iconbutton.dart'; 2 | import 'package:duino/components/adaptive-components/adaptive-navbar.dart'; 3 | import 'package:duino/components/adaptive-components/adaptive-scaffold.dart'; 4 | import 'package:duino/styles.dart'; 5 | import 'package:eva_icons_flutter/eva_icons_flutter.dart'; 6 | import 'package:flutter/cupertino.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:url_launcher/url_launcher.dart'; 9 | 10 | class AboutView extends StatelessWidget { 11 | @override 12 | Widget build(BuildContext context) { 13 | return AdaptiveScaffold( 14 | navBar: AdaptiveNavBar( 15 | backgroundColor: Styles.of(context).barBackgroundColor, 16 | leading: AdaptiveIconButton( 17 | child: Padding( 18 | padding: const EdgeInsets.all(8.0), 19 | child: Icon( 20 | CupertinoIcons.back, 21 | color: Styles.of(context).textStyle.color, 22 | ), 23 | ), 24 | onPressed: () { 25 | Navigator.of(context).pop(); 26 | }, 27 | ), 28 | middle: Text( 29 | 'About', 30 | style: Styles.of(context).navTitleTextStyle, 31 | ), 32 | ), 33 | child: CustomScrollView( 34 | cacheExtent: MediaQuery.of(context).size.height, 35 | physics: AlwaysScrollableScrollPhysics(), 36 | slivers: [ 37 | SliverPadding( 38 | padding: EdgeInsets.all(16), 39 | sliver: SliverToBoxAdapter( 40 | child: Column( 41 | crossAxisAlignment: CrossAxisAlignment.start, 42 | children: [ 43 | Text( 44 | 'Guides, tips, suggestions, and contributions', 45 | style: Styles.of(context) 46 | .textStyle 47 | .copyWith(fontSize: 20, fontWeight: FontWeight.bold), 48 | ), 49 | CupertinoButton( 50 | padding: EdgeInsets.only(), 51 | child: Row( 52 | mainAxisSize: MainAxisSize.min, 53 | children: [ 54 | Icon( 55 | EvaIcons.githubOutline, 56 | color: Styles.adaptiveBlueColor, 57 | size: 20, 58 | ), 59 | SizedBox( 60 | width: 4, 61 | ), 62 | Text( 63 | 'Github', 64 | style: Styles.of(context).textStyle.copyWith( 65 | fontSize: 16, color: Styles.adaptiveBlueColor), 66 | ) 67 | ], 68 | ), 69 | onPressed: () async { 70 | const url = 'https://github.com/davebaraka/duino'; 71 | if (await canLaunch(url)) await launch(url); 72 | }, 73 | ), 74 | SizedBox( 75 | height: 8, 76 | ), 77 | Text( 78 | 'Learn more about the designer and developer', 79 | style: Styles.of(context) 80 | .textStyle 81 | .copyWith(fontSize: 20, fontWeight: FontWeight.bold), 82 | ), 83 | CupertinoButton( 84 | padding: EdgeInsets.only(), 85 | child: Row( 86 | mainAxisSize: MainAxisSize.min, 87 | children: [ 88 | Icon( 89 | EvaIcons.atOutline, 90 | color: Styles.adaptiveBlueColor, 91 | size: 20, 92 | ), 93 | SizedBox( 94 | width: 4, 95 | ), 96 | Text( 97 | 'Dev', 98 | style: Styles.of(context).textStyle.copyWith( 99 | fontSize: 16, color: Styles.adaptiveBlueColor), 100 | ) 101 | ], 102 | ), 103 | onPressed: () async { 104 | const url = 'https://davebaraka.dev'; 105 | if (await canLaunch(url)) await launch(url); 106 | }, 107 | ), 108 | SizedBox( 109 | height: 8, 110 | ), 111 | Text( 112 | 'Terms and Conditions', 113 | style: Styles.of(context) 114 | .textStyle 115 | .copyWith(fontSize: 20, fontWeight: FontWeight.bold), 116 | ), 117 | CupertinoButton( 118 | padding: EdgeInsets.only(), 119 | child: Row( 120 | mainAxisSize: MainAxisSize.min, 121 | children: [ 122 | Icon( 123 | EvaIcons.externalLinkOutline, 124 | color: Styles.adaptiveBlueColor, 125 | size: 20, 126 | ), 127 | SizedBox( 128 | width: 4, 129 | ), 130 | Text( 131 | 'Terms', 132 | style: Styles.of(context).textStyle.copyWith( 133 | fontSize: 16, color: Styles.adaptiveBlueColor), 134 | ) 135 | ], 136 | ), 137 | onPressed: () async { 138 | const url = 139 | 'https://github.com/davebaraka/duino/blob/master/TERMS.md'; 140 | if (await canLaunch(url)) await launch(url); 141 | }, 142 | ), 143 | SizedBox( 144 | height: 8, 145 | ), 146 | Text( 147 | 'Privacy Policy', 148 | style: Styles.of(context) 149 | .textStyle 150 | .copyWith(fontSize: 20, fontWeight: FontWeight.bold), 151 | ), 152 | CupertinoButton( 153 | padding: EdgeInsets.only(), 154 | child: Row( 155 | mainAxisSize: MainAxisSize.min, 156 | children: [ 157 | Icon( 158 | EvaIcons.externalLinkOutline, 159 | color: Styles.adaptiveBlueColor, 160 | size: 20, 161 | ), 162 | SizedBox( 163 | width: 4, 164 | ), 165 | Text( 166 | 'Policy', 167 | style: Styles.of(context).textStyle.copyWith( 168 | fontSize: 16, color: Styles.adaptiveBlueColor), 169 | ) 170 | ], 171 | ), 172 | onPressed: () async { 173 | const url = 174 | 'https://github.com/davebaraka/duino/blob/master/POLICY.md'; 175 | if (await canLaunch(url)) await launch(url); 176 | }, 177 | ), 178 | SizedBox( 179 | height: 8, 180 | ), 181 | Text( 182 | 'Attributions', 183 | style: Styles.of(context) 184 | .textStyle 185 | .copyWith(fontSize: 20, fontWeight: FontWeight.bold), 186 | ), 187 | SizedBox( 188 | height: 8, 189 | ), 190 | Row( 191 | children: [ 192 | Expanded( 193 | child: Text( 194 | 'Icons made by Eucalyp from flaticon.com', 195 | style: Styles.of(context) 196 | .textStyle 197 | .copyWith(fontSize: 16), 198 | ), 199 | ), 200 | ], 201 | ), 202 | SizedBox( 203 | height: 16, 204 | ), 205 | Text( 206 | 'Version', 207 | style: Styles.of(context) 208 | .textStyle 209 | .copyWith(fontSize: 20, fontWeight: FontWeight.bold), 210 | ), 211 | SizedBox( 212 | height: 8, 213 | ), 214 | Row( 215 | children: [ 216 | Text( 217 | 'v0.0.4', 218 | style: Styles.of(context) 219 | .textStyle 220 | .copyWith(fontSize: 16), 221 | ), 222 | ], 223 | ), 224 | ], 225 | ), 226 | ), 227 | ) 228 | ], 229 | )); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /lib/views/connect-vieiw/components/device-component.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:duino/components/adaptive-components/adaptive-material.dart'; 4 | import 'package:duino/components/adaptive-components/adaptive-theme.dart'; 5 | import 'package:duino/models/bledevice-model.dart'; 6 | import 'package:duino/providers/bluetooth-provider.dart'; 7 | import 'package:duino/styles.dart'; 8 | import 'package:duino/views/connect-vieiw/components/dialog-component.dart'; 9 | import 'package:flutter/cupertino.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 12 | import 'package:provider/provider.dart'; 13 | 14 | /// Device list item. 15 | class DeviceComponent extends StatelessWidget { 16 | final BleDevice bleDevice; 17 | 18 | DeviceComponent({@required this.bleDevice}); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | BluetoothProvider bluetoothProvider = 23 | Provider.of(context, listen: false); 24 | return AdaptiveMaterial( 25 | child: AdaptiveTheme( 26 | themeData: Theme.of(context).copyWith(splashColor: Colors.transparent), 27 | child: InkWell( 28 | onTap: () async { 29 | if (bluetoothProvider.bleDeviceState == 30 | PeripheralConnectionState.disconnecting || 31 | bluetoothProvider.bleDeviceState == 32 | PeripheralConnectionState.connecting) { 33 | Platform.isIOS 34 | ? await cupertinoWaitingDialog(context) 35 | : await androidWaitingDialog(context); 36 | } else { 37 | if (bluetoothProvider.bleDevice != null && 38 | bleDevice.id == bluetoothProvider.bleDevice.id) { 39 | Platform.isIOS 40 | ? await cupertinoDisconnectDialog(context) 41 | : await androidDisconnectDialog(context); 42 | } else { 43 | Platform.isIOS 44 | ? await cupertinoConnectDialog(context, bleDevice) 45 | : await androidConnectDialog(context, bleDevice); 46 | } 47 | } 48 | }, 49 | child: Container( 50 | padding: EdgeInsets.fromLTRB(16, 16, 16, 16), 51 | child: Column( 52 | crossAxisAlignment: CrossAxisAlignment.start, 53 | children: [ 54 | Text( 55 | bleDevice.name, 56 | style: Styles.of(context).textStyle, 57 | overflow: TextOverflow.ellipsis, 58 | maxLines: 1, 59 | ), 60 | Text( 61 | bleDevice.id, 62 | style: Styles.of(context).textStyle.copyWith( 63 | color: Styles.adaptiveGrayColor, fontSize: 12), 64 | overflow: TextOverflow.ellipsis, 65 | maxLines: 1, 66 | ) 67 | ], 68 | )), 69 | ), 70 | ), 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/views/connect-vieiw/components/dialog-component.dart: -------------------------------------------------------------------------------- 1 | import 'package:duino/models/bledevice-model.dart'; 2 | import 'package:duino/providers/bluetooth-provider.dart'; 3 | import 'package:duino/styles.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | // iOS Connect Dialog 9 | cupertinoConnectDialog(BuildContext context, BleDevice bleDevice) async { 10 | BluetoothProvider bluetoothProvider = 11 | Provider.of(context, listen: false); 12 | await showCupertinoDialog( 13 | context: context, 14 | builder: (_) => CupertinoAlertDialog( 15 | title: Text('Connect to'), 16 | content: SingleChildScrollView( 17 | child: Column( 18 | children: [ 19 | Text( 20 | bleDevice.name, 21 | style: Styles.of(context).textStyle, 22 | overflow: TextOverflow.ellipsis, 23 | maxLines: 1, 24 | ), 25 | SizedBox( 26 | height: 2, 27 | ), 28 | Text( 29 | bleDevice.id, 30 | style: Styles.of(context) 31 | .textStyle 32 | .copyWith(color: Styles.adaptiveGrayColor, fontSize: 12), 33 | ) 34 | ], 35 | )), 36 | actions: [ 37 | CupertinoDialogAction( 38 | child: Text('Yes'), 39 | onPressed: () { 40 | bluetoothProvider.connect(bleDevice); 41 | Navigator.of(context).pop(); 42 | }, 43 | ), 44 | CupertinoDialogAction( 45 | child: Text( 46 | 'No', 47 | style: Styles.of(context) 48 | .textStyle 49 | .copyWith(color: Styles.adaptiveRedColor), 50 | ), 51 | onPressed: () { 52 | Navigator.of(context).pop(); 53 | }, 54 | ) 55 | ], 56 | )); 57 | } 58 | 59 | // android Connect Dialog 60 | androidConnectDialog(BuildContext context, BleDevice bleDevice) async { 61 | BluetoothProvider bluetoothProvider = 62 | Provider.of(context, listen: false); 63 | await showDialog( 64 | context: context, 65 | builder: (_) => AlertDialog( 66 | backgroundColor: Styles.of(context).primaryColor, 67 | title: Text('Connect to'), 68 | content: SingleChildScrollView( 69 | child: Column( 70 | crossAxisAlignment: CrossAxisAlignment.start, 71 | children: [ 72 | Text( 73 | bleDevice.name, 74 | style: Styles.of(context).textStyle, 75 | overflow: TextOverflow.ellipsis, 76 | maxLines: 1, 77 | ), 78 | SizedBox( 79 | height: 2, 80 | ), 81 | Text( 82 | bleDevice.id, 83 | style: Styles.of(context) 84 | .textStyle 85 | .copyWith(color: Styles.adaptiveGrayColor, fontSize: 12), 86 | ) 87 | ], 88 | )), 89 | actions: [ 90 | FlatButton( 91 | splashColor: Theme.of(context).splashColor, 92 | highlightColor: Theme.of(context).highlightColor, 93 | child: Text( 94 | 'No', 95 | style: Styles.of(context) 96 | .textStyle 97 | .copyWith(color: Styles.adaptiveRedColor), 98 | ), 99 | onPressed: () { 100 | Navigator.of(context).pop(); 101 | }, 102 | ), 103 | FlatButton( 104 | splashColor: Theme.of(context).splashColor, 105 | highlightColor: Theme.of(context).highlightColor, 106 | child: Text( 107 | 'Yes', 108 | style: TextStyle(color: Styles.adaptiveBlueColor), 109 | ), 110 | onPressed: () { 111 | bluetoothProvider.connect(bleDevice); 112 | Navigator.of(context).pop(); 113 | }, 114 | ), 115 | ], 116 | )); 117 | } 118 | 119 | // iOS Disconnect Dialog 120 | cupertinoDisconnectDialog(BuildContext context) async { 121 | BluetoothProvider bluetoothProvider = 122 | Provider.of(context, listen: false); 123 | await showCupertinoDialog( 124 | context: context, 125 | builder: (_) => CupertinoAlertDialog( 126 | title: Text('Disconnect from'), 127 | content: SingleChildScrollView( 128 | child: Column( 129 | children: [ 130 | Text( 131 | bluetoothProvider.bleDevice.name, 132 | style: Styles.of(context).textStyle, 133 | overflow: TextOverflow.ellipsis, 134 | maxLines: 1, 135 | ), 136 | SizedBox( 137 | height: 2, 138 | ), 139 | Text( 140 | bluetoothProvider.bleDevice.id, 141 | style: Styles.of(context) 142 | .textStyle 143 | .copyWith(color: Styles.adaptiveGrayColor, fontSize: 12), 144 | ) 145 | ], 146 | )), 147 | actions: [ 148 | CupertinoDialogAction( 149 | child: Text('Yes'), 150 | onPressed: () { 151 | bluetoothProvider.disconnect(); 152 | Navigator.of(context).pop(); 153 | }, 154 | ), 155 | CupertinoDialogAction( 156 | child: Text( 157 | 'No', 158 | style: Styles.of(context) 159 | .textStyle 160 | .copyWith(color: Styles.adaptiveRedColor), 161 | ), 162 | onPressed: () { 163 | Navigator.of(context).pop(); 164 | }, 165 | ) 166 | ], 167 | )); 168 | } 169 | 170 | // android Disconnect Dialog 171 | androidDisconnectDialog(BuildContext context) async { 172 | BluetoothProvider bluetoothProvider = 173 | Provider.of(context, listen: false); 174 | await showDialog( 175 | context: context, 176 | builder: (_) => AlertDialog( 177 | backgroundColor: Styles.of(context).primaryColor, 178 | title: Text('Disconnect from'), 179 | content: SingleChildScrollView( 180 | child: Column( 181 | crossAxisAlignment: CrossAxisAlignment.start, 182 | children: [ 183 | Text( 184 | bluetoothProvider.bleDevice.name, 185 | style: Styles.of(context).textStyle, 186 | overflow: TextOverflow.ellipsis, 187 | maxLines: 1, 188 | ), 189 | SizedBox( 190 | height: 2, 191 | ), 192 | Text( 193 | bluetoothProvider.bleDevice.id, 194 | style: Styles.of(context) 195 | .textStyle 196 | .copyWith(color: Styles.adaptiveGrayColor, fontSize: 12), 197 | ) 198 | ], 199 | )), 200 | actions: [ 201 | FlatButton( 202 | splashColor: Theme.of(context).splashColor, 203 | highlightColor: Theme.of(context).highlightColor, 204 | child: Text( 205 | 'No', 206 | style: Styles.of(context) 207 | .textStyle 208 | .copyWith(color: Styles.adaptiveRedColor), 209 | ), 210 | onPressed: () { 211 | Navigator.of(context).pop(); 212 | }, 213 | ), 214 | FlatButton( 215 | splashColor: Theme.of(context).splashColor, 216 | highlightColor: Theme.of(context).highlightColor, 217 | child: Text( 218 | 'Yes', 219 | style: TextStyle(color: Styles.adaptiveBlueColor), 220 | ), 221 | onPressed: () { 222 | bluetoothProvider.disconnect(); 223 | Navigator.of(context).pop(); 224 | }, 225 | ), 226 | ], 227 | )); 228 | } 229 | 230 | // iOS Waiting Dialog 231 | cupertinoWaitingDialog(BuildContext context) async { 232 | await showCupertinoDialog( 233 | context: context, 234 | builder: (_) => CupertinoAlertDialog( 235 | title: Text('In progress'), 236 | content: SingleChildScrollView( 237 | child: Text( 238 | 'Device is communicating. Please try again.', 239 | style: Styles.of(context).textStyle, 240 | )), 241 | actions: [ 242 | CupertinoDialogAction( 243 | child: Text('Dismiss'), 244 | onPressed: () { 245 | Navigator.of(context).pop(); 246 | }, 247 | ), 248 | ], 249 | )); 250 | } 251 | 252 | // android Waiting Dialog 253 | androidWaitingDialog(BuildContext context) async { 254 | await showDialog( 255 | context: context, 256 | builder: (_) => AlertDialog( 257 | backgroundColor: Styles.of(context).primaryColor, 258 | title: Text('In progress'), 259 | content: SingleChildScrollView( 260 | child: Text( 261 | 'Device is communicating. Please try again.', 262 | style: Styles.of(context).textStyle, 263 | )), 264 | actions: [ 265 | FlatButton( 266 | splashColor: Theme.of(context).splashColor, 267 | highlightColor: Theme.of(context).highlightColor, 268 | child: Text( 269 | 'Dismiss', 270 | style: TextStyle(color: Styles.adaptiveBlueColor), 271 | ), 272 | onPressed: () { 273 | Navigator.of(context).pop(); 274 | }, 275 | ), 276 | ], 277 | )); 278 | } 279 | -------------------------------------------------------------------------------- /lib/views/connect-vieiw/components/nodevice-component.dart: -------------------------------------------------------------------------------- 1 | import 'package:duino/providers/bluetooth-provider.dart'; 2 | import 'package:duino/styles.dart'; 3 | import 'package:duino/views/connect-vieiw/providers/connect-provider.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | /// Display user feedback of connection discovery. 10 | class NoDeviceComponent extends StatelessWidget { 11 | final String defaultMessage = "Discovered devices will show here."; 12 | final String androidMessage = "Location access is required to initiate scans for Bluetooth devices. Please allow \'Duino\' to access location in your device\'s settings."; 13 | final bool showDefaultMessage; 14 | final bool showAndroidMessage; 15 | 16 | NoDeviceComponent({this.showDefaultMessage, this.showAndroidMessage}); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | String message; 21 | BluetoothState bluetoothState = 22 | Provider.of(context).bluetoothState; 23 | bool isScanning = Provider.of(context).isScanning; 24 | switch (bluetoothState) { 25 | case BluetoothState.UNAUTHORIZED: 26 | message = 27 | 'Bluetooth permission denied. Please go to your device\'s settings and allow \'Duino\' to access Bluetooth.'; 28 | break; 29 | case BluetoothState.UNKNOWN: 30 | message = showDefaultMessage ?? false 31 | ? defaultMessage 32 | : 'No devices discovered. Please make sure Bluetooth is on and you have allowed \'Duino\' to access Bluetooth in your device\'s settings.'; 33 | break; 34 | case BluetoothState.UNSUPPORTED: 35 | message = 'Sorry, your device does not have Bluetooth.'; 36 | break; 37 | case BluetoothState.POWERED_ON: 38 | message = showDefaultMessage ?? false 39 | ? defaultMessage 40 | : 'No devices discovered.'; 41 | break; 42 | case BluetoothState.POWERED_OFF: 43 | message = 'Please turn on your device\'s Bluetooth.'; 44 | break; 45 | case BluetoothState.RESETTING: 46 | message = 'Please turn on your device\'s Bluetooth.'; 47 | break; 48 | default: 49 | message = 50 | 'No devices discovered. Please make sure Bluetooth is on and you have allowed \'Duino\' to access Bluetooth in your device\'s settings.'; 51 | } 52 | if (isScanning) message = 'Scanning...'; 53 | if (showAndroidMessage ?? false) message = androidMessage; 54 | return Container( 55 | padding: EdgeInsets.fromLTRB(16, 16, 16, 16), 56 | child: Text( 57 | message, 58 | style: Styles.of(context).textStyle, 59 | )); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/views/connect-vieiw/components/status-component.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:duino/components/adaptive-components/adaptive-material.dart'; 4 | import 'package:duino/models/bledevice-model.dart'; 5 | import 'package:duino/providers/bluetooth-provider.dart'; 6 | import 'package:duino/styles.dart'; 7 | import 'package:duino/views/connect-vieiw/components/dialog-component.dart'; 8 | import 'package:eva_icons_flutter/eva_icons_flutter.dart'; 9 | import 'package:flutter/cupertino.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 12 | import 'package:provider/provider.dart'; 13 | 14 | // Display current bluetooth/connected device status. 15 | class StatusComponent extends StatelessWidget { 16 | @override 17 | Widget build(BuildContext context) { 18 | BluetoothProvider bluetoothProvider = 19 | Provider.of(context); 20 | final BleDevice bleDevice = bluetoothProvider.bleDevice; 21 | final PeripheralConnectionState bleDeviceState = 22 | bluetoothProvider.bleDeviceState; 23 | final BluetoothState bluetoothState = bluetoothProvider.bluetoothState; 24 | 25 | if ((bluetoothState == BluetoothState.POWERED_ON || 26 | bluetoothState == BluetoothState.UNKNOWN)) { 27 | switch (bleDeviceState) { 28 | case PeripheralConnectionState.disconnected: 29 | return _buildHeader( 30 | context: context, 31 | text: 'No Device Connected', 32 | backgroundColor: Styles.of(context).primaryContrastingColor, 33 | iconData: EvaIcons.minusCircleOutline, 34 | textColor: Styles.of(context).textStyle.color, 35 | ); 36 | case PeripheralConnectionState.connecting: 37 | return _buildHeader( 38 | context: context, 39 | text: 'Connecting to ${bleDevice.name}', 40 | textColor: Styles.adaptiveWhiteColor, 41 | backgroundColor: Styles.adaptiveOrangeColor, 42 | iconData: EvaIcons.activityOutline, 43 | ); 44 | case PeripheralConnectionState.disconnecting: 45 | return _buildHeader( 46 | context: context, 47 | text: 'Disconnecting from ${bleDevice.name}', 48 | textColor: Styles.adaptiveWhiteColor, 49 | backgroundColor: Styles.adaptiveOrangeColor, 50 | iconData: EvaIcons.activityOutline, 51 | ); 52 | case PeripheralConnectionState.connected: 53 | return _buildHeader( 54 | context: context, 55 | text: 'Connected to ${bleDevice.name}', 56 | textColor: Styles.adaptiveWhiteColor, 57 | backgroundColor: Styles.adaptiveGreenColor, 58 | iconData: EvaIcons.checkmarkCircle, 59 | ); 60 | default: 61 | return _buildHeader( 62 | context: context, 63 | text: 'No Device Connected', 64 | backgroundColor: Styles.of(context).primaryContrastingColor, 65 | iconData: EvaIcons.minusCircleOutline, 66 | textColor: Styles.of(context).textStyle.color, 67 | ); 68 | } 69 | } else { 70 | switch (bluetoothState) { 71 | case BluetoothState.UNAUTHORIZED: 72 | return _buildHeader( 73 | context: context, 74 | text: 'Bluetooth Unauthorized', 75 | backgroundColor: Styles.of(context).primaryContrastingColor, 76 | iconData: EvaIcons.minusCircleOutline, 77 | textColor: Styles.of(context).textStyle.color); 78 | case BluetoothState.UNSUPPORTED: 79 | return _buildHeader( 80 | context: context, 81 | text: 'Bluetooth Unavailable', 82 | backgroundColor: Styles.of(context).primaryContrastingColor, 83 | iconData: EvaIcons.minusCircleOutline, 84 | textColor: Styles.of(context).textStyle.color); 85 | default: 86 | return _buildHeader( 87 | context: context, 88 | text: 'Bluetooth Off', 89 | backgroundColor: Styles.of(context).primaryContrastingColor, 90 | iconData: EvaIcons.minusCircleOutline, 91 | textColor: Styles.of(context).textStyle.color); 92 | } 93 | } 94 | } 95 | 96 | /// Builds a persistent header status for bluetooth state and device state. 97 | SliverPersistentHeader _buildHeader( 98 | {BuildContext context, 99 | String text, 100 | Color textColor, 101 | Color backgroundColor, 102 | IconData iconData}) { 103 | if (backgroundColor == Styles.adaptiveGreenColor) { 104 | return SliverPersistentHeader( 105 | pinned: true, 106 | delegate: _SliverPersistentHeaderDelegate( 107 | backgroundColor: backgroundColor, 108 | child: Theme( 109 | data: Theme.of(context).copyWith(splashColor: Colors.transparent), 110 | child: AdaptiveMaterial( 111 | child: InkWell( 112 | onTap: () async { 113 | Platform.isIOS 114 | ? await cupertinoDisconnectDialog(context) 115 | : await androidDisconnectDialog(context); 116 | }, 117 | child: Row( 118 | mainAxisAlignment: MainAxisAlignment.start, 119 | children: [ 120 | SizedBox( 121 | width: 16, 122 | ), 123 | Icon( 124 | iconData, 125 | color: textColor, 126 | ), 127 | SizedBox( 128 | width: 8, 129 | ), 130 | Expanded( 131 | child: Text( 132 | text, 133 | overflow: TextOverflow.ellipsis, 134 | maxLines: 1, 135 | style: Styles.of(context).textStyle.copyWith( 136 | fontSize: 16, 137 | fontWeight: FontWeight.bold, 138 | color: textColor), 139 | ), 140 | ), 141 | SizedBox( 142 | width: 16, 143 | ), 144 | ], 145 | ), 146 | ), 147 | ), 148 | ), 149 | ), 150 | ); 151 | } else { 152 | return SliverPersistentHeader( 153 | pinned: true, 154 | delegate: _SliverPersistentHeaderDelegate( 155 | backgroundColor: backgroundColor, 156 | child: Row( 157 | mainAxisAlignment: MainAxisAlignment.start, 158 | children: [ 159 | SizedBox( 160 | width: 16, 161 | ), 162 | Icon( 163 | iconData, 164 | color: textColor, 165 | ), 166 | SizedBox( 167 | width: 8, 168 | ), 169 | Expanded( 170 | child: Text( 171 | text, 172 | overflow: TextOverflow.ellipsis, 173 | maxLines: 1, 174 | style: Styles.of(context).textStyle.copyWith( 175 | fontSize: 16, 176 | fontWeight: FontWeight.bold, 177 | color: textColor), 178 | ), 179 | ), 180 | SizedBox( 181 | width: 16, 182 | ), 183 | ], 184 | ), 185 | )); 186 | } 187 | } 188 | } 189 | 190 | /// Persistent header layout . 191 | class _SliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate { 192 | final Widget child; 193 | final Color backgroundColor; 194 | 195 | _SliverPersistentHeaderDelegate( 196 | {@required this.child, @required this.backgroundColor}); 197 | 198 | @override 199 | double get minExtent => 36; 200 | @override 201 | double get maxExtent => 36; 202 | 203 | @override 204 | Widget build( 205 | BuildContext context, double shrinkOffset, bool overlapsContent) { 206 | return Container(height: 36, color: backgroundColor, child: child); 207 | } 208 | 209 | @override 210 | bool shouldRebuild(_SliverPersistentHeaderDelegate oldDelegate) { 211 | return true; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /lib/views/connect-vieiw/connect-view.dart: -------------------------------------------------------------------------------- 1 | import 'package:duino/components/adaptive-components/adaptive-activityindicator.dart'; 2 | import 'package:duino/components/adaptive-components/adaptive-iconbutton.dart'; 3 | import 'package:duino/components/adaptive-components/adaptive-navbar.dart'; 4 | import 'package:duino/components/adaptive-components/adaptive-scaffold.dart'; 5 | import 'package:duino/providers/bluetooth-provider.dart'; 6 | import 'package:duino/styles.dart'; 7 | import 'package:duino/views/connect-vieiw/components/nodevice-component.dart'; 8 | import 'package:duino/views/connect-vieiw/components/status-component.dart'; 9 | import 'package:duino/views/connect-vieiw/providers/connect-provider.dart'; 10 | import 'package:flutter/cupertino.dart'; 11 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 12 | import 'package:provider/provider.dart'; 13 | 14 | /// Connect screen. 15 | class ConnectView extends StatelessWidget { 16 | @override 17 | Widget build(BuildContext context) { 18 | ConnectProvider _connectProvider = Provider.of(context); 19 | return AdaptiveScaffold( 20 | navBar: AdaptiveNavBar( 21 | backgroundColor: Styles.of(context).barBackgroundColor, 22 | middle: Text( 23 | 'Connect', 24 | style: Styles.of(context).navTitleTextStyle, 25 | ), 26 | leading: AdaptiveIconButton( 27 | child: Padding( 28 | padding: const EdgeInsets.all(8.0), 29 | child: Icon( 30 | CupertinoIcons.back, 31 | color: Styles.of(context).textStyle.color, 32 | ), 33 | ), 34 | onPressed: () { 35 | Navigator.of(context).pop(); 36 | }, 37 | ), 38 | ), 39 | backgroundColor: Styles.of(context).scaffoldBackgroundColor, 40 | child: CustomScrollView( 41 | physics: AlwaysScrollableScrollPhysics(), 42 | cacheExtent: MediaQuery.of(context).size.height, 43 | slivers: [ 44 | StatusComponent(), 45 | SliverToBoxAdapter( 46 | child: Padding( 47 | padding: const EdgeInsets.fromLTRB(16, 16, 0, 0), 48 | child: Row( 49 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 50 | children: [ 51 | Text( 52 | 'Devices', 53 | style: Styles.of(context).textStyle.copyWith( 54 | fontSize: 24, fontWeight: FontWeight.bold), 55 | ), 56 | Container( 57 | height: 28, 58 | padding: EdgeInsets.only(), 59 | child: CupertinoButton( 60 | padding: EdgeInsets.fromLTRB(8, 0, 16, 0), 61 | onPressed: () async { 62 | await _connectProvider.startScan( 63 | Provider.of(context, 64 | listen: false)); 65 | }, 66 | child: AnimatedSwitcher( 67 | duration: Duration(milliseconds: 500), 68 | child: Align( 69 | alignment: Alignment.centerRight, 70 | child: _connectProvider.isScanning 71 | ? AdaptiveActivityIndicator( 72 | color: Styles.of(context) 73 | .textStyle 74 | .color, 75 | ) 76 | : Text( 77 | 'Scan', 78 | overflow: TextOverflow.ellipsis, 79 | maxLines: 1, 80 | style: Styles.of(context) 81 | .textStyle 82 | .copyWith( 83 | fontSize: 16, 84 | color: 85 | Styles.adaptiveBlueColor), 86 | ), 87 | ))), 88 | ) 89 | ], 90 | ), 91 | ), 92 | ), 93 | Consumer(builder: (_, bluetoothProvider, ___) { 94 | if (bluetoothProvider.bluetoothState == 95 | BluetoothState.POWERED_ON || 96 | bluetoothProvider.bluetoothState == 97 | BluetoothState.UNKNOWN) { 98 | } else { 99 | _connectProvider.widgets = [NoDeviceComponent()]; 100 | } 101 | return SliverList( 102 | delegate: SliverChildBuilderDelegate( 103 | (context, index) => _connectProvider.widgets[index], 104 | childCount: _connectProvider.widgets.length), 105 | ); 106 | }) 107 | ])); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/views/connect-vieiw/providers/connect-provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:device_info/device_info.dart'; 5 | import 'package:duino/models/bledevice-model.dart'; 6 | import 'package:duino/providers/bluetooth-provider.dart'; 7 | import 'package:duino/views/connect-vieiw/components/device-component.dart'; 8 | import 'package:duino/views/connect-vieiw/components/nodevice-component.dart'; 9 | import 'package:flutter/cupertino.dart'; 10 | import 'package:flutter_ble_lib/flutter_ble_lib.dart'; 11 | import 'package:permission_handler/permission_handler.dart'; 12 | 13 | class ConnectProvider extends ChangeNotifier { 14 | StreamSubscription scanSubscription; 15 | StreamSubscription scanningSubscription; 16 | bool isScanning = false; 17 | bool _mounted = true; 18 | BleManager bleManager; 19 | List widgets = [ 20 | NoDeviceComponent( 21 | showDefaultMessage: true, 22 | ) 23 | ]; 24 | 25 | /// Scan for bluetooth devices. 26 | Future startScan(BluetoothProvider bluetoothProvider) async { 27 | bleManager = bluetoothProvider.bleManager; 28 | if (!isScanning && 29 | (bluetoothProvider.bluetoothState == BluetoothState.POWERED_ON || 30 | bluetoothProvider.bluetoothState == BluetoothState.UNKNOWN)) { 31 | try { 32 | isScanning = true; 33 | widgets.clear(); 34 | widgets = [NoDeviceComponent()]; 35 | notify(); 36 | List bleDevices = []; 37 | await _checkPermissions(); 38 | scanSubscription = bluetoothProvider.bleManager 39 | .startPeripheralScan() 40 | .listen((scanResult) { 41 | BleDevice bleDevice = BleDevice(scanResult); 42 | if (scanResult.advertisementData.localName != null && 43 | !bleDevices.contains(bleDevice)) { 44 | if (bleDevices.isEmpty) widgets.clear(); 45 | bleDevices.add(bleDevice); 46 | widgets.add(DeviceComponent(bleDevice: bleDevice)); 47 | notify(); 48 | } 49 | }, onError: (e) async { 50 | print(e); 51 | await _cancelScan(); 52 | }); 53 | 54 | scanningSubscription = Future.delayed(Duration(seconds: 8), () async { 55 | await _cancelScan(); 56 | if (bleDevices.isEmpty) widgets = [NoDeviceComponent()]; 57 | isScanning = false; 58 | notify(); 59 | }).asStream().listen((onData) {}); 60 | } catch (e) { 61 | _cancelScan(); 62 | widgets = [ 63 | NoDeviceComponent( 64 | showAndroidMessage: true, 65 | ) 66 | ]; 67 | isScanning = false; 68 | notify(); 69 | } 70 | } else if (!isScanning && 71 | bluetoothProvider.bluetoothState != BluetoothState.POWERED_ON && 72 | bluetoothProvider.bluetoothState != BluetoothState.UNKNOWN) { 73 | widgets = [NoDeviceComponent()]; 74 | isScanning = false; 75 | notify(); 76 | } 77 | } 78 | 79 | /// Cancel scan and clean up. 80 | Future _cancelScan() async { 81 | try { 82 | if (scanSubscription != null) await scanSubscription.cancel(); 83 | if (bleManager != null) await bleManager.stopPeripheralScan(); 84 | if (scanningSubscription != null) await scanningSubscription.cancel(); 85 | } catch (e) { 86 | print(e); 87 | } 88 | } 89 | 90 | /// Android only. Based on documentation. 91 | /// We can only check runtime permissions on Android 6.0 and later 92 | Future _checkPermissions() async { 93 | if (Platform.isAndroid) { 94 | AndroidDeviceInfo androidInfo = 95 | await DeviceInfoPlugin().androidInfo.catchError((e) => print(e)); 96 | if (androidInfo != null && androidInfo.version.sdkInt < 23) { 97 | return; 98 | } 99 | if (!await Permission.location.request().isGranted) { 100 | return Future.error(Exception("Location permission not granted")); 101 | } 102 | } 103 | } 104 | 105 | /// Notify listeners based on state. 106 | void notify() => _mounted ? notifyListeners() : null; 107 | 108 | @override 109 | void dispose() { 110 | _mounted = false; 111 | _cancelScan(); 112 | super.dispose(); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lib/views/home-view/components/action-component.dart: -------------------------------------------------------------------------------- 1 | import 'package:duino/components/adaptive-components/adaptive-theme.dart'; 2 | import 'package:duino/styles.dart'; 3 | import 'package:eva_icons_flutter/eva_icons_flutter.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | /// Individual action. 8 | class ActionComponent extends StatelessWidget { 9 | final String title; 10 | final String subtitle; 11 | final String image; 12 | final VoidCallback onPressed; 13 | 14 | ActionComponent( 15 | {@required this.title, 16 | @required this.subtitle, 17 | @required this.image, 18 | @required this.onPressed}); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Padding( 23 | padding: const EdgeInsets.only(bottom: 8), 24 | child: AdaptiveTheme( 25 | themeData: Theme.of(context).copyWith(splashColor: Colors.transparent), 26 | child: Card( 27 | clipBehavior: Clip.antiAlias, 28 | color: Styles.of(context).primaryColor, 29 | shape: RoundedRectangleBorder( 30 | borderRadius: BorderRadius.circular(24), 31 | ), 32 | child: InkWell( 33 | onTap: onPressed, 34 | child: Container( 35 | height: 96, 36 | child: Row( 37 | children: [ 38 | Container( 39 | padding: EdgeInsets.all(16), 40 | child: Center( 41 | child: Image.asset( 42 | image, 43 | filterQuality: FilterQuality.high, 44 | height: 64, 45 | width: 64, 46 | ), 47 | ), 48 | ), 49 | Expanded( 50 | child: Column( 51 | mainAxisAlignment: MainAxisAlignment.center, 52 | crossAxisAlignment: CrossAxisAlignment.start, 53 | children: [ 54 | Text(title, 55 | style: Styles.of(context).textStyle.copyWith( 56 | fontSize: 20, fontWeight: FontWeight.bold)), 57 | Text( 58 | subtitle, 59 | style: Styles.of(context) 60 | .textStyle 61 | .copyWith(fontSize: 16), 62 | maxLines: 1, 63 | overflow: TextOverflow.ellipsis, 64 | ) 65 | ]), 66 | ), 67 | Padding( 68 | padding: const EdgeInsets.all(8.0), 69 | child: Icon( 70 | EvaIcons.chevronRight, 71 | color: Styles.of(context).textStyle.color, 72 | ), 73 | ) 74 | ], 75 | ), 76 | ), 77 | ), 78 | ), 79 | ), 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/views/home-view/home-view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:duino/components/adaptive-components/adaptive-customscrollview.dart'; 4 | import 'package:duino/components/adaptive-components/adaptive-navbar.dart'; 5 | import 'package:duino/components/adaptive-components/adaptive-scaffold.dart'; 6 | import 'package:duino/components/state-component.dart'; 7 | import 'package:duino/styles.dart'; 8 | import 'package:duino/views/home-view/components/action-component.dart'; 9 | import 'package:flutter/cupertino.dart'; 10 | import 'package:flutter/material.dart'; 11 | 12 | /// The main view listing of all actions. 13 | class HomeView extends StatelessWidget { 14 | @override 15 | Widget build(BuildContext context) { 16 | return AdaptiveScaffold( 17 | backgroundColor: Styles.of(context).scaffoldBackgroundColor, 18 | child: AdaptiveCustomScrollView( 19 | navBar: AdaptiveNavBar( 20 | backgroundColor: Styles.of(context).barBackgroundColor, 21 | largeTitle: Text( 22 | 'Duino', 23 | style: Styles.of(context).navLargeTitleTextStyle, 24 | ), 25 | trailing: StateComponent()), 26 | child: SliverPadding( 27 | padding: EdgeInsets.only(left: 16, right: 16), 28 | sliver: SliverList( 29 | delegate: SliverChildListDelegate.fixed([ 30 | if (Platform.isAndroid) 31 | SizedBox( 32 | height: 16, 33 | ), 34 | ActionComponent( 35 | title: 'Connect', 36 | subtitle: 'Pair a bluetooth module', 37 | image: 'assets/bluetooth.png', 38 | onPressed: () { 39 | Navigator.of(context).pushNamed('/ConnectView', 40 | arguments: {'ANIM': 'PLATFORM-D', 'DATA': {}}); 41 | }, 42 | ), 43 | ActionComponent( 44 | title: 'Remote', 45 | subtitle: 'Keypad and D-pad', 46 | image: 'assets/remote.png', 47 | onPressed: () { 48 | Navigator.of(context).pushNamed('/RemoteView', 49 | arguments: {'ANIM': 'PLATFORM-D', 'DATA': {}}); 50 | }, 51 | ), 52 | ActionComponent( 53 | title: 'Joystick', 54 | subtitle: 'Virtual joystick', 55 | image: 'assets/joystick.png', 56 | onPressed: () { 57 | Navigator.of(context).pushNamed('/JoystickView', 58 | arguments: {'ANIM': 'PLATFORM-D', 'DATA': {}}); 59 | }, 60 | ), 61 | ActionComponent( 62 | title: 'Tilt Pad', 63 | subtitle: 'Use device sensor', 64 | image: 'assets/gyroscope.png', 65 | onPressed: () { 66 | Navigator.of(context).pushNamed('/TiltView', 67 | arguments: {'ANIM': 'PLATFORM-D', 'DATA': {}}); 68 | }, 69 | ), 70 | ActionComponent( 71 | title: 'About', 72 | subtitle: 'Learn more', 73 | image: 'assets/about.png', 74 | onPressed: () { 75 | Navigator.of(context).pushNamed('/AboutView', 76 | arguments: {'ANIM': 'PLATFORM-D', 'DATA': {}}); 77 | }, 78 | ) 79 | ])), 80 | )), 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/views/joystick-view/joystick-view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:duino/components/adaptive-components/adaptive-iconbutton.dart'; 4 | import 'package:duino/components/adaptive-components/adaptive-navbar.dart'; 5 | import 'package:duino/components/adaptive-components/adaptive-scaffold.dart'; 6 | import 'package:duino/components/joystick-component.dart/joystick-component.dart'; 7 | import 'package:duino/components/state-component.dart'; 8 | import 'package:duino/components/util-components/math-util.dart'; 9 | import 'package:duino/providers/bluetooth-provider.dart'; 10 | import 'package:duino/styles.dart'; 11 | import 'package:flutter/cupertino.dart'; 12 | import 'package:provider/provider.dart'; 13 | 14 | class JoystickView extends StatelessWidget { 15 | @override 16 | Widget build(BuildContext context) { 17 | BluetoothProvider bluetoothProvider = 18 | Provider.of(context, listen: false); 19 | return AdaptiveScaffold( 20 | navBar: AdaptiveNavBar( 21 | backgroundColor: Styles.of(context).barBackgroundColor, 22 | leading: AdaptiveIconButton( 23 | child: Padding( 24 | padding: const EdgeInsets.all(8.0), 25 | child: Icon( 26 | CupertinoIcons.back, 27 | color: Styles.of(context).textStyle.color, 28 | ), 29 | ), 30 | onPressed: () { 31 | Navigator.of(context).pop(); 32 | }, 33 | ), 34 | middle: Text( 35 | 'Joystick', 36 | style: Styles.of(context).navTitleTextStyle, 37 | ), 38 | trailing: Padding( 39 | padding: Platform.isIOS 40 | ? const EdgeInsets.fromLTRB(0, 0, 16, 0) 41 | : EdgeInsets.only(), 42 | child: StateComponent(), 43 | ), 44 | ), 45 | child: Padding( 46 | padding: const EdgeInsets.fromLTRB(16, 16, 16, 16), 47 | child: JoystickComponent( 48 | interval: Duration(milliseconds: 75), 49 | onDirectionChanged: (double degrees, double distance) { 50 | String de; 51 | String di; 52 | de = degrees 53 | .round() 54 | .toStringAsFixed(0) 55 | .padLeft(2, "00") 56 | .padLeft(3, "0"); 57 | di = MathUtil.map((distance * 1000), 0, 1000, 0, 255) 58 | .toStringAsFixed(0) 59 | .padLeft(2, "00") 60 | .padLeft(3, "0"); 61 | bluetoothProvider.write('$de$di#'); 62 | }, 63 | showArrows: false, 64 | backgroundColor: Styles.of(context).primaryContrastingColor, 65 | innerCircleColor: Styles.of(context).primaryColor, 66 | ), 67 | )); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/views/remote-view/components/button-component.dart: -------------------------------------------------------------------------------- 1 | import 'package:duino/components/adaptive-components/adaptive-material.dart'; 2 | import 'package:duino/providers/bluetooth-provider.dart'; 3 | import 'package:duino/styles.dart'; 4 | import 'package:duino/views/remote-view/providers/remote-provider.dart'; 5 | import 'package:eva_icons_flutter/eva_icons_flutter.dart'; 6 | import 'package:flutter/cupertino.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | class ButtonComponent extends StatelessWidget { 11 | final String number; 12 | ButtonComponent({@required this.number}); 13 | 14 | bool isVisible(RemoteProvider remoteProvider, String number) { 15 | return (remoteProvider.groupValue == 1 && 16 | ['2', '4', '6', '8'].contains(number)) || 17 | remoteProvider.groupValue == 0; 18 | } 19 | 20 | IconData buildIcon(number) { 21 | switch (number) { 22 | case '2': 23 | return EvaIcons.chevronUpOutline; 24 | case '4': 25 | return EvaIcons.chevronLeftOutline; 26 | case '6': 27 | return EvaIcons.chevronRightOutline; 28 | case '8': 29 | return EvaIcons.chevronDownOutline; 30 | default: 31 | return null; 32 | } 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | BluetoothProvider bluetoothProvider = 38 | Provider.of(context, listen: false); 39 | RemoteProvider remoteProvider = Provider.of(context); 40 | bool visible = isVisible(remoteProvider, number); 41 | return AnimatedSwitcher( 42 | transitionBuilder: (Widget child, Animation animation) { 43 | return ScaleTransition(child: child, scale: animation); 44 | }, 45 | duration: Duration(milliseconds: 125), 46 | child: visible 47 | ? Container( 48 | height: 72, 49 | width: 72, 50 | decoration: BoxDecoration( 51 | shape: BoxShape.circle, 52 | color: Styles.of(context).primaryContrastingColor), 53 | child: Theme( 54 | data: Theme.of(context) 55 | .copyWith(splashColor: Colors.transparent), 56 | child: ClipOval( 57 | child: AdaptiveMaterial( 58 | child: InkWell( 59 | onHighlightChanged: 60 | visible && remoteProvider.groupValue == 1 61 | ? (bool focus) { 62 | if (focus) { 63 | String direction = number; 64 | switch (direction) { 65 | case '2': 66 | direction = 'N'; 67 | break; 68 | case '4': 69 | direction = 'W'; 70 | break; 71 | case '6': 72 | direction = 'E'; 73 | break; 74 | case '8': 75 | direction = 'S'; 76 | break; 77 | 78 | default: 79 | direction = ''; 80 | break; 81 | } 82 | bluetoothProvider.write(direction); 83 | } else { 84 | bluetoothProvider.write("#"); 85 | } 86 | } 87 | : null, 88 | onTap: remoteProvider.groupValue == 0 89 | ? () { 90 | bluetoothProvider.write(number + "#"); 91 | } 92 | : visible ? () {} : null, 93 | child: Center( 94 | child: AnimatedSwitcher( 95 | transitionBuilder: 96 | (Widget child, Animation animation) { 97 | return ScaleTransition( 98 | child: child, scale: animation); 99 | }, 100 | child: remoteProvider.groupValue == 1 101 | ? Icon( 102 | buildIcon(number), 103 | size: 64, 104 | color: Styles.of(context).textStyle.color, 105 | ) 106 | : Text(number, 107 | style: Styles.of(context) 108 | .textStyle 109 | .copyWith( 110 | fontSize: 24, 111 | fontWeight: FontWeight.bold)), 112 | duration: Duration(milliseconds: 250), 113 | ), 114 | ), 115 | ), 116 | ), 117 | )), 118 | ) 119 | : SizedBox( 120 | width: 72, 121 | height: 72, 122 | ), 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/views/remote-view/providers/remote-provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class RemoteProvider with ChangeNotifier { 4 | int groupValue = 0; 5 | 6 | void updateGroupValue(int value) { 7 | groupValue = value; 8 | notifyListeners(); 9 | } 10 | } -------------------------------------------------------------------------------- /lib/views/remote-view/remote-view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:duino/components/adaptive-components/adaptive-iconbutton.dart'; 4 | import 'package:duino/components/adaptive-components/adaptive-navbar.dart'; 5 | import 'package:duino/components/adaptive-components/adaptive-scaffold.dart'; 6 | import 'package:duino/components/adaptive-components/adaptive-widget.dart'; 7 | import 'package:duino/components/state-component.dart'; 8 | import 'package:duino/styles.dart'; 9 | import 'package:duino/views/remote-view/components/button-component.dart'; 10 | import 'package:duino/views/remote-view/providers/remote-provider.dart'; 11 | import 'package:flutter/cupertino.dart'; 12 | import 'package:flutter/material.dart'; 13 | import 'package:provider/provider.dart'; 14 | 15 | class RemoteView extends StatelessWidget { 16 | @override 17 | Widget build(BuildContext context) { 18 | return AdaptiveScaffold( 19 | navBar: AdaptiveNavBar( 20 | elevation: 0, 21 | leading: AdaptiveIconButton( 22 | child: Padding( 23 | padding: const EdgeInsets.all(8.0), 24 | child: Icon( 25 | CupertinoIcons.back, 26 | color: Styles.of(context).textStyle.color, 27 | ), 28 | ), 29 | onPressed: () { 30 | Navigator.of(context).pop(); 31 | }, 32 | ), 33 | backgroundColor: Styles.of(context).barBackgroundColor, 34 | middle: Text( 35 | 'Remote', 36 | style: Styles.of(context).navTitleTextStyle, 37 | ), 38 | trailing: Padding( 39 | padding: Platform.isIOS 40 | ? const EdgeInsets.fromLTRB(0, 0, 16, 0) 41 | : EdgeInsets.only(), 42 | child: StateComponent(), 43 | ), 44 | ), 45 | child: Column( 46 | crossAxisAlignment: CrossAxisAlignment.stretch, 47 | children: [ 48 | Consumer( 49 | builder: (_, remoteProvider, __) => AdaptiveWidget( 50 | iOS: Padding( 51 | padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), 52 | child: CupertinoSlidingSegmentedControl( 53 | children: { 54 | 0: Text( 55 | 'Keypad', 56 | style: TextStyle( 57 | color: Styles.of(context).textStyle.color), 58 | ), 59 | 1: Text('D-pad', 60 | style: TextStyle( 61 | color: Styles.of(context).textStyle.color)) 62 | }, 63 | onValueChanged: (int value) { 64 | remoteProvider.updateGroupValue(value); 65 | }, 66 | groupValue: remoteProvider.groupValue, 67 | ), 68 | ), 69 | android: DefaultTabController( 70 | length: 2, 71 | child: TabBar( 72 | indicatorColor: Styles.of(context).textStyle.color, 73 | onTap: (int value) { 74 | remoteProvider.updateGroupValue(value); 75 | }, 76 | tabs: [ 77 | Padding( 78 | padding: EdgeInsets.all(8), 79 | child: Text( 80 | 'Keypad', 81 | style: TextStyle( 82 | color: Styles.of(context).textStyle.color), 83 | ), 84 | ), 85 | Padding( 86 | padding: const EdgeInsets.all(8.0), 87 | child: Text('D-pad', 88 | style: TextStyle( 89 | color: Styles.of(context).textStyle.color)), 90 | ) 91 | ], 92 | ), 93 | ), 94 | )), 95 | Expanded( 96 | child: Padding( 97 | padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), 98 | child: Column( 99 | mainAxisAlignment: MainAxisAlignment.center, 100 | children: [ 101 | Row( 102 | crossAxisAlignment: CrossAxisAlignment.center, 103 | mainAxisAlignment: MainAxisAlignment.center, 104 | children: [ 105 | ButtonComponent( 106 | number: '1', 107 | ), 108 | SizedBox( 109 | width: 24, 110 | ), 111 | ButtonComponent( 112 | number: '2', 113 | ), 114 | SizedBox( 115 | width: 24, 116 | ), 117 | ButtonComponent( 118 | number: '3', 119 | ) 120 | ], 121 | ), 122 | SizedBox( 123 | height: 16, 124 | ), 125 | Row( 126 | crossAxisAlignment: CrossAxisAlignment.center, 127 | mainAxisAlignment: MainAxisAlignment.center, 128 | children: [ 129 | ButtonComponent( 130 | number: '4', 131 | ), 132 | SizedBox( 133 | width: 24, 134 | ), 135 | ButtonComponent( 136 | number: '5', 137 | ), 138 | SizedBox( 139 | width: 24, 140 | ), 141 | ButtonComponent( 142 | number: '6', 143 | ) 144 | ], 145 | ), 146 | SizedBox( 147 | height: 16, 148 | ), 149 | Row( 150 | crossAxisAlignment: CrossAxisAlignment.center, 151 | mainAxisAlignment: MainAxisAlignment.center, 152 | children: [ 153 | ButtonComponent( 154 | number: '7', 155 | ), 156 | SizedBox( 157 | width: 24, 158 | ), 159 | ButtonComponent( 160 | number: '8', 161 | ), 162 | SizedBox( 163 | width: 24, 164 | ), 165 | ButtonComponent( 166 | number: '9', 167 | ) 168 | ], 169 | ), 170 | SizedBox( 171 | height: 16, 172 | ), 173 | Row( 174 | crossAxisAlignment: CrossAxisAlignment.center, 175 | mainAxisAlignment: MainAxisAlignment.center, 176 | children: [ 177 | ButtonComponent( 178 | number: '0', 179 | ), 180 | ], 181 | ) 182 | ]), 183 | ), 184 | ), 185 | ], 186 | ), 187 | ); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /lib/views/tilt-view/components/ring-component.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:math'; 3 | 4 | import 'package:duino/providers/bluetooth-provider.dart'; 5 | import 'package:duino/styles.dart'; 6 | import 'package:flutter/cupertino.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:provider/provider.dart'; 9 | import 'package:sensors/sensors.dart'; 10 | 11 | class RingComponent extends StatefulWidget { 12 | @override 13 | _RingComponentState createState() => _RingComponentState(); 14 | } 15 | 16 | class _RingComponentState extends State { 17 | DateTime callbackTimestamp = DateTime.now(); 18 | Duration interval = Duration(milliseconds: 75); 19 | 20 | AccelerometerEvent accelerometerEvent; 21 | StreamSubscription accelerometerStream; 22 | double roll = 0; 23 | double pitch = 0; 24 | 25 | String normalize(int value) { 26 | if (value < 0) { 27 | String tmp = value 28 | .toString() 29 | .substring(1) 30 | .padLeft(2, "00") 31 | .padLeft(3, "0") 32 | .padLeft(4, "-"); 33 | return tmp; 34 | } else { 35 | return value 36 | .toString() 37 | .padLeft(2, "000") 38 | .padLeft(3, "00") 39 | .padLeft(4, "0"); 40 | } 41 | } 42 | 43 | bool _canWriteToDevice() { 44 | int intervalMilliseconds = interval.inMilliseconds; 45 | int timestampMilliseconds = callbackTimestamp.millisecondsSinceEpoch; 46 | int currentTimeMilliseconds = DateTime.now().millisecondsSinceEpoch; 47 | 48 | if (currentTimeMilliseconds - timestampMilliseconds <= 49 | intervalMilliseconds) { 50 | return false; 51 | } 52 | return true; 53 | } 54 | 55 | @override 56 | void initState() { 57 | super.initState(); 58 | BluetoothProvider bluetoothProvider = 59 | Provider.of(context, listen: false); 60 | accelerometerStream = 61 | accelerometerEvents.listen((AccelerometerEvent event) { 62 | double tmpRoll = (atan2(event.y, event.z) * 180 / pi); 63 | double tmpPitch = 64 | (atan2(-event.x, sqrt(event.y * event.y + event.z * event.z)) * 65 | 180 / 66 | pi); 67 | if (roll != tmpRoll || pitch != tmpPitch) { 68 | setState(() { 69 | roll = tmpRoll; 70 | pitch = tmpPitch; 71 | 72 | String r = normalize(roll.round()); 73 | String p = normalize(pitch.round()); 74 | 75 | if (_canWriteToDevice()) { 76 | bluetoothProvider.write('$r$p#'); 77 | callbackTimestamp = DateTime.now(); 78 | } 79 | }); 80 | } 81 | }); 82 | } 83 | 84 | @override 85 | void dispose() { 86 | accelerometerStream.cancel(); 87 | super.dispose(); 88 | } 89 | 90 | @override 91 | Widget build(BuildContext context) { 92 | return Center( 93 | child: Transform( 94 | alignment: Alignment.center, 95 | transform: Matrix4.identity() 96 | ..setEntry(3, 2, 0.001) 97 | ..rotateX(roll * (pi / 180)) 98 | ..rotateY(pitch * 2 * (pi / 180)), 99 | child: Container( 100 | width: MediaQuery.of(context).size.width * .75, 101 | height: MediaQuery.of(context).size.width * .75, 102 | decoration: BoxDecoration( 103 | shape: BoxShape.circle, 104 | gradient: LinearGradient( 105 | begin: Alignment.topCenter, 106 | end: Alignment.bottomCenter, 107 | colors: [ 108 | Color(0xFF9B32FE), 109 | Color(0xFF5B6BEB), 110 | Color(0xFF0FAED4) 111 | ])), 112 | child: Center( 113 | child: Container( 114 | decoration: BoxDecoration( 115 | shape: BoxShape.circle, 116 | color: Styles.of(context).barBackgroundColor, 117 | ), 118 | height: MediaQuery.of(context).size.width * .65, 119 | width: MediaQuery.of(context).size.width * .65, 120 | )), 121 | ), 122 | ), 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/views/tilt-view/tilt-view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:duino/components/adaptive-components/adaptive-iconbutton.dart'; 4 | import 'package:duino/components/adaptive-components/adaptive-navbar.dart'; 5 | import 'package:duino/components/adaptive-components/adaptive-scaffold.dart'; 6 | import 'package:duino/components/state-component.dart'; 7 | import 'package:duino/styles.dart'; 8 | import 'package:duino/views/tilt-view/components/ring-component.dart'; 9 | import 'package:flutter/cupertino.dart'; 10 | 11 | class TiltView extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) { 14 | return AdaptiveScaffold( 15 | navBar: AdaptiveNavBar( 16 | backgroundColor: Styles.of(context).barBackgroundColor, 17 | leading: AdaptiveIconButton( 18 | child: Padding( 19 | padding: const EdgeInsets.all(8.0), 20 | child: Icon( 21 | CupertinoIcons.back, 22 | color: Styles.of(context).textStyle.color, 23 | ), 24 | ), 25 | onPressed: () { 26 | Navigator.of(context).pop(); 27 | }, 28 | ), 29 | middle: Text( 30 | 'Tilt Pad', 31 | style: Styles.of(context).navTitleTextStyle, 32 | ), 33 | trailing: Padding( 34 | padding: Platform.isIOS 35 | ? const EdgeInsets.fromLTRB(0, 0, 16, 0) 36 | : EdgeInsets.only(), 37 | child: StateComponent(), 38 | ), 39 | ), 40 | child: RingComponent()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.13" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.6.0" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.4.1" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.0.0" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.3" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.12" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.1.4" 60 | cupertino_icons: 61 | dependency: "direct main" 62 | description: 63 | name: cupertino_icons 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.1.3" 67 | device_info: 68 | dependency: "direct main" 69 | description: 70 | name: device_info 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.4.2+3" 74 | eva_icons_flutter: 75 | dependency: "direct main" 76 | description: 77 | name: eva_icons_flutter 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "2.0.0" 81 | flutter: 82 | dependency: "direct main" 83 | description: flutter 84 | source: sdk 85 | version: "0.0.0" 86 | flutter_ble_lib: 87 | dependency: "direct main" 88 | description: 89 | name: flutter_ble_lib 90 | url: "https://pub.dartlang.org" 91 | source: hosted 92 | version: "2.2.4" 93 | flutter_test: 94 | dependency: "direct dev" 95 | description: flutter 96 | source: sdk 97 | version: "0.0.0" 98 | flutter_web_plugins: 99 | dependency: transitive 100 | description: flutter 101 | source: sdk 102 | version: "0.0.0" 103 | image: 104 | dependency: transitive 105 | description: 106 | name: image 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "2.1.12" 110 | js: 111 | dependency: transitive 112 | description: 113 | name: js 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "0.6.1+1" 117 | matcher: 118 | dependency: transitive 119 | description: 120 | name: matcher 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "0.12.6" 124 | meta: 125 | dependency: transitive 126 | description: 127 | name: meta 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.1.8" 131 | nested: 132 | dependency: transitive 133 | description: 134 | name: nested 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "0.0.4" 138 | path: 139 | dependency: transitive 140 | description: 141 | name: path 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "1.6.4" 145 | permission_handler: 146 | dependency: "direct main" 147 | description: 148 | name: permission_handler 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "5.0.0+hotfix.5" 152 | permission_handler_platform_interface: 153 | dependency: transitive 154 | description: 155 | name: permission_handler_platform_interface 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "2.0.0" 159 | petitparser: 160 | dependency: transitive 161 | description: 162 | name: petitparser 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "2.4.0" 166 | plugin_platform_interface: 167 | dependency: transitive 168 | description: 169 | name: plugin_platform_interface 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "1.0.2" 173 | provider: 174 | dependency: "direct main" 175 | description: 176 | name: provider 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "4.0.5+1" 180 | quiver: 181 | dependency: transitive 182 | description: 183 | name: quiver 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "2.1.3" 187 | sensors: 188 | dependency: "direct main" 189 | description: 190 | name: sensors 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "0.4.2" 194 | sky_engine: 195 | dependency: transitive 196 | description: flutter 197 | source: sdk 198 | version: "0.0.99" 199 | source_span: 200 | dependency: transitive 201 | description: 202 | name: source_span 203 | url: "https://pub.dartlang.org" 204 | source: hosted 205 | version: "1.7.0" 206 | stack_trace: 207 | dependency: transitive 208 | description: 209 | name: stack_trace 210 | url: "https://pub.dartlang.org" 211 | source: hosted 212 | version: "1.9.3" 213 | stream_channel: 214 | dependency: transitive 215 | description: 216 | name: stream_channel 217 | url: "https://pub.dartlang.org" 218 | source: hosted 219 | version: "2.0.0" 220 | string_scanner: 221 | dependency: transitive 222 | description: 223 | name: string_scanner 224 | url: "https://pub.dartlang.org" 225 | source: hosted 226 | version: "1.0.5" 227 | term_glyph: 228 | dependency: transitive 229 | description: 230 | name: term_glyph 231 | url: "https://pub.dartlang.org" 232 | source: hosted 233 | version: "1.1.0" 234 | test_api: 235 | dependency: transitive 236 | description: 237 | name: test_api 238 | url: "https://pub.dartlang.org" 239 | source: hosted 240 | version: "0.2.15" 241 | typed_data: 242 | dependency: transitive 243 | description: 244 | name: typed_data 245 | url: "https://pub.dartlang.org" 246 | source: hosted 247 | version: "1.1.6" 248 | url_launcher: 249 | dependency: "direct main" 250 | description: 251 | name: url_launcher 252 | url: "https://pub.dartlang.org" 253 | source: hosted 254 | version: "5.4.5" 255 | url_launcher_macos: 256 | dependency: transitive 257 | description: 258 | name: url_launcher_macos 259 | url: "https://pub.dartlang.org" 260 | source: hosted 261 | version: "0.0.1+5" 262 | url_launcher_platform_interface: 263 | dependency: transitive 264 | description: 265 | name: url_launcher_platform_interface 266 | url: "https://pub.dartlang.org" 267 | source: hosted 268 | version: "1.0.6" 269 | url_launcher_web: 270 | dependency: transitive 271 | description: 272 | name: url_launcher_web 273 | url: "https://pub.dartlang.org" 274 | source: hosted 275 | version: "0.1.1+2" 276 | vector_math: 277 | dependency: transitive 278 | description: 279 | name: vector_math 280 | url: "https://pub.dartlang.org" 281 | source: hosted 282 | version: "2.0.8" 283 | xml: 284 | dependency: transitive 285 | description: 286 | name: xml 287 | url: "https://pub.dartlang.org" 288 | source: hosted 289 | version: "3.6.1" 290 | sdks: 291 | dart: ">=2.7.0 <3.0.0" 292 | flutter: ">=1.12.13+hotfix.5 <2.0.0" 293 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: duino 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 0.0.4+10 15 | 16 | environment: 17 | sdk: ">=2.6.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^0.1.2 26 | 27 | # Using the latest until https://github.com/Polidea/FlutterBleLib/issues/364 pushed to release 28 | flutter_ble_lib: ^2.2.4 29 | provider: ^4.0.5 30 | eva_icons_flutter: ^2.0.0 31 | sensors: ^0.4.2 32 | url_launcher: ^5.4.5 33 | permission_handler: ^5.0.0+hotfix.5 34 | device_info: ^0.4.2+3 35 | 36 | dev_dependencies: 37 | flutter_test: 38 | sdk: flutter 39 | 40 | 41 | # For information on the generic Dart part of this file, see the 42 | # following page: https://dart.dev/tools/pub/pubspec 43 | 44 | # The following section is specific to Flutter. 45 | flutter: 46 | 47 | # The following line ensures that the Material Icons font is 48 | # included with your application, so that you can use the icons in 49 | # the material Icons class. 50 | uses-material-design: true 51 | 52 | # To add assets to your application, add an assets section, like this: 53 | assets: 54 | - assets/ 55 | 56 | # An image asset can refer to one or more resolution-specific "variants", see 57 | # https://flutter.dev/assets-and-images/#resolution-aware. 58 | 59 | # For details regarding adding assets from package dependencies, see 60 | # https://flutter.dev/assets-and-images/#from-packages 61 | 62 | # To add custom fonts to your application, add a fonts section here, 63 | # in this "flutter" section. Each entry in this list should have a 64 | # "family" key with the font family name, and a "fonts" key with a 65 | # list giving the asset and other descriptors for the font. For 66 | # example: 67 | # fonts: 68 | # - family: Schyler 69 | # fonts: 70 | # - asset: fonts/Schyler-Regular.ttf 71 | # - asset: fonts/Schyler-Italic.ttf 72 | # style: italic 73 | # - family: Trajan Pro 74 | # fonts: 75 | # - asset: fonts/TrajanPro.ttf 76 | # - asset: fonts/TrajanPro_Bold.ttf 77 | # weight: 700 78 | # 79 | # For details regarding fonts from package dependencies, 80 | # see https://flutter.dev/custom-fonts/#from-packages 81 | --------------------------------------------------------------------------------