├── .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 |
--------------------------------------------------------------------------------