diff --git a/android/controller/src/main/java/org/openbot/controller/ConnectionSelector.kt b/android/controller/src/main/java/org/openbot/controller/ConnectionSelector.kt index b5cc7b2fd..a873a6f14 100644 --- a/android/controller/src/main/java/org/openbot/controller/ConnectionSelector.kt +++ b/android/controller/src/main/java/org/openbot/controller/ConnectionSelector.kt @@ -18,7 +18,7 @@ object ConnectionSelector { } fun getConnection(): ILocalConnection { - val connected = isConnectedViaWifi(context) + val connected = isConnectedViaWifi(context) || isWifiApEnabled(context) return if (connected) NetworkServiceConnection else NearbyConnection } @@ -30,4 +30,14 @@ object ConnectionSelector { return networkId > 0 } + + private fun isWifiApEnabled(context: Context): Boolean { + val wifiManager = context.getSystemService(WIFI_SERVICE) as WifiManager + return try { + val method = wifiManager.javaClass.getDeclaredMethod("isWifiApEnabled") + method.invoke(wifiManager) as? Boolean == true + } catch (ignored: Throwable) { + false + } + } } diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/VideoViewWebRTC.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/VideoViewWebRTC.kt index 22e287e30..fb8e4436d 100644 --- a/android/controller/src/main/java/org/openbot/controller/customComponents/VideoViewWebRTC.kt +++ b/android/controller/src/main/java/org/openbot/controller/customComponents/VideoViewWebRTC.kt @@ -136,8 +136,15 @@ class VideoViewWebRTC @JvmOverloads constructor( val initializationOptions = PeerConnectionFactory.InitializationOptions.builder(context) .createInitializationOptions() PeerConnectionFactory.initialize(initializationOptions) + + val options = PeerConnectionFactory.Options().apply { + networkIgnoreMask = 16 + disableEncryption = false + disableNetworkMonitor = true + } factory = PeerConnectionFactory.builder().setVideoEncoderFactory(encoderFactory) - .setVideoDecoderFactory(decoderFactory).createPeerConnectionFactory() + .setVideoDecoderFactory(decoderFactory) + .setOptions(options).createPeerConnectionFactory() } private fun initializePeerConnections() { diff --git a/android/robot/src/main/java/org/openbot/common/ControlsFragment.java b/android/robot/src/main/java/org/openbot/common/ControlsFragment.java index 21435ef99..f5818ff53 100644 --- a/android/robot/src/main/java/org/openbot/common/ControlsFragment.java +++ b/android/robot/src/main/java/org/openbot/common/ControlsFragment.java @@ -242,6 +242,14 @@ private void handlePhoneControllerEvents() { commandType = event.getString("command"); } else if (event.has("driveCmd")) { commandType = Constants.CMD_DRIVE; + } else if (event.has("server")) { + for (int i = 0; i < serverSpinner.getAdapter().getCount(); i++) { + if(event.getString("server").equals("noServerFound")){ + serverSpinner.setSelection(0); + } else if(event.getString("server").equals(serverSpinner.getAdapter().getItem(i))){ + serverSpinner.setSelection(i); + } + } } switch (commandType) { @@ -287,7 +295,7 @@ private void handlePhoneControllerEvents() { error -> { Log.d(null, "Error occurred in ControllerToBotEventBus: " + error); }, - event -> event.has("command") || event.has("driveCmd") // filter out everything else + event -> event.has("command") || event.has("driveCmd") || event.has("server") // filter out everything else ); } diff --git a/android/robot/src/main/java/org/openbot/env/ConnectionSelector.java b/android/robot/src/main/java/org/openbot/env/ConnectionSelector.java index 46a7d28ba..2814efa1f 100644 --- a/android/robot/src/main/java/org/openbot/env/ConnectionSelector.java +++ b/android/robot/src/main/java/org/openbot/env/ConnectionSelector.java @@ -3,6 +3,9 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.net.wifi.WifiManager; + +import java.lang.reflect.Method; public class ConnectionSelector { private static final String TAG = "ConnectionManager"; @@ -37,7 +40,7 @@ ILocalConnection getConnection() { return connection; } - if (isConnectedViaWifi()) { + if (isConnectedViaWifi() || isWifiApEnabled()) { connection = networkConnection; } else { connection = nearbyConnection; @@ -52,4 +55,17 @@ private boolean isConnectedViaWifi() { NetworkInfo mWifi = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); return mWifi.isConnected(); } + + private boolean isWifiApEnabled() { + WifiManager wifiManager = + (WifiManager) _context.getSystemService(Context.WIFI_SERVICE); + try { + final Method method = + wifiManager.getClass().getDeclaredMethod("isWifiApEnabled"); + Boolean result = (Boolean) method.invoke(wifiManager); + return Boolean.TRUE.equals(result); + } catch (final Throwable ignored) {} + + return false; + } } diff --git a/android/robot/src/main/java/org/openbot/env/NetworkServiceConnection.java b/android/robot/src/main/java/org/openbot/env/NetworkServiceConnection.java index 400de6edb..6246a40ff 100644 --- a/android/robot/src/main/java/org/openbot/env/NetworkServiceConnection.java +++ b/android/robot/src/main/java/org/openbot/env/NetworkServiceConnection.java @@ -136,6 +136,7 @@ public void onServiceFound(NsdServiceInfo service) { try { if (service.getServiceType().equals(SERVICE_TYPE) && service.getServiceName().equals(SERVICE_NAME_CONTROLLER)) { + Timber.e("found service"); mNsdManager.resolveService(service, mResolveListener); } else if (service.getServiceName().equals(MY_SERVICE_NAME)) { Log.d(TAG, "Same machine: " + MY_SERVICE_NAME); @@ -289,7 +290,6 @@ void runReceiver(Scanner reader) { try { while (true) { String msg = reader.nextLine().trim(); - if (!stopped) { ((Activity) context).runOnUiThread(() -> dataReceivedCallback.dataReceived(msg)); } diff --git a/android/robot/src/main/java/org/openbot/env/WebRtcServer.java b/android/robot/src/main/java/org/openbot/env/WebRtcServer.java index fd49e2d57..e3b0a6fcc 100644 --- a/android/robot/src/main/java/org/openbot/env/WebRtcServer.java +++ b/android/robot/src/main/java/org/openbot/env/WebRtcServer.java @@ -407,10 +407,16 @@ private void initializePeerConnectionFactory() { PeerConnectionFactory.InitializationOptions.builder(context).createInitializationOptions(); PeerConnectionFactory.initialize(initializationOptions); + PeerConnectionFactory.Options options = new PeerConnectionFactory.Options(); + options.networkIgnoreMask = 16; + options.disableEncryption = false; + options.disableNetworkMonitor = true; + factory = PeerConnectionFactory.builder() .setVideoEncoderFactory(encoderFactory) .setVideoDecoderFactory(decoderFactory) + .setOptions(options) .createPeerConnectionFactory(); } diff --git a/android/robot/src/main/java/org/openbot/objectNav/ObjectNavFragment.java b/android/robot/src/main/java/org/openbot/objectNav/ObjectNavFragment.java index be04a4950..5bbf32cc5 100644 --- a/android/robot/src/main/java/org/openbot/objectNav/ObjectNavFragment.java +++ b/android/robot/src/main/java/org/openbot/objectNav/ObjectNavFragment.java @@ -58,6 +58,7 @@ public class ObjectNavFragment extends CameraFragment { private Detector detector; + private boolean mirrorControl; private Matrix frameToCropTransform; private Bitmap croppedBitmap; private int sensorOrientation; @@ -146,6 +147,8 @@ public void onNothingSelected(AdapterView parent) {} binding.cameraToggle.setOnClickListener(v -> toggleCamera()); + binding.mirrorControl.setOnClickListener(v -> mirrorControl()); + List models = getModelNames(f -> f.type.equals(Model.TYPE.DETECTOR) && f.pathType != Model.PATH_TYPE.URL); initModelSpinner(binding.modelSpinner, models, preferencesManager.getObjectNavModel()); @@ -228,6 +231,10 @@ public void onNothingSelected(AdapterView parent) {} }); } + private void mirrorControl() { + mirrorControl = !mirrorControl; + } + private void updateCropImageInfo() { // Timber.i("%s x %s",getPreviewSize().getWidth(), getPreviewSize().getHeight()); // Timber.i("%s x %s",getMaxAnalyseImageSize().getWidth(), @@ -477,7 +484,12 @@ protected void processFrame(Bitmap bitmap, ImageProxy image) { } tracker.trackResults(mappedRecognitions, frameNum); - handleDriveCommand(tracker.updateTarget()); + Control target = tracker.updateTarget(); + if (mirrorControl) { + handleDriveCommand(target.mirror()); + } else { + handleDriveCommand(target); + } binding.trackingOverlay.postInvalidate(); } diff --git a/android/robot/src/main/java/org/openbot/server/ServerCommunication.java b/android/robot/src/main/java/org/openbot/server/ServerCommunication.java index 04fc31bbd..9d540500a 100644 --- a/android/robot/src/main/java/org/openbot/server/ServerCommunication.java +++ b/android/robot/src/main/java/org/openbot/server/ServerCommunication.java @@ -39,6 +39,7 @@ public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { @Override public void onServiceResolved(NsdServiceInfo serviceInfo) { + Timber.e("serviceInfo %s", serviceInfo.getServiceName()); servers.put(serviceInfo.getServiceName(), serviceInfo); try { serverListener.onServerListChange(servers.keySet()); diff --git a/android/robot/src/main/java/org/openbot/vehicle/Control.java b/android/robot/src/main/java/org/openbot/vehicle/Control.java index 94f25d82b..e5d89938b 100644 --- a/android/robot/src/main/java/org/openbot/vehicle/Control.java +++ b/android/robot/src/main/java/org/openbot/vehicle/Control.java @@ -16,4 +16,8 @@ public float getLeft() { public float getRight() { return right; } + + public Control mirror() { + return new Control(this.right, this.left); + } } diff --git a/android/robot/src/main/res/drawable/ic_mirror_control.xml b/android/robot/src/main/res/drawable/ic_mirror_control.xml new file mode 100644 index 000000000..12ccc2ef8 --- /dev/null +++ b/android/robot/src/main/res/drawable/ic_mirror_control.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/android/robot/src/main/res/drawable/ic_mirror_control_off.xml b/android/robot/src/main/res/drawable/ic_mirror_control_off.xml new file mode 100644 index 000000000..0b02a48e6 --- /dev/null +++ b/android/robot/src/main/res/drawable/ic_mirror_control_off.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/robot/src/main/res/drawable/mirror_control.xml b/android/robot/src/main/res/drawable/mirror_control.xml new file mode 100644 index 000000000..387d0c578 --- /dev/null +++ b/android/robot/src/main/res/drawable/mirror_control.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/robot/src/main/res/layout/fragment_object_nav.xml b/android/robot/src/main/res/layout/fragment_object_nav.xml index 8eee6817c..f8ff6cfc6 100644 --- a/android/robot/src/main/res/layout/fragment_object_nav.xml +++ b/android/robot/src/main/res/layout/fragment_object_nav.xml @@ -65,7 +65,7 @@ android:id="@+id/usbToggle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginEnd="16dp" + android:layout_marginEnd="8dp" android:button="@drawable/usb_toggle" app:layout_constraintBottom_toBottomOf="@+id/camera_toggle" app:layout_constraintEnd_toStartOf="@+id/camera_toggle" @@ -75,7 +75,7 @@ android:id="@+id/bleToggle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginEnd="16dp" + android:layout_marginEnd="8dp" android:button="@drawable/ble_toggle" app:layout_constraintBottom_toBottomOf="@+id/camera_toggle" app:layout_constraintEnd_toStartOf="@+id/camera_toggle" @@ -85,15 +85,25 @@ android:id="@+id/camera_toggle" android:layout_width="40dp" android:layout_height="40dp" - android:layout_marginRight="16dp" + android:layout_marginEnd="8dp" android:background="@android:color/transparent" android:scaleType="center" android:src="@drawable/ic_cameraswitch" android:text="@string/camera_facing_back" app:layout_constraintTop_toTopOf="@+id/autoLinearLayout" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@+id/mirrorControl" app:tint="@color/openBotBlue" /> + + + +

+ +- Please follow the instructions similar to the ones mentioned above for running Flutter in the terminal and directly run using the ``run`` button for future repetitions. + +

+ +

+ +## Connection + +When the controller app is started, it immediately tries to connect to the robot and shows the following screen: + +

+ +

+ +To connect the controller to the robot, set the robot's control mode to **Phone**. +For example, in the `FreeRoamFragment` the phone mode is activated like this: + +

+ +

+ +Once connected, the controller app will look like this: + +

+ +

+ +Here you can select to drive the robot by tilting the phone, or by using the on-screen controls. + +***Note:*** This should be sufficient to connect, but if the connection cannot be established after 30 seconds, toggle +the `Control` setting on the bot app to `Gamepad` and then to `Phone` again to re-initiate the connection. If that +fails, exit the controller app and start it again. Toggle the control mode again on the robot app. + +## Operation + +### On-screen controls + +This mode allows the user to control the robot car via two sliders in `Dual Drive` mode. You can turn left/right by +moving the slider thumb up and down on each side. The wheels on each side turn forward/backward when moving the thumb +above/below the center of the slider. + +

+ +

+ +- ``Indicators``: You can also set the left/right turn indicators by clicking on the arrows on the top-left of the screen. + +- ``Switch Camera``: switch between the front and back camera modes. +- ``Mute``: enable/disable audio transmission. +- ``Mirror view``: mirror the the video feed. + +### Tilt to drive + +The controller can also use its accelerometer motion sensor to drive the robot. If you select this option, the +controller will enter a full-screen (Zen) mode with only the video showing and `brake` and `accelerator` pedals. To +exit this mode, double-tap on the screen. + +Here is a picture of the `tilt mode` screen: + +

+ +

+ +Use the `accelerator` and `brake` buttons to move forward/backward. + +- Pressing the `accelerator` will accelerate the robot to full speed within 2 seconds. When you release the button, the + robot will slow down to a stop (stop speed set to 0% of the maximum speed, can be adjusted). +- Pressing the `brake` button will immediately stop the robot. If we hold the brake for another second, the robot will + start moving backwards until it reaches the maximum reverse speed in one second. When we let go of the brake, the + robot will come to a stop. +- The robot is steered by tilting the controller phone left or right. + +Here is a [Technical Overview](../../docs/technical/OpenBotController.pdf) of the controller app. diff --git a/controller/flutter/analysis_options.yaml b/controller/flutter/analysis_options.yaml new file mode 100644 index 000000000..61b6c4de1 --- /dev/null +++ b/controller/flutter/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/controller/flutter/android/.gitignore b/controller/flutter/android/.gitignore new file mode 100644 index 000000000..6f568019d --- /dev/null +++ b/controller/flutter/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/controller/flutter/android/app/build.gradle b/controller/flutter/android/app/build.gradle new file mode 100644 index 000000000..522b7fee1 --- /dev/null +++ b/controller/flutter/android/app/build.gradle @@ -0,0 +1,73 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = '11' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "org.openbot.flutter_controller" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + minSdkVersion 21 + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + +} diff --git a/controller/flutter/android/app/src/debug/AndroidManifest.xml b/controller/flutter/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..eac323660 --- /dev/null +++ b/controller/flutter/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/controller/flutter/android/app/src/main/AndroidManifest.xml b/controller/flutter/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..0f9060ae6 --- /dev/null +++ b/controller/flutter/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/controller/flutter/android/app/src/main/kotlin/com/itinker/openbot/controller/openbot_controller/MainActivity.kt b/controller/flutter/android/app/src/main/kotlin/com/itinker/openbot/controller/openbot_controller/MainActivity.kt new file mode 100644 index 000000000..ab2c64255 --- /dev/null +++ b/controller/flutter/android/app/src/main/kotlin/com/itinker/openbot/controller/openbot_controller/MainActivity.kt @@ -0,0 +1,6 @@ +package org.openbot.flutter_controller + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/controller/flutter/android/app/src/main/res/drawable-v21/launch_background.xml b/controller/flutter/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/controller/flutter/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/controller/flutter/android/app/src/main/res/drawable/ic_controller.xml b/controller/flutter/android/app/src/main/res/drawable/ic_controller.xml new file mode 100644 index 000000000..a3324a0e8 --- /dev/null +++ b/controller/flutter/android/app/src/main/res/drawable/ic_controller.xml @@ -0,0 +1,10 @@ + + + diff --git a/controller/flutter/android/app/src/main/res/drawable/launch_background.xml b/controller/flutter/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/controller/flutter/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/controller/flutter/android/app/src/main/res/values-night/styles.xml b/controller/flutter/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/controller/flutter/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/controller/flutter/android/app/src/main/res/values/styles.xml b/controller/flutter/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..cb1ef8805 --- /dev/null +++ b/controller/flutter/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/controller/flutter/android/app/src/profile/AndroidManifest.xml b/controller/flutter/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..e199889ce --- /dev/null +++ b/controller/flutter/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/controller/flutter/android/build.gradle b/controller/flutter/android/build.gradle new file mode 100644 index 000000000..3cdaac958 --- /dev/null +++ b/controller/flutter/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/controller/flutter/android/gradle.properties b/controller/flutter/android/gradle.properties new file mode 100644 index 000000000..94adc3a3f --- /dev/null +++ b/controller/flutter/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/controller/flutter/android/gradle/wrapper/gradle-wrapper.properties b/controller/flutter/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..cb24abda1 --- /dev/null +++ b/controller/flutter/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/controller/flutter/android/settings.gradle b/controller/flutter/android/settings.gradle new file mode 100644 index 000000000..44e62bcf0 --- /dev/null +++ b/controller/flutter/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/controller/flutter/images/arrow_icon.png b/controller/flutter/images/arrow_icon.png new file mode 100644 index 000000000..07bf9a455 Binary files /dev/null and b/controller/flutter/images/arrow_icon.png differ diff --git a/controller/flutter/images/camera_icon_blue.png b/controller/flutter/images/camera_icon_blue.png new file mode 100644 index 000000000..4bf764524 Binary files /dev/null and b/controller/flutter/images/camera_icon_blue.png differ diff --git a/controller/flutter/images/camera_icon_white.png b/controller/flutter/images/camera_icon_white.png new file mode 100644 index 000000000..f9dec4ea9 Binary files /dev/null and b/controller/flutter/images/camera_icon_white.png differ diff --git a/controller/flutter/images/controller_icon.png b/controller/flutter/images/controller_icon.png new file mode 100644 index 000000000..90fcec7d0 Binary files /dev/null and b/controller/flutter/images/controller_icon.png differ diff --git a/controller/flutter/images/cross_icon.png b/controller/flutter/images/cross_icon.png new file mode 100644 index 000000000..1791855db Binary files /dev/null and b/controller/flutter/images/cross_icon.png differ diff --git a/controller/flutter/images/forward_icon.png b/controller/flutter/images/forward_icon.png new file mode 100644 index 000000000..5f1bb80e1 Binary files /dev/null and b/controller/flutter/images/forward_icon.png differ diff --git a/controller/flutter/images/left_indicator_icon_blue.png b/controller/flutter/images/left_indicator_icon_blue.png new file mode 100644 index 000000000..a1dcc980e Binary files /dev/null and b/controller/flutter/images/left_indicator_icon_blue.png differ diff --git a/controller/flutter/images/left_indicator_icon_white.png b/controller/flutter/images/left_indicator_icon_white.png new file mode 100644 index 000000000..e6560d374 Binary files /dev/null and b/controller/flutter/images/left_indicator_icon_white.png differ diff --git a/controller/flutter/images/mirror_view_icon_blue.png b/controller/flutter/images/mirror_view_icon_blue.png new file mode 100644 index 000000000..3958c888a Binary files /dev/null and b/controller/flutter/images/mirror_view_icon_blue.png differ diff --git a/controller/flutter/images/mirror_view_icon_white.png b/controller/flutter/images/mirror_view_icon_white.png new file mode 100644 index 000000000..4d8856333 Binary files /dev/null and b/controller/flutter/images/mirror_view_icon_white.png differ diff --git a/controller/flutter/images/openbot_icon.png b/controller/flutter/images/openbot_icon.png new file mode 100644 index 000000000..402721291 Binary files /dev/null and b/controller/flutter/images/openbot_icon.png differ diff --git a/controller/flutter/images/reverse_icon.png b/controller/flutter/images/reverse_icon.png new file mode 100644 index 000000000..3f4511f81 Binary files /dev/null and b/controller/flutter/images/reverse_icon.png differ diff --git a/controller/flutter/images/right_indicator_icon_blue.png b/controller/flutter/images/right_indicator_icon_blue.png new file mode 100644 index 000000000..3f681753e Binary files /dev/null and b/controller/flutter/images/right_indicator_icon_blue.png differ diff --git a/controller/flutter/images/right_indicator_icon_white.png b/controller/flutter/images/right_indicator_icon_white.png new file mode 100644 index 000000000..d65ed6413 Binary files /dev/null and b/controller/flutter/images/right_indicator_icon_white.png differ diff --git a/controller/flutter/images/speaker_icon_blue.png b/controller/flutter/images/speaker_icon_blue.png new file mode 100644 index 000000000..07d14b742 Binary files /dev/null and b/controller/flutter/images/speaker_icon_blue.png differ diff --git a/controller/flutter/images/speaker_icon_white.png b/controller/flutter/images/speaker_icon_white.png new file mode 100644 index 000000000..3d28da03a Binary files /dev/null and b/controller/flutter/images/speaker_icon_white.png differ diff --git a/controller/flutter/images/tilting_phone_icon.png b/controller/flutter/images/tilting_phone_icon.png new file mode 100644 index 000000000..b28a006ec Binary files /dev/null and b/controller/flutter/images/tilting_phone_icon.png differ diff --git a/controller/flutter/ios/.gitignore b/controller/flutter/ios/.gitignore new file mode 100644 index 000000000..7a7f9873a --- /dev/null +++ b/controller/flutter/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/controller/flutter/ios/Flutter/AppFrameworkInfo.plist b/controller/flutter/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..9625e105d --- /dev/null +++ b/controller/flutter/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/controller/flutter/ios/Flutter/Debug.xcconfig b/controller/flutter/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..ec97fc6f3 --- /dev/null +++ b/controller/flutter/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/controller/flutter/ios/Flutter/Release.xcconfig b/controller/flutter/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..c4855bfe2 --- /dev/null +++ b/controller/flutter/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/controller/flutter/ios/Podfile b/controller/flutter/ios/Podfile new file mode 100644 index 000000000..88359b225 --- /dev/null +++ b/controller/flutter/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/controller/flutter/ios/Podfile.lock b/controller/flutter/ios/Podfile.lock new file mode 100644 index 000000000..dd2d16c7d --- /dev/null +++ b/controller/flutter/ios/Podfile.lock @@ -0,0 +1,63 @@ +PODS: + - Flutter (1.0.0) + - flutter_webrtc (0.9.17): + - Flutter + - WebRTC-SDK (= 104.5112.07) + - fluttertoast (0.0.2): + - Flutter + - Toast + - nsd_ios (0.0.1): + - Flutter + - path_provider_ios (0.0.1): + - Flutter + - sensors_plus (0.0.1): + - Flutter + - Toast (4.0.0) + - video_player_avfoundation (0.0.1): + - Flutter + - WebRTC-SDK (104.5112.07) + +DEPENDENCIES: + - Flutter (from `Flutter`) + - flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`) + - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) + - nsd_ios (from `.symlinks/plugins/nsd_ios/ios`) + - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) + - sensors_plus (from `.symlinks/plugins/sensors_plus/ios`) + - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`) + +SPEC REPOS: + trunk: + - Toast + - WebRTC-SDK + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + flutter_webrtc: + :path: ".symlinks/plugins/flutter_webrtc/ios" + fluttertoast: + :path: ".symlinks/plugins/fluttertoast/ios" + nsd_ios: + :path: ".symlinks/plugins/nsd_ios/ios" + path_provider_ios: + :path: ".symlinks/plugins/path_provider_ios/ios" + sensors_plus: + :path: ".symlinks/plugins/sensors_plus/ios" + video_player_avfoundation: + :path: ".symlinks/plugins/video_player_avfoundation/ios" + +SPEC CHECKSUMS: + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + flutter_webrtc: 6767eafaa728c1e58026e901ca19f3fd5d713397 + fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0 + nsd_ios: 8c37babdc6538e3350dbed3a52674d2edde98173 + path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 + sensors_plus: 5717760720f7e6acd96fdbd75b7428f5ad755ec2 + Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 + video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff + WebRTC-SDK: ef83f71e443c135d98de109940343bcf6d313e8f + +PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 + +COCOAPODS: 1.11.3 diff --git a/controller/flutter/ios/Runner.xcodeproj/project.pbxproj b/controller/flutter/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..448131cc1 --- /dev/null +++ b/controller/flutter/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,551 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 57E47C2C5FEAE99AF5F2AE1A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54BA7247337E0A245A73D6A9 /* Pods_Runner.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 12422A21C1A28063ECC1F0EB /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 54BA7247337E0A245A73D6A9 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 645D7AF33408D8AD39F70275 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C76119F8219D15E881E1801D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 57E47C2C5FEAE99AF5F2AE1A /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1332E30E92A9E05F0D5BAD3B /* Frameworks */ = { + isa = PBXGroup; + children = ( + 54BA7247337E0A245A73D6A9 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 6A6817DAB07E0132BD519C08 /* Pods */ = { + isa = PBXGroup; + children = ( + 12422A21C1A28063ECC1F0EB /* Pods-Runner.debug.xcconfig */, + C76119F8219D15E881E1801D /* Pods-Runner.release.xcconfig */, + 645D7AF33408D8AD39F70275 /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 6A6817DAB07E0132BD519C08 /* Pods */, + 1332E30E92A9E05F0D5BAD3B /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 7BF258D0A0DC18EBA6A432AC /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + F8107A95ACB56E45E835D2F8 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 7BF258D0A0DC18EBA6A432AC /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + F8107A95ACB56E45E835D2F8 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 45P94DZS6G; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.openbot.openbotController; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 45P94DZS6G; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.openbot.openbotController; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 45P94DZS6G; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.openbot.openbotController; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/controller/flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/controller/flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/controller/flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/controller/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/controller/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/controller/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/controller/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/controller/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/controller/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/controller/flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/controller/flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..c87d15a33 --- /dev/null +++ b/controller/flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/controller/flutter/ios/Runner.xcworkspace/contents.xcworkspacedata b/controller/flutter/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/controller/flutter/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/controller/flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/controller/flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/controller/flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/controller/flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/controller/flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/controller/flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/controller/flutter/ios/Runner/AppDelegate.swift b/controller/flutter/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/controller/flutter/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..28c6bf030 Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..f091b6b0b Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cde12118 Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..d0ef06e7e Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..dcdc2306c Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..c8f9ed8f5 Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..75b2d164a Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..c4df70d39 Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..6a84f41e1 Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..d0e1f5853 Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/controller/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +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. \ No newline at end of file diff --git a/controller/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard b/controller/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/controller/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/controller/flutter/ios/Runner/Base.lproj/Main.storyboard b/controller/flutter/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/controller/flutter/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/controller/flutter/ios/Runner/Info.plist b/controller/flutter/ios/Runner/Info.plist new file mode 100644 index 000000000..da92a1674 --- /dev/null +++ b/controller/flutter/ios/Runner/Info.plist @@ -0,0 +1,55 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Openbot Controller + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + openbot_controller + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + NSBonjourServices + + _openbot._tcp. + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/controller/flutter/ios/Runner/Runner-Bridging-Header.h b/controller/flutter/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/controller/flutter/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/controller/flutter/lib/buttonCommands/buttonCommands.dart b/controller/flutter/lib/buttonCommands/buttonCommands.dart new file mode 100644 index 000000000..02cc537f9 --- /dev/null +++ b/controller/flutter/lib/buttonCommands/buttonCommands.dart @@ -0,0 +1,19 @@ +import '../globals.dart'; + +class ButtonCommands { + static void toSwitchCamera() { + clientSocket?.writeln("{command: SWITCH_CAMERA}"); + } + + static void toLeftIndicator() { + clientSocket?.writeln("{command: INDICATOR_LEFT}"); + } + + static void toRightIndicator() { + clientSocket?.writeln("{command: INDICATOR_RIGHT}"); + } + + static void toStopIndicator() { + clientSocket?.writeln("{command: INDICATOR_STOP}"); + } +} diff --git a/controller/flutter/lib/globals.dart b/controller/flutter/lib/globals.dart new file mode 100644 index 000000000..cc23cdec3 --- /dev/null +++ b/controller/flutter/lib/globals.dart @@ -0,0 +1,3 @@ +import 'dart:io'; + +Socket? clientSocket; diff --git a/controller/flutter/lib/main.dart b/controller/flutter/lib/main.dart new file mode 100644 index 000000000..fecbe6744 --- /dev/null +++ b/controller/flutter/lib/main.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:openbot_controller/screens/controller.dart'; + +void main() { + // We need to call it manually, + // because we going to call setPreferredOrientations() + // before the runApp() call + WidgetsFlutterBinding.ensureInitialized(); + + // Than we setup preferred orientations, + // and only after it finished we run our app + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); + SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft]) + .then((value) => runApp(const Controller())); +} diff --git a/controller/flutter/lib/screens/component/blinkingButton.dart b/controller/flutter/lib/screens/component/blinkingButton.dart new file mode 100644 index 000000000..a8e105994 --- /dev/null +++ b/controller/flutter/lib/screens/component/blinkingButton.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; + +class MyBlinkingButton extends StatefulWidget { + final String indicator; + + const MyBlinkingButton(this.indicator, {super.key}); + + @override + _MyBlinkingButtonState createState() => _MyBlinkingButtonState(); +} + +class _MyBlinkingButtonState extends State + with SingleTickerProviderStateMixin { + late AnimationController _animationController; + + @override + void initState() { + _animationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 500)); + _animationController.repeat(reverse: true); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: _animationController, + child: (widget.indicator == "LEFT") + ? Image.asset( + "images/left_indicator_icon_white.png", + height: 23, + width: 23, + ) + : Image.asset( + "images/right_indicator_icon_white.png", + height: 23, + width: 23, + )); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } +} diff --git a/controller/flutter/lib/screens/component/onScreenIcon.dart b/controller/flutter/lib/screens/component/onScreenIcon.dart new file mode 100644 index 000000000..d3b3cb40f --- /dev/null +++ b/controller/flutter/lib/screens/component/onScreenIcon.dart @@ -0,0 +1,193 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart'; +import 'package:openbot_controller/buttonCommands/buttonCommands.dart'; +import 'package:openbot_controller/screens/component/blinkingButton.dart'; + +class OnScreenIcon extends StatefulWidget { + final dynamic updateMirrorView; + final bool indicatorLeft; + final bool indicatorRight; + final RTCPeerConnection? peerConnection; + + const OnScreenIcon(this.updateMirrorView, this.indicatorLeft, + this.indicatorRight, this.peerConnection, + {super.key}); + + @override + State createState() { + return OnScreenIconState(); + } +} + +class OnScreenIconState extends State { + bool mirrorView = false; + bool speaker = false; + bool leftIndicator = false; + bool rightIndicator = false; + + @override + void didUpdateWidget(covariant OnScreenIcon oldWidget) { + // TODO: implement didUpdateWidget + setState(() { + leftIndicator = widget.indicatorLeft; + rightIndicator = widget.indicatorRight; + }); + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + child: Row( + children: [ + GestureDetector( + onTap: () { + setState(() { + mirrorView = !mirrorView; + widget.updateMirrorView.call(); + }); + }, // Image tapped + child: Container( + padding: const EdgeInsets.all(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(45), + color: mirrorView + ? const Color(0xFF0071C5).withOpacity(0.5) + : Colors.white.withOpacity(0.5), + ), + child: Image.asset( + mirrorView + ? "images/mirror_view_icon_white.png" + : "images/mirror_view_icon_blue.png", + height: 23, + width: 23, + ), + )), + const SizedBox( + width: 15, + ), + GestureDetector( + onTap: () async { + setState(() { + speaker = !speaker; + }); + if (widget.peerConnection != null) { + List receivers = + await widget.peerConnection!.receivers; + RTCRtpReceiver firstReceiver = receivers[0]; + if (receivers.isNotEmpty) { + if (speaker) { + firstReceiver.track!.enabled = false; + } else { + firstReceiver.track!.enabled = true; + } + } + } + }, // Image tapped + child: Container( + // height: 50, + // width: 50, + padding: const EdgeInsets.all(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(45), + color: speaker + ? const Color(0xFF0071C5).withOpacity(0.5) + : Colors.white.withOpacity(0.5), + ), + child: Image.asset( + speaker + ? "images/speaker_icon_white.png" + : "images/speaker_icon_blue.png", + height: 23, + width: 23, + ), + )), + const SizedBox( + width: 15, + ), + InkWell( + borderRadius: const BorderRadius.all(Radius.circular(45)), + onTap: () { + ButtonCommands.toSwitchCamera(); + }, // Image tapped + child: Container( + padding: const EdgeInsets.all(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(45), + color: Colors.white.withOpacity(0.5), + ), + child: Image.asset( + "images/camera_icon_blue.png", + height: 23, + width: 23, + ), + )), + const SizedBox( + width: 15, + ), + GestureDetector( + onTap: () { + if (rightIndicator) { + ButtonCommands.toStopIndicator(); + ButtonCommands.toLeftIndicator(); + } else { + if (leftIndicator) { + ButtonCommands.toStopIndicator(); + } else { + ButtonCommands.toLeftIndicator(); + } + } + }, // Image tapped + child: Container( + padding: const EdgeInsets.all(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(45), + color: leftIndicator + ? const Color(0xFF0071C5).withOpacity(0.5) + : Colors.white.withOpacity(0.5), + ), + child: leftIndicator + ? const MyBlinkingButton("LEFT") + : Image.asset( + "images/left_indicator_icon_blue.png", + height: 23, + width: 23, + ), + )), + const SizedBox( + width: 15, + ), + GestureDetector( + onTap: () { + if (leftIndicator) { + ButtonCommands.toStopIndicator(); + ButtonCommands.toRightIndicator(); + } else { + if (rightIndicator) { + ButtonCommands.toStopIndicator(); + } else { + ButtonCommands.toRightIndicator(); + } + } + }, // Image tapped + child: Container( + padding: const EdgeInsets.all(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(45), + color: rightIndicator + ? const Color(0xFF0071C5).withOpacity(0.5) + : Colors.white.withOpacity(0.5), + ), + child: rightIndicator + ? const MyBlinkingButton("RIGHT") + : Image.asset( + "images/right_indicator_icon_blue.png", + height: 23, + width: 23, + ), + )), + ], + ), + ); + } +} diff --git a/controller/flutter/lib/screens/controlSelector.dart b/controller/flutter/lib/screens/controlSelector.dart new file mode 100644 index 000000000..f45e11193 --- /dev/null +++ b/controller/flutter/lib/screens/controlSelector.dart @@ -0,0 +1,396 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:nsd/nsd.dart'; +import 'package:openbot_controller/globals.dart'; +import 'package:openbot_controller/screens/tiltingPhoneMode.dart'; + +import 'onScreenMode.dart'; + +class ControlSelector extends StatefulWidget { + final dynamic updateMirrorView; + final bool indicatorLeft; + final bool indicatorRight; + final List networkServices; + final RTCPeerConnection? peerConnection; + + const ControlSelector(this.updateMirrorView, this.indicatorLeft, + this.indicatorRight, this.networkServices, this.peerConnection, + {super.key}); + + @override + State createState() { + return ControlSelectorState(); + } +} + +class ControlSelectorState extends State { + bool isTiltingPhoneMode = false; + bool isScreenMode = false; + + // Initial Selected Value + String dropDownValue = 'No server'; + late List> items = []; + + @override + void initState() { + super.initState(); + // items.clear(); + } + + // Function to generate DropdownMenuItem widgets + List> buildDropdownMenuItems() { + items = [ + DropdownMenuItem( + value: 'No server', + child: Container( + height: 30, + width: 85, + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(3)), + color: Color(0xFF0071C5), + ), + alignment: Alignment.center, + child: const Text( + 'No server', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 13, + color: Color(0xFFffffff), + ), + ), + ), + ), + ]; + items.addAll(widget.networkServices.map((discovery) { + return DropdownMenuItem( + value: discovery.name, + child: Container( + height: 30, + width: 85, + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(3)), + color: Color(0xFF0071C5), + ), + alignment: Alignment.center, + child: Text( + discovery.name ?? '', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 13, + color: Color(0xFFffffff), + ), + ), + ), + ); + })); + return items; + } + + @override + Widget build(BuildContext context) { + if (isTiltingPhoneMode) { + return GestureDetector( + onDoubleTap: () { + setState(() { + isTiltingPhoneMode = false; + }); + }, + child: const TiltingPhoneMode()); + } else if (isScreenMode) { + return GestureDetector( + onDoubleTap: () { + setState(() { + isScreenMode = false; + }); + }, + child: OnScreenMode(widget.updateMirrorView, widget.indicatorLeft, + widget.indicatorRight, widget.peerConnection), + ); + } else { + return Scaffold( + backgroundColor: Colors.transparent, + body: Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () { + Fluttertoast.showToast( + msg: "Double tap on screen to get back", + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.BOTTOM, + backgroundColor: Colors.grey, + textColor: Colors.white, + fontSize: 18); + setState(() { + isScreenMode = true; + }); + }, + child: Container( + height: 180, + width: 180, + padding: const EdgeInsets.all(20), + margin: const EdgeInsets.all(20), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + color: Color(0xFF292929), + boxShadow: [ + BoxShadow( + blurRadius: 0.5, + spreadRadius: 0.1, + color: Color(0xFFffffff), + offset: Offset( + -1, + -1, + ), + ), + BoxShadow( + blurRadius: 1, + spreadRadius: 0.1, + color: Color(0xFF000000), + offset: Offset( + -1, + -1, + ), + ), + ]), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.asset( + "images/controller_icon.png", + height: 18, + width: 18, + ), + const SizedBox( + height: 20, + ), + const Text( + "Use On-Screen Controls to Drive", + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: Color(0xFFffffff), + ), + ), + const SizedBox( + height: 55, + ), + Container( + alignment: Alignment.bottomRight, + child: Image.asset( + "images/arrow_icon.png", + width: 50, + ), + ), + ]), + ), + ), + GestureDetector( + onTap: () { + Fluttertoast.showToast( + msg: "Double tap on screen to get back", + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.BOTTOM, + backgroundColor: Colors.grey, + textColor: Colors.white, + fontSize: 18); + setState(() { + isTiltingPhoneMode = true; + }); + }, + child: Container( + height: 180, + width: 180, + padding: const EdgeInsets.all(20), + margin: const EdgeInsets.all(20), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + color: Color(0xFF292929), + boxShadow: [ + BoxShadow( + blurRadius: 0.5, + spreadRadius: 0.1, + color: Color(0xFFffffff), + offset: Offset( + -1, + -1, + ), + ), + BoxShadow( + blurRadius: 1, + spreadRadius: 0.1, + color: Color(0xFF000000), + offset: Offset( + -1, + -1, + ), + ), + ]), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.asset( + "images/tilting_phone_icon.png", + height: 18, + width: 18, + ), + const SizedBox( + height: 20, + ), + const Text( + "Drive by tilting\nthe phone", + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: Color(0xFFffffff), + ), + ), + const SizedBox( + height: 55, + ), + Container( + alignment: Alignment.bottomRight, + child: Image.asset( + "images/arrow_icon.png", + width: 50, + ), + ), + ]), + ), + ), + ], + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + DropdownButton( + value: dropDownValue, + borderRadius: const BorderRadius.all(Radius.circular(3)), + underline: Container(), + dropdownColor: const Color(0xFF0071C5), + style: const TextStyle( + fontSize: 13, + color: Color(0xFFffffff), + ), + menuMaxHeight: 150, + items: buildDropdownMenuItems(), + onChanged: (String? serverName) { + setState(() { + dropDownValue = serverName!; + }); + if (serverName != "No server") { + clientSocket?.writeln("{server: $serverName}"); + } else { + clientSocket?.writeln("{server: noServerFound}"); + } + }, + ), + GestureDetector( + onTap: () { + clientSocket?.writeln("{command: LOGS}"); + }, + child: Container( + height: 30, + width: 85, + margin: const EdgeInsets.all(20), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(3)), + color: Color(0xFF0071C5), + ), + child: const Center( + child: Text( + "Logs", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 13, + color: Color(0xFFffffff), + ), + ), + ), + )), + GestureDetector( + onTap: () { + clientSocket?.writeln("{command: NOISE}"); + }, + child: Container( + height: 30, + width: 85, + margin: const EdgeInsets.all(20), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(3)), + color: Color(0xFF0071C5), + ), + child: const Center( + child: Text( + "Noise", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 13, + color: Color(0xFFffffff), + ), + ), + ), + )), + GestureDetector( + onTap: () { + clientSocket?.writeln("{command: NETWORK}"); + }, + child: Container( + height: 30, + width: 85, + margin: const EdgeInsets.all(20), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(3)), + color: Color(0xFF0071C5), + ), + child: const Center( + child: Text( + "Network", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 13, + color: Color(0xFFffffff), + ), + ), + ), + )), + GestureDetector( + onTap: () { + clientSocket?.writeln("{command: DRIVE_MODE}"); + }, + child: Container( + height: 30, + width: 85, + margin: const EdgeInsets.all(20), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(3)), + color: Color(0xFF0071C5), + ), + child: const Center( + child: Text( + "Game", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 13, + color: Color(0xFFffffff), + ), + ), + ), + )), + ], + ) + ], + ))); + } + } +} diff --git a/controller/flutter/lib/screens/controller.dart b/controller/flutter/lib/screens/controller.dart new file mode 100644 index 000000000..fc07cd040 --- /dev/null +++ b/controller/flutter/lib/screens/controller.dart @@ -0,0 +1,342 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:nsd/nsd.dart'; +import 'package:openbot_controller/globals.dart'; +import 'package:openbot_controller/screens/controlSelector.dart'; + +import '../utils/constants.dart'; +import 'discoveringDevices.dart'; + +const String serviceTypeRegister = '_openbot._tcp'; +const utf8encoder = Utf8Encoder(); + +class Controller extends StatefulWidget { + const Controller({Key? key}) : super(key: key); + + @override + State createState() => ControllerState(); +} + +class ControllerState extends State { + final List services = []; + final registrations = []; + ServerSocket? _serverSocket; + Stream? _broadcast; + bool videoView = false; + bool mirroredVideo = false; + bool indicatorLeft = false; + bool indicatorRight = false; + var _nextPort = 56360; + + int get nextPort => _nextPort++; + + setMirrorVideo() { + setState(() { + mirroredVideo = !mirroredVideo; + }); + } + + final RTCVideoRenderer _remoteVideoRenderer = RTCVideoRenderer(); + RTCPeerConnection? _peerConnection; + + Future videoConnection() async { + initRenderers(); + _createPeerConnection().then((pc) { + _peerConnection = pc; + }); + } + + initRenderers() async { + await _remoteVideoRenderer.initialize(); + } + + void handleWebRtcEvent(type, sdp, id, label, candidate) async { + var description = { + "type": type, + "sdp": sdp, + }; + if (description["type"] == "offer") { + _setRemoteDescription(description); + } + if (description["type"] == "candidate") { + var candidateValue = { + "id": id, + "label": label, + "candidate": candidate, + }; + _addCandidate(candidateValue); + } + } + + void _setRemoteDescription(doc) async { + RTCSessionDescription description = + RTCSessionDescription(doc["sdp"], doc["type"]); + await _peerConnection + ?.setRemoteDescription(description) + .whenComplete(() => createAnswer()); + } + + void _addCandidate(candidateValue) async { + dynamic candidate = RTCIceCandidate(candidateValue['candidate'], + candidateValue['id'], candidateValue['label']); + await _peerConnection?.addCandidate(candidate); + } + + Future _createPeerConnection() async { + Map configuration = Constants.peerConfiguration; + final Map offerSdpConstraints = + Constants.offerSdpConstraints; + + RTCPeerConnection pc = + await createPeerConnection(configuration, offerSdpConstraints); + + pc.onIceCandidate = (e) { + if (e.candidate != null) { + var output = { + 'type': 'candidate', + 'candidate': e.candidate.toString(), + 'sdpMid': e.sdpMid.toString(), + 'sdpMLineIndex': e.sdpMLineIndex, + }; + final message = jsonEncode(output); + sendMessage(message); + } + }; + + pc.onIceConnectionState = (e) { + log("onIceConnectionState = $e"); + }; + + pc.onAddStream = (MediaStream stream) { + _remoteVideoRenderer.srcObject = stream; + setState(() { + _remoteVideoRenderer; + }); + }; + + return pc; + } + + void createAnswer() async { + final Map offerSdpConstraints = + Constants.offerSdpConstraints; + RTCSessionDescription? description = + await _peerConnection?.createAnswer(offerSdpConstraints); + await _peerConnection?.setLocalDescription(description!); + var data = { + 'type': 'answer', + 'sdp': description?.sdp.toString(), + }; + sendMessage(data); + } + + void sendMessage(message) async { + var newMessage = jsonEncode(message); + clientSocket?.writeln({"webrtc_event": newMessage}); + } + + ControllerState() { + enableLogging(LogTopic.calls); + } + + @override + void initState() { + super.initState(); + registerNewService(); + videoConnection(); + getNewDiscoverServices(); + } + + Future getNewDiscoverServices() async { + final discovery = await startDiscovery('_openbot-server._tcp.'); + discovery.addServiceListener((service, status) { + if (status == ServiceStatus.found) { + services.add(service); + } + }); + } + + Future registerNewService() async { + var port = nextPort; + final service = Service( + name: 'OPEN_BOT_CONTROLLER', + host: InternetAddress.anyIPv4.address, + type: serviceTypeRegister, + port: port, + txt: Constants.textAttribute); + + final registration = await register(service); + _serverSocket = await ServerSocket.bind(service.host, port); + _serverSocket?.listen((socket) { + log('Connection from' + ' ${socket.remoteAddress.address}:${socket.remotePort}'); + if (clientSocket != null) { + socket.close(); + } else { + clientSocket = socket; + _broadcast = clientSocket?.asBroadcastStream(); + + _broadcast?.map((data) => String.fromCharCodes(data)).listen( + (message) { + Map msgInObject; + try { + var jsonArr = message.split("\n"); + for (var element in jsonArr) { + var jsonMsg = json.encode(element); + if (jsonMsg.isNotEmpty && jsonMsg != "\"\"") { + msgInObject = json.decode(json.decode(jsonMsg)); + if (msgInObject["status"] != null) { + processMessageFromBot(msgInObject["status"]); + } + } + } + } catch (e) { + log("error in parsing msg: $e"); + } + }, + onDone: () { + if (kDebugMode) { + print('client left'); + } + setState(() { + videoView = false; + }); + socket.destroy(); + socket.close(); + clientSocket?.destroy(); + clientSocket = null; + setState(() {}); + }, + ); + } + }); + setState(() { + registrations.add(registration); + }); + } + + setDeviceConnected(status) { + if (status == "true") { + setState(() { + videoView = true; + }); + } else if (status == "false") { + setState(() { + videoView = false; + }); + } + } + + Future dismissRegistration(Registration registration) async { + setState(() { + /// remove fast, without confirmation, to avoid "onDismissed" error. + registrations.remove(registration); + }); + + await unregister(registration); + } + + @override + Widget build(BuildContext context) { + if (videoView) { + return MaterialApp( + home: Stack( + children: [ + RTCVideoView( + _remoteVideoRenderer, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, + mirror: mirroredVideo, + ), + ControlSelector(setMirrorVideo, indicatorLeft, indicatorRight, + services, _peerConnection) + ], + ), + debugShowCheckedModeBanner: false, + ); + } else { + return const MaterialApp( + home: DiscoveringDevice(), + debugShowCheckedModeBanner: false, + ); + } + } + + void processMessageFromBot(items) { + String sdp = ""; + String type = ""; + String id = ""; + int label = 0; + String candidate = ""; + if (items["CONNECTION_ACTIVE"] != null) { + setDeviceConnected(items["CONNECTION_ACTIVE"]); + } + + if (items["VIDEO_PROTOCOL"] != null) { + if (items["VIDEO_PROTOCOL"] == "RTSP") { + Fluttertoast.showToast( + msg: + "RTSP not supported by this controller. For video, set your main app to use WebRTC.", + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.BOTTOM, + backgroundColor: Colors.grey, + textColor: Colors.white, + fontSize: 14); + log("RTSP not supported by this controller. For video, set your main app to use WebRTC."); + } + } + + if (items["INDICATOR_LEFT"] != null) { + if (items["INDICATOR_LEFT"] == "true") { + setState(() { + indicatorLeft = true; + }); + } else { + setState(() { + indicatorLeft = false; + }); + } + } + + if (items["INDICATOR_RIGHT"] != null) { + if (items["INDICATOR_RIGHT"] == "true") { + setState(() { + indicatorRight = true; + }); + } else { + setState(() { + indicatorRight = false; + }); + } + } + + if (items["WEB_RTC_EVENT"] != null) { + var webRTCResponse; + if (items["WEB_RTC_EVENT"] is String) { + webRTCResponse = json.decode(items["WEB_RTC_EVENT"]); + } else { + webRTCResponse = items["WEB_RTC_EVENT"]; + } + if (webRTCResponse["type"].toString() == "offer") { + setState(() { + type = webRTCResponse["type"]; + sdp = webRTCResponse["sdp"]; + }); + } + if (webRTCResponse["type"].toString() == "candidate") { + setState(() { + type = webRTCResponse["type"]; + id = webRTCResponse["id"]; + label = webRTCResponse["label"]; + candidate = webRTCResponse["candidate"].toString(); + }); + } + handleWebRtcEvent(type, sdp, id, label, candidate); + } + } +} diff --git a/controller/flutter/lib/screens/discoveringDevices.dart b/controller/flutter/lib/screens/discoveringDevices.dart new file mode 100644 index 000000000..fd33c4ef6 --- /dev/null +++ b/controller/flutter/lib/screens/discoveringDevices.dart @@ -0,0 +1,44 @@ +import 'package:blinking_text/blinking_text.dart'; +import 'package:flutter/material.dart'; + +class DiscoveringDevice extends StatefulWidget { + const DiscoveringDevice({super.key}); + + @override + State createState() { + return DiscoveringDeviceState(); + } +} + +class DiscoveringDeviceState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + alignment: Alignment.center, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "images/openbot_icon.png", + height: 130, + width: 130, + ), + const Padding( + padding: EdgeInsets.all(10), + child: BlinkText("Searching for OpenBot...", + style: TextStyle(fontSize: 25, color: Colors.black), + beginColor: Colors.black, + endColor: Colors.white, + // times: 10, + duration: Duration(milliseconds: 500))), + const Text( + "Select Phone as control mode in the bot app to Connect", + style: TextStyle(fontSize: 20), + ), + ], + )), + ); + } +} diff --git a/controller/flutter/lib/screens/driveCommandReducer.dart b/controller/flutter/lib/screens/driveCommandReducer.dart new file mode 100644 index 000000000..30d801efe --- /dev/null +++ b/controller/flutter/lib/screens/driveCommandReducer.dart @@ -0,0 +1,26 @@ +import 'package:openbot_controller/globals.dart'; +import 'package:openbot_controller/utils/forwardSpeed.dart'; + +class DriveCommandReducer { + static double lastRight = 0; + static double lastLeft = 0; + static double withinRange = .02; + + static void filter(double rightValue, double leftValue) { + if (isDifferent(rightValue, leftValue)) { + lastLeft = leftValue; + lastRight = rightValue; + String msg = + "{driveCmd: {r:${rightValue.toPrecision(2)}, l:${leftValue.toPrecision(2)}}}"; + clientSocket?.writeln(msg); + } + } + + static bool isDifferent(double right, double left) { + if ((left - lastLeft).abs() <= withinRange && + (right - lastRight).abs() <= withinRange) { + return false; + } + return true; + } +} diff --git a/controller/flutter/lib/screens/onScreenMode.dart b/controller/flutter/lib/screens/onScreenMode.dart new file mode 100644 index 000000000..88d204c6e --- /dev/null +++ b/controller/flutter/lib/screens/onScreenMode.dart @@ -0,0 +1,212 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart'; +import 'package:openbot_controller/screens/driveCommandReducer.dart'; +import 'package:openbot_controller/utils/forwardSpeed.dart'; + +import 'component/onScreenIcon.dart'; + +class OnScreenMode extends StatefulWidget { + final dynamic updateMirrorView; + final bool indicatorLeft; + final bool indicatorRight; + final RTCPeerConnection? peerConnection; + + const OnScreenMode(this.updateMirrorView, this.indicatorLeft, + this.indicatorRight, this.peerConnection, + {super.key}); + + @override + State createState() { + return OnScreenModeState(); + } +} + +class OnScreenModeState extends State { + double sliderValueLeft = 0; + double sliderValueRight = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.transparent, + body: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + margin: const EdgeInsets.only(left: 50), + child: Theme( + data: Theme.of(context).copyWith( + sliderTheme: SliderThemeData( + thumbShape: SquareSliderComponentShape(), + trackShape: const MyRoundedRectSliderTrackShape(), + ), + ), + child: RotatedBox( + quarterTurns: -1, + child: Slider( + value: sliderValueLeft, + onChanged: (value) => { + setState(() => sliderValueLeft = value.toPrecision(2)), + DriveCommandReducer.filter( + sliderValueRight, sliderValueLeft) + }, + onChangeEnd: (value) => { + setState(() => sliderValueLeft = 0), + DriveCommandReducer.filter( + sliderValueRight, sliderValueLeft) + }, + min: -1, + max: 1, + activeColor: Colors.white, + inactiveColor: const Color(0xFF292929), + ), + )), + ), + Container( + alignment: AlignmentDirectional.bottomEnd, + margin: const EdgeInsets.only(bottom: 20), + child: OnScreenIcon(widget.updateMirrorView, widget.indicatorLeft, + widget.indicatorRight, widget.peerConnection), + ), + Container( + margin: const EdgeInsets.only(right: 50), + child: Theme( + data: Theme.of(context).copyWith( + sliderTheme: SliderThemeData( + thumbShape: SquareSliderComponentShape(), + trackShape: const MyRoundedRectSliderTrackShape(), + ), + ), + child: RotatedBox( + quarterTurns: -1, + child: Slider( + value: sliderValueRight, + onChanged: (value) => { + setState(() => sliderValueRight = value.toPrecision(2)), + DriveCommandReducer.filter( + sliderValueRight, sliderValueLeft) + }, + onChangeEnd: (value) => { + setState(() => sliderValueRight = 0), + DriveCommandReducer.filter( + sliderValueRight, sliderValueLeft) + }, + min: -1, + max: 1, + activeColor: Colors.white, + inactiveColor: const Color(0xFF292929), + ), + ))), + ], + ), + ); + } +} + +class SquareSliderComponentShape extends SliderComponentShape { + @override + Size getPreferredSize(bool isEnabled, bool isDiscrete) { + return const Size(20, 50); + } + + @override + void paint(PaintingContext context, Offset center, + {required Animation activationAnimation, + required Animation enableAnimation, + required bool isDiscrete, + required TextPainter labelPainter, + required RenderBox parentBox, + required SliderThemeData sliderTheme, + required TextDirection textDirection, + required double value, + required double textScaleFactor, + required Size sizeWithOverflow}) { + final Canvas canvas = context.canvas; + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromCenter(center: center, width: 10, height: 55), + const Radius.circular(2), + ), + Paint()..color = const Color(0xFF0071c5), + ); + } +} + +class MyRoundedRectSliderTrackShape extends SliderTrackShape + with BaseSliderTrackShape { + const MyRoundedRectSliderTrackShape(); + + @override + void paint( + PaintingContext context, + Offset offset, { + required RenderBox parentBox, + required SliderThemeData sliderTheme, + required Animation enableAnimation, + required TextDirection textDirection, + required Offset thumbCenter, + Offset? secondaryOffset, + bool isDiscrete = false, + bool isEnabled = false, + double additionalTrackHeight = 30, + }) { + if (sliderTheme.trackHeight == null) { + return; + } + + final ColorTween activeTrackColorTween = ColorTween( + begin: sliderTheme.disabledActiveTrackColor, + end: sliderTheme.activeTrackColor); + final ColorTween inactiveTrackColorTween = ColorTween( + begin: sliderTheme.disabledInactiveTrackColor, + end: sliderTheme.inactiveTrackColor); + final Paint activePaint = Paint() + ..color = activeTrackColorTween.evaluate(enableAnimation)!; + final Paint inactivePaint = Paint() + ..color = inactiveTrackColorTween.evaluate(enableAnimation)!; + final Paint leftTrackPaint; + final Paint rightTrackPaint; + switch (textDirection) { + case TextDirection.ltr: + leftTrackPaint = activePaint; + rightTrackPaint = inactivePaint; + break; + case TextDirection.rtl: + leftTrackPaint = inactivePaint; + rightTrackPaint = activePaint; + break; + } + + final Rect trackRect = getPreferredRect( + parentBox: parentBox, + offset: offset, + sliderTheme: sliderTheme, + isEnabled: isEnabled, + isDiscrete: isDiscrete, + ); + const Radius activeTrackRadius = Radius.circular(5); + + context.canvas.drawRRect( + RRect.fromLTRBAndCorners( + trackRect.left, + trackRect.top - (additionalTrackHeight / 2), + thumbCenter.dx, + trackRect.bottom + (additionalTrackHeight / 2), + topLeft: activeTrackRadius, + bottomLeft: activeTrackRadius, + ), + leftTrackPaint, + ); + context.canvas.drawRRect( + RRect.fromLTRBAndCorners( + thumbCenter.dx, + trackRect.top - (additionalTrackHeight / 2), + trackRect.right, + trackRect.bottom + (additionalTrackHeight / 2), + topRight: activeTrackRadius, + bottomRight: activeTrackRadius, + ), + rightTrackPaint, + ); + } +} diff --git a/controller/flutter/lib/screens/registeration.dart b/controller/flutter/lib/screens/registeration.dart new file mode 100644 index 000000000..9637bde0f --- /dev/null +++ b/controller/flutter/lib/screens/registeration.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:nsd/nsd.dart'; + +import '../utils/constants.dart'; + +class RegistrationWidget extends StatelessWidget { + final Registration registration; + + RegistrationWidget(this.registration) : super(key: ValueKey(registration.id)); + + @override + Widget build(BuildContext context) { + final service = registration.service; + return Card( + margin: const EdgeInsets.fromLTRB(16, 4, 16, 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ListTile( + leading: const Icon(Icons.wifi_tethering), + title: Text('Registration ${Constants.shorten(registration.id)}'), + subtitle: Text( + 'Name: ${service.name} ▪️ ' + 'Type: ${service.type} ▪️ ' + 'Host: ${service.host} ▪️ ' + 'Port: ${service.port}', + style: const TextStyle(color: Colors.black, fontSize: 12), + ), + ), + const SizedBox( + height: 8, + ), + ], + ), + ); + } +} diff --git a/controller/flutter/lib/screens/tiltingPhoneMode.dart b/controller/flutter/lib/screens/tiltingPhoneMode.dart new file mode 100644 index 000000000..4479199da --- /dev/null +++ b/controller/flutter/lib/screens/tiltingPhoneMode.dart @@ -0,0 +1,173 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:openbot_controller/utils/forwardSpeed.dart'; +import 'package:openbot_controller/utils/phoneSensorToDualDriveConverter.dart'; +import 'package:sensors_plus/sensors_plus.dart'; + +import 'driveCommandReducer.dart'; + +class TiltingPhoneMode extends StatefulWidget { + const TiltingPhoneMode({super.key}); + + @override + State createState() { + return TiltingPhoneModeState(); + } +} + +class TiltingPhoneModeState extends State { + Timer? forwardSpeedTimer; + Timer? backwardSpeedTimer; + bool forward = false; + bool reverse = false; + late double azimuth; + late double pitch; + late double roll; + var phoneAccelerometerToDualDriveConverted = + PhoneSensorToDualDriveConverter(); + double leftSpeedValue = 0; + double rightSpeedValue = 0; + + @override + void initState() { + accelerometerEvents.listen((event) { + azimuth = event.x; + pitch = event.y; + roll = event.z; + var sliderValues = + phoneAccelerometerToDualDriveConverted.convert(azimuth, pitch, roll); + leftSpeedValue = sliderValues.left; + rightSpeedValue = sliderValues.right; + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async { + return true; + }, + child: Scaffold( + backgroundColor: Colors.transparent, + body: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + GestureDetector( + onTapDown: (details) { + setState(() { + ForwardSpeed.reset(); + reverse = true; + }); + }, + onTapUp: (details) { + setState(() { + ForwardSpeed.reset(); + reverse = false; + }); + }, + onLongPressStart: (detail) { + setState(() { + reverse = true; + backwardSpeedTimer = Timer.periodic( + const Duration(milliseconds: 333), (t) { + double decrementSpeed = ForwardSpeed.minNegative / 3; + ForwardSpeed.decrementNegative(decrementSpeed); + DriveCommandReducer.filter( + rightSpeedValue, leftSpeedValue); + }); + }); + }, + onLongPressEnd: (detail) { + setState(() { + reverse = false; + }); + if (backwardSpeedTimer != null) { + ForwardSpeed.reset(); + DriveCommandReducer.filter(0, 0); + backwardSpeedTimer!.cancel(); + } + }, + // Image tapped + child: Container( + margin: const EdgeInsets.fromLTRB(40, 0, 0, 20), + // padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: reverse + ? const Color(0xFF292929) + : const Color(0xFF292929).withOpacity(0.3), + ), + child: Image.asset( + "images/reverse_icon.png", + height: 80, + width: 64, + ), + )), + GestureDetector( + onTapDown: (details) { + setState(() { + forward = true; + ForwardSpeed.reset(); + }); + }, + onTapUp: (details) { + setState(() { + forward = false; + ForwardSpeed.reset(); + }); + }, + onLongPressStart: (detail) { + setState(() { + forward = true; + forwardSpeedTimer = Timer.periodic( + const Duration(milliseconds: 200), (t) { + double incrementValue = + (ForwardSpeed.max - ForwardSpeed.value) / 5; + ForwardSpeed.increment(incrementValue); + DriveCommandReducer.filter( + rightSpeedValue, leftSpeedValue); + }); + }); + }, + onLongPressEnd: (detail) { + setState(() { + forward = false; + }); + if (forwardSpeedTimer != null) { + ForwardSpeed.reset(); + DriveCommandReducer.filter(0, 0); + forwardSpeedTimer!.cancel(); + } + }, + // Image tapped + child: Container( + margin: const EdgeInsets.fromLTRB(0, 0, 40, 20), + // padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: forward + ? const Color(0xFF292929) + : const Color(0xFF292929).withOpacity(0.3), + ), + child: Image.asset( + "images/forward_icon.png", + height: 110, + width: 70, + ), + )), + ], + )) + ], + ), + ), + ); + } +} diff --git a/controller/flutter/lib/utils/constants.dart b/controller/flutter/lib/utils/constants.dart new file mode 100644 index 000000000..9f41a3688 --- /dev/null +++ b/controller/flutter/lib/utils/constants.dart @@ -0,0 +1,30 @@ +import 'dart:typed_data'; + +import '../screens/Controller.dart'; + +class Constants { + static Map textAttribute = { + 'a-string': utf8encoder.convert('κόσμε'), + 'a-blank': Uint8List(0), + 'a-null': null, + }; + + static Map peerConfiguration = { + "iceServers": [ + {"url": "stun:stun.l.google.com:19302"}, + ] + }; + + static Map offerSdpConstraints = { + "mandatory": { + "OfferToReceiveAudio": "false", + "OfferToReceiveVideo": "true", + }, + "optional": [], + }; + + /// Shortens the id for display on-screen. + static String shorten(String? id) { + return id?.toString().substring(0, 4) ?? 'unknown'; + } +} diff --git a/controller/flutter/lib/utils/forwardSpeed.dart b/controller/flutter/lib/utils/forwardSpeed.dart new file mode 100644 index 000000000..3c47e8585 --- /dev/null +++ b/controller/flutter/lib/utils/forwardSpeed.dart @@ -0,0 +1,54 @@ +import 'dart:math'; + +class ForwardSpeed { + static const max = 1.0; + static const min = 0.0; + static const minNegative = -1.0; + static double value = 0; + + static void increment(double incrementValue) { + if (value + incrementValue > max) { + value = max; + } else { + value = (value + incrementValue).toPrecision(2); + } + } + + static void decrement(double decrementValue) { + if (value - decrementValue < min) { + value = (value - decrementValue).toPrecision(2); + } else { + value = min; + } + } + + static void decrementNegative(double decrementValue) { + if (value + decrementValue > minNegative) { + value = (value + decrementValue).toPrecision(2); + } else { + value = minNegative; + } + } + + static void reset() { + value = 0; + } + + static void setTo(double minSpeed) { + if (minSpeed < min && minSpeed > max) { + return; + } + value = minSpeed; + } + + static bool isMin() { + return value <= min; + } +} + +extension Precision on double { + double toPrecision(int fractionDigits) { + num mod = pow(10, fractionDigits.toDouble()); + return ((this * mod).round().toDouble() / mod); + } +} diff --git a/controller/flutter/lib/utils/phoneSensorToDualDriveConverter.dart b/controller/flutter/lib/utils/phoneSensorToDualDriveConverter.dart new file mode 100644 index 000000000..e6ce7a888 --- /dev/null +++ b/controller/flutter/lib/utils/phoneSensorToDualDriveConverter.dart @@ -0,0 +1,39 @@ +import 'package:openbot_controller/utils/forwardSpeed.dart'; + +class PhoneSensorToDualDriveConverter { + static double g = 9.81; + + DualDriveValues convert(double azimuth, double pitch, double roll) { + double leftSpeed = 0; + double rightSpeed = 0; + double forwardSpeed = 0; + + forwardSpeed = ForwardSpeed.value; + leftSpeed = forwardSpeed + (pitch / (g / 2)) * forwardSpeed; + rightSpeed = forwardSpeed - (pitch / (g / 2)) * forwardSpeed; + return DualDriveValues(leftSpeed, rightSpeed); + } +} + +class DualDriveValues { + double MAX = 1.0; + double MIN = -1.0; + double left = 0.0; + double right = 0.0; + + DualDriveValues(double left, double right) { + this.left = clean(left); + this.right = clean(right); + } + + double clean(double value) { + double ret = value; + if (value > MAX) { + ret = MAX; + } + if (value < MIN) { + ret = MIN; + } + return ret; + } +} diff --git a/controller/flutter/pubspec.lock b/controller/flutter/pubspec.lock new file mode 100644 index 000000000..eabcf9292 --- /dev/null +++ b/controller/flutter/pubspec.lock @@ -0,0 +1,554 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + blinking_text: + dependency: "direct main" + description: + name: blinking_text + sha256: "3f0c300f9f67ff3455e303a7dea7825bd96965d17295e4e831f29040c0379e69" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + url: "https://pub.dev" + source: hosted + version: "1.17.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + url: "https://pub.dev" + source: hosted + version: "3.0.2" + csslib: + dependency: transitive + description: + name: csslib + sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 + url: "https://pub.dev" + source: hosted + version: "0.17.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + url: "https://pub.dev" + source: hosted + version: "1.0.5" + dart_webrtc: + dependency: transitive + description: + name: dart_webrtc + sha256: "8949301071ac7c3fb8f78ef7dffc48652db16f2ec63d84078b608f2ca27cca38" + url: "https://pub.dev" + source: hosted + version: "1.0.17" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" + source: hosted + version: "2.0.1" + flutter_speed_dial: + dependency: "direct main" + description: + name: flutter_speed_dial + sha256: aba767df2df60ccb10ff9fecadd197c83e49ef4c3bc80bc55865eb4d023781d6 + url: "https://pub.dev" + source: hosted + version: "5.1.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_webrtc: + dependency: "direct main" + description: + name: flutter_webrtc + sha256: "4a3194900981ac7a2aa1e68a37903d8910cc3459c7a1ab68ad0407310cea1642" + url: "https://pub.dev" + source: hosted + version: "0.9.34" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "7cc92eabe01e3f1babe1571c5560b135dfc762a34e41e9056881e2196b178ec1" + url: "https://pub.dev" + source: hosted + version: "8.1.2" + html: + dependency: transitive + description: + name: html + sha256: d9793e10dbe0e6c364f4c59bf3e01fb33a9b2a674bc7a1081693dba0614b6269 + url: "https://pub.dev" + source: hosted + version: "0.15.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + lints: + dependency: transitive + description: + name: lints + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + url: "https://pub.dev" + source: hosted + version: "0.12.15" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + nsd: + dependency: "direct main" + description: + name: nsd + sha256: "55b1f27a75e427ef3ba20d445b341a1c3666209f8a71559b43233877b8d3af08" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + nsd_android: + dependency: transitive + description: + name: nsd_android + sha256: "7a38d0b2d21f1e578cd3020940b95b22d5260413dc0c8cf30a987a4e410b166d" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + nsd_ios: + dependency: transitive + description: + name: nsd_ios + sha256: "7034134dd89595362d5e464030081b0d542120a558ab7fe6227df44365df3e8a" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + nsd_macos: + dependency: transitive + description: + name: nsd_macos + sha256: "2403b8d599f50fc9179db1420a0ffc25bfa8bbeb814aa31ca0a71f804fc938da" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + nsd_platform_interface: + dependency: transitive + description: + name: nsd_platform_interface + sha256: "2f4033fa13cc45375253bf348abdb9712004e656462205543ec9506b43c67bb2" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + nsd_windows: + dependency: transitive + description: + name: nsd_windows + sha256: "06601efdd3268cbce4b90f8e23ae1dab445c97c661fba417821ce118add722e7" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "050e8e85e4b7fecdf2bb3682c1c64c4887a183720c802d323de8a5fd76d372dd" + url: "https://pub.dev" + source: hosted + version: "2.0.11" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: a776c088d671b27f6e3aa8881d64b87b3e80201c64e8869b811325de7a76c15e + url: "https://pub.dev" + source: hosted + version: "2.0.22" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + sha256: "03d639406f5343478352433f00d3c4394d52dac8df3d847869c5e2333e0bbce8" + url: "https://pub.dev" + source: hosted + version: "2.0.11" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: ab0987bf95bc591da42dffb38c77398fc43309f0b9b894dcc5d6f40c4b26c379 + url: "https://pub.dev" + source: hosted + version: "2.1.7" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + sha256: "2a97e7fbb7ae9dcd0dfc1220a78e9ec3e71da691912e617e8715ff2a13086ae8" + url: "https://pub.dev" + source: hosted + version: "2.0.6" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c + url: "https://pub.dev" + source: hosted + version: "2.1.3" + platform: + dependency: transitive + description: + name: platform + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + platform_detect: + dependency: transitive + description: + name: platform_detect + sha256: "14afcb6ffcd93745e39a288db53d1d6522ea25d71f7993c13a367a86c437b54d" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + url: "https://pub.dev" + source: hosted + version: "2.1.3" + process: + dependency: transitive + description: + name: process + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" + source: hosted + version: "4.2.4" + provider: + dependency: "direct main" + description: + name: provider + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + url: "https://pub.dev" + source: hosted + version: "6.0.5" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + sensors_plus: + dependency: "direct main" + description: + name: sensors_plus + sha256: "362c8f4f001838b90dd5206b898bbad941bc0142479eab9a3415f0f79e622908" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + sensors_plus_platform_interface: + dependency: transitive + description: + name: sensors_plus_platform_interface + sha256: "95f0cc08791b8bf0c41c5fa99c84be2a7d5bf60a811ddc17e1438b1e68caf0d3" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + sensors_plus_web: + dependency: transitive + description: + name: sensors_plus_web + sha256: fca8d7d9ab6233b2a059952666415508e252420be1ef54f092d07884da53ec5e + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf: + dependency: "direct main" + description: + name: shelf + sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + url: "https://pub.dev" + source: hosted + version: "1.4.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" + source: hosted + version: "1.9.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + url: "https://pub.dev" + source: hosted + version: "0.5.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" + source: hosted + version: "3.0.7" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: "86b4fb9e30613ef4ff7e47367bfec4b080ab17205b7d969cd12bbebde49476b1" + url: "https://pub.dev" + source: hosted + version: "2.4.10" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: "984388511230bac63feb53b2911a70e829fe0976b6b2213f5c579c4e0a882db3" + url: "https://pub.dev" + source: hosted + version: "2.3.10" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: d9f7a46d6a77680adb03ec05a381025d6e890ebe636637c6c3014cc3926b97e9 + url: "https://pub.dev" + source: hosted + version: "2.3.8" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "42bb75de5e9b79e1f20f1d95f688fac0f95beac4d89c6eb2cd421724d4432dae" + url: "https://pub.dev" + source: hosted + version: "6.0.1" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: b649b07b8f8f553bee4a97a0a53d0fe78a70b115eafaf0105b612b32b05ddb99 + url: "https://pub.dev" + source: hosted + version: "2.0.13" + webrtc_interface: + dependency: transitive + description: + name: webrtc_interface + sha256: "0ac4693f921c81005edefd2f43b9fe84b0ed54481474fe1ee16b789b0c84a77c" + url: "https://pub.dev" + source: hosted + version: "1.0.13" + win32: + dependency: transitive + description: + name: win32 + sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + url: "https://pub.dev" + source: hosted + version: "3.1.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + url: "https://pub.dev" + source: hosted + version: "0.2.0+3" +sdks: + dart: ">=3.0.0-0 <4.0.0" + flutter: ">=3.0.0" diff --git a/controller/flutter/pubspec.yaml b/controller/flutter/pubspec.yaml new file mode 100644 index 000000000..c61a16442 --- /dev/null +++ b/controller/flutter/pubspec.yaml @@ -0,0 +1,115 @@ +name: openbot_controller +description: controller application to control android and ios openbot application + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=2.18.5 <3.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + sensors_plus: ^1.2.2 + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + nsd: ^2.3.0 + provider: ^6.0.2 + flutter_speed_dial: ^5.1.0 + uuid: ^3.0.6 + shelf: ^1.3.0 + blinking_text: ^1.0.2 + video_player: ^2.4.10 + flutter_webrtc: ^0.9.34 + fluttertoast: ^8.1.2 +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - images/openbot_icon.png + - images/arrow_icon.png + - images/controller_icon.png + - images/tilting_phone_icon.png + - images/cross_icon.png + - images/mirror_view_icon_blue.png + - images/speaker_icon_blue.png + - images/camera_icon_blue.png + - images/left_indicator_icon_blue.png + - images/right_indicator_icon_blue.png + - images/mirror_view_icon_white.png + - images/speaker_icon_white.png + - images/camera_icon_white.png + - images/left_indicator_icon_white.png + - images/right_indicator_icon_white.png + - images/forward_icon.png + - images/reverse_icon.png +# - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/docs/images/android_editor.jpg b/docs/images/android_editor.jpg new file mode 100644 index 000000000..e6516b15f Binary files /dev/null and b/docs/images/android_editor.jpg differ diff --git a/docs/images/editor_android.jpg b/docs/images/editor_android.jpg new file mode 100644 index 000000000..c07b726c1 Binary files /dev/null and b/docs/images/editor_android.jpg differ diff --git a/docs/images/flutter_controller_connected.jpg b/docs/images/flutter_controller_connected.jpg new file mode 100644 index 000000000..98426c429 Binary files /dev/null and b/docs/images/flutter_controller_connected.jpg differ diff --git a/docs/images/flutter_controller_dual_drive_mode.jpg b/docs/images/flutter_controller_dual_drive_mode.jpg new file mode 100644 index 000000000..fd13ad9ee Binary files /dev/null and b/docs/images/flutter_controller_dual_drive_mode.jpg differ diff --git a/docs/images/flutter_controller_home.jpg b/docs/images/flutter_controller_home.jpg new file mode 100644 index 000000000..9e4957761 Binary files /dev/null and b/docs/images/flutter_controller_home.jpg differ diff --git a/docs/images/flutter_controller_tilt_mode.jpg b/docs/images/flutter_controller_tilt_mode.jpg new file mode 100644 index 000000000..8c660848e Binary files /dev/null and b/docs/images/flutter_controller_tilt_mode.jpg differ diff --git a/docs/images/free-roam-fragment-selection.jpg b/docs/images/free-roam-fragment-selection.jpg new file mode 100644 index 000000000..aa4eccb03 Binary files /dev/null and b/docs/images/free-roam-fragment-selection.jpg differ diff --git a/docs/images/phone_selection.gif b/docs/images/phone_selection.gif new file mode 100644 index 000000000..ef7de08c0 Binary files /dev/null and b/docs/images/phone_selection.gif differ diff --git a/docs/images/run_editor.jpg b/docs/images/run_editor.jpg new file mode 100644 index 000000000..d8ac3e3c1 Binary files /dev/null and b/docs/images/run_editor.jpg differ diff --git a/policy/frontend/yarn.lock b/policy/frontend/yarn.lock index d5f15a154..41b69fe28 100644 --- a/policy/frontend/yarn.lock +++ b/policy/frontend/yarn.lock @@ -9821,29 +9821,29 @@ selfsigned@^1.10.7: node-forge "^0.10.0" "semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: - version "5.7.1" - resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== semver@7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== semver@7.3.2: version "7.3.2" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== semver@^6.0.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@^7.2.1, semver@^7.3.2: - version "7.3.4" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" @@ -10720,9 +10720,9 @@ toidentifier@1.0.1: integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== tough-cookie@^4.0.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" - integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== + version "4.1.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" + integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== dependencies: psl "^1.1.33" punycode "^2.1.1" @@ -11338,9 +11338,9 @@ which@^2.0.1, which@^2.0.2: isexe "^2.0.0" word-wrap@^1.2.3, word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + version "1.2.4" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" + integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== workbox-background-sync@^5.1.4: version "5.1.4"