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"