diff --git a/android/app/build.gradle b/android/app/build.gradle index d10fc75..291639d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -54,8 +54,9 @@ android { // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdk = 23 targetSdk = flutter.targetSdkVersion - versionCode = 64 - versionName = '1.5.64' + versionCode = 68 + versionName = '1.5.68' + multiDexEnabled =true } // defaultConfig { @@ -95,5 +96,6 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // implementation platform('com.google.firebase:firebase-bom:32.1.1') implementation 'com.stripe:paymentsheet:20.47.0' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2' } diff --git a/android/app/google-services.json b/android/app/google-services.json index 3ded064..2009fef 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -5,6 +5,78 @@ "storage_bucket": "ride-b1bd8.appspot.com" }, "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:594687661098:android:8ec72f5f8b0b0ab8595f53", + "android_client_info": { + "package_name": "com.example.sefer_admin1" + } + }, + "oauth_client": [ + { + "client_id": "594687661098-2u640akrb3k7sak5t0nqki6f4v6hq1bq.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCyfwRXTwSTLOFQSQgN5p7QZgGJVZnEKq0" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "594687661098-2hfb9gumub3j60vb7mqtq794k8spihuh.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "594687661098-8e26699cris2k3nj5msj1osi59it9kpf.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.mobileapp.store.ride" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:594687661098:android:f81fcce13962121a595f53", + "android_client_info": { + "package_name": "com.example.service" + } + }, + "oauth_client": [ + { + "client_id": "594687661098-2u640akrb3k7sak5t0nqki6f4v6hq1bq.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCyfwRXTwSTLOFQSQgN5p7QZgGJVZnEKq0" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "594687661098-2hfb9gumub3j60vb7mqtq794k8spihuh.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "594687661098-8e26699cris2k3nj5msj1osi59it9kpf.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.mobileapp.store.ride" + } + } + ] + } + } + }, { "client_info": { "mobilesdk_app_id": "1:594687661098:android:683982cbf71fa423595f53", @@ -12,6 +84,58 @@ "package_name": "com.mobileapp.store.ride" } }, + "oauth_client": [ + { + "client_id": "594687661098-2dhoogl7be9phobfbu8bbg1sj567iv88.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.mobileapp.store.ride", + "certificate_hash": "9bf3876c66e490f30cd7982fa972d8e52e0edbb6" + } + }, + { + "client_id": "594687661098-4f8qbb4r223su1pphor33l3oe0ie2v46.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.mobileapp.store.ride", + "certificate_hash": "765bbb7c5d30bc58a7ba44372db614d6bbe6e34d" + } + }, + { + "client_id": "594687661098-2u640akrb3k7sak5t0nqki6f4v6hq1bq.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCyfwRXTwSTLOFQSQgN5p7QZgGJVZnEKq0" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "594687661098-2hfb9gumub3j60vb7mqtq794k8spihuh.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "594687661098-8e26699cris2k3nj5msj1osi59it9kpf.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.mobileapp.store.ride" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:594687661098:android:b7ce96c17eb928ca595f53", + "android_client_info": { + "package_name": "com.sefer.driver" + } + }, "oauth_client": [ { "client_id": "594687661098-2u640akrb3k7sak5t0nqki6f4v6hq1bq.apps.googleusercontent.com", @@ -49,6 +173,22 @@ } }, "oauth_client": [ + { + "client_id": "594687661098-7mj1ngkp5aodosos3gsr4252qfemuvan.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.sefer_driver", + "certificate_hash": "765bbb7c5d30bc58a7ba44372db614d6bbe6e34d" + } + }, + { + "client_id": "594687661098-op7a9cpgm9dilgh8nl48bu6aor55f7qj.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.sefer_driver", + "certificate_hash": "6f83a0b80b7e1b30b3dd42811cbc2c60ee931a3b" + } + }, { "client_id": "594687661098-2u640akrb3k7sak5t0nqki6f4v6hq1bq.apps.googleusercontent.com", "client_type": 3 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6a14f65..52fbb87 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,10 +1,14 @@ - + + + + @@ -14,32 +18,48 @@ + + + + + + + + + android:theme="@style/LaunchTheme"> + + + - - + @@ -49,28 +69,77 @@ - - + android:exported="false" + android:foregroundServiceType="location" /> + + + + + + + + + + + + - + android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" + android:exported="true" + android:permission="com.google.android.c2dm.permission.SEND"> + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/java/com/sefer_driver/MyFirebaseMessagingService.kt b/android/app/src/main/java/com/sefer_driver/MyFirebaseMessagingService.kt new file mode 100644 index 0000000..6fb47a0 --- /dev/null +++ b/android/app/src/main/java/com/sefer_driver/MyFirebaseMessagingService.kt @@ -0,0 +1,15 @@ +package com.sefer_driver + +import android.app.IntentService +import android.content.Intent +import android.content.Context + +// TODO: Rename actions, choose action names that describe tasks that this +// IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS +private const val ACTION_FOO = "com.sefer_driver.action.FOO" +private const val ACTION_BAZ = "com.sefer_driver.action.BAZ" + +// TODO: Rename parameters +private const val EXTRA_PARAM1 = "com.sefer_driver.extra.PARAM1" +private const val EXTRA_PARAM2 = "com.sefer_driver.extra.PARAM2" + diff --git a/android/app/src/main/kotlin/com/sefer_driver/MainActivity.kt b/android/app/src/main/kotlin/com/sefer_driver/MainActivity.kt index 67061a9..fee2cc8 100644 --- a/android/app/src/main/kotlin/com/sefer_driver/MainActivity.kt +++ b/android/app/src/main/kotlin/com/sefer_driver/MainActivity.kt @@ -1,8 +1,21 @@ package com.sefer_driver -import io.flutter.embedding.android.FlutterActivity - +import android.content.Intent import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.embedding.engine.FlutterEngine -class MainActivity: FlutterFragmentActivity() { -} \ No newline at end of file +class MainActivity : FlutterFragmentActivity() { + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + try { + super.configureFlutterEngine(MyApplication.flutterEngine) + } catch (e: UninitializedPropertyAccessException) { + super.configureFlutterEngine(flutterEngine) + } + } + + private fun bringAppToForeground() { + val intent = Intent(this, MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) + startActivity(intent) + } +} diff --git a/android/app/src/main/kotlin/com/sefer_driver/MyApplication.kt b/android/app/src/main/kotlin/com/sefer_driver/MyApplication.kt new file mode 100644 index 0000000..75b7ece --- /dev/null +++ b/android/app/src/main/kotlin/com/sefer_driver/MyApplication.kt @@ -0,0 +1,41 @@ +package com.sefer_driver + +import android.app.Application +import android.content.Intent +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.embedding.engine.dart.DartExecutor +import io.flutter.plugin.common.MethodChannel + +class MyApplication : Application() { + companion object { + lateinit var instance: MyApplication + private set + + val flutterEngine: FlutterEngine by lazy { + FlutterEngine(instance).apply { + dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault()) + } + } + } + + override fun onCreate() { + super.onCreate() + instance = this + + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.sefer_driver/app_lifecycle") + .setMethodCallHandler { call, result -> + if (call.method == "bringAppToForeground") { + bringAppToForeground() + result.success(null) + } else { + result.notImplemented() + } + } + } + + private fun bringAppToForeground() { + val intent = Intent(applicationContext, MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) + startActivity(intent) + } +} diff --git a/android/app/src/main/res/layout/custom_notification.xml b/android/app/src/main/res/layout/custom_notification.xml new file mode 100644 index 0000000..b9b0215 --- /dev/null +++ b/android/app/src/main/res/layout/custom_notification.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 545d524..3b173d8 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -3,4 +3,6 @@ default_channel AIzaSyCyfwRXTwSTLOFQSQgN5p7QZgGJVZnEKq0 + FCM Notifications + Notifications from Firebase Cloud Messaging \ No newline at end of file diff --git a/assets/fonts/josefin.ttf b/assets/fonts/josefin.ttf deleted file mode 100644 index 505477e..0000000 Binary files a/assets/fonts/josefin.ttf and /dev/null differ diff --git a/assets/fonts/mohanad.ttf b/assets/fonts/mohanad.ttf deleted file mode 100644 index 854341c..0000000 Binary files a/assets/fonts/mohanad.ttf and /dev/null differ diff --git a/assets/images/logo.gif b/assets/images/logo.gif index 2a94831..02c031e 100644 Binary files a/assets/images/logo.gif and b/assets/images/logo.gif differ diff --git a/assets/images/logo1.png b/assets/images/logo1.png new file mode 100644 index 0000000..797ab90 Binary files /dev/null and b/assets/images/logo1.png differ diff --git a/assets/notify.mp3 b/assets/notify.mp3 deleted file mode 100644 index d10bc28..0000000 Binary files a/assets/notify.mp3 and /dev/null differ diff --git a/assets/order.mp3 b/assets/order.mp3 new file mode 100644 index 0000000..b2b0ba2 Binary files /dev/null and b/assets/order.mp3 differ diff --git a/bubble-master/.gitignore b/bubble-master/.gitignore new file mode 100755 index 0000000..af412f1 --- /dev/null +++ b/bubble-master/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ diff --git a/bubble-master/.metadata b/bubble-master/.metadata new file mode 100755 index 0000000..27b30b4 --- /dev/null +++ b/bubble-master/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 + channel: stable + +project_type: plugin diff --git a/bubble-master/CHANGELOG.md b/bubble-master/CHANGELOG.md new file mode 100755 index 0000000..1c63343 --- /dev/null +++ b/bubble-master/CHANGELOG.md @@ -0,0 +1,14 @@ +## 0.0.1 + +* Created and implemented startBubbleHead and closeBubbleHead usecases for bubble head package + + +## 0.0.2 +* Fix read-me documentation + +## 0.0.3 +* Fix read-me documentation (added `Buy me a coffee link`) + +## 0.0.4 +* Added optional parameter to enable or disable send-app-to-background +* Updated documentation \ No newline at end of file diff --git a/bubble-master/LICENSE b/bubble-master/LICENSE new file mode 100755 index 0000000..a7446aa --- /dev/null +++ b/bubble-master/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 chrisoftech + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bubble-master/README.md b/bubble-master/README.md new file mode 100755 index 0000000..63dffed --- /dev/null +++ b/bubble-master/README.md @@ -0,0 +1,128 @@ +# bubble_head + + +A flutter plugin to enable you launch a bubble while putting your application to background and upon clicking the bubble brings your application back to foreground + +## Getting Started +### Add dependency + +```yaml + dependencies: + bubble_head: ^0.0.4 +``` + + +### Add in android-manifest file (**../main/AndroidManifest.xml**) + +If you are unsure on where to do this, you can reference the example project AndroidManifest.xml file [here](example/android/app/src/main/AndroidManifest.xml) + + +Add `SYSTEM_ALERT_WINDOW` permission in manifest +```xml + +``` + +NOTE: For best UX practices, you should request for `SYSTEM_ALERT_WINDOW` permission on your application launch (if permission `status` is not granted) +To request for permission, we advise the use of this [package](https://pub.dev/packages/permission_handler) + + +Add `intent-filter` in activity tag + +```xml + + + + +``` + +Add `service` in application tag +```xml + +``` + +### Note: To set bubble icon, create `assets/images` folder path and add your png icon with name `icon.png` to the directory (ensure to import assets in your `pubspec.yaml` file) + +**GIF illustration** + +[![](example/assets/images/bubble_head_example.gif)](example/assets/images/bubble_head_example.gif "Bubble-head example") + +### Examples + +**To start bubble** +[This puts your app in background and can be re-launched (brought to foreground) on tap of the bubble] + +```dart + Bubble _bubble = new Bubble(); + + Future startBubbleHead() async { + + try { + await _bubble.startBubbleHead(); + } on PlatformException { + print('Failed to call startBubbleHead'); + } + } +``` + +**To stop/close bubble** + +```dart + Bubble _bubble = new Bubble(); + + Future stopBubbleHead() async { + + try { + await _bubble.stopBubbleHead(); + } on PlatformException { + print('Failed to call stopBubbleHead'); + } + } +``` + +You can prevent the default action of putting your application in background when starting `bubble_head` by setting `sendAppToBackground` parameter when starting bubble head (if you choose to use another means of sending your application to background) + +```dart + Bubble _bubble = new Bubble(); + + Future startBubbleHead() async { + + try { + // this will only display the bubble-head without sending the application to background + await _bubble.startBubbleHead(sendAppToBackground: false); + } on PlatformException { + print('Failed to call startBubbleHead'); + } + } +``` + + +**Other parameters** +(You can choose to tweak **optional** parameters when initializing bubble) + + +```dart + Bubble({ + this.shouldBounce = true, + this.allowDragToClose = true, + this.showCloseButton = false, + }); +``` +```dart + Bubble().startBubbleHead(sendAppToBackground: true); +``` + +**Parameter Definition** +- shouldBounce - Defaults to `True` +(Adds animation to bubble-head) +- allowDragToClose - Defaults to `True` +(Enables dragging bubble to bottom screen to exit) +- showCloseButton - Defaults to `False` +(Adds a close button icon to the bubble-head) +- sendAppToBackground - Defaults to `True` +(Sends application to background) + +## [Buy me a Coffee](https://www.buymeacoffee.com/dsaved) + diff --git a/bubble-master/android/.gitignore b/bubble-master/android/.gitignore new file mode 100755 index 0000000..4c9e2c6 --- /dev/null +++ b/bubble-master/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/bubble-master/android/build.gradle b/bubble-master/android/build.gradle new file mode 100755 index 0000000..328bd9b --- /dev/null +++ b/bubble-master/android/build.gradle @@ -0,0 +1,35 @@ +group 'com.dsaved.bubblehead.bubble' +version '1.0' + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.3' + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' + +android { + namespace 'com.dsaved.bubblehead.bubble' + compileSdkVersion 30 + + defaultConfig { + minSdkVersion 16 + } +} + +dependencies { + implementation 'com.google.android.material:material:1.4.0' +} diff --git a/bubble-master/android/gradle.properties b/bubble-master/android/gradle.properties new file mode 100755 index 0000000..46c1f16 --- /dev/null +++ b/bubble-master/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/bubble-master/android/gradle/wrapper/gradle-wrapper.properties b/bubble-master/android/gradle/wrapper/gradle-wrapper.properties new file mode 100755 index 0000000..e392edb --- /dev/null +++ b/bubble-master/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-6.7-all.zip diff --git a/bubble-master/android/settings.gradle b/bubble-master/android/settings.gradle new file mode 100755 index 0000000..a1c42ae --- /dev/null +++ b/bubble-master/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'bubble' diff --git a/bubble-master/android/src/main/AndroidManifest.xml b/bubble-master/android/src/main/AndroidManifest.xml new file mode 100755 index 0000000..adf9d32 --- /dev/null +++ b/bubble-master/android/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/bubble-master/android/src/main/java/com/dsaved/bubblehead/bubble/BubbleHeadService.java b/bubble-master/android/src/main/java/com/dsaved/bubblehead/bubble/BubbleHeadService.java new file mode 100755 index 0000000..7b86119 --- /dev/null +++ b/bubble-master/android/src/main/java/com/dsaved/bubblehead/bubble/BubbleHeadService.java @@ -0,0 +1,456 @@ +package com.dsaved.bubblehead.bubble; + +import android.annotation.SuppressLint; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.os.Build; +import android.os.CountDownTimer; +import android.os.Handler; +import android.os.IBinder; +import android.util.Base64; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; + +public class BubbleHeadService extends Service implements View.OnClickListener { + private WindowManager mWindowManager; + private View mFloatingWidgetView; + private ImageView remove_image_view; + private final Point szWindow = new Point(); + private View removeFloatingWidgetView; + private static boolean showCloseButton = false, _bounce = true, _dragToClose = true, _sendAppToBackground = true; + private boolean _continueToSnap = false; + + private int x_init_cord, y_init_cord, x_init_margin, y_init_margin; + static Bitmap _image; + + // Set the value for showing close button to true or false + public static void shouldShowCloseButton(Boolean show) { + showCloseButton = show; + } + + // set to true to enable bouncing + public static void bounce(Boolean bounce) { + _bounce = bounce; + } + + // Set the value for drag to close to enable dragging bubble to close + public static void dragToClose(Boolean dragToClose) { + _dragToClose = dragToClose; + } + + public static void sendAppToBackground(Boolean sendAppToBackground) { + _sendAppToBackground = sendAppToBackground; + } + + public static void startService(Context activity, String image) { + byte[] decodedBytes = Base64.decode(image, Base64.DEFAULT); + _image = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length); + + // send application to background if this is true + if (_sendAppToBackground) { + Intent i = new Intent(); + i.setAction(Intent.ACTION_MAIN); + i.addCategory(Intent.CATEGORY_HOME); + activity.startActivity(i); + } + + Intent intent = new Intent(activity, BubbleHeadService.class); + activity.startService(intent); + } + + public static void stopService(Context activity) { + Intent intent = new Intent(activity, BubbleHeadService.class); + activity.stopService(intent); + } + + // create an empty constructor + public BubbleHeadService() { + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @SuppressLint({"ClickableViewAccessibility", "InflateParams"}) + @Override + public void onCreate() { + super.onCreate(); + // init WindowManager + mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); + getWindowManagerDefaultDisplay(); + + // Init LayoutInflater + LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); + + // Inflate the removing view layout we created + removeFloatingWidgetView = inflater.inflate(R.layout.bubble_head_remove_widget, null); + + // Add the view to the window. + WindowManager.LayoutParams paramRemove; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + paramRemove = new WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_PHONE, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + } else { + paramRemove = new WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + } + + // Specify the view position + paramRemove.gravity = Gravity.TOP | Gravity.LEFT; + + // Initially the Removing widget view is not visible, so set visibility to GONE + removeFloatingWidgetView.setVisibility(View.GONE); + remove_image_view = (ImageView) removeFloatingWidgetView.findViewById(R.id.remove_img); + + // Add the view to the window + mWindowManager.addView(removeFloatingWidgetView, paramRemove); + + // Inflate the floating view layout we created + mFloatingWidgetView = inflater.inflate(R.layout.layout_bubble_head, null); + + // Add the view to the window. + WindowManager.LayoutParams params; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_PHONE, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + } else { + params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + } + + // Specify the view position + params.gravity = Gravity.TOP | Gravity.LEFT; + + // Initially view will be added to top-left corner, you change x-y coordinates according to your need + params.x = 0; + params.y = 100; + + // Add the view to the window + mWindowManager.addView(mFloatingWidgetView, params); + + //set image to chatHead + ImageView chatHeadImage = mFloatingWidgetView.findViewById(R.id.chat_head_profile_iv); + chatHeadImage.setImageBitmap(_image); + + // find id of close image button + ImageView closeBubbleHead = mFloatingWidgetView.findViewById(R.id.close_bubble_head); + closeBubbleHead.setOnClickListener(this); + if (!showCloseButton) { + closeBubbleHead.setVisibility(View.GONE); + } + + implementTouchListenerToFloatingWidgetView(); + } + + private void getWindowManagerDefaultDisplay() { + mWindowManager.getDefaultDisplay().getSize(szWindow); + } + + @SuppressLint("ClickableViewAccessibility") + private void implementTouchListenerToFloatingWidgetView() { + _continueToSnap = true; + // Drag and move chat head using user's touch action. + mFloatingWidgetView.findViewById(R.id.root_container); + mFloatingWidgetView.setOnTouchListener(new View.OnTouchListener() { + long time_start = 0, time_end = 0; + + boolean isLongClick = false; + boolean inBounded = false; + int remove_img_width = 0, remove_img_height = 0; + + final Handler handler_longClick = new Handler(); + final Runnable runnable_longClick = new Runnable() { + @Override + public void run() { + isLongClick = true; + removeFloatingWidgetView.setVisibility(View.VISIBLE); + onFloatingWidgetLongClick(); + } + }; + + @Override + public boolean onTouch(View v, MotionEvent event) { + WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) mFloatingWidgetView.getLayoutParams(); + + int x_cord = (int) event.getRawX(); + int y_cord = (int) event.getRawY(); + + int x_cord_Destination, y_cord_Destination; + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + time_start = System.currentTimeMillis(); + + if (_dragToClose) { + handler_longClick.postDelayed(runnable_longClick, 100); + } + remove_img_width = remove_image_view.getLayoutParams().width; + remove_img_height = remove_image_view.getLayoutParams().height; + + x_init_cord = x_cord; + y_init_cord = y_cord; + + // remember the initial position. + x_init_margin = layoutParams.x; + y_init_margin = layoutParams.y; + return true; + case MotionEvent.ACTION_UP: + isLongClick = false; + removeFloatingWidgetView.setVisibility(View.GONE); + remove_image_view.getLayoutParams().height = remove_img_height; + remove_image_view.getLayoutParams().width = remove_img_width; + + if (_dragToClose) { + handler_longClick.removeCallbacks(runnable_longClick); + } + + // If user drag and drop the floating widget view + // into remove view then stop the service + if (inBounded) { + stopSelf(); + inBounded = false; + break; + } + + // Difference between initial coordinate and current coordinate + int x_diff = x_cord - x_init_cord; + int y_diff = y_cord - y_init_cord; + + // check if action move is little as move happen on view with just a tap + if (Math.abs(x_diff) < 5 && Math.abs(y_diff) < 5) { + time_end = System.currentTimeMillis(); + // only perform click if time is less than 200ms + if ((time_end - time_start) < 200) { + onFloatingWidgetClick(); + } + } + + y_cord_Destination = y_init_margin + y_diff; + + int barHeight = getStatusBarHeight(); + if (y_cord_Destination < 0) { + y_cord_Destination = 0; + } else if (y_cord_Destination + (mFloatingWidgetView.getHeight() + barHeight) > szWindow.y) { + y_cord_Destination = szWindow.y - (mFloatingWidgetView.getHeight() + barHeight); + } + + layoutParams.y = y_cord_Destination; + + inBounded = false; + + // reset position + resetPosition(x_cord); + return true; + case MotionEvent.ACTION_MOVE: + int x_diff_move = x_cord - x_init_cord; + int y_diff_move = y_cord - y_init_cord; + + x_cord_Destination = x_init_margin + x_diff_move; + y_cord_Destination = y_init_margin + y_diff_move; + + // If user long click the floating view, update remove view + if (isLongClick) { + int x_bound_left = szWindow.x / 2 - (int) (remove_img_width * 1.5); + int x_bound_right = szWindow.x / 2 + (int) (remove_img_width * 1.5); + int y_bound_top = szWindow.y - (int) (remove_img_height * 1.5); + + // If Floating view comes under Remove View update Window Manager + if ((x_cord >= x_bound_left && x_cord <= x_bound_right) && y_cord >= y_bound_top) { + inBounded = true; + + int x_cord_remove = (int) ((szWindow.x - (remove_img_height * 1.5)) / 2); + int y_cord_remove = (int) (szWindow.y - ((remove_img_width * 1.5) + getStatusBarHeight())); + + if (remove_image_view.getLayoutParams().height == remove_img_height) { + WindowManager.LayoutParams param_remove = (WindowManager.LayoutParams) removeFloatingWidgetView.getLayoutParams(); + param_remove.x = x_cord_remove; + param_remove.y = y_cord_remove; + + mWindowManager.updateViewLayout(removeFloatingWidgetView, param_remove); + } + + layoutParams.x = x_cord_remove + (Math.abs(removeFloatingWidgetView.getWidth() - mFloatingWidgetView.getWidth())) / 2; + layoutParams.y = y_cord_remove + (Math.abs(removeFloatingWidgetView.getHeight() - mFloatingWidgetView.getHeight())) / 2; + + // Update the layout with new X & Y coordinate + mWindowManager.updateViewLayout(mFloatingWidgetView, layoutParams); + break; + } else { + // If Floating window gets out of the Remove view update Remove view again + inBounded = false; + remove_image_view.getLayoutParams().height = remove_img_height; + remove_image_view.getLayoutParams().width = remove_img_width; +// onFloatingWidgetClick(); + } + + } + + layoutParams.x = x_cord_Destination; + layoutParams.y = y_cord_Destination; + + // Update the layout with new X & Y coordinate + mWindowManager.updateViewLayout(mFloatingWidgetView, layoutParams); + return true; + } + return false; + } + }); + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.close_bubble_head) { + stopSelf(); + } + } + + private void onFloatingWidgetLongClick() { + // Get remove Floating view params + WindowManager.LayoutParams removeParams = (WindowManager.LayoutParams) removeFloatingWidgetView.getLayoutParams(); + + // get x and y coordinates of remove view + int x_cord = (szWindow.x - removeFloatingWidgetView.getWidth()) / 2; + int y_cord = szWindow.y - (removeFloatingWidgetView.getHeight() + getStatusBarHeight()); + + removeParams.x = x_cord; + removeParams.y = y_cord; + + // Update Remove view params + mWindowManager.updateViewLayout(removeFloatingWidgetView, removeParams); + } + + // Reset position of Floating Widget view on dragging + private void resetPosition(int x_cord_now) { + if (_continueToSnap) { + if (x_cord_now <= szWindow.x / 2) { + snapToLeft(x_cord_now); + } else { + snapToRight(x_cord_now); + } + } + } + + private void snapToLeft(final int current_x_cord) { + final int x = szWindow.x - current_x_cord; + new CountDownTimer(500, 5) { + final WindowManager.LayoutParams mParams = (WindowManager.LayoutParams) mFloatingWidgetView.getLayoutParams(); + + public void onTick(long t) { + long step = (500 - t) / 5; + mParams.x = -(int) (current_x_cord * current_x_cord * step); + if (_bounce) { + mParams.x = -(int) (double) bounceValue(step, x); + } + mWindowManager.updateViewLayout(mFloatingWidgetView, mParams); + } + + public void onFinish() { + mParams.x = 0; + mWindowManager.updateViewLayout(mFloatingWidgetView, mParams); + } + }.start(); + } + + private void snapToRight(final int current_x_cord) { + new CountDownTimer(500, 5) { + final WindowManager.LayoutParams mParams = (WindowManager.LayoutParams) mFloatingWidgetView.getLayoutParams(); + + public void onTick(long t) { + long step = (500 - t) / 5; + mParams.x = (int) (szWindow.x + (current_x_cord * current_x_cord * step) - mFloatingWidgetView.getWidth()); + if (_bounce) { + mParams.x = szWindow.x + (int) (double) bounceValue(step, current_x_cord) - mFloatingWidgetView.getWidth(); + } + mWindowManager.updateViewLayout(mFloatingWidgetView, mParams); + } + + public void onFinish() { + mParams.x = szWindow.x - mFloatingWidgetView.getWidth(); + mWindowManager.updateViewLayout(mFloatingWidgetView, mParams); + } + }.start(); + } + + private double bounceValue(long step, long scale) { + return scale * Math.exp(-0.15 * step) * Math.cos(0.08 * step); + } + + private int getStatusBarHeight() { + return (int) Math.ceil(25 * getApplicationContext().getResources().getDisplayMetrics().density); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + getWindowManagerDefaultDisplay(); + WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) mFloatingWidgetView.getLayoutParams(); + + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { + if (layoutParams.y + (mFloatingWidgetView.getHeight() + getStatusBarHeight()) > szWindow.y) { + layoutParams.y = szWindow.y - (mFloatingWidgetView.getHeight() + getStatusBarHeight()); + mWindowManager.updateViewLayout(mFloatingWidgetView, layoutParams); + } + + if (layoutParams.x != 0 && layoutParams.x < szWindow.x) { + resetPosition(szWindow.x); + } + } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { + if (layoutParams.x > szWindow.x) { + resetPosition(szWindow.x); + } + } + } + + private void onFloatingWidgetClick() { + _continueToSnap = false; + // bring the application to front + Intent it = new Intent("intent.bring.app.to.foreground"); + it.setComponent(new ComponentName(getPackageName(), getApplicationContext().getPackageName() + ".MainActivity")); + it.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getApplicationContext().startActivity(it); + + // stop the service + stopSelf(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mFloatingWidgetView != null) { + mWindowManager.removeView(mFloatingWidgetView); + } + if (removeFloatingWidgetView != null) { + mWindowManager.removeView(removeFloatingWidgetView); + } + } +} diff --git a/bubble-master/android/src/main/java/com/dsaved/bubblehead/bubble/BubblePlugin.java b/bubble-master/android/src/main/java/com/dsaved/bubblehead/bubble/BubblePlugin.java new file mode 100755 index 0000000..1dc7421 --- /dev/null +++ b/bubble-master/android/src/main/java/com/dsaved/bubblehead/bubble/BubblePlugin.java @@ -0,0 +1,95 @@ +package com.dsaved.bubblehead.bubble; + +import android.content.Context; +import android.os.Build; +import android.provider.Settings; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; + +/** + * BubblePlugin + */ +public class BubblePlugin implements FlutterPlugin, MethodCallHandler, ActivityAware { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private MethodChannel channel; + private Context activity; + + + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { + channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "com.dsaved.bubble.head"); + channel.setMethodCallHandler(this); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { + if (call.method.equals("startBubbleHead")) { + startBubbleHead(result, call); + } else if (call.method.equals("stopBubbleHead")) { + BubbleHeadService.stopService(activity); + } else { + result.notImplemented(); + } + } + + @RequiresApi(api = Build.VERSION_CODES.M) + public void startBubbleHead(@NonNull Result result, MethodCall call) { + if (Settings.canDrawOverlays(activity)) { + boolean bounce = call.argument("bounce"); + BubbleHeadService.bounce(bounce); + + boolean showClose = call.argument("showClose"); + BubbleHeadService.shouldShowCloseButton(showClose); + + boolean dragToClose = call.argument("dragToClose"); + BubbleHeadService.dragToClose(dragToClose); + + boolean sendAppToBackground = call.argument("sendAppToBackground"); + BubbleHeadService.sendAppToBackground(sendAppToBackground); + + String imageByte = call.argument("image"); + BubbleHeadService.startService(activity, imageByte); + } else { + //Permission is not available + result.error("EPERMNOTGRANTED", "permission not available", "Please request permission for: android.permission.SYSTEM_ALERT_WINDOW. with out this permission you cannot launch the bubble head."); + } + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + channel.setMethodCallHandler(null); + } + + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + this.activity = binding.getActivity(); + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + + } + + @Override + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + + } + + @Override + public void onDetachedFromActivity() { + + } +} diff --git a/bubble-master/android/src/main/res/drawable/circle_shape.xml b/bubble-master/android/src/main/res/drawable/circle_shape.xml new file mode 100755 index 0000000..39b30cf --- /dev/null +++ b/bubble-master/android/src/main/res/drawable/circle_shape.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/bubble-master/android/src/main/res/drawable/ic_aspect_ratio_black_24dp.xml b/bubble-master/android/src/main/res/drawable/ic_aspect_ratio_black_24dp.xml new file mode 100755 index 0000000..fd01d34 --- /dev/null +++ b/bubble-master/android/src/main/res/drawable/ic_aspect_ratio_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/bubble-master/android/src/main/res/drawable/ic_close_black_24dp.xml b/bubble-master/android/src/main/res/drawable/ic_close_black_24dp.xml new file mode 100755 index 0000000..20a88e1 --- /dev/null +++ b/bubble-master/android/src/main/res/drawable/ic_close_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/bubble-master/android/src/main/res/drawable/ic_close_white_24dp.xml b/bubble-master/android/src/main/res/drawable/ic_close_white_24dp.xml new file mode 100755 index 0000000..7fb0631 --- /dev/null +++ b/bubble-master/android/src/main/res/drawable/ic_close_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/bubble-master/android/src/main/res/drawable/ic_launcher.png b/bubble-master/android/src/main/res/drawable/ic_launcher.png new file mode 100755 index 0000000..cde69bc Binary files /dev/null and b/bubble-master/android/src/main/res/drawable/ic_launcher.png differ diff --git a/bubble-master/android/src/main/res/drawable/white_circle_shape.xml b/bubble-master/android/src/main/res/drawable/white_circle_shape.xml new file mode 100755 index 0000000..53a328a --- /dev/null +++ b/bubble-master/android/src/main/res/drawable/white_circle_shape.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/bubble-master/android/src/main/res/layout/bubble_head_remove_widget.xml b/bubble-master/android/src/main/res/layout/bubble_head_remove_widget.xml new file mode 100755 index 0000000..532c606 --- /dev/null +++ b/bubble-master/android/src/main/res/layout/bubble_head_remove_widget.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/bubble-master/android/src/main/res/layout/layout_bubble_head.xml b/bubble-master/android/src/main/res/layout/layout_bubble_head.xml new file mode 100755 index 0000000..b237150 --- /dev/null +++ b/bubble-master/android/src/main/res/layout/layout_bubble_head.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + diff --git a/bubble-master/android/src/main/res/values/styles.xml b/bubble-master/android/src/main/res/values/styles.xml new file mode 100755 index 0000000..ce24e43 --- /dev/null +++ b/bubble-master/android/src/main/res/values/styles.xml @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/bubble-master/lib/bubble.dart b/bubble-master/lib/bubble.dart new file mode 100755 index 0000000..03820a2 --- /dev/null +++ b/bubble-master/lib/bubble.dart @@ -0,0 +1,39 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/services.dart'; + +class Bubble { + static const MethodChannel _channel = + const MethodChannel('com.dsaved.bubble.head'); + + bool shouldBounce; + bool showCloseButton; + bool allowDragToClose; + + Bubble({ + this.shouldBounce = true, + this.allowDragToClose = true, + this.showCloseButton = false, + }); + + /// puts app in background and shows floaty-bubble head + Future startBubbleHead({bool sendAppToBackground = true}) async { + ByteData bytes = await rootBundle.load('assets/images/logo1.png'); + var buffer = bytes.buffer; + var encodedImage = base64.encode(Uint8List.view(buffer)); + await _channel.invokeMethod('startBubbleHead', { + "image": encodedImage, + "bounce": shouldBounce, + "showClose": showCloseButton, + "dragToClose": allowDragToClose, + "sendAppToBackground": sendAppToBackground, + }); + } + + /// closes floaty-bubble head + Future stopBubbleHead() async { + await _channel.invokeMethod('stopBubbleHead'); + } +} diff --git a/bubble-master/pubspec.lock b/bubble-master/pubspec.lock new file mode 100755 index 0000000..203ecd5 --- /dev/null +++ b/bubble-master/pubspec.lock @@ -0,0 +1,189 @@ +# 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" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" + source: hosted + version: "14.2.1" +sdks: + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/bubble-master/pubspec.yaml b/bubble-master/pubspec.yaml new file mode 100755 index 0000000..1a07ea9 --- /dev/null +++ b/bubble-master/pubspec.yaml @@ -0,0 +1,63 @@ +name: bubble_head +description: A flutter plugin to enable you launch a bubble while putting your application to background and upon clicking the bubble brings your application back to foreground +version: 0.0.4 +homepage: https://github.com/chrisoftech/bubble +# publish_to: + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=1.20.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +# 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. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' and Android 'package' identifiers should not ordinarily + # be modified. They are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: com.dsaved.bubblehead.bubble + pluginClass: BubblePlugin + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - assets/images/ + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your plugin package, 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 in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/firebase.json b/firebase.json index c712f94..4f7fa9b 100644 --- a/firebase.json +++ b/firebase.json @@ -1 +1 @@ -{"functions":[{"source":"functions","codebase":"default","ignore":["node_modules",".git","firebase-debug.log","firebase-debug.*.log"],"predeploy":["npm --prefix \"$RESOURCE_DIR\" run lint"]}],"flutter":{"platforms":{"android":{"default":{"projectId":"ride-b1bd8","appId":"1:594687661098:android:46557bd4f534b5bb595f53","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"ride-b1bd8","appId":"1:594687661098:ios:4f236057ba0383b0595f53","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"ride-b1bd8","configurations":{"android":"1:594687661098:android:46557bd4f534b5bb595f53","ios":"1:594687661098:ios:4f236057ba0383b0595f53"}}}}}} \ No newline at end of file +{"functions":[{"source":"functions","codebase":"default","ignore":["node_modules",".git","firebase-debug.log","firebase-debug.*.log"],"predeploy":["npm --prefix \"$RESOURCE_DIR\" run lint"]}],"flutter":{"platforms":{"android":{"default":{"projectId":"ride-b1bd8","appId":"1:594687661098:android:b7ce96c17eb928ca595f53","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"ride-b1bd8","appId":"1:594687661098:ios:4f236057ba0383b0595f53","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"ride-b1bd8","configurations":{"android":"1:594687661098:android:b7ce96c17eb928ca595f53","ios":"1:594687661098:ios:4f236057ba0383b0595f53","macos":"1:594687661098:ios:6f69eee1449be943595f53","web":"1:594687661098:web:62d8388476ec91ec595f53","windows":"1:594687661098:web:d9f43a2091395d87595f53"}}},"macos":{"default":{"projectId":"ride-b1bd8","appId":"1:594687661098:ios:6f69eee1449be943595f53","uploadDebugSymbols":false,"fileOutput":"macos/Runner/GoogleService-Info.plist"}}}}} \ No newline at end of file diff --git a/flutter_overlay_apps-main/.gitignore b/flutter_overlay_apps-main/.gitignore new file mode 100644 index 0000000..9be145f --- /dev/null +++ b/flutter_overlay_apps-main/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/flutter_overlay_apps-main/.metadata b/flutter_overlay_apps-main/.metadata new file mode 100644 index 0000000..62d25ea --- /dev/null +++ b/flutter_overlay_apps-main/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: b101bfe32f634566e7cb2791a9efe19cf8828b15 + channel: beta + +project_type: plugin diff --git a/flutter_overlay_apps-main/CHANGELOG.md b/flutter_overlay_apps-main/CHANGELOG.md new file mode 100644 index 0000000..3e7e332 --- /dev/null +++ b/flutter_overlay_apps-main/CHANGELOG.md @@ -0,0 +1,20 @@ +## 1.2.0 + +* Upgraded gradle +* Added Back Button navigation action + +## 1.1.2 + +* exposed overlay StreamController + +## 1.0.2 + +* enabled keyboard input + +## 1.0.1 + +* Update app from overlay fix + +## 1.0.0 + +* Ininial release diff --git a/flutter_overlay_apps-main/LICENSE b/flutter_overlay_apps-main/LICENSE new file mode 100644 index 0000000..caf2d49 --- /dev/null +++ b/flutter_overlay_apps-main/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022, Eddie Genius + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/flutter_overlay_apps-main/README.md b/flutter_overlay_apps-main/README.md new file mode 100644 index 0000000..d496264 --- /dev/null +++ b/flutter_overlay_apps-main/README.md @@ -0,0 +1,179 @@ +# flutter_overlay_apps + +Android plugin for displaying flutter app over other apps + +## Usage + +Add dependency to pubspec.yaml file + + +### Android +You'll need to add the `SYSTEM_ALERT_WINDOW` permission and `OverlayService` to your Android Manifest. +```XML + + + ... + + +``` + +### Entry point +Inside `main.dart` create an entry point for your Overlay widget; +NOTE: `MaterialApp` is required +```dart +// overlay entry point +@pragma("vm:entry-point") +void showOverlay() { + runApp(const MaterialApp( + debugShowCheckedModeBanner: false, + home: Material(child: Text("My overlay")) + )); +} +``` + + +### Methods +To open an overlay, call `FlutterOverlayApps.showOverlay()`. +Default `height` & `width` is fill screen + +```dart +FlutterOverlayApps.showOverlay(height: 300, width: 400, alignment: OverlayAlignment.center); +``` + +To close the overlay widget call +```dart +FlutterOverlayApps.closeOverlay(); +``` +To send data to and from Overlay widget, call +```dart +FlutterOverlayApps.sendDataToAndFromOverlay(); +``` +For listening to broadcasted data, stream the messages by calling +```dart +FlutterOverlayApps.overlayListener().listen((data) { + print(data); +}); +``` + +### Code Example +```dart +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter_overlay_apps/flutter_overlay_apps.dart'; + +void main() { + runApp(const MyApp()); +} + +// overlay entry point +@pragma("vm:entry-point") +void showOverlay() { + runApp(const MaterialApp( + debugShowCheckedModeBanner: false, + home: MyOverlaContent() + )); +} + + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: Center( + child: ElevatedButton( + onPressed: () async { + + // Open overlay + await FlutterOverlayApps.showOverlay(height: 300, width: 400, alignment: OverlayAlignment.center); + + // send data to ovelay + await Future.delayed(const Duration(seconds: 2)); + FlutterOverlayApps.sendDataToAndFromOverlay("Hello from main app"); + }, + child: const Text("showOverlay") + ), + ), + ), + ); + } +} + + + +class MyOverlaContent extends StatefulWidget { + const MyOverlaContent({ Key? key }) : super(key: key); + + @override + State createState() => _MyOverlaContentState(); +} + +class _MyOverlaContentState extends State { + String _dataFromApp = "Hey send data"; + + @override + void initState() { + super.initState(); + + // lisent for any data from the main app + FlutterOverlayApps.overlayListener().listen((event) { + setState(() { + _dataFromApp = event.toString(); + }); + }); + } + + @override + Widget build(BuildContext context) { + return Material( + child: InkWell( + onTap: (){ + // close overlay + FlutterOverlayApps.closeOverlay(); + }, + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Center(child: Text(_dataFromApp, style: const TextStyle(color: Colors.red),)), + ), + ), + ); + } +} +``` + +## Android back button +The back navigation button on android will close the overlay app. To prevent this, set `closeOnBackButton` to `false`. Example; + +```dart +await FlutterOverlayApps.showOverlay( + height: 300, + closeOnBackButton: false, + alignment: OverlayAlignment.center); +``` + +When `closeOnBackButton` is set to `false`, an action will be sent via the stream controller. You can get the action like the example below; + +```dart +var overlayStreamController = FlutterOverlayApps.overlayListener(); + +overlayStreamController.stream.listen((event) { + if(event['method'] == 'backButton'){ + // handle back button + if(mounted && Navigator.of(context).canPop()){ + Navigator.of(context).pop(); + } + } +}) +``` + +Support the plugin Buy Me A Coffee diff --git a/flutter_overlay_apps-main/analysis_options.yaml b/flutter_overlay_apps-main/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/flutter_overlay_apps-main/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/flutter_overlay_apps-main/android/.gitignore b/flutter_overlay_apps-main/android/.gitignore new file mode 100644 index 0000000..161bdcd --- /dev/null +++ b/flutter_overlay_apps-main/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/flutter_overlay_apps-main/android/build.gradle b/flutter_overlay_apps-main/android/build.gradle new file mode 100644 index 0000000..79a5e7b --- /dev/null +++ b/flutter_overlay_apps-main/android/build.gradle @@ -0,0 +1,52 @@ +group 'com.phan_tech.flutter_overlay_apps' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.6.10' + + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 31 + namespace 'com.phan_tech.flutter_overlay_apps' // Specify the namespace here + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + minSdkVersion 16 + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/flutter_overlay_apps-main/android/gradle/wrapper/gradle-wrapper.jar b/flutter_overlay_apps-main/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/flutter_overlay_apps-main/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/flutter_overlay_apps-main/android/gradle/wrapper/gradle-wrapper.properties b/flutter_overlay_apps-main/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ffed3a2 --- /dev/null +++ b/flutter_overlay_apps-main/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/flutter_overlay_apps-main/android/gradlew b/flutter_overlay_apps-main/android/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/flutter_overlay_apps-main/android/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/flutter_overlay_apps-main/android/gradlew.bat b/flutter_overlay_apps-main/android/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/flutter_overlay_apps-main/android/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/flutter_overlay_apps-main/android/settings.gradle b/flutter_overlay_apps-main/android/settings.gradle new file mode 100644 index 0000000..c577374 --- /dev/null +++ b/flutter_overlay_apps-main/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flutter_overlay_apps' diff --git a/flutter_overlay_apps-main/android/src/main/AndroidManifest.xml b/flutter_overlay_apps-main/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..bb97f39 --- /dev/null +++ b/flutter_overlay_apps-main/android/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + diff --git a/flutter_overlay_apps-main/android/src/main/kotlin/com/phan_tech/flutter_overlay_apps/FlutterOverlayAppsPlugin.kt b/flutter_overlay_apps-main/android/src/main/kotlin/com/phan_tech/flutter_overlay_apps/FlutterOverlayAppsPlugin.kt new file mode 100644 index 0000000..97a7a68 --- /dev/null +++ b/flutter_overlay_apps-main/android/src/main/kotlin/com/phan_tech/flutter_overlay_apps/FlutterOverlayAppsPlugin.kt @@ -0,0 +1,131 @@ +package com.phan_tech.flutter_overlay_apps + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.Settings +import androidx.annotation.NonNull +import androidx.annotation.RequiresApi +import io.flutter.FlutterInjector +import io.flutter.embedding.engine.FlutterEngineCache +import io.flutter.embedding.engine.FlutterEngineGroup +import io.flutter.embedding.engine.dart.DartExecutor +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.* +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result + +const val mainAppMethodChannel: String = "com.phan_tech/flutter_overlay_apps" +const val overlayAppMethodChannel: String = "com.phan_tech/flutter_overlay_apps/overlay" +const val overlayAppMessageChannel: String = "com.phan_tech/flutter_overlay_apps/overlay/messenger" + +/** FlutterOverlayAppsPlugin */ +class FlutterOverlayAppsPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, BasicMessageChannel.MessageHandler { + private lateinit var channel: MethodChannel + private lateinit var messenger: BasicMessageChannel + private lateinit var context: Context + private var activity: Activity? = null + + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activity = binding.activity + context = binding.activity.applicationContext + WindowSetup.messenger = messenger + WindowSetup.messenger!!.setMessageHandler(this) + + val engineGroup = FlutterEngineGroup(context) + val dartEntrypoint = DartExecutor.DartEntrypoint( + FlutterInjector.instance().flutterLoader().findAppBundlePath(), + "showOverlay" + ) + val engine = engineGroup.createAndRunEngine(context, dartEntrypoint) + FlutterEngineCache.getInstance().put("my_engine_id", engine) + } + + override fun onDetachedFromActivityForConfigChanges() { + activity = null + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + activity = binding.activity + } + + override fun onDetachedFromActivity() { + activity = null + } + + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, mainAppMethodChannel) + channel.setMethodCallHandler(this) + + messenger = BasicMessageChannel(flutterPluginBinding.binaryMessenger, overlayAppMessageChannel, JSONMessageCodec.INSTANCE) + messenger.setMessageHandler(this) + } + + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { + val currentActivity = activity + if (currentActivity == null) { + result.error("activity_not_attached", "Activity is not attached", null) + return + } + + when (call.method) { + "showOverlay" -> { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + result.error("1", "SDK version is lower than 23", "Requires Android SDK 23 and above") + } else if (!checkPermissions()) { + requestPermissions() + } else { + val height = call.argument("height") + val width = call.argument("width") + val alignment = call.argument("alignment") + val closeOnBackButton = call.argument("closeOnBackButton") + + WindowSetup.width = width ?: -1 + WindowSetup.height = height ?: -1 + WindowSetup.closeOnBackButton = closeOnBackButton ?: true + WindowSetup.setGravityFromAlignment(alignment ?: "center") + currentActivity.startService(Intent(context, OverlayService::class.java)) + result.success(true) + } + } + else -> { + result.notImplemented() + } + } + } + + override fun onMessage(message: Any?, reply: BasicMessageChannel.Reply) { + val engine = FlutterEngineCache.getInstance().get("my_engine_id") + if (engine != null) { + val overlayMessageChannel = BasicMessageChannel(engine.dartExecutor, overlayAppMessageChannel, JSONMessageCodec.INSTANCE) + overlayMessageChannel.send(message, reply) + } else { + reply.reply(null) + } + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun checkPermissions(): Boolean { + return Settings.canDrawOverlays(context) + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun requestPermissions() { + activity?.let { + it.startActivity( + Intent( + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:${it.packageName}") + ) + ) + } + } +} diff --git a/flutter_overlay_apps-main/android/src/main/kotlin/com/phan_tech/flutter_overlay_apps/OverlayService.kt b/flutter_overlay_apps-main/android/src/main/kotlin/com/phan_tech/flutter_overlay_apps/OverlayService.kt new file mode 100644 index 0000000..f7f11a9 --- /dev/null +++ b/flutter_overlay_apps-main/android/src/main/kotlin/com/phan_tech/flutter_overlay_apps/OverlayService.kt @@ -0,0 +1,97 @@ +package com.phan_tech.flutter_overlay_apps + +import android.app.Service +import android.content.Intent +import android.graphics.PixelFormat +import android.os.Build +import android.os.IBinder +import android.view.KeyEvent +import android.view.WindowManager +import android.widget.FrameLayout +import androidx.annotation.RequiresApi +import io.flutter.embedding.android.FlutterView +import io.flutter.embedding.engine.FlutterEngineCache +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.JSONMessageCodec +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import org.json.JSONObject + + +class OverlayService : Service() { + private var windowManager: WindowManager? = null + private lateinit var flutterView: FlutterView + private val flutterChannel = MethodChannel(FlutterEngineCache.getInstance().get("my_engine_id")!!.dartExecutor, overlayAppMethodChannel) + private val overlayMessageChannel = BasicMessageChannel(FlutterEngineCache.getInstance().get("my_engine_id")!!.dartExecutor, overlayAppMessageChannel, JSONMessageCodec.INSTANCE) + + override fun onBind(intent: Intent?): IBinder? { + // Not used + return null + } + + @RequiresApi(Build.VERSION_CODES.O) + override fun onCreate() { + super.onCreate() + + val engine = FlutterEngineCache.getInstance().get("my_engine_id")!! + engine.lifecycleChannel.appIsResumed() + + flutterView = object: FlutterView(applicationContext){ + override fun dispatchKeyEvent(event: KeyEvent): Boolean { + return if (event.keyCode == KeyEvent.KEYCODE_BACK) { + // handle the back button code; + if(WindowSetup.closeOnBackButton){ + stopService(Intent(baseContext, OverlayService().javaClass)) + windowManager?.removeView(flutterView) + }else{ + // send message + overlayMessageChannel.send(JSONObject("{\"method\": \"backButton\"}"))// {"method" "backButton"} + + } + + true + } else super.dispatchKeyEvent(event) + } + + } + + flutterView.attachToFlutterEngine(FlutterEngineCache.getInstance().get("my_engine_id")!!) + flutterView.fitsSystemWindows = true + + flutterChannel.setMethodCallHandler{ methodCall: MethodCall, result: MethodChannel.Result -> + if(methodCall.method == "close"){ + val closed = stopService(Intent(baseContext, OverlayService().javaClass)) + result.success(closed) + } + } + overlayMessageChannel.setMessageHandler(MyHandler()) + + + windowManager = getSystemService(WINDOW_SERVICE) as WindowManager? + + val params = WindowManager.LayoutParams( + WindowSetup.width, + WindowSetup.height, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY + else WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT + ) + params.flags = params.flags and WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv() + params.gravity = WindowSetup.gravity + windowManager!!.addView(flutterView, params) + } + + + override fun onDestroy() { + super.onDestroy() + windowManager!!.removeView(flutterView) + } +} + +class MyHandler: BasicMessageChannel.MessageHandler{ + override fun onMessage(message: Any?, reply: BasicMessageChannel.Reply) { + WindowSetup.messenger!!.send(message) + } + +} \ No newline at end of file diff --git a/flutter_overlay_apps-main/android/src/main/kotlin/com/phan_tech/flutter_overlay_apps/WindowSetup.kt b/flutter_overlay_apps-main/android/src/main/kotlin/com/phan_tech/flutter_overlay_apps/WindowSetup.kt new file mode 100644 index 0000000..b99a055 --- /dev/null +++ b/flutter_overlay_apps-main/android/src/main/kotlin/com/phan_tech/flutter_overlay_apps/WindowSetup.kt @@ -0,0 +1,53 @@ +package com.phan_tech.flutter_overlay_apps + +import android.annotation.SuppressLint +import android.view.Gravity +import android.view.WindowManager +import io.flutter.plugin.common.BasicMessageChannel + +object WindowSetup { + var height: Int = -1 + var width: Int = WindowManager.LayoutParams.MATCH_PARENT + var gravity: Int = Gravity.CENTER + var messenger : BasicMessageChannel? = null + var closeOnBackButton : Boolean = true; + + @SuppressLint("RtlHardcoded") + fun setGravityFromAlignment(alignment: String){ + when { + alignment.lowercase() == "topLeft".lowercase() -> { + gravity = Gravity.TOP or Gravity.LEFT + } + alignment.lowercase() == "topCenter".lowercase() -> { + gravity = Gravity.TOP + } + alignment.lowercase() == "topRight".lowercase() -> { + gravity = Gravity.TOP or Gravity.RIGHT + } + + + alignment.lowercase() == "centerLeft".lowercase() -> { + gravity = Gravity.CENTER or Gravity.LEFT + } + alignment.lowercase() == "center".lowercase() -> { + gravity = Gravity.CENTER + } + alignment.lowercase() == "centerRight".lowercase() -> { + gravity = Gravity.CENTER or Gravity.RIGHT + } + + + alignment.lowercase() == "bottomLeft".lowercase() -> { + gravity = Gravity.BOTTOM or Gravity.LEFT + } + alignment.lowercase() == "bottomCenter".lowercase() -> { + gravity = Gravity.BOTTOM + } + alignment.lowercase() == "bottomRight".lowercase() -> { + gravity = Gravity.BOTTOM or Gravity.RIGHT + } + + + } + } +} \ No newline at end of file diff --git a/flutter_overlay_apps-main/lib/flutter_overlay_apps.dart b/flutter_overlay_apps-main/lib/flutter_overlay_apps.dart new file mode 100644 index 0000000..9e2b8e0 --- /dev/null +++ b/flutter_overlay_apps-main/lib/flutter_overlay_apps.dart @@ -0,0 +1,84 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; + +const int overlaySizeFill = -1; +const String _mainAppMethodChannel = "com.phan_tech./flutter_overlay_apps"; +const String _overlayAppMethodChannel = + "com.phan_tech/flutter_overlay_apps/overlay"; +const String _overlayAppMessageChannel = + "com.phan_tech/flutter_overlay_apps/overlay/messenger"; + +class FlutterOverlayApps { + static const MethodChannel _channel = MethodChannel(_mainAppMethodChannel); + + // overlay methodChanel + static const MethodChannel _overlayChannel = + MethodChannel(_overlayAppMethodChannel); + //Overlay BasicMessageChannel + static const BasicMessageChannel _overlayMessageChannel = + BasicMessageChannel(_overlayAppMessageChannel, JSONMessageCodec()); + + /// Open overLay content + /// Takes optional; + /// - int [height] default is [overlaySizeFill] + /// - int [width] default is [overlaySizeFill] + /// - OverlayAlignment [width] default is [alignment] [OverlayAlignment.center] + /// - bool [closeOnBackButton] default is `true` + static Future showOverlay( + {int height = overlaySizeFill, + int width = overlaySizeFill, + bool closeOnBackButton = true, + OverlayAlignment alignment = OverlayAlignment.center}) async { + final bool? _res = await _channel.invokeMethod('showOverlay', { + "height": height, + "width": width, + "alignment": alignment.name, + "closeOnBackButton": closeOnBackButton + }); + return _res; + } + + /// Closes overlau if open + static Future closeOverlay() async { + final bool? _res = await _overlayChannel.invokeMethod('close'); + return _res; + } + + /// broadcast data to and from overlay app + /// the supported data type are; + /// - [int], [double], [bool], [String], null + /// - [List] of supported types + /// - [Map] of supported types + static Future sendDataToAndFromOverlay(dynamic data) async { + return await _overlayMessageChannel.send(data); + } + + /// Streams message shared between overlay and main app + static final StreamController _controller = StreamController(); + static StreamController overlayListener() { + _overlayMessageChannel.setMessageHandler((message) async { + _controller.add(message); + return message; + }); + return _controller; + } + + /// dispose overlay controller + static void disposeOverlayListener() { + _controller.close(); + } +} + +/// Overlay alignment on screen +enum OverlayAlignment { + topLeft, + topCenter, + topRight, + centerLeft, + center, + centerRight, + bottomLeft, + bottomCenter, + bottomRight +} diff --git a/flutter_overlay_apps-main/pubspec.yaml b/flutter_overlay_apps-main/pubspec.yaml new file mode 100644 index 0000000..d4d42e9 --- /dev/null +++ b/flutter_overlay_apps-main/pubspec.yaml @@ -0,0 +1,27 @@ +name: flutter_overlay_apps +description: Android plugin for displaying flutter widgets over other apps +version: 1.2.0 +homepage: https://github.com/EddieKamau/flutter_overlay_apps + +environment: + sdk: ">=2.16.0 <4.0.0" + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^1.0.0 + + +flutter: + + plugin: + platforms: + android: + package: com.phan_tech.flutter_overlay_apps + pluginClass: FlutterOverlayAppsPlugin + diff --git a/flutter_overlay_apps-main/test/flutter_overlay_apps_test.dart b/flutter_overlay_apps-main/test/flutter_overlay_apps_test.dart new file mode 100644 index 0000000..8aa3449 --- /dev/null +++ b/flutter_overlay_apps-main/test/flutter_overlay_apps_test.dart @@ -0,0 +1,24 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_overlay_apps/flutter_overlay_apps.dart'; + +void main() { + const MethodChannel channel = + MethodChannel('com.phan_tech./flutter_overlay_apps'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return true; + }); + }); + + tearDown(() { + channel.setMockMethodCallHandler(null); + }); + + test('getPlatformVersion', () async { + expect(await FlutterOverlayApps.showOverlay(), true); + }); +} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a6f7ec7..ff10977 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -61,6 +61,8 @@ PODS: - GoogleUtilities/UserDefaults (~> 7.8) - nanopb (< 2.30911.0, >= 2.30908.0) - Flutter (1.0.0) + - flutter_contacts (0.0.1): + - Flutter - flutter_image_compress_common (1.0.0): - Flutter - Mantle @@ -283,6 +285,7 @@ DEPENDENCIES: - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - Flutter (from `Flutter`) + - flutter_contacts (from `.symlinks/plugins/flutter_contacts/ios`) - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) @@ -369,6 +372,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_messaging/ios" Flutter: :path: Flutter + flutter_contacts: + :path: ".symlinks/plugins/flutter_contacts/ios" flutter_image_compress_common: :path: ".symlinks/plugins/flutter_image_compress_common/ios" flutter_local_notifications: @@ -441,6 +446,7 @@ SPEC CHECKSUMS: FirebaseInstallations: 913cf60d0400ebd5d6b63a28b290372ab44590dd FirebaseMessaging: 087a7c7cadef7b9239f005bc4db823894844f323 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + flutter_contacts: edb1c5ce76aa433e20e6cb14c615f4c0b66e0983 flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist index 2d31548..3e8dac8 100644 --- a/ios/Runner/GoogleService-Info.plist +++ b/ios/Runner/GoogleService-Info.plist @@ -6,6 +6,8 @@ 594687661098-9fnj82nef9oagl98prigdf8qne3ddbto.apps.googleusercontent.com REVERSED_CLIENT_ID com.googleusercontent.apps.594687661098-9fnj82nef9oagl98prigdf8qne3ddbto + ANDROID_CLIENT_ID + 594687661098-2dhoogl7be9phobfbu8bbg1sj567iv88.apps.googleusercontent.com API_KEY AIzaSyCf2mW2h0HD8ZYjwh4VOa2ladw6MJkCDTM GCM_SENDER_ID diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 6649464..09237cf 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,18 @@ + UIBackgroundModes + + fetch + location + remote-notification + + BGTaskSchedulerPermittedIdentifiers + + $(PRODUCT_BUNDLE_IDENTIFIER) + + NSContactsUsageDescription + This app requires contacts access to function properly. CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion @@ -21,7 +33,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 51 + 52 CFBundleSignature ???? CFBundleURLTypes @@ -36,7 +48,7 @@ CFBundleVersion - 4.0.51 + 4.0.52 FirebaseAppDelegateProxyEnabled NO GMSApiKey @@ -51,8 +63,8 @@ LSRequiresIPhoneOS NSCameraUsageDescription - This app requires access to your camera in order to scan QR codes and capture - images for uploading and access to connect to a call. + This app requires access to your camera in order to scan QR codes and capture images + for uploading and access to connect to a call. NSFaceIDUsageDescription Use Face ID to securely authenticate payment accounts. NSLocationAlwaysAndWhenInUseUsageDescription @@ -72,12 +84,6 @@ Explanation of why your app needs access to the photo library. UIApplicationSupportsIndirectInputEvents - UIBackgroundModes - - fetch - location - remote-notification - UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/lib/constant/box_name.dart b/lib/constant/box_name.dart index e6332df..d4331d9 100644 --- a/lib/constant/box_name.dart +++ b/lib/constant/box_name.dart @@ -4,6 +4,9 @@ class BoxName { static const String googlaMapApp = "googlaMapApp"; static const String lang = "lang"; + static const String myListString = "myListString"; + static const String myList = "myList"; + static const String bodyOrder = "bodyOrder"; static const String gender = "gender"; static const String carType = "carType"; static const String isFirstTime = "isFirstTime"; diff --git a/lib/constant/colors.dart b/lib/constant/colors.dart index 5abe23d..9a14399 100644 --- a/lib/constant/colors.dart +++ b/lib/constant/colors.dart @@ -21,8 +21,9 @@ class AppColor { // For dynamic elements like gradients static List gradientStartEnd = [ - const Color(0xFF1DA1F2), // Start with primary color - const Color(0xFF0C7ABF), // End with a slightly darker shade of Twitter blue + Color.fromARGB(255, 40, 158, 232), // Start with primary color + Color.fromARGB( + 255, 44, 63, 75), // End with a slightly darker shade of Twitter blue ]; static List secondaryGradientStartEnd = [ diff --git a/lib/constant/links.dart b/lib/constant/links.dart index 4ce9ea9..aa78f5d 100644 --- a/lib/constant/links.dart +++ b/lib/constant/links.dart @@ -257,6 +257,9 @@ class AppLink { static String sendmany = "https://sms.kazumi.me/api/sms/send-many"; static String checkCredit = "https://sms.kazumi.me/api/sms/check-credit"; static String checkStatus = "https://sms.kazumi.me/api/sms/check-status"; + static String getSender = "$server/auth/sms/getSender.php"; + static String updatePhoneInvalidSMS = + "$server/auth/sms/updatePhoneInvalidSMS.php"; //////////////service/////////// diff --git a/lib/constant/style.dart b/lib/constant/style.dart index 961aa75..9d13b27 100644 --- a/lib/constant/style.dart +++ b/lib/constant/style.dart @@ -10,29 +10,30 @@ class AppStyle { fontSize: 40, color: AppColor.accentColor, fontFamily: box.read(BoxName.lang) == 'ar' - ? 'mohanad' - : GoogleFonts.josefinSans().fontFamily); + // ?GoogleFonts.notoNaskhArabic().fontFamily + ? GoogleFonts.notoNaskhArabic().fontFamily + : GoogleFonts.roboto().fontFamily); static TextStyle headTitle2 = TextStyle( fontWeight: FontWeight.bold, fontSize: 26, color: AppColor.writeColor, fontFamily: box.read(BoxName.lang) == 'ar' - ? 'mohanad' - : GoogleFonts.josefinSans().fontFamily); + ? GoogleFonts.notoNaskhArabic().fontFamily + : GoogleFonts.roboto().fontFamily); static TextStyle title = TextStyle( fontWeight: FontWeight.normal, fontSize: box.read(BoxName.lang) == 'ar' ? 14 : 16, color: AppColor.writeColor, fontFamily: box.read(BoxName.lang) == 'ar' - ? 'mohanad' - : GoogleFonts.josefinSans().fontFamily); + ? GoogleFonts.notoNaskhArabic().fontFamily + : GoogleFonts.roboto().fontFamily); static TextStyle subtitle = TextStyle( fontWeight: FontWeight.bold, fontSize: 13, color: AppColor.writeColor, fontFamily: box.read(BoxName.lang) == 'ar' - ? 'mohanad' - : GoogleFonts.josefinSans().fontFamily); + ? GoogleFonts.notoNaskhArabic().fontFamily + : GoogleFonts.roboto().fontFamily); static TextStyle number = const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, @@ -42,9 +43,9 @@ class AppStyle { static BoxDecoration boxDecoration = const BoxDecoration( boxShadow: [ BoxShadow( - color: AppColor.accentColor, blurRadius: 2, offset: Offset(1, 2)), + color: AppColor.accentColor, blurRadius: 5, offset: Offset(2, 4)), BoxShadow( - color: AppColor.accentColor, blurRadius: 2, offset: Offset(-1, -1)) + color: AppColor.accentColor, blurRadius: 5, offset: Offset(-2, -2)) ], color: AppColor.secondaryColor, borderRadius: BorderRadius.all( @@ -53,9 +54,13 @@ class AppStyle { static BoxDecoration boxDecoration1 = const BoxDecoration( boxShadow: [ BoxShadow( - color: AppColor.accentColor, blurRadius: 2, offset: Offset(1, 2)), + color: Color.fromARGB(255, 237, 230, 230), + blurRadius: 5, + offset: Offset(2, 4)), BoxShadow( - color: AppColor.accentColor, blurRadius: 2, offset: Offset(-1, -1)) + color: Color.fromARGB(255, 242, 237, 237), + blurRadius: 5, + offset: Offset(-2, -2)) ], color: AppColor.secondaryColor, borderRadius: BorderRadius.all( diff --git a/lib/constant/table_names.dart b/lib/constant/table_names.dart index 47cd831..b3c8353 100644 --- a/lib/constant/table_names.dart +++ b/lib/constant/table_names.dart @@ -6,4 +6,5 @@ class TableName { static const String rideLocation = "rideLocation"; static const String faceDetectTimes = "faceDetectTimes"; static const String captainNotification = "captainNotification"; + static const String applyRideFromOverLay = "applyRideFromOverLay"; } diff --git a/lib/controller/auth/captin/invit_controller.dart b/lib/controller/auth/captin/invit_controller.dart index 0227201..b02c2d7 100644 --- a/lib/controller/auth/captin/invit_controller.dart +++ b/lib/controller/auth/captin/invit_controller.dart @@ -1,14 +1,18 @@ import 'dart:convert'; import 'package:SEFER/constant/box_name.dart'; +import 'package:SEFER/constant/colors.dart'; import 'package:SEFER/constant/links.dart'; import 'package:SEFER/controller/functions/crud.dart'; import 'package:SEFER/controller/home/payment/captain_wallet_controller.dart'; import 'package:SEFER/views/widgets/mydialoug.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_contacts/contact.dart'; +import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:get/get.dart'; import '../../../main.dart'; +import '../../../print.dart'; import '../../functions/launch.dart'; import '../../notification/notification_captain_controller.dart'; @@ -38,6 +42,40 @@ class InviteController extends GetxController { } } + Future pickContact() async { + try { + print('Requesting contact permission...'); + if (await FlutterContacts.requestPermission(readonly: true)) { + print('Permission granted. Opening external contact picker...'); + final Contact? contact = await FlutterContacts.openExternalPick(); + if (contact != null) { + print('Contact picked: ${contact.displayName}'); + if (contact.phones.isNotEmpty) { + print('Phone number found: ${contact.phones.first.number}'); + invitePhoneController.text = contact.phones.first.number; + update(); + } else { + print('Selected contact has no phone number.'); + Get.snackbar('No phone number'.tr, + 'The selected contact does not have a phone number.'.tr); + } + } else { + print('No contact selected or picker was cancelled.'); + Get.snackbar('No contact selected'.tr, 'Please select a contact'.tr); + } + } else { + print('Permission denied by user or system.'); + Get.snackbar('Permission denied'.tr, + 'Contact permission is required to pick a contact'.tr); + } + } catch (e) { + print('Error picking contact: $e'); + print('Stack trace: ${StackTrace.current}'); + Get.snackbar( + 'Error'.tr, 'An error occurred while picking a contact: $e'.tr); + } + } + void onSelectDriverInvitation(int index) async { MyDialog().getDialog( driverInvitationData[index]['countOfInvitDriver'] < 100 @@ -89,7 +127,7 @@ class InviteController extends GetxController { "driverId": box.read(BoxName.driverID), "inviterDriverPhone": '+2${invitePhoneController.text}' }); - + Log.print('response: ${response}'); if (response != 'failure') { var d = jsonDecode(response); Get.snackbar('Success', 'Invite sent successfully'.tr); @@ -109,7 +147,9 @@ class InviteController extends GetxController { invitePhoneController.clear(); } else { - Get.snackbar('Error', 'Failed to send invite'.tr); + Get.snackbar('Error'.tr, "Invite code already used".tr, + backgroundColor: AppColor.redColor, + duration: const Duration(seconds: 4)); } // } catch (e) { // print('Error sending invite: $e'); diff --git a/lib/controller/auth/captin/login_captin_controller.dart b/lib/controller/auth/captin/login_captin_controller.dart index 505bf56..132e639 100644 --- a/lib/controller/auth/captin/login_captin_controller.dart +++ b/lib/controller/auth/captin/login_captin_controller.dart @@ -55,7 +55,7 @@ class LoginDriverController extends GetxController { 'id': driverID, }); print(res); - if (res == 'Failure') { + if (res == 'failure') { //Failure if (box.read(BoxName.phoneVerified).toString() == '1') { Get.offAll(() => EgyptCardAI()); diff --git a/lib/controller/auth/captin/register_captin_controller.dart b/lib/controller/auth/captin/register_captin_controller.dart index 6a478bc..ec5742c 100644 --- a/lib/controller/auth/captin/register_captin_controller.dart +++ b/lib/controller/auth/captin/register_captin_controller.dart @@ -79,6 +79,21 @@ class RegisterCaptainController extends GetxController { update(); } + bool isValidEgyptianPhoneNumber(String phoneNumber) { + // Remove any whitespace from the phone number + phoneNumber = phoneNumber.replaceAll(RegExp(r'\s+'), ''); + + // Check if the phone number has exactly 11 digits + if (phoneNumber.length != 11) { + return false; + } + + // Check if the phone number starts with 010, 011, 012, or 015 + RegExp validPrefixes = RegExp(r'^01[0125]'); + + return validPrefixes.hasMatch(phoneNumber); + } + sendOtpMessage() async { SmsEgyptController smsEgyptController = Get.put(SmsEgyptController()); @@ -87,21 +102,38 @@ class RegisterCaptainController extends GetxController { update(); if (formKey3.currentState!.validate()) { if (box.read(BoxName.countryCode) == 'Egypt') { - var responseCheker = await CRUD() - .post(link: AppLink.checkPhoneNumberISVerfiedDriver, payload: { - 'phone_number': '+2${phoneController.text}', - }); - if (responseCheker != 'failure') { - var d = jsonDecode(responseCheker); - if (d['message'][0]['is_verified'].toString() == '1') { - Get.snackbar('Phone number is verified before'.tr, '', - backgroundColor: AppColor.greenColor); - box.write(BoxName.isVerified, '1'); - box.write(BoxName.phone, '+2${phoneController.text}'); - await Get.put(LoginDriverController()).loginUsingCredentials( - box.read(BoxName.driverID).toString(), - box.read(BoxName.emailDriver).toString(), - ); + if (isValidEgyptianPhoneNumber(phoneController.text)) { + var responseCheker = await CRUD() + .post(link: AppLink.checkPhoneNumberISVerfiedDriver, payload: { + 'phone_number': '+2${phoneController.text}', + }); + if (responseCheker != 'failure') { + var d = jsonDecode(responseCheker); + if (d['message'][0]['is_verified'].toString() == '1') { + Get.snackbar('Phone number is verified before'.tr, '', + backgroundColor: AppColor.greenColor); + box.write(BoxName.phoneVerified, '1'); + box.write(BoxName.phone, '+2${phoneController.text}'); + await Get.put(LoginDriverController()).loginUsingCredentials( + box.read(BoxName.driverID).toString(), + box.read(BoxName.emailDriver).toString(), + ); + } else { + await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: { + 'phone_number': '+2${phoneController.text}', + 'token_code': randomNumber.toString(), + "driverId": box.read(BoxName.driverID), + "email": box.read(BoxName.emailDriver), + }); + + await smsEgyptController.sendSmsEgypt( + phoneController.text.toString(), randomNumber.toString()); + + isSent = true; + + isLoading = false; + update(); + } } else { await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: { 'phone_number': '+2${phoneController.text}', @@ -112,25 +144,15 @@ class RegisterCaptainController extends GetxController { await smsEgyptController.sendSmsEgypt( phoneController.text.toString(), randomNumber.toString()); + isSent = true; isLoading = false; update(); } } else { - await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: { - 'phone_number': '+2${phoneController.text}', - 'token_code': randomNumber.toString(), - "driverId": box.read(BoxName.driverID), - "email": box.read(BoxName.emailDriver), - }); - - await smsEgyptController.sendSmsEgypt( - phoneController.text.toString(), randomNumber.toString()); - isSent = true; - - isLoading = false; - update(); + Get.snackbar('Phone Number wrong'.tr, '', + backgroundColor: AppColor.redColor); } } } diff --git a/lib/controller/auth/google_sign.dart b/lib/controller/auth/google_sign.dart index 9209dde..fe67d1f 100644 --- a/lib/controller/auth/google_sign.dart +++ b/lib/controller/auth/google_sign.dart @@ -1,4 +1,5 @@ import 'package:SEFER/constant/box_name.dart'; +import 'package:SEFER/constant/colors.dart'; import 'package:SEFER/controller/auth/captin/login_captin_controller.dart'; import 'package:SEFER/main.dart'; import 'package:SEFER/views/auth/captin/cards/sms_signup.dart'; @@ -35,22 +36,69 @@ class GoogleSignInHelper { } } + // static Future signInFromLogin() async { + // // try { + // // final GoogleSignInAccount? googleUser = await _googleSignIn.signIn(); + // // if (googleUser != null) { + // // await _handleSignUp(googleUser); + // // // if (box.read(BoxName.countryCode) == 'Egypt') { + // // await Get.find().loginUsingCredentials( + // // box.read(BoxName.driverID).toString(), + // // box.read(BoxName.emailDriver).toString(), + // // ); + // // // } else if (box.read(BoxName.countryCode) == 'Jordan') { + // // // // Get.to(() => AiPage()); + // // // } + // // } + // // return googleUser; + // // } catch (error) { + // // return null; + // // } + // try { + // final GoogleSignInAccount? googleUser = await _googleSignIn.signIn(); + // if (googleUser != null) { + // // Handle sign-up logic + // await _handleSignUp(googleUser); + // + // // Get country code + // final String countryCode = box.read(BoxName.countryCode).toString(); + // final String driverID = box.read(BoxName.driverID).toString(); + // final String emailDriver = box.read(BoxName.emailDriver).toString(); + // + // // Log-in using credentials based on country code + // if (countryCode == 'Egypt') { + // await Get.find() + // .loginUsingCredentials(driverID, emailDriver); + // } else if (countryCode == 'Jordan') { + // // Add logic for Jordan if needed, e.g., navigate to AiPage + // // Get.to(() => AiPage()); + // } + // } + // return googleUser; + // } catch (error) { + // Get.snackbar('Google Sign-In error', '$error', + // backgroundColor: AppColor.redColor); + // // Log error details + // print('Google Sign-In error: $error'); + // return null; + // } + // } static Future signInFromLogin() async { try { final GoogleSignInAccount? googleUser = await _googleSignIn.signIn(); if (googleUser != null) { await _handleSignUp(googleUser); - // if (box.read(BoxName.countryCode) == 'Egypt') { await Get.find().loginUsingCredentials( box.read(BoxName.driverID).toString(), box.read(BoxName.emailDriver).toString(), ); - // } else if (box.read(BoxName.countryCode) == 'Jordan') { - // // Get.to(() => AiPage()); - // } } return googleUser; } catch (error) { + Get.snackbar('Google Sign-In error', '$error', + backgroundColor: AppColor.redColor); + // Log error details + print('Google Sign-In error: $error'); return null; } } diff --git a/lib/controller/firebase/bring_app_foreground.dart b/lib/controller/firebase/bring_app_foreground.dart new file mode 100644 index 0000000..a99f2c5 --- /dev/null +++ b/lib/controller/firebase/bring_app_foreground.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class AppLifecycleManager { + static const platform = MethodChannel('com.sefer_driver/app_lifecycle'); + + static Future bringAppToForeground() async { + try { + debugPrint('Attempting to bring app to foreground'); + await platform.invokeMethod('bringAppToForeground'); + debugPrint('Method invocation completed'); + } on PlatformException catch (e) { + debugPrint("Failed to bring app to foreground: '${e.message}'."); + } catch (e) { + debugPrint("Unexpected error: $e"); + } + } +} diff --git a/lib/controller/firebase/firbase_messge.dart b/lib/controller/firebase/firbase_messge.dart index 948aa68..798091e 100644 --- a/lib/controller/firebase/firbase_messge.dart +++ b/lib/controller/firebase/firbase_messge.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'dart:io'; import 'package:SEFER/controller/home/captin/home_captain_controller.dart'; -import 'package:SEFER/views/home/Captin/home_captain/widget/call_page.dart'; import 'package:SEFER/views/widgets/mydialoug.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; @@ -79,9 +78,27 @@ class FirebaseMessagesController extends GetxController { } }); FirebaseMessaging.onBackgroundMessage((RemoteMessage message) async { - // Handle background message - if (message.data.isNotEmpty && message.notification != null) { - fireBaseTitles(message); + if (message.notification!.title! == 'Order'.tr) { + if (Platform.isAndroid) { + NotificationController() + .showNotification('Order'.tr, '', 'order', 'order_page_payload'); + } + // await FirebaseMessagesController().showOverlayNotification(message); + var myListString = message.data['DriverList']; + // var points = message.data['PolylineJson']; + + var myList = jsonDecode(myListString) as List; + // var myPoints = jsonDecode(points) as List; + driverToken = myList[14].toString(); + // This is for location using and uploading status + Get.put(HomeCaptainController()).changeRideId(); + update(); + Get.to(() => OrderRequestPage(), arguments: { + 'myListString': myListString, + 'DriverList': myList, + // 'PolylineJson': myPoints, + 'body': message.notification!.body + }); } }); @@ -95,8 +112,9 @@ class FirebaseMessagesController extends GetxController { Future fireBaseTitles(RemoteMessage message) async { if (message.notification!.title! == 'Order'.tr) { if (Platform.isAndroid) { - NotificationController().showNotification('Order'.tr, '', 'order'); + NotificationController().showNotification('Order'.tr, '', 'order', ''); } + // await FirebaseMessagesController().showOverlayNotification(message); var myListString = message.data['DriverList']; // var points = message.data['PolylineJson']; @@ -113,8 +131,10 @@ class FirebaseMessagesController extends GetxController { 'body': message.notification!.body }); } else if (message.notification!.title == 'Cancel Trip') { - NotificationController().showNotification( - 'Cancel Trip'.tr, 'Passenger Cancel Trip'.tr, 'cancel'); + // if (Platform.isAndroid) { + // NotificationController().showNotification( + // 'Cancel Trip'.tr, 'Passenger Cancel Trip'.tr, 'cancel', ''); + // } cancelTripDialog(); } else if (message.notification!.title! == 'token change') { // NotificationController() @@ -122,14 +142,18 @@ class FirebaseMessagesController extends GetxController { // GoogleSignInHelper.signOut(); GoogleSignInHelper.signOut(); } else if (message.notification!.title! == 'message From passenger') { - NotificationController() - .showNotification('message From passenger', ''.tr, 'tone2'); + if (Platform.isAndroid) { + NotificationController() + .showNotification('message From passenger', ''.tr, 'tone2', ''); + } passengerDialog(message.notification!.body!); update(); } else if (message.notification!.title! == 'face detect') { - NotificationController() - .showNotification('face detect'.tr, ''.tr, 'tone2'); + if (Platform.isAndroid) { + NotificationController() + .showNotification('face detect'.tr, ''.tr, 'tone2', ''); + } String result0 = await faceDetector(); // Handle the result here, e.g., show a dialog or update the UI var result = jsonDecode(result0); @@ -156,19 +180,20 @@ class FirebaseMessagesController extends GetxController { } else if (message.notification!.title! == 'Hi ,I will go now') { // Get.snackbar('Hi ,I will go now', '', // backgroundColor: AppColor.greenColor); - NotificationController().showNotification( - 'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'tone2'); + if (Platform.isAndroid) { + NotificationController().showNotification( + 'Passenger come to you'.tr, 'Hi ,I will go now'.tr, 'tone2', ''); + } update(); } else if (message.notification!.title! == 'Call Income'.tr) { try { var myListString = message.data['passengerList']; var driverList = jsonDecode(myListString) as List; // if (Platform.isAndroid) { - NotificationController().showNotification( - 'Call Income'.tr, - message.notification!.body!, - 'iphone_ringtone', - ); + if (Platform.isAndroid) { + NotificationController().showNotification('Call Income'.tr, + message.notification!.body!, 'iphone_ringtone', ''); + } // } // Assuming GetMaterialApp is initialized and context is valid for navigation // Get.to(() => PassengerCallPage( @@ -183,11 +208,10 @@ class FirebaseMessagesController extends GetxController { var myListString = message.data['passengerList']; var driverList = jsonDecode(myListString) as List; // if (Platform.isAndroid) { - NotificationController().showNotification( - 'Call Income'.tr, - message.notification!.body!, - 'iphone_ringtone', - ); + if (Platform.isAndroid) { + NotificationController().showNotification('Call Income'.tr, + message.notification!.body!, 'iphone_ringtone', ''); + } // } // Assuming GetMaterialApp is initialized and context is valid for navigation // Get.to(() => CallPage( @@ -198,11 +222,13 @@ class FirebaseMessagesController extends GetxController { } catch (e) {} } else if (message.notification!.title! == "Criminal Document Required".tr) { - NotificationController().showNotification( - "Criminal Document Required".tr, - message.notification!.body!, - 'tone2', - ); + if (Platform.isAndroid) { + NotificationController().showNotification( + "Criminal Document Required".tr, + message.notification!.body!, + 'tone2', + ''); + } MyDialog().getDialog( "Criminal Document Required".tr, 'You should have upload it .'.tr, () { @@ -215,10 +241,7 @@ class FirebaseMessagesController extends GetxController { var driverList = jsonDecode(myListString) as List; if (Platform.isAndroid) { NotificationController().showNotification( - 'Call End'.tr, - message.notification!.body!, - 'tone2', - ); + 'Call End'.tr, message.notification!.body!, 'tone2', ''); } // Assuming GetMaterialApp is initialized and context is valid for navigation // Get.off(const CallPage()); @@ -247,11 +270,14 @@ class FirebaseMessagesController extends GetxController { 'body': message.notification!.body }); } else if (message.notification!.title! == 'Order Applied'.tr) { - NotificationController().showNotification( - 'The order Accepted by another Driver'.tr, - 'We regret to inform you that another driver has accepted this order.' - .tr, - 'order'); + if (Platform.isAndroid) { + NotificationController().showNotification( + 'The order Accepted by another Driver'.tr, + 'We regret to inform you that another driver has accepted this order.' + .tr, + 'order', + ''); + } } } @@ -623,3 +649,34 @@ class FirebaseMessagesController extends GetxController { } } } + +class OverlayContent extends StatelessWidget { + final String title; + final String body; + + OverlayContent(this.title, this.body); + + @override + Widget build(BuildContext context) { + return Material( + child: Container( + padding: EdgeInsets.all(16.0), + color: Colors.white, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + title, + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8.0), + Text( + body, + style: TextStyle(fontSize: 16), + ), + ], + ), + ), + ); + } +} diff --git a/lib/controller/firebase/local_notification.dart b/lib/controller/firebase/local_notification.dart index d98b6db..ddfe21b 100644 --- a/lib/controller/firebase/local_notification.dart +++ b/lib/controller/firebase/local_notification.dart @@ -1,6 +1,15 @@ +import 'dart:convert'; + +import 'package:SEFER/constant/box_name.dart'; +import 'package:SEFER/constant/colors.dart'; +import 'package:SEFER/views/home/Captin/orderCaptin/order_request_page.dart'; +import 'package:SEFER/views/home/my_wallet/walet_captain.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:get/get.dart'; +import '../../main.dart'; +import '../../print.dart'; + class NotificationController extends GetxController { final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); @@ -8,22 +17,108 @@ class NotificationController extends GetxController { // Initializes the local notifications plugin Future initNotifications() async { const AndroidInitializationSettings android = - AndroidInitializationSettings('@mipmap/launcher_icon'); + AndroidInitializationSettings('app_icon'); + const InitializationSettings initializationSettings = InitializationSettings(android: android); - await _flutterLocalNotificationsPlugin.initialize(initializationSettings); + + await _flutterLocalNotificationsPlugin.initialize( + initializationSettings, + onDidReceiveNotificationResponse: onDidReceiveNotificationResponse, + onDidReceiveBackgroundNotificationResponse: + onDidReceiveBackgroundNotificationResponse, + ); + + // Create a notification channel + const AndroidNotificationChannel channel = AndroidNotificationChannel( + 'order_channel', // Channel ID + 'Order Notifications', // Channel name + description: + 'This channel is used for order notifications.', // Channel description + importance: Importance.max, + ); + + // Register the channel with the system + await _flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(channel); } // Displays a notification with the given title and message - void showNotification(String title, String message, String tone) async { - AndroidNotificationDetails android = AndroidNotificationDetails( - 'your channel id', 'your channel name', - importance: Importance.max, - priority: Priority.high, - showWhen: false, - sound: RawResourceAndroidNotificationSound(tone)); + void showNotification( + String title, String message, String tone, String payLoad) async { + BigTextStyleInformation bigTextStyleInformation = BigTextStyleInformation( + message, + contentTitle: title.tr, + htmlFormatContent: true, + htmlFormatContentTitle: true, + ); + + AndroidNotificationDetails android = + AndroidNotificationDetails('order_channel', 'Order Notifications', + importance: Importance.max, + priority: Priority.high, + styleInformation: bigTextStyleInformation, + playSound: true, + sound: RawResourceAndroidNotificationSound(tone), + audioAttributesUsage: AudioAttributesUsage.alarm, + visibility: NotificationVisibility.public, + autoCancel: false, + color: AppColor.primaryColor, + showProgress: true, + showWhen: true, + subText: message, + actions: [ + AndroidNotificationAction( + allowGeneratedReplies: true, + 'id', + title.tr, + titleColor: AppColor.bronze, + showsUserInterface: true, + ) + ], + category: AndroidNotificationCategory.call); NotificationDetails details = NotificationDetails(android: android); - await _flutterLocalNotificationsPlugin.show(0, title, message, details); + + await _flutterLocalNotificationsPlugin.show(0, title, message, details, + payload: payLoad); + + // payload: 'order_page_payload'); + } + + // Callback when the notification is tapped + void onDidReceiveNotificationResponse(NotificationResponse response) { + // jsonDecode(response.payload); + print('Notification tapped!'); + if (response.payload != null) { + print('Notification payload: ${response.payload}'); + // if (response.payload != 'order_page_payload') { + // Log.print('arguments: ${box.read(BoxName.rideArguments)}'); + closeOverLay(); + Get.to(() => OrderRequestPage(), + arguments: {'myListString': response.payload}); + // } + } + } + + void onDidReceiveLocalNotification( + int id, String? title, String? body, String? payload) async { + // display a dialog with the notification details, tap ok to go to another page + } + // Callback when the notification is tapped while the app is in the background + void onDidReceiveBackgroundNotificationResponse( + NotificationResponse response) { + print('Notification tapped while app is in background!'); + if (response.payload != null) { + print('Notification payload: ${response.payload}'); + if (response.payload == 'order') { + Get.to(() => OrderRequestPage(), + arguments: box.read(BoxName.rideArguments)); + closeOverLay(); + Log.print('arguments: ${box.read(BoxName.rideArguments)}'); + } + } } } diff --git a/lib/controller/firebase/order_lay.dart b/lib/controller/firebase/order_lay.dart new file mode 100644 index 0000000..7d5dc75 --- /dev/null +++ b/lib/controller/firebase/order_lay.dart @@ -0,0 +1,37 @@ +import 'package:SEFER/constant/links.dart'; +import 'package:SEFER/controller/functions/crud.dart'; +import 'package:SEFER/views/home/Captin/home_captain/home_captin.dart'; +import 'package:SEFER/views/home/Captin/orderCaptin/order_request_page.dart'; +import 'package:SEFER/views/widgets/elevated_btn.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class OverlayContent1 extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Material( + child: Container( + padding: EdgeInsets.all(16.0), + color: Colors.white, + child: MyElevatedButton( + title: 'go to order', + onPressed: () async { + var res = await CRUD().post( + link: AppLink.addFeedBack, + payload: { + "passengerId": 'dddddd', + "feedBack": "eeeee", + }, + ); + print(res); + if (res != 'failure') { + Navigator.push( + context, MaterialPageRoute(builder: (cont) => HomeCaptain())); + // Get.to(OrderRequestPage()); + } + }, + ), + ), + ); + } +} diff --git a/lib/controller/functions/crud.dart b/lib/controller/functions/crud.dart index d47df94..ec554dd 100644 --- a/lib/controller/functions/crud.dart +++ b/lib/controller/functions/crud.dart @@ -29,6 +29,9 @@ class CRUD { }, ); // if (response.statusCode == 200) { + // Log.print('response: ${response.request}'); + // Log.print('response: ${response.body}'); + // Log.print('response: ${payload}'); var jsonData = jsonDecode(response.body); if (jsonData['status'] == 'success') { return response.body; @@ -230,7 +233,7 @@ class CRUD { if (jsonData['status'] == 'success') { return response.body; } else { - String errorMessage = jsonData['message']; + // String errorMessage = jsonData['message']; // Get.snackbar('Error'.tr, errorMessage.tr, // backgroundColor: AppColor.redColor); return (jsonData['status']); diff --git a/lib/controller/functions/location_background_controller.dart b/lib/controller/functions/location_background_controller.dart index d901200..1a18f94 100644 --- a/lib/controller/functions/location_background_controller.dart +++ b/lib/controller/functions/location_background_controller.dart @@ -28,7 +28,7 @@ class LocationBackgroundController extends GetxController { await BackgroundLocation.setAndroidNotification( title: "Background Location", message: "Tracking location...", - icon: "@mipmap/ic_launcher", + icon: "@mipmap/launcher_icon", ); // Set the location update interval to 5 seconds diff --git a/lib/controller/functions/location_controller.dart b/lib/controller/functions/location_controller.dart index 3dcfa67..53c160c 100644 --- a/lib/controller/functions/location_controller.dart +++ b/lib/controller/functions/location_controller.dart @@ -38,7 +38,7 @@ class LocationController extends GetxController { getLocation(); // startLocationUpdates(); - totalPoints = Get.put(CaptainWalletController()).totalPoints; + totalPoints = Get.put(CaptainWalletController()).totalPoints.toString(); // isActive = Get.put(HomeCaptainController()).isActive; } @@ -47,7 +47,8 @@ class LocationController extends GetxController { _locationTimer = Timer.periodic(const Duration(seconds: 5), (timer) async { try { - totalPoints = Get.find().totalPoints; + totalPoints = + Get.find().totalPoints.toString(); isActive = Get.find().isActive; if (isActive) { if (double.parse(totalPoints) > -3000) { diff --git a/lib/controller/functions/overlay_permisssion.dart b/lib/controller/functions/overlay_permisssion.dart new file mode 100644 index 0000000..86d70c2 --- /dev/null +++ b/lib/controller/functions/overlay_permisssion.dart @@ -0,0 +1,56 @@ +import 'dart:io'; + +import 'package:SEFER/views/widgets/mydialoug.dart'; +import 'package:flutter_overlay_window/flutter_overlay_window.dart'; +import 'package:get/get.dart'; +import 'package:permission_handler/permission_handler.dart'; + +Future getPermissionOverlay() async { + if (Platform.isAndroid) { + final bool status = await FlutterOverlayWindow.isPermissionGranted(); + if (status == false) { + MyDialog().getDialog( + 'Allow overlay permission'.tr, + 'To display orders instantly, please grant permission to draw over other apps.' + .tr, + () async { + Get.back(); + await FlutterOverlayWindow.requestPermission(); + }, + ); + } + } +} + +Future getPermissionLocation() async { + final PermissionStatus status = await Permission.location.request(); + if (status.isDenied) { + MyDialog().getDialog( + 'Enable Location Permission'.tr, // {en:ar} + 'Allowing location access will help us display orders near you. Please enable it now.' + .tr, // {en:ar} + () async { + Get.back(); + await FlutterOverlayWindow.requestPermission(); + }, + ); + } +} + +Future getOverLay(String myListString) async { + bool isOverlayActive = await FlutterOverlayWindow.isActive(); + if (isOverlayActive) { + await FlutterOverlayWindow.closeOverlay(); + } + await FlutterOverlayWindow.showOverlay( + enableDrag: true, + flag: OverlayFlag.focusPointer, + visibility: NotificationVisibility.visibilityPublic, + positionGravity: PositionGravity.auto, + height: 700, + width: WindowSize.matchParent, + startPosition: const OverlayPosition(0, -150), + ); + + await FlutterOverlayWindow.shareData(myListString); +} diff --git a/lib/controller/functions/sms_egypt_controller.dart b/lib/controller/functions/sms_egypt_controller.dart index 19625d2..d172e90 100644 --- a/lib/controller/functions/sms_egypt_controller.dart +++ b/lib/controller/functions/sms_egypt_controller.dart @@ -4,21 +4,37 @@ import 'package:SEFER/constant/api_key.dart'; import 'package:SEFER/constant/box_name.dart'; import 'package:SEFER/constant/info.dart'; import 'package:SEFER/constant/links.dart'; +import 'package:SEFER/controller/auth/captin/register_captin_controller.dart'; +import 'package:SEFER/controller/functions/crud.dart'; import 'package:SEFER/main.dart'; import 'package:SEFER/views/widgets/elevated_btn.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; +import '../../print.dart'; +import '../auth/captin/login_captin_controller.dart'; + class SmsEgyptController extends GetxController { var headers = {'Content-Type': 'application/json'}; + Future getSender() async { + var res = await CRUD().get(link: AppLink.getSender, payload: {}); + if (res != 'failure') { + var d = jsonDecode(res)['message'][0]['senderId'].toString(); + return d; + } else { + return "Sefer Egy"; + } + } + Future sendSmsEgypt(String phone, otp) async { + String sender = await getSender(); var body = jsonEncode({ "username": AppInformation.appName, "password": AK.smsPasswordEgypt, //'E)Pu=an/@Z', "message": "${AppInformation.appName} app code is $otp\ncopy it to app", "language": box.read(BoxName.lang) == 'en' ? "e" : 'r', - "sender": "Sefer Egy", // todo add sefer sender name + "sender": sender, //"Sefer Egy", // todo add sefer sender name "receiver": "2$phone" }); @@ -28,7 +44,20 @@ class SmsEgyptController extends GetxController { headers: headers, ); - if (res.statusCode == 200) { + if (jsonDecode(res.body)['message'].toString() != "Success") { + await CRUD().post(link: AppLink.updatePhoneInvalidSMS, payload: { + "phone_number": + '+2${Get.find().phoneController.text}' + }); + box.write(BoxName.phoneDriver, + '+2${Get.find().phoneController.text}'); + box.write(BoxName.phoneVerified, '1'); + + await Get.put(LoginDriverController()).loginUsingCredentials( + box.read(BoxName.driverID).toString(), + box.read(BoxName.emailDriver).toString(), + ); + } else { Get.defaultDialog( title: 'You will receive code in sms message'.tr, middleText: '', diff --git a/lib/controller/functions/upload_image.dart b/lib/controller/functions/upload_image.dart index 506f281..d046f5d 100644 --- a/lib/controller/functions/upload_image.dart +++ b/lib/controller/functions/upload_image.dart @@ -101,54 +101,57 @@ class ImageController extends GetxController { } choosImage(String link, String imageType) async { - final pickedImage = await picker.pickImage( - source: ImageSource.camera, - preferredCameraDevice: CameraDevice.rear, - ); + try { + final pickedImage = await picker.pickImage( + source: ImageSource.camera, + preferredCameraDevice: CameraDevice.rear, + ); - if (pickedImage == null) return; + if (pickedImage == null) return; - image = File(pickedImage.path); + image = File(pickedImage.path); - croppedFile = await ImageCropper().cropImage( - sourcePath: image!.path, - uiSettings: [ - AndroidUiSettings( + croppedFile = await ImageCropper().cropImage( + sourcePath: image!.path, + uiSettings: [ + AndroidUiSettings( toolbarTitle: 'Cropper'.tr, toolbarColor: AppColor.blueColor, toolbarWidgetColor: AppColor.yellowColor, initAspectRatio: CropAspectRatioPreset.original, - lockAspectRatio: false), - IOSUiSettings( - title: 'Cropper'.tr, - ), - ], - ); + lockAspectRatio: false, + ), + IOSUiSettings( + title: 'Cropper'.tr, + ), + ], + ); - if (croppedFile == null) return; + if (croppedFile == null) return; - myImage = File(croppedFile!.path); - isloading = true; - update(); + myImage = File(croppedFile!.path); + isloading = true; + update(); + + // Rotate the compressed image + File processedImage = await rotateImageIfNeeded(File(croppedFile!.path)); + File compressedImage = await compressImage(processedImage); + + print('link =$link'); - // Rotate the compressed image - // File rotatedImage = await rotateImage(compressedImage); - File processedImage = await rotateImageIfNeeded(File(croppedFile!.path)); - File compressedImage = await compressImage(processedImage); - print('link =$link'); - try { await uploadImage( compressedImage, { 'driverID': box.read(BoxName.driverID) ?? box.read(BoxName.passengerID), - 'imageType': imageType + 'imageType': imageType, }, link, ); } catch (e) { + print('Error in choosImage: $e'); Get.snackbar('Image Upload Failed'.tr, e.toString(), - backgroundColor: AppColor.redColor); + backgroundColor: AppColor.primaryColor); } finally { isloading = false; update(); diff --git a/lib/controller/home/captin/home_captain_controller.dart b/lib/controller/home/captin/home_captain_controller.dart index 224cf60..6ae98f6 100644 --- a/lib/controller/home/captin/home_captain_controller.dart +++ b/lib/controller/home/captin/home_captain_controller.dart @@ -229,7 +229,7 @@ class HomeCaptainController extends GetxController { getAllPayment(); startPeriodicExecution(); onMapCreated(mapHomeCaptainController!); - totalPoints = Get.find().totalPoints; + totalPoints = Get.find().totalPoints.toString(); getRefusedOrderByCaptain(); // LocationController().getLocation(); super.onInit(); diff --git a/lib/controller/home/captin/map_driver_controller.dart b/lib/controller/home/captin/map_driver_controller.dart index 66d4c9d..c8cafb1 100644 --- a/lib/controller/home/captin/map_driver_controller.dart +++ b/lib/controller/home/captin/map_driver_controller.dart @@ -26,8 +26,10 @@ class MapDriverController extends GetxController { bool isLoading = true; final formKey1 = GlobalKey(); final formKey2 = GlobalKey(); + final formKeyCancel = GlobalKey(); final messageToPassenger = TextEditingController(); final sosEmergincyNumberCotroller = TextEditingController(); + final cancelTripCotroller = TextEditingController(); List data = []; List dataDestination = []; LatLngBounds? boundsData; @@ -165,6 +167,30 @@ class MapDriverController extends GetxController { update(); } + cancelTripFromDriverAfterApplied() async { + if (formKeyCancel.currentState!.validate()) { + await CRUD().post(link: AppLink.updateRides, payload: { + "id": rideId.toString(), // Convert to String + "status": 'CancelFromDriverAfterApply' + }); + await CRUD().post(link: AppLink.addCancelRideFromPassenger, payload: { + "rideID": rideId.toString(), + "driverID": box.read(BoxName.driverID).toString(), + "passengerID": passengerId.toString(), + "note": cancelTripCotroller.text.toString() + }); + FirebaseMessagesController().sendNotificationToDriverMAP( + "Cancel Trip from driver".tr, + "Trip Cancelled from driver. We are looking for a new driver. Please wait." + .tr, + tokenPassenger, + [], + 'cancel.wav', + ); + Get.offAll(HomeCaptain()); + } + } + void startTimerToShowPassengerInfoWindowFromDriver() async { if (box.read(BoxName.rideStatus) == 'Begin') { isPassengerInfoWindow = false; diff --git a/lib/controller/home/captin/order_request_controller.dart b/lib/controller/home/captin/order_request_controller.dart index 20ca358..794e2ce 100644 --- a/lib/controller/home/captin/order_request_controller.dart +++ b/lib/controller/home/captin/order_request_controller.dart @@ -33,6 +33,12 @@ class OrderRequestController extends GetxController { super.onInit(); } + getRideDEtailsForBackgroundOrder() async { + await CRUD().get(link: AppLink.getRidesDetails, payload: { + 'id': box.read(BoxName.myList)[2].toString(), + }); + } + void addCustomStartIcon() async { // Create the marker with the resized image diff --git a/lib/controller/home/payment/captain_wallet_controller.dart b/lib/controller/home/payment/captain_wallet_controller.dart index 819bbe6..14e6410 100644 --- a/lib/controller/home/payment/captain_wallet_controller.dart +++ b/lib/controller/home/payment/captain_wallet_controller.dart @@ -106,18 +106,18 @@ class CaptainWalletController extends GetxController { update(); var res = await CRUD().get( link: AppLink.getAllPaymentFromRide, - payload: {'driverID': box.read(BoxName.driverID)}, + payload: {'driverID': box.read(BoxName.driverID).toString()}, ); // isLoading = false; if (res != 'failure') { walletDate = jsonDecode(res); - totalAmount = walletDate['message'][0]['total_amount'] ?? '0'; + totalAmount = walletDate['message'][0]['total_amount'].toString(); update(); var res1 = await CRUD().get( link: AppLink.getAllPaymentVisa, - payload: {'driverID': box.read(BoxName.driverID)}); + payload: {'driverID': box.read(BoxName.driverID).toString()}); walletDateVisa = jsonDecode(res1); - totalAmountVisa = walletDateVisa['message'][0]['diff'] ?? '0'; + totalAmountVisa = walletDateVisa['message'][0]['diff'].toString(); update(); } else { @@ -132,12 +132,13 @@ class CaptainWalletController extends GetxController { var res = await CRUD().get( link: AppLink.getDriverPaymentPoints, - payload: {'driverID': box.read(BoxName.driverID)}, + payload: {'driverID': box.read(BoxName.driverID).toString()}, ); isLoading = false; // update(); - walletDriverPointsDate = jsonDecode(res); + if (res != 'failure') { + walletDriverPointsDate = jsonDecode(res); double totalPointsDouble = double.parse( walletDriverPointsDate['message'][0]['total_amount'].toString()); totalPoints = totalPointsDouble.toStringAsFixed(0); @@ -283,7 +284,7 @@ class CaptainWalletController extends GetxController { ); if (res != 'failure') { var json = jsonDecode(res); - kazan = double.parse(json['message'][0]['kazan']); + kazan = double.parse(json['message'][0]['kazan'].toString()); // naturePrice = double.parse(json['message'][0]['naturePrice']); // heavyPrice = double.parse(json['message'][0]['heavyPrice']); // latePrice = double.parse(json['message'][0]['latePrice']); diff --git a/lib/controller/local/local_controller.dart b/lib/controller/local/local_controller.dart index 41621fc..5a73d12 100644 --- a/lib/controller/local/local_controller.dart +++ b/lib/controller/local/local_controller.dart @@ -10,7 +10,7 @@ class LocaleController extends GetxController { String countryCode = ''; void restartApp() { // Get.offAll(MyApp); - runApp(const MyApp()); + runApp(MyApp()); } ThemeData appTheme = themeEnglish; diff --git a/lib/controller/local/translations.dart b/lib/controller/local/translations.dart index 696f874..937c5bf 100644 --- a/lib/controller/local/translations.dart +++ b/lib/controller/local/translations.dart @@ -150,7 +150,29 @@ class MyTranslation extends Translations { "This driver is not registered": "هذا السائق غير مسجل", 'insert amount': "أدخل المبلغ", "phone number of driver": "رقم هاتف السائق", - "Transfer budget": "نقل الميزانية", + "Transfer budget": "نقل الميزانية", "Comfort": "كمفورت", + "Speed": "سبيد", + "Lady": "ليدي", "Permission denied": "تم رفض الإذن", + "Contact permission is required to pick a contact": + "مطلوب إذن الوصول إلى جهات الاتصال لاختيار جهة اتصال", + "No contact selected": "لم يتم تحديد جهة اتصال", + "Please select a contact": "يرجى تحديد جهة اتصال", + "No phone number": "لا يوجد رقم هاتف", + "The selected contact does not have a phone number": + "جهة الاتصال المحددة لا تحتوي على رقم هاتف", + "Error": "خطأ", + "An error occurred while picking a contact": + "حدث خطأ أثناء اختيار جهة الاتصال", + "Are you sure you want to cancel this trip?": + "هل أنت متأكد من أنك تريد إلغاء هذه الرحلة؟", + "Cancel Trip from driver": "إلغاء الرحلة من السائق", + "Why do you want to cancel this trip?": + "لماذا تريد إلغاء هذه الرحلة؟", + "Write the reason for canceling the trip": "اكتب سبب الإلغاء:", + "Trip Cancelled from driver. We are looking for a new driver. Please wait.": + "تم إلغاء الرحلة من قبل السائق. نحن نبحث عن سائق جديد. من فضلك انتظر.", + "Delivery": "توصيل", + "Mashwari": "‏مشواري", "Total Net": "صافي الإجمالي", "Special Order": "طلب خاص", "Speed Order": "طلب سريع", "No data yet!": "لا توجد بيانات حتى الآن!", @@ -200,6 +222,7 @@ class MyTranslation extends Translations { "History Page": "سجل الرحلات ", "Finished": "مكتملة", "Trip Detail": "تفاصيل الرحلة", + "Invite code already used": "تم استخدام رمز الدعوة بالفعل", "Price is": "السعر", "Times of Trip": "أوقات الرحلة", "Time to Passenger is": "الوقت للوصول للراكب", @@ -258,8 +281,17 @@ class MyTranslation extends Translations { "التقط صورة لرخصة سيارتك من الأمام", "Capture an Image of Your ID Document front": "التقاط صورة لوثيقة هويتك من الأمام", - "NationalID": "الرقم الوطني", + "NationalID": "الرقم الوطني", "reject your order.": "رفض طلبك.", + "Order Under Review": "الطلب قيد المراجعة", + "is reviewing your order. They may need more information or a higher price.": + "يتم مراجعة طلبك. قد يحتاجون إلى مزيد من المعلومات أو سعر أعلى.", "FullName": "الاسم الكامل", + "Enable Location Permission": "تمكين إذن الموقع", + 'Allowing location access will help us display orders near you. Please enable it now.': + "سيساعدنا السماح بالوصول إلى الموقع في عرض الطلبات القريبة منك. يرجى تمكينه الآن.", + "Allow overlay permission": "السماح بإذن‏الظهور فوق التطبيقات", + "To display orders instantly, please grant permission to draw over other apps.": + "لعرض الطلبات على الفور، يرجى منح إذن لرسم فوق التطبيقات الأخرى.", "InspectionResult": "نتيجة الفحص", "Criminal Record": "صحيفة الحالة الجنائية", "The email or phone number is already registered.": @@ -331,10 +363,13 @@ class MyTranslation extends Translations { "Invite sent successfully": "تم إرسال الدعوة بنجاح", "Failed to send invite": "فشل إرسال الدعوة", "An error occurred": "حدث خطأ", + "Air Condition Trip": "رحلة تكييف ", + "Passenger name: ": "اسم الراكب: ", "Criminal Document Required": "الفيش الجنائي مطلوب", "Criminal Document": "الفيش الجنائي", "Marital Status": "الحالة الاجتماعية", "Full Name (Marital)": "الاسم الكامل (الزوجي)", + "Payment Method": "طريقة الدفع", "Expiration Date": "تاريخ الانتهاء", "Capture an Image of Your ID Document Back": "التقاط صورة للجهة الخلفية من وثيقة الهوية الخاصة بك", diff --git a/lib/controller/payment/paymob/paymob_wallet.dart b/lib/controller/payment/paymob/paymob_wallet.dart index 1c95682..a18646c 100644 --- a/lib/controller/payment/paymob/paymob_wallet.dart +++ b/lib/controller/payment/paymob/paymob_wallet.dart @@ -4,6 +4,8 @@ import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; +import '../../../print.dart'; + class PaymobResponseWallet { final bool success; final String? transactionID; @@ -142,7 +144,7 @@ class PaymobPaymentWallet { }) async { final Map data = { "source": { - "identifier": "01010101010", // Replace with actual source identifier + "identifier": box.read(BoxName.phoneDriver).toString(), "subtype": "WALLET", }, "payment_token": paymentToken, @@ -217,6 +219,7 @@ class PaymobPaymentWallet { ), ); final urlWallet = await _getWalletUrl(paymentToken: purchaseToken); + Log.print('urlWallet: ${urlWallet}'); if (context.mounted) { final response = await PaymobIFrameWallet.show( diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart index 45ef319..285d626 100644 --- a/lib/firebase_options.dart +++ b/lib/firebase_options.dart @@ -17,10 +17,7 @@ import 'package:flutter/foundation.dart' class DefaultFirebaseOptions { static FirebaseOptions get currentPlatform { if (kIsWeb) { - throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for web - ' - 'you can reconfigure this by running the FlutterFire CLI again.', - ); + return web; } switch (defaultTargetPlatform) { case TargetPlatform.android: @@ -28,15 +25,9 @@ class DefaultFirebaseOptions { case TargetPlatform.iOS: return ios; case TargetPlatform.macOS: - throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for macos - ' - 'you can reconfigure this by running the FlutterFire CLI again.', - ); + return macos; case TargetPlatform.windows: - throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for windows - ' - 'you can reconfigure this by running the FlutterFire CLI again.', - ); + return windows; case TargetPlatform.linux: throw UnsupportedError( 'DefaultFirebaseOptions have not been configured for linux - ' @@ -51,7 +42,7 @@ class DefaultFirebaseOptions { static const FirebaseOptions android = FirebaseOptions( apiKey: 'AIzaSyCyfwRXTwSTLOFQSQgN5p7QZgGJVZnEKq0', - appId: '1:594687661098:android:46557bd4f534b5bb595f53', + appId: '1:594687661098:android:b7ce96c17eb928ca595f53', messagingSenderId: '594687661098', projectId: 'ride-b1bd8', storageBucket: 'ride-b1bd8.appspot.com', @@ -63,8 +54,40 @@ class DefaultFirebaseOptions { messagingSenderId: '594687661098', projectId: 'ride-b1bd8', storageBucket: 'ride-b1bd8.appspot.com', - iosClientId: - '594687661098-9fnj82nef9oagl98prigdf8qne3ddbto.apps.googleusercontent.com', + androidClientId: '594687661098-2dhoogl7be9phobfbu8bbg1sj567iv88.apps.googleusercontent.com', + iosClientId: '594687661098-9fnj82nef9oagl98prigdf8qne3ddbto.apps.googleusercontent.com', iosBundleId: 'com.sefer.driver', ); -} + + static const FirebaseOptions web = FirebaseOptions( + apiKey: 'AIzaSyAVtyV7YVMeLbwA1UlNPxV9FhCzT0kjeAE', + appId: '1:594687661098:web:62d8388476ec91ec595f53', + messagingSenderId: '594687661098', + projectId: 'ride-b1bd8', + authDomain: 'ride-b1bd8.firebaseapp.com', + storageBucket: 'ride-b1bd8.appspot.com', + measurementId: 'G-Y3HFEC6F4N', + ); + + static const FirebaseOptions macos = FirebaseOptions( + apiKey: 'AIzaSyCf2mW2h0HD8ZYjwh4VOa2ladw6MJkCDTM', + appId: '1:594687661098:ios:6f69eee1449be943595f53', + messagingSenderId: '594687661098', + projectId: 'ride-b1bd8', + storageBucket: 'ride-b1bd8.appspot.com', + androidClientId: '594687661098-2dhoogl7be9phobfbu8bbg1sj567iv88.apps.googleusercontent.com', + iosClientId: '594687661098-8e26699cris2k3nj5msj1osi59it9kpf.apps.googleusercontent.com', + iosBundleId: 'com.mobileapp.store.ride', + ); + + static const FirebaseOptions windows = FirebaseOptions( + apiKey: 'AIzaSyAVtyV7YVMeLbwA1UlNPxV9FhCzT0kjeAE', + appId: '1:594687661098:web:d9f43a2091395d87595f53', + messagingSenderId: '594687661098', + projectId: 'ride-b1bd8', + authDomain: 'ride-b1bd8.firebaseapp.com', + storageBucket: 'ride-b1bd8.appspot.com', + measurementId: 'G-C3DWQ8Z062', + ); + +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 228edf7..e1d92d5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,68 +1,125 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; - +import 'package:SEFER/constant/box_name.dart'; import 'package:SEFER/controller/payment/paymob/paymob_response.dart'; +import 'package:SEFER/views/home/Captin/orderCaptin/order_request_page.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_overlay_window/flutter_overlay_window.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_stripe/flutter_stripe.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:flutter/services.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'constant/api_key.dart'; -import 'constant/credential.dart'; import 'constant/info.dart'; import 'controller/firebase/firbase_messge.dart'; import 'controller/firebase/local_notification.dart'; import 'controller/functions/location_controller.dart'; +import 'controller/functions/overlay_permisssion.dart'; import 'controller/local/local_controller.dart'; import 'controller/local/translations.dart'; import 'controller/payment/paymob/paymob_wallet.dart'; import 'firebase_options.dart'; import 'models/db_sql.dart'; +import 'print.dart'; import 'splash_screen_page.dart'; -import 'views/home/Captin/orderCaptin/order_request_page.dart'; +import 'views/home/Captin/orderCaptin/order_over_lay.dart'; final box = GetStorage(); const storage = FlutterSecureStorage(); - final PaymobPayment paymobPayment = PaymobPayment(); final PaymobPaymentWallet paymobPaymentWallet = PaymobPaymentWallet(); - DbSql sql = DbSql.instance; + @pragma('vm:entry-point') Future backgroundMessageHandler(RemoteMessage message) async { await Firebase.initializeApp(); - if (message.data.isNotEmpty && message.notification != null) { - FirebaseMessagesController().fireBaseTitles(message); + if (Platform.isAndroid) { + if (message.notification != null && message.notification!.title != null) { + if (message.notification?.title == 'Order') { + var myListString = message.data['DriverList'] ?? '[]'; + Log.print('myListString: $myListString'); + + // Decode the JSON string to a list + var myList; + try { + myList = jsonDecode(myListString) as List; + } catch (e) { + Log.print('Error decoding JSON: $e'); + myList = []; + } + + Future.delayed(const Duration(seconds: 1)); + NotificationController().showNotification( + message.notification!.title.toString(), + message.notification!.body.toString(), + 'order', + myListString, + ); + + bool isOverlayActive = await FlutterOverlayWindow.isActive(); + if (isOverlayActive) { + await FlutterOverlayWindow.closeOverlay(); + } + await FlutterOverlayWindow.showOverlay( + enableDrag: true, + flag: OverlayFlag.focusPointer, + visibility: NotificationVisibility.visibilityPublic, + positionGravity: PositionGravity.auto, + height: 700, + width: WindowSize.matchParent, + startPosition: const OverlayPosition(0, -150), + ); + + await FlutterOverlayWindow.shareData(myList); + + // Bubble().startBubbleHead(sendAppToBackground: true); + } + } else { + FirebaseMessagesController().fireBaseTitles(message); + } } } -Future handleBackgroundNotificationClick(RemoteMessage message) async { - await Firebase.initializeApp(); +final GlobalKey navigatorKey = GlobalKey(); - var myListString = message.data['DriverList']; - var myList = jsonDecode(myListString) as List; +@pragma('vm:entry-point') +void overlayMain() async { + WidgetsFlutterBinding.ensureInitialized(); - Get.to(() => OrderRequestPage(), arguments: { - 'myListString': myListString, - 'DriverList': myList, - 'body': message.notification?.body, - }); + runApp(const MaterialApp( + debugShowCheckedModeBanner: false, + home: OrderOverlay(), + )); +} + +void closeOverLay() { + FlutterOverlayWindow.closeOverlay(); } void main() async { WidgetsFlutterBinding.ensureInitialized(); WakelockPlus.enable(); - await LocationController().startLocationUpdates(); - if (Platform.isAndroid) { - await NotificationController().initNotifications(); + // Request location permission first + PermissionStatus status = await Permission.location.request(); + if (status.isDenied) { + WidgetsBinding.instance.addPostFrameCallback((_) { + getPermissionLocation(); + }); + // Handle the case when permission is denied + // You might want to show a dialog explaining why the permission is needed + return; } + // Set up the overlay entry point + // FlutterOverlayWindow.overlayMain = overlayMain; + await LocationController().startLocationUpdates(); await GetStorage.init(); - Stripe.publishableKey = AK.publishableKeyStripe; if (Platform.isAndroid || Platform.isIOS) { @@ -72,7 +129,7 @@ void main() async { await FirebaseMessagesController().requestFirebaseMessagingPermission(); FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler); - + NotificationController().initNotifications(); List initializationTasks = [ FirebaseMessagesController().getNotificationSettings(), FirebaseMessagesController().getToken(), @@ -98,24 +155,35 @@ void main() async { iFrameID: 837992, ); - runApp(const MyApp()); + runApp(MyApp()); } class MyApp extends StatelessWidget { - const MyApp({super.key}); + MyApp({super.key}); @override Widget build(BuildContext context) { LocaleController localController = Get.put(LocaleController()); - // checkForUpdate(context); return GetMaterialApp( - title: AppInformation.appName, - translations: MyTranslation(), - debugShowCheckedModeBanner: false, - locale: localController.language, - theme: localController.appTheme, - key: UniqueKey(), - initialRoute: '/', - home: SplashScreen()); + navigatorKey: navigatorKey, + title: AppInformation.appName, + translations: MyTranslation(), + debugShowCheckedModeBanner: false, + locale: localController.language, + theme: localController.appTheme, + key: UniqueKey(), + initialRoute: '/', + // home: SplashScreen(), + routes: { + '/order': (context) => OrderRequestPage(), + }, + getPages: [ + GetPage(name: '/', page: () => SplashScreen()), + GetPage( + name: '/order-page', + page: () => OrderRequestPage(), + arguments: box.read(BoxName.rideArguments)), + ], + ); } } diff --git a/lib/models/db_sql.dart b/lib/models/db_sql.dart index 0caa829..620c9ba 100644 --- a/lib/models/db_sql.dart +++ b/lib/models/db_sql.dart @@ -78,6 +78,47 @@ class DbSql { faceDetectTimes INTEGER ) '''); + await db.execute(''' + CREATE TABLE IF NOT EXISTS ${TableName.applyRideFromOverLay}( + id INTEGER PRIMARY KEY AUTOINCREMENT, + start_location_lat TEXT, + start_location_lng TEXT, + end_location_lat TEXT, + end_location_lng TEXT, + total_passenger TEXT, + total_driver TEXT, + duration_to_ride TEXT, + distance TEXT, + driver_id TEXT, + passenger_id TEXT, + passenger_name TEXT, + passenger_token_fcm TEXT, + passenger_phone TEXT, + duration_by_passenger TEXT, + distance_by_passenger TEXT, + is_wallet_checked TEXT, + driver_token TEXT, + duration_to_passenger TEXT, + ride_id TEXT, + ride_timer_begin TEXT, + driver_id_duplicate TEXT, + duration_to_ride_duplicate TEXT, + way_points TEXT, + place_coordinate_0 TEXT, + place_coordinate_1 TEXT, + place_coordinate_2 TEXT, + place_coordinate_3 TEXT, + place_coordinate_4 TEXT, + cost_for_driver TEXT, + passenger_wallet_total TEXT, + passenger_email TEXT, + start_name_address TEXT, + end_name_address TEXT, + car_type TEXT, + kazan TEXT, + passenger_rate TEXT + ) +'''); }, ); } diff --git a/lib/views/auth/captin/invite_driver_screen.dart b/lib/views/auth/captin/invite_driver_screen.dart index ac02ce0..00151d4 100644 --- a/lib/views/auth/captin/invite_driver_screen.dart +++ b/lib/views/auth/captin/invite_driver_screen.dart @@ -25,11 +25,24 @@ class InviteDriverScreen extends StatelessWidget { style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 20), - MyTextForm( - controller: controller.invitePhoneController, - label: 'Enter driver\'s phone'.tr, - hint: 'Enter driver\'s phone'.tr, - type: TextInputType.phone), + Row( + children: [ + Expanded( + child: MyTextForm( + controller: controller.invitePhoneController, + label: 'Enter driver\'s phone'.tr, + hint: 'Enter driver\'s phone'.tr, + type: TextInputType.phone, + ), + ), + IconButton( + icon: Icon(Icons.contacts), + onPressed: () async { + await controller.pickContact(); + }, + ), + ], + ), const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, @@ -49,7 +62,6 @@ class InviteDriverScreen extends StatelessWidget { const SizedBox(height: 20), GetBuilder(builder: (controller) { return SizedBox( - // decoration: AppStyle.boxDecoration, height: Get.height * .4, child: controller.driverInvitationData.isEmpty ? Center( @@ -61,7 +73,6 @@ class InviteDriverScreen extends StatelessWidget { : ListView.builder( itemCount: controller.driverInvitationData.length, itemBuilder: (context, index) { - // Ensure the 'countOfInvitDriver' key exists and is parseable as an int int countOfInvitDriver = 0; if (controller.driverInvitationData[index] .containsKey('countOfInvitDriver')) { @@ -72,25 +83,24 @@ class InviteDriverScreen extends StatelessWidget { 0; } - // Calculate the progress value, ensuring it is between 0 and 1 double progressValue = countOfInvitDriver / 100.0; if (progressValue > 1.0) progressValue = 1.0; if (progressValue < 0.0) progressValue = 0.0; - return Container( - margin: const EdgeInsets.symmetric(vertical: 8.0), - child: Stack( - alignment: AlignmentDirectional.center, - children: [ - InkWell( - onTap: () async { - controller.onSelectDriverInvitation(index); - }, - child: Container( + return InkWell( + onTap: () async { + controller.onSelectDriverInvitation(index); + }, + child: Container( + margin: const EdgeInsets.symmetric(vertical: 8.0), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), - color: AppColor.accentColor.withOpacity( - .5), // Background color of the container + color: + AppColor.accentColor.withOpacity(.5), ), width: Get.width * .85, child: ClipRRect( @@ -104,10 +114,11 @@ class InviteDriverScreen extends StatelessWidget { ), ), ), - ), - Text( - '${controller.driverInvitationData[index]['invitorName']} ${controller.driverInvitationData[index]['countOfInvitDriver']} / 100 ${'Trip'.tr}'), - ], + Text( + '${controller.driverInvitationData[index]['invitorName']} ${controller.driverInvitationData[index]['countOfInvitDriver']} / 100 ${'Trip'.tr}', + ), + ], + ), ), ); }, diff --git a/lib/views/home/Captin/driver_map_page.dart b/lib/views/home/Captin/driver_map_page.dart index f338a0b..9c0fd55 100644 --- a/lib/views/home/Captin/driver_map_page.dart +++ b/lib/views/home/Captin/driver_map_page.dart @@ -5,8 +5,10 @@ import 'package:get/get.dart'; import 'package:SEFER/controller/home/captin/map_driver_controller.dart'; import 'package:SEFER/views/widgets/my_scafold.dart'; +import '../../../constant/colors.dart'; import '../../../controller/functions/location_controller.dart'; import '../../Rate/rate_passenger.dart'; +import '../../widgets/my_textField.dart'; import 'mapDriverWidgets/driver_end_ride_bar.dart'; import 'mapDriverWidgets/google_driver_map_page.dart'; import 'mapDriverWidgets/google_map_app.dart'; @@ -36,6 +38,7 @@ class PassengerLocationMapPage extends StatelessWidget { body: [ GoogleDriverMap(locationController: locationController), const PassengerInfoWindow(), + CancelWidget(mapDriverController: mapDriverController), driverEndRideBar(), const SosConnect(), speedCircle(), @@ -46,6 +49,70 @@ class PassengerLocationMapPage extends StatelessWidget { } } +class CancelWidget extends StatelessWidget { + const CancelWidget({ + super.key, + required this.mapDriverController, + }); + + final MapDriverController mapDriverController; + + @override + Widget build(BuildContext context) { + return Positioned( + top: 10, + left: 5, + child: GestureDetector( + onTap: () { + Get.defaultDialog( + title: "Are you sure you want to cancel this trip?".tr, + titleStyle: AppStyle.title, + content: Column( + children: [ + Text("Why do you want to cancel this trip?".tr), + Form( + key: mapDriverController.formKeyCancel, + child: MyTextForm( + controller: mapDriverController.cancelTripCotroller, + label: "Write the reason for canceling the trip".tr, + hint: "Write the reason for canceling the trip".tr, + type: TextInputType.name, + )) + ], + ), + confirm: MyElevatedButton( + title: 'OK'.tr, + onPressed: () async { + // todo add cancel and inform passenger to get new driver + await mapDriverController + .cancelTripFromDriverAfterApplied(); + Get.back(); + }), + cancel: MyElevatedButton( + title: 'NO'.tr, + kolor: AppColor.redColor, + onPressed: () { + Get.back(); + })); + }, + child: Container( + decoration: BoxDecoration( + color: AppColor.redColor, + borderRadius: BorderRadius.circular(15)), + child: const Padding( + padding: EdgeInsets.all(3), + child: Icon( + Icons.clear, + size: 40, + color: AppColor.secondaryColor, + ), + ), + ), + ), + ); + } +} + class PricesWindow extends StatelessWidget { const PricesWindow({ super.key, diff --git a/lib/views/home/Captin/home_captain/home_captin.dart b/lib/views/home/Captin/home_captain/home_captin.dart index e22bd88..580e3f8 100644 --- a/lib/views/home/Captin/home_captain/home_captin.dart +++ b/lib/views/home/Captin/home_captain/home_captin.dart @@ -1,5 +1,6 @@ import 'package:SEFER/constant/box_name.dart'; import 'package:SEFER/controller/home/captin/map_driver_controller.dart'; +import 'package:SEFER/views/notification/available_rides_page.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; @@ -12,6 +13,7 @@ import '../../../../constant/info.dart'; import '../../../../constant/style.dart'; import '../../../../constant/table_names.dart'; import '../../../../controller/functions/location_controller.dart'; +import '../../../../controller/functions/overlay_permisssion.dart'; import '../../../../controller/functions/package_info.dart'; import '../../../../controller/home/captin/home_captain_controller.dart'; import '../../../../controller/home/captin/order_request_controller.dart'; @@ -32,8 +34,10 @@ class HomeCaptain extends StatelessWidget { Widget build(BuildContext context) { Get.put(OrderRequestController()); Get.put(HomeCaptainController()); + Get.put(CaptainWalletController()); WidgetsBinding.instance.addPostFrameCallback((_) { - checkForUpdate(context); + // checkForUpdate(context); + getPermissionOverlay(); _showFirstTimeOfferNotification(context); }); return Scaffold( @@ -240,6 +244,28 @@ class HomeCaptain extends StatelessWidget { // ), // ), // ), + Positioned( + bottom: Get.height * .3, + right: 6, + child: AnimatedContainer( + duration: const Duration(microseconds: 200), + width: homeCaptainController.widthMapTypeAndTraffic, + decoration: BoxDecoration( + border: Border.all(color: AppColor.blueColor), + color: AppColor.secondaryColor, + borderRadius: BorderRadius.circular(15)), + child: IconButton( + onPressed: () { + Get.to(() => AvailableRidesPage()); + }, + icon: const Icon( + Icons.train_sharp, + size: 29, + color: AppColor.blueColor, + ), + ), + ), + ), leftMainMenuCaptainIcons(), box.read(BoxName.rideStatus) == 'Applied' || box.read(BoxName.rideStatus) == 'Begin' @@ -401,8 +427,8 @@ void _showFirstTimeOfferNotification(BuildContext context) { ), ), onPressed: () { - Navigator.of(context).pop(); _markAsNotFirstTime(); + Navigator.of(context).pop(); }, ), ], @@ -416,7 +442,7 @@ void _showFirstTimeOfferNotification(BuildContext context) { } bool _checkIfFirstTime() { - if (box.read(BoxName.isFirstTime) == null) { + if (box.read(BoxName.isFirstTime).toString() == '') { return true; } else { return false; @@ -424,5 +450,5 @@ bool _checkIfFirstTime() { } void _markAsNotFirstTime() { - box.write(BoxName.isFirstTime, false); + box.write(BoxName.isFirstTime, 'false'); } diff --git a/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart b/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart index 7812d76..6e8b0ba 100644 --- a/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart +++ b/lib/views/home/Captin/home_captain/widget/left_menu_map_captain.dart @@ -2,14 +2,15 @@ import 'package:SEFER/constant/box_name.dart'; import 'package:SEFER/main.dart'; import 'package:flutter/material.dart'; import 'package:flutter_font_icons/flutter_font_icons.dart'; +import 'package:flutter_overlay_window/flutter_overlay_window.dart'; import 'package:get/get.dart'; import 'package:SEFER/controller/home/captin/home_captain_controller.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import '../../../../../constant/colors.dart'; +import '../../../../../print.dart'; import '../../../../Rate/ride_calculate_driver.dart'; import '../../../../../controller/functions/location_controller.dart'; -import '../../../../auth/captin/cards/egypt_card_a_i.dart'; GetBuilder leftMainMenuCaptainIcons() { return GetBuilder( @@ -168,16 +169,125 @@ GetBuilder leftMainMenuCaptainIcons() { // color: AppColor.secondaryColor, // border: Border.all(color: AppColor.blueColor), // borderRadius: BorderRadius.circular(15)), - // child: IconButton( - // onPressed: () async { - // Get.to(() => EgyptCardAI()); - // }, - // icon: const Icon( - // FontAwesome5.grin_tears, - // size: 29, - // color: AppColor.blueColor, - // ), - // ), + // child: Builder(builder: (context) { + // return IconButton( + // onPressed: () async { + // // Get.to(() => EgyptCardAI()); + // // print(box.read(BoxName.myList)); + // + // List d = [ + // "30.003028,31.2419628", + // "30.0955661,31.2665336", + // "160.00", + // "25.92", + // "1488", + // "16.93", + // "114243034311436865474", + // "113172279072358305645", + // "hamza ayed", + // "c9kqjnLqu08yogitln6B1Y:APA91bHyFJ7E7zv6-HIikwr6FrlMbi4Hc8L1STMPE99iPKqK4Gddwv8r9qZOCadsz9qTEJZ6KLEE9ruTJI6N8dKfK4CXez5pme5WIs14-1QGo29s07fQOniZgIlJV5XFL3yqzPRSUmn3", + // "+201023248456", + // "1 min", + // "1 m", + // "false", + // "em3j-v3PQlecGsTKFNU1wc:APA91bFjHq8xHpzeQwUMoyUtZ0J3oR6yXKUavrB_gBl9npUZe-qZtax-Raq4QBbdKv0AmtLKm0BfBd6N_592HBv4CVa41ii4122W3hr-BCUKKzJhzZcK8m0YjbWbtpvgJRD8uD_nuMk9", + // "0", + // "238", + // "false", + // "114243034311436865474", + // "1488", + // "startEnd", + // "30.049307749732176,31.274291574954987", + // "", + // "", + // "", + // "", + // "17.73", + // "0", + // "hamzaayedflutter@gmail.com", + // "الفسطاط، حي مصر القديمة، مصر", + // " الزاوية الحمراء، محافظة القاهرة، مصر", + // "Speed", + // "8", + // "5.00" + // ]; + // + // try { + // print('Before showing overlay: ${box.read('some_key')}'); + // + // // Ensure any existing overlay is closed before showing a new one + // + // bool isOverlayActive = + // await FlutterOverlayWindow.isActive(); + // if (isOverlayActive) { + // await FlutterOverlayWindow.closeOverlay(); + // } + // await FlutterOverlayWindow.showOverlay( + // enableDrag: true, + // overlayTitle: d[0], + // overlayContent: d[1], + // flag: OverlayFlag.focusPointer, + // visibility: NotificationVisibility.visibilityPublic, + // positionGravity: PositionGravity.auto, + // height: 700, + // width: WindowSize.matchParent, + // startPosition: const OverlayPosition(0, -170), + // ); + // + // await FlutterOverlayWindow.shareData(d); + // + // print('After showing overlay: ${box.read('some_key')}'); + // } catch (e) { + // print('Error showing overlay: $e'); + // } + // // final Bubble _bubble = Bubble(showCloseButton: true); + // // try { + // // await _bubble.startBubbleHead(sendAppToBackground: false); + // // } on PlatformException { + // // print('Failed to call startBubbleHead'); + // // } + // + // // Bubble().startBubbleHead(sendAppToBackground: true); + // // } + // + // // Future stopBubbleHead() async { + // // try { + // // await _bubble.stopBubbleHead(); + // // } on PlatformException { + // // print('Failed to call stopBubbleHead'); + // // } + // // } + // + // // // send data to ovelay + // }, + // icon: const Icon( + // FontAwesome5.grin_tears, + // size: 29, + // color: AppColor.blueColor, + // ), + // ); + // }), + // ), + // AnimatedContainer( + // duration: const Duration(microseconds: 200), + // width: controller.widthMapTypeAndTraffic, + // decoration: BoxDecoration( + // color: AppColor.secondaryColor, + // border: Border.all(color: AppColor.blueColor), + // borderRadius: BorderRadius.circular(15)), + // child: Builder(builder: (context) { + // return IconButton( + // onPressed: () async { + // // Log.print('box: ${box.read(BoxName.rideStatus)}'); + // Log.print('box: ${box.read(BoxName.tokenDriver)}'); + // }, + // icon: const Icon( + // FontAwesome5.closed_captioning, + // size: 29, + // color: AppColor.blueColor, + // ), + // ); + // }), // ), ], )), diff --git a/lib/views/home/Captin/mapDriverWidgets/passenger_info_window.dart b/lib/views/home/Captin/mapDriverWidgets/passenger_info_window.dart index 5e11550..ce836ee 100644 --- a/lib/views/home/Captin/mapDriverWidgets/passenger_info_window.dart +++ b/lib/views/home/Captin/mapDriverWidgets/passenger_info_window.dart @@ -9,10 +9,8 @@ import 'package:SEFER/controller/firebase/firbase_messge.dart'; import 'package:SEFER/controller/home/captin/map_driver_controller.dart'; import 'package:SEFER/views/widgets/elevated_btn.dart'; -import '../../../../constant/box_name.dart'; import '../../../../constant/style.dart'; import '../../../../controller/functions/launch.dart'; -import '../../../../main.dart'; class PassengerInfoWindow extends StatelessWidget { const PassengerInfoWindow({ @@ -57,13 +55,6 @@ class PassengerInfoWindow extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - IconButton( - onPressed: () { - print(box - .read(BoxName.rideStatus)); - }, - icon: const Icon(Icons.add), - ), GestureDetector( onTap: () async { controller.isSocialPressed = diff --git a/lib/views/home/Captin/orderCaptin/order_over_lay.dart b/lib/views/home/Captin/orderCaptin/order_over_lay.dart new file mode 100644 index 0000000..9436cc7 --- /dev/null +++ b/lib/views/home/Captin/orderCaptin/order_over_lay.dart @@ -0,0 +1,314 @@ +import 'dart:async'; +import 'package:SEFER/constant/colors.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_overlay_window/flutter_overlay_window.dart'; +import 'package:get/get.dart'; +import 'package:just_audio/just_audio.dart'; +import '../../../../constant/box_name.dart'; +import '../../../../constant/style.dart'; +import '../../../../main.dart'; + +class OrderOverlay extends StatefulWidget { + const OrderOverlay({Key? key}) : super(key: key); + + @override + State createState() => _OrderOverlayState(); +} + +class _OrderOverlayState extends State + with WidgetsBindingObserver { + List d = []; + Timer? _timer; + double _progress = 1.0; + bool _isOverlayActive = false; + final AudioPlayer _audioPlayer = AudioPlayer(); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + _setupOverlayListener(); + } + + void _setupOverlayListener() { + FlutterOverlayWindow.overlayListener.listen((event) { + if (mounted) { + setState(() { + d = event; + _resetAndStartTimer(); + }); + } + }); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + _checkOverlayStatus(); + } + } + + void _checkOverlayStatus() async { + bool isActive = await FlutterOverlayWindow.isActive(); + if (isActive && mounted) { + _resetAndStartTimer(); + } + } + + void _resetAndStartTimer() { + _timer?.cancel(); + setState(() { + _progress = 1.0; + _isOverlayActive = true; + }); + _playAudio(); + _startTimer(); + } + + void _startTimer() { + _timer = Timer.periodic(const Duration(milliseconds: 100), (timer) { + if (!_isOverlayActive) { + timer.cancel(); + _stopAudio(); + return; + } + if (mounted) { + setState(() { + _progress -= + 1 / 100; // Decrease progress over 15 seconds (150 * 100ms) + if (_progress <= 0) { + timer.cancel(); + _rejectOrder(); + _stopAudio(); + } + }); + } + }); + } + + void _playAudio() async { + try { + await _audioPlayer.setAsset( + 'assets/order.mp3', + preload: true, + initialPosition: Duration.zero, + ); + await _audioPlayer.play(); + } catch (e) { + print('An error occurred while playing the audio: $e'); + } + } + + void _stopAudio() { + _audioPlayer.stop(); + } + + @override + void dispose() { + _timer?.cancel(); + _stopAudio(); + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + String duration = (double.parse(d[4].toString()) / 60).toStringAsFixed(0); + String price = d[2].toString().split('.')[0]; + return Material( + color: Colors.transparent, + child: Center( + child: Container( + padding: const EdgeInsets.all(12.0), + width: double.infinity, + height: 450, // Adjust height as needed + decoration: BoxDecoration( + gradient: const LinearGradient(colors: [ + AppColor.blueColor, + AppColor.blueColor, + ]), + borderRadius: BorderRadius.circular(12.0), + ), + child: GestureDetector( + onTap: () async { + bool isOverlayActive = await FlutterOverlayWindow.isActive(); + if (isOverlayActive) { + await FlutterOverlayWindow.closeOverlay(); + } + }, + child: ListView( + // mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ListTile( + leading: _buildPriceAvatar(price), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + d.isNotEmpty ? d[8] : '', // Customer name + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColor.secondaryColor, + ), + ), + Container( + decoration: BoxDecoration( + border: Border.all( + color: AppColor.redColor, width: 2)), + child: Padding( + padding: const EdgeInsets.all(3), + child: Text( + "${d[5]} KM", + style: AppStyle.number.copyWith( + color: AppColor.secondaryColor, fontSize: 18), + ), + )), + const Text('🛣️') + ], + ), + // subtitle: Text(d.isNotEmpty ? d[10] : ''), // Phone number + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildDetailRow("🟢", d.isNotEmpty ? d[29] : ''), + _buildDetailRow("🔴".tr, d.isNotEmpty ? d[30] : ''), + _buildDetailRow( + "‏المسافة للراكب", d.isNotEmpty ? d[12] : ''), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildDetailRow("‏مدة الرحلة".tr, '$duration '), + _buildDetailRow("‏نوع الطلب".tr, _getRideType(d[31])) + ], + ), + const SizedBox( + height: 30, + ), + const SizedBox(height: 8), + LinearProgressIndicator( + value: _progress, + minHeight: 15, + backgroundColor: Colors.white.withOpacity(0.3), + valueColor: + const AlwaysStoppedAnimation(Colors.white), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildDetailRow(String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + value, + style: AppStyle.title.copyWith(color: AppColor.secondaryColor), + ), + Text( + label, + style: const TextStyle( + fontWeight: FontWeight.bold, color: AppColor.secondaryColor), + ), + ], + ), + ); + } + + Widget _buildPriceAvatar(String price) { + return Container( + width: 80, + height: 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: const RadialGradient( + colors: [Color(0xFF4CAF50), Color(0xFF2E7D32)], + center: Alignment.center, + radius: 0.8, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Center( + child: Text( + '\$$price', + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ); + } + + Widget _buildInfoRow(IconData icon, String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Icon(icon, color: Colors.white.withOpacity(0.8), size: 24), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: TextStyle( + color: Colors.white.withOpacity(0.8), fontSize: 14), + ), + Text( + value, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold), + ), + ], + ), + ), + ], + ), + ); + } + + String _getRideType(String type) { + switch (type) { + case 'Comfort': + return '‏كمفورت ❄️'; + case 'Lady': + return '‏ليدي 👩'; + case 'Speed': + return '‏‏‏سبيد 🔻'; + case 'Mashwari': + return '‏مشواري'; + case 'Rayeh Gai': + return 'رايح جاي'; + default: + return ''; + } + } + + void _rejectOrder() async { + box.write(BoxName.rideStatus, 'reject'); + bool isOverlayActive = await FlutterOverlayWindow.isActive(); + if (isOverlayActive) { + await FlutterOverlayWindow.closeOverlay(); + } + } +} diff --git a/lib/views/home/Captin/orderCaptin/order_request_page.dart b/lib/views/home/Captin/orderCaptin/order_request_page.dart index cfe58cf..8651738 100644 --- a/lib/views/home/Captin/orderCaptin/order_request_page.dart +++ b/lib/views/home/Captin/orderCaptin/order_request_page.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:SEFER/controller/home/captin/home_captain_controller.dart'; import 'package:SEFER/views/widgets/mydialoug.dart'; import 'package:flutter/material.dart'; @@ -23,23 +25,36 @@ class OrderRequestPage extends StatelessWidget { Get.put(OrderRequestController()); @override Widget build(BuildContext context) { +//TODO show order from start page from sql or api + final arguments = Get.arguments; final myListString = arguments['myListString']; - final myList = arguments['DriverList']; + var myList; + + // Check if 'DriverList' is null or empty + if (arguments['DriverList'] == null || arguments['DriverList'].isEmpty) { + myList = jsonDecode(myListString); + } else { + myList = arguments['DriverList']; + } + // final pointsList = arguments['PolylineJson']; - final body = arguments['body']; + // final body = arguments['body']; Duration durationToAdd = Duration(seconds: int.parse(myList[4])); int hours = durationToAdd.inHours; int minutes = (durationToAdd.inMinutes % 60).round(); - orderRequestController.startTimer(myList[6].toString(), body.toString()); - var coords = myList[0].split(','); - var coordDestination = myList[1].split(','); + orderRequestController.startTimer( + myList[6].toString(), + myList[16].toString(), + ); + var cords = myList[0].split(','); + var cordDestination = myList[1].split(','); // Parse to double - double latPassengerLocation = double.parse(coords[0]); - double lngPassengerLocation = double.parse(coords[1]); - double latPassengerDestination = double.parse(coordDestination[0]); - double lngPassengerDestination = double.parse(coordDestination[1]); + double latPassengerLocation = double.parse(cords[0]); + double lngPassengerLocation = double.parse(cords[1]); + double latPassengerDestination = double.parse(cordDestination[0]); + double lngPassengerDestination = double.parse(cordDestination[1]); List pointsDirection = [ LatLng(latPassengerLocation, lngPassengerLocation), @@ -71,378 +86,422 @@ class OrderRequestPage extends StatelessWidget { body: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 6), - child: ListView( - // crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // SizedBox(height: 200, child: Text(pointsList.toString())), - // Text(message.notification!.body.toString()), - SizedBox( - height: Get.height * .33, - child: GoogleMap( - initialCameraPosition: CameraPosition( - zoom: 12, - target: Get.find().myLocation), - cameraTargetBounds: CameraTargetBounds(bounds), - myLocationButtonEnabled: true, - trafficEnabled: true, - buildingsEnabled: true, - mapToolbarEnabled: true, - myLocationEnabled: true, - markers: { - Marker( - markerId: MarkerId('MyLocation'.tr), - position: LatLng( - latPassengerLocation, lngPassengerLocation), - draggable: true, - icon: orderRequestController.startIcon), - Marker( - markerId: MarkerId('Destination'.tr), - position: LatLng( - latPassengerDestination, lngPassengerDestination), - draggable: true, - icon: orderRequestController.endIcon), - }, - polylines: { - Polyline( - zIndex: 1, - consumeTapEvents: true, - geodesic: true, - endCap: Cap.buttCap, - startCap: Cap.buttCap, - visible: true, - polylineId: const PolylineId('routeOrder'), - points: pointsDirection, - color: AppColor.primaryColor, - width: 2, - ), - }, - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Card( - elevation: 3, - color: myList[20].toString() == 'haveSteps' - ? AppColor.greenColor - : AppColor.secondaryColor, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton.icon( - onPressed: () { - String mapUrl = - 'https://www.google.com/maps/dir/${myList[0]}/${myList[1]}/'; - showInBrowser(mapUrl); - }, - icon: const Icon(Icons.map), - label: myList[20].toString() == 'haveSteps' - ? Text( - 'Trip has Steps'.tr, - style: AppStyle.title, - ) - : Text('Routs of Trip'.tr, - style: AppStyle.title)), - Container( - color: myList[13].toString() == 'true' - ? AppColor.deepPurpleAccent - : AppColor.greenColor, - child: myList[13].toString() == - 'true' //Visa or Cash Method from notify to driver - ? Text( - 'Visa', - style: AppStyle.title, - ) - : Text('Cash', style: AppStyle.title), - ) - ], + child: Container( + color: const Color.fromARGB(255, 210, 201, 201), + child: ListView( + // crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // SizedBox(height: 200, child: Text(pointsList.toString())), + // Text(message.notification!.body.toString()), + SizedBox( + height: Get.height * .33, + child: GoogleMap( + initialCameraPosition: CameraPosition( + zoom: 12, + target: Get.find().myLocation), + cameraTargetBounds: CameraTargetBounds(bounds), + myLocationButtonEnabled: true, + trafficEnabled: true, + buildingsEnabled: true, + mapToolbarEnabled: true, + myLocationEnabled: true, + markers: { + Marker( + markerId: MarkerId('MyLocation'.tr), + position: LatLng( + latPassengerLocation, lngPassengerLocation), + draggable: true, + icon: orderRequestController.startIcon), + Marker( + markerId: MarkerId('Destination'.tr), + position: LatLng(latPassengerDestination, + lngPassengerDestination), + draggable: true, + icon: orderRequestController.endIcon), + }, + polylines: { + Polyline( + zIndex: 1, + consumeTapEvents: true, + geodesic: true, + endCap: Cap.buttCap, + startCap: Cap.buttCap, + visible: true, + polylineId: const PolylineId('routeOrder'), + points: pointsDirection, + color: AppColor.primaryColor, + width: 2, + ), + }, ), ), - ), - - Container( - decoration: AppStyle.boxDecoration1, - child: Padding( - padding: const EdgeInsets.all(8.0), + Padding( + padding: const EdgeInsets.all(8.0), + child: Card( + elevation: 3, + color: myList[20].toString() == 'haveSteps' + ? AppColor.greenColor + : AppColor.secondaryColor, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Text( - double.parse(myList[2]).toStringAsFixed(2), - style: AppStyle.headTitle2, - ), - AnimatedContainer( - duration: const Duration(seconds: 5), - curve: Curves.easeInOut, - child: AnimatedSize( + TextButton.icon( + onPressed: () { + String mapUrl = + 'https://www.google.com/maps/dir/${myList[0]}/${myList[1]}/'; + showInBrowser(mapUrl); + }, + icon: const Icon(Icons.map), + label: myList[20].toString() == 'haveSteps' + ? Text( + 'Trip has Steps'.tr, + style: AppStyle.title, + ) + : Text('Payment Method'.tr, + style: AppStyle.title)), + Container( + decoration: AppStyle.boxDecoration.copyWith( + color: myList[13].toString() == 'true' + ? AppColor.deepPurpleAccent + : AppColor.greenColor, + ), + child: myList[13].toString() == + 'true' //Visa or Cash Method from notify to driver + ? Text( + 'Visa', + style: AppStyle.title, + ) + : Text('Cash', style: AppStyle.title), + ) + ], + ), + ), + ), + + Container( + decoration: AppStyle.boxDecoration1, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + double.parse(myList[2]).toStringAsFixed(2), + style: AppStyle.headTitle2, + ), + AnimatedContainer( duration: const Duration(seconds: 5), curve: Curves.easeInOut, - child: myList[31].toString() == 'Comfort' - ? const Icon( - Icons.ac_unit, - color: AppColor.blueColor, - size: 50, - ) - : const SizedBox(), + child: AnimatedSize( + duration: const Duration(seconds: 5), + curve: Curves.easeInOut, + child: myList[31].toString() == 'Comfort' + ? Column( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + const Icon( + Icons.ac_unit, + color: AppColor.blueColor, + size: 50, + ), + Text( + 'Air condition Trip'.tr, + style: AppStyle.subtitle, + ), + ], + ) + : const SizedBox(), + ), ), - ), - Text( - myList[31].toString(), - style: AppStyle.title - .copyWith(color: AppColor.greenColor), - ), - ], - )), - ), - Container( - height: Get.height * .15, - width: Get.width * .9, - decoration: AppStyle.boxDecoration1, - child: ListView( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const Icon( - Icons.arrow_circle_up, - color: AppColor.greenColor, - ), - Text( - myList[12] + ' ' + ' (${myList[11]}) ', - style: AppStyle.title, - ), - ], - ), - Text( - myList[29], - style: AppStyle.title, - ), - ], - ), - Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const Icon( - Icons.arrow_circle_up, - color: AppColor.greenColor, - ), - Text( - myList[5] + ' ' + ' (${myList[4]}) ', - style: AppStyle.title, - ), - ], - ), - Text( - myList[30], - style: AppStyle.title, - ), - ], - ), - ], + Text( + myList[31].toString().tr, + style: AppStyle.title + .copyWith(color: AppColor.greenColor), + ), + ], + )), ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: RichText( - text: TextSpan( - text: 'Passenger name : ' - .tr, // Changed text to be more generic - style: AppStyle.subtitle, - children: [ - TextSpan( - text: myList[8], - style: AppStyle - .title), // Assuming myList[8] holds passenger name - TextSpan(text: ' (', style: AppStyle.subtitle), - TextSpan( - text: myList[33].toString(), - style: AppStyle - .title), // Assuming 'rate' holds the passenger rate - TextSpan(text: ' ⭐)', style: AppStyle.subtitle), - ], + const SizedBox( + height: 5, + ), + Container( + height: Get.height * .15, + width: Get.width * .9, + decoration: AppStyle.boxDecoration1, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 5, vertical: 1), + child: ListView( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Icon( + Icons.arrow_circle_up, + color: AppColor.greenColor, + ), + Text( + myList[12] + ' ' + ' (${myList[11]}) ', + style: AppStyle.title, + ), + ], + ), + Text( + myList[29], + style: AppStyle.title, + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Icon( + Icons.arrow_circle_down, + color: AppColor.redColor, + ), + Text( + myList[5] + ' ' + ' (${myList[4]}) ', + style: AppStyle.title, + ), + ], + ), + Text( + myList[30], + style: AppStyle.title, + ), + ], + ), + ], + ), ), ), - ), - - Padding( - padding: const EdgeInsets.all(4), - child: Container( - color: AppColor.greenColor.withOpacity(.5), + Padding( + padding: const EdgeInsets.all(8.0), child: RichText( text: TextSpan( - text: 'Cost Of Trip IS '.tr, - style: AppStyle.title, + text: "Passenger name: " + .tr, // Changed text to be more generic + style: AppStyle.subtitle, children: [ TextSpan( - text: myList[26], style: AppStyle.headTitle2), + text: myList[8], + style: AppStyle + .title), // Assuming myList[8] holds passenger name + TextSpan(text: ' (', style: AppStyle.subtitle), + TextSpan( + text: myList[33].toString(), + style: AppStyle + .title), // Assuming 'rate' holds the passenger rate + TextSpan(text: ' ⭐)', style: AppStyle.subtitle), ], ), ), ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - MyElevatedButton( - kolor: AppColor.greenColor, - title: 'Accept Order'.tr, - onPressed: () async { - box.write(BoxName.statusDriverLocation, 'on'); - orderRequestController.endTimer(); - orderRequestController.changeApplied(); - await CRUD().postFromDialogue( - link: AppLink.addDriverOrder, - payload: { - 'driver_id': myList[6].toString(), - // box.read(BoxName.driverID).toString(), - 'order_id': body.toString(), - 'status': 'Apply' + Padding( + padding: const EdgeInsets.all(4), + child: Container( + color: AppColor.greenColor.withOpacity(.5), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + RichText( + text: TextSpan( + text: 'Cost Of Trip IS '.tr, + style: AppStyle.title, + children: [ + TextSpan( + text: myList[26], + style: AppStyle.headTitle2), + ], + ), + ), + RichText( + text: TextSpan( + text: 'Total net'.tr, + style: AppStyle.title, + children: [ + TextSpan( + text: (double.parse(myList[2]) - + double.parse(myList[32])) + .toStringAsFixed(2), + style: AppStyle.headTitle2), + ], + ), + ), + ], + ), + ), + ), + + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + MyElevatedButton( + kolor: AppColor.greenColor, + title: 'Accept Order'.tr, + onPressed: () async { + Get.put(HomeCaptainController()).changeRideId(); + box.write(BoxName.statusDriverLocation, 'on'); + orderRequestController.endTimer(); + orderRequestController.changeApplied(); + await CRUD().postFromDialogue( + link: AppLink.addDriverOrder, + payload: { + 'driver_id': myList[6].toString(), + // box.read(BoxName.driverID).toString(), + 'order_id': myList[16].toString(), + 'status': 'Apply' + }); + var res = await CRUD().post( + link: AppLink.updateRideAndCheckIfApplied, + payload: { + 'id': myList[16], + 'rideTimeStart': DateTime.now().toString(), + 'status': 'Apply', + 'driver_id': myList[6].toString(), + }); + if (res == 'failure') { + MyDialog().getDialog( + "This ride is already applied by another driver." + .tr, + '', () { + Get.back(); }); - var res = await CRUD().post( - link: AppLink.updateRideAndCheckIfApplied, - payload: { - 'id': myList[16], - 'rideTimeStart': DateTime.now().toString(), - 'status': 'Apply', - 'driver_id': box.read(BoxName.driverID), - }); - if (res == 'failure') { - MyDialog().getDialog( - "This ride is already applied by another driver." - .tr, - '', () { + } else { + List bodyToPassenger = [ + myList[6].toString(), //driver id + myList[8].toString(), // driver name + myList[9].toString(), //token driver + ]; + FirebaseMessagesController() + .sendNotificationToPassengerToken( + 'Apply Ride'.tr, + 'your ride is applied'.tr, + // arguments['DriverList'][9].toString(), + myList[9].toString(), + // box.read(BoxName.tokenDriver).toString(), + bodyToPassenger, + 'start.wav'); Get.back(); - }); - } else { + box.write(BoxName.rideArguments, { + 'passengerLocation': myList[0].toString(), + 'passengerDestination': myList[1].toString(), + 'Duration': myList[4].toString(), + 'totalCost': myList[26].toString(), + 'Distance': myList[5].toString(), + 'name': myList[8].toString(), + 'phone': myList[10].toString(), + 'email': myList[28].toString(), + 'WalletChecked': myList[13].toString(), + 'tokenPassenger': myList[9].toString(), + 'direction': + 'https://www.google.com/maps/dir/${myList[0]}/${myList[1]}/', + 'DurationToPassenger': myList[15].toString(), + 'rideId': myList[16].toString(), + 'passengerId': myList[7].toString(), + 'driverId': myList[18].toString(), + 'durationOfRideValue': myList[19].toString(), + 'paymentAmount': myList[2].toString(), + 'paymentMethod': myList[13].toString() == 'true' + ? 'visa' + : 'cash', + 'isHaveSteps': myList[20].toString(), + 'step0': myList[21].toString(), + 'step1': myList[22].toString(), + 'step2': myList[23].toString(), + 'step3': myList[24].toString(), + 'step4': myList[25].toString(), + 'passengerWalletBurc': myList[26].toString(), + 'timeOfOrder': DateTime.now().toString(), + 'totalPassenger': myList[2].toString(), + 'carType': myList[31].toString(), + 'kazan': myList[32].toString(), + 'startNameLocation': myList[29].toString(), + 'endNameLocation': myList[30].toString(), + }); + 'passengerID =${box.read(BoxName.rideArguments)}'; + Get.to(() => PassengerLocationMapPage(), + arguments: box.read(BoxName.rideArguments)); + } + // Get.back(); + }, + ), + GetBuilder( + builder: (timerController) { + final isNearEnd = timerController.remainingTime <= + 5; // Define a threshold for "near end" + + return Stack( + alignment: Alignment.center, + children: [ + CircularProgressIndicator( + value: timerController.progress, + // Set the color based on the "isNearEnd" condition + color: isNearEnd ? Colors.red : Colors.blue, + ), + Text( + '${timerController.remainingTime}', + style: AppStyle.number, + ), + ], + ); + }, + ), + MyElevatedButton( + title: 'Refuse Order'.tr, + onPressed: () async { + orderRequestController.endTimer(); List bodyToPassenger = [ box.read(BoxName.driverID).toString(), box.read(BoxName.nameDriver).toString(), box.read(BoxName.tokenDriver).toString(), ]; + FirebaseMessagesController() .sendNotificationToPassengerToken( - 'Apply Ride', - 'your ride is applied'.tr, - // arguments['DriverList'][9].toString(), - arguments['DriverList'][9].toString(), - // box.read(BoxName.tokenDriver).toString(), + 'Order Under Review'.tr, + '${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}', + myList[9].toString(), bodyToPassenger, - 'start.wav'); - Get.back(); - box.write(BoxName.rideArguments, { - 'passengerLocation': myList[0].toString(), - 'passengerDestination': myList[1].toString(), - 'Duration': myList[4].toString(), - 'totalCost': myList[26].toString(), - 'Distance': myList[5].toString(), - 'name': myList[8].toString(), - 'phone': myList[10].toString(), - 'email': myList[28].toString(), - 'WalletChecked': myList[13].toString(), - 'tokenPassenger': myList[9].toString(), - 'direction': - 'https://www.google.com/maps/dir/${myList[0]}/${myList[1]}/', - 'DurationToPassenger': myList[15].toString(), - 'rideId': myList[16].toString(), - 'passengerId': myList[7].toString(), - 'driverId': myList[18].toString(), - 'durationOfRideValue': myList[19].toString(), - 'paymentAmount': myList[2].toString(), - 'paymentMethod': myList[13].toString() == 'true' - ? 'visa' - : 'cash', - 'isHaveSteps': myList[20].toString(), - 'step0': myList[21].toString(), - 'step1': myList[22].toString(), - 'step2': myList[23].toString(), - 'step3': myList[24].toString(), - 'step4': myList[25].toString(), - 'passengerWalletBurc': myList[26].toString(), - 'timeOfOrder': DateTime.now().toString(), - 'totalPassenger': myList[2].toString(), - 'carType': myList[31].toString(), - 'kazan': myList[32].toString(), - 'startNameLocation': myList[29].toString(), - 'endNameLocation': myList[30].toString(), - }); - 'passengerID =${box.read(BoxName.rideArguments)}'; - Get.to(() => PassengerLocationMapPage(), - arguments: box.read(BoxName.rideArguments)); - } - // Get.back(); - }, - ), - GetBuilder( - builder: (timerController) { - final isNearEnd = timerController.remainingTime <= - 5; // Define a threshold for "near end" + 'notification.wav'); - return Stack( - alignment: Alignment.center, - children: [ - CircularProgressIndicator( - value: timerController.progress, - // Set the color based on the "isNearEnd" condition - color: isNearEnd ? Colors.red : Colors.blue, - ), - Text( - '${timerController.remainingTime}', - style: AppStyle.number, - ), - ], - ); - }, - ), - MyElevatedButton( - title: 'Refuse Order'.tr, - onPressed: () async { - orderRequestController.endTimer(); - List bodyToPassenger = [ - box.read(BoxName.driverID).toString(), - box.read(BoxName.nameDriver).toString(), - box.read(BoxName.tokenDriver).toString(), - ]; - - FirebaseMessagesController() - .sendNotificationToPassengerToken( - 'Refused Ride'.tr, - '${box.read(BoxName.nameDriver)} ${'reject your order.'.tr}', - arguments['DriverList'][9].toString(), - // box.read(BoxName.tokenDriver).toString(), - bodyToPassenger, - 'cancel.wav'); - orderRequestController.refuseOrder( - myList[16].toString(), - ); - orderRequestController.addRideToNotificationDriverString( + orderRequestController.refuseOrder( myList[16].toString(), - myList[29].toString(), - myList[30].toString(), - '${DateTime.now().year}-${DateTime.now().month}-${DateTime.now().day}', - '${DateTime.now().hour}:${DateTime.now().minute}', - myList[2].toString(), - myList[7].toString(), - 'wait', - myList[31].toString(), - myList[33].toString(), - myList[2].toString(), - myList[5].toString(), - myList[4].toString()); //duration - }, - kolor: AppColor.redColor, - ), - ], - ), - ) - ], + ); + orderRequestController.addRideToNotificationDriverString( + myList[16].toString(), + myList[29].toString(), + myList[30].toString(), + '${DateTime.now().year}-${DateTime.now().month}-${DateTime.now().day}', + '${DateTime.now().hour}:${DateTime.now().minute}', + myList[2].toString(), + myList[7].toString(), + 'wait', + myList[31].toString(), + myList[33].toString(), + myList[2].toString(), + myList[5].toString(), + myList[4].toString()); //duration + }, + kolor: AppColor.redColor, + ), + ], + ), + ) + ], + ), ), ) ], diff --git a/lib/views/home/Captin/orderCaptin/order_speed_request.dart b/lib/views/home/Captin/orderCaptin/order_speed_request.dart index ea9d976..5b4ec06 100644 --- a/lib/views/home/Captin/orderCaptin/order_speed_request.dart +++ b/lib/views/home/Captin/orderCaptin/order_speed_request.dart @@ -131,7 +131,7 @@ class OrderSpeedRequest extends StatelessWidget { ? AppColor.greenColor : AppColor.secondaryColor, child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ TextButton.icon( onPressed: () { @@ -145,12 +145,14 @@ class OrderSpeedRequest extends StatelessWidget { 'Trip has Steps'.tr, style: AppStyle.title, ) - : Text('Routs of Trip'.tr, + : Text('Payment Method'.tr, style: AppStyle.title)), Container( - color: myList[13].toString() == 'true' - ? AppColor.deepPurpleAccent - : AppColor.greenColor, + decoration: AppStyle.boxDecoration.copyWith( + color: myList[13].toString() == 'true' + ? AppColor.deepPurpleAccent + : AppColor.greenColor, + ), child: myList[13].toString() == 'true' //Visa or Cash Method from notify to driver ? Text( @@ -182,90 +184,99 @@ class OrderSpeedRequest extends StatelessWidget { duration: const Duration(seconds: 5), curve: Curves.easeInOut, child: myList[31].toString() == 'Comfort' - ? const Icon( - Icons.ac_unit, - color: AppColor.blueColor, - size: 50, + ? Column( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + const Icon( + Icons.ac_unit, + color: AppColor.blueColor, + size: 50, + ), + Text( + 'Air condition Trip'.tr, + style: AppStyle.subtitle, + ), + ], ) : const SizedBox(), ), ), Text( - myList[31].toString(), + myList[31].toString().tr, style: AppStyle.title .copyWith(color: AppColor.greenColor), ), ], )), ), + const SizedBox( + height: 5, + ), Container( height: Get.height * .15, width: Get.width * .9, decoration: AppStyle.boxDecoration1, - child: ListView( - // mainAxisAlignment: MainAxisAlignment.spaceAround, - // crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const Icon( - Icons.arrow_circle_up, - color: AppColor.greenColor, - ), - Text( - myList[12] + ' ' + ' (${myList[11]}) ', - style: AppStyle.title, - ), - ], - ), - Text( - myList[29], - style: AppStyle.title, - ), - ], - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const Icon( - Icons.arrow_circle_up, - color: AppColor.greenColor, - ), - Text( - myList[5] + ' ' + ' (${myList[4]}) ', - style: AppStyle.title, - ), - ], - ), - Text( - myList[30], - style: AppStyle.title, - ), - ], - ), - ], - ), - ], + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 5, vertical: 1), + child: ListView( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Icon( + Icons.arrow_circle_up, + color: AppColor.greenColor, + ), + Text( + myList[12] + ' ' + ' (${myList[11]}) ', + style: AppStyle.title, + ), + ], + ), + Text( + myList[29], + style: AppStyle.title, + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Icon( + Icons.arrow_circle_down, + color: AppColor.redColor, + ), + Text( + myList[5] + ' ' + ' (${myList[4]}) ', + style: AppStyle.title, + ), + ], + ), + Text( + myList[30], + style: AppStyle.title, + ), + ], + ), + ], + ), ), ), Padding( padding: const EdgeInsets.all(8.0), child: RichText( text: TextSpan( - text: 'Passenger name : ' + text: "Passenger name: " .tr, // Changed text to be more generic style: AppStyle.subtitle, children: [ @@ -283,21 +294,42 @@ class OrderSpeedRequest extends StatelessWidget { ), ), ), + Padding( padding: const EdgeInsets.all(4), child: Container( - color: AppColor.deepPurpleAccent, - child: RichText( - text: TextSpan( - text: 'Cost Of Trip IS '.tr, - style: AppStyle.title, - children: [ - TextSpan(text: myList[26], style: AppStyle.headTitle2), - ], - ), + color: AppColor.greenColor.withOpacity(.5), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + RichText( + text: TextSpan( + text: 'Cost Of Trip IS '.tr, + style: AppStyle.title, + children: [ + TextSpan( + text: myList[26], style: AppStyle.headTitle2), + ], + ), + ), + RichText( + text: TextSpan( + text: 'Total net'.tr, + style: AppStyle.title, + children: [ + TextSpan( + text: (double.parse(myList[2]) - + double.parse(myList[32])) + .toStringAsFixed(2), + style: AppStyle.headTitle2), + ], + ), + ), + ], ), ), ), + Padding( padding: const EdgeInsets.all(8.0), child: Row( diff --git a/lib/views/home/my_wallet/walet_captain.dart b/lib/views/home/my_wallet/walet_captain.dart index 399addc..c3e3ab3 100644 --- a/lib/views/home/my_wallet/walet_captain.dart +++ b/lib/views/home/my_wallet/walet_captain.dart @@ -48,15 +48,17 @@ class WalletCaptain extends StatelessWidget { const SizedBox(), Container( // decoration: AppStyle.boxDecoration1.copyWith( - color: double.parse( - captainWalletController.totalPoints) < + color: double.parse(captainWalletController + .totalPoints + .toString()) < 0 && - double.parse( - captainWalletController.totalPoints) > + double.parse(captainWalletController + .totalPoints + .toString()) > -3000 ? AppColor.yellowColor - : double.parse( - captainWalletController.totalPoints) < + : double.parse(captainWalletController.totalPoints + .toString()) < -3000 ? AppColor.redColor : AppColor.greenColor, diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 670b3e4..2c25330 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 5FF20D1C7FA311876020D2FA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0D357E3E229DEB5E9B861200 /* GoogleService-Info.plist */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -60,11 +61,12 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0D357E3E229DEB5E9B861200 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* ride.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ride.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* ride.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ride.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -125,6 +127,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 0D357E3E229DEB5E9B861200 /* GoogleService-Info.plist */, ); sourceTree = ""; }; @@ -284,6 +287,7 @@ files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + 5FF20D1C7FA311876020D2FA /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/macos/Runner/GoogleService-Info.plist b/macos/Runner/GoogleService-Info.plist index a9e1b89..e61bc7a 100644 --- a/macos/Runner/GoogleService-Info.plist +++ b/macos/Runner/GoogleService-Info.plist @@ -3,9 +3,11 @@ CLIENT_ID - 594687661098-od4d3lpsdba79shpjmala0a3lrps4spi.apps.googleusercontent.com + 594687661098-8e26699cris2k3nj5msj1osi59it9kpf.apps.googleusercontent.com REVERSED_CLIENT_ID - com.googleusercontent.apps.594687661098-od4d3lpsdba79shpjmala0a3lrps4spi + com.googleusercontent.apps.594687661098-8e26699cris2k3nj5msj1osi59it9kpf + ANDROID_CLIENT_ID + 594687661098-2dhoogl7be9phobfbu8bbg1sj567iv88.apps.googleusercontent.com API_KEY AIzaSyCf2mW2h0HD8ZYjwh4VOa2ladw6MJkCDTM GCM_SENDER_ID @@ -13,7 +15,7 @@ PLIST_VERSION 1 BUNDLE_ID - com.mobileapp.store.ride.RunnerTests + com.mobileapp.store.ride PROJECT_ID ride-b1bd8 STORAGE_BUCKET @@ -29,6 +31,6 @@ IS_SIGNIN_ENABLED GOOGLE_APP_ID - 1:594687661098:ios:e8ca02ed508d4737595f53 + 1:594687661098:ios:6f69eee1449be943595f53 \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index b849849..c27ba6e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -90,6 +90,13 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + bubble_head: + dependency: "direct main" + description: + path: bubble-master + relative: true + source: path + version: "0.0.4" build: dependency: transitive description: @@ -555,10 +562,18 @@ packages: dependency: transitive description: name: flutter_cache_manager - sha256: "395d6b7831f21f3b989ebedbb785545932adb9afe2622c1ffacf7f4b53a7e544" + sha256: ceff65d74d907b1b772e22cf04daad60fb472461638977d9fae8b00a63e01e3d url: "https://pub.dev" source: hosted - version: "3.3.2" + version: "3.3.3" + flutter_contacts: + dependency: "direct main" + description: + name: flutter_contacts + sha256: d9de65f7e8f277b60fa59d699fd916f9aeeb7ec77c9cc631745b4c9068e0b627 + url: "https://pub.dev" + source: hosted + version: "1.1.8" flutter_font_icons: dependency: "direct main" description: @@ -655,6 +670,21 @@ packages: url: "https://pub.dev" source: hosted version: "7.2.0" + flutter_overlay_apps: + dependency: "direct main" + description: + path: flutter_overlay_apps-main + relative: true + source: path + version: "1.2.0" + flutter_overlay_window: + dependency: "direct main" + description: + name: flutter_overlay_window + sha256: af871da9270bf5d67e5a657d2e286dfafa43f3417e98fa9e3c06da3f9d0f340e + url: "https://pub.dev" + source: hosted + version: "0.4.4" flutter_paypal: dependency: "direct main" description: @@ -1677,10 +1707,10 @@ packages: dependency: transitive description: name: rxdart - sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" url: "https://pub.dev" source: hosted - version: "0.27.7" + version: "0.28.0" sanitize_html: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e99ce52..e507e31 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,11 @@ dependencies: sdk: flutter secure_string_operations: path: ./secure_string_operations + flutter_overlay_apps: + path: ./flutter_overlay_apps-main + bubble_head: + path: ./bubble-master + # flutter_overlay_window: ^0.4.4 cupertino_icons: ^1.0.2 firebase_messaging: ^15.0.3 firebase_core: ^3.2.0 @@ -62,6 +67,10 @@ dependencies: url: https://github.com/dharmik-dalwadi-seaflux/background_location.git ref: master + # flutter_overlay_apps: + # git: + # url: https://github.com/Hamza-Ayed/flutter_overlay_apps.git + # ref: main record: ^5.0.5 dio: ^5.4.3+1 webview_flutter: ^4.7.0 @@ -73,6 +82,10 @@ dependencies: firebase_auth: ^5.1.2 package_info_plus: ^8.0.0 flutter_image_compress: ^2.3.0 + flutter_contacts: ^1.1.8 + flutter_overlay_window: ^0.4.4 + # dash_bubble: ^2.0.0 + # bubble_head: ^0.0.4 # google_mlkit_face_detection: ^0.11.0 dev_dependencies: @@ -109,12 +122,12 @@ flutter: - shorebird.yaml fonts: - - family: mohanad - fonts: - - asset: assets/fonts/mohanad.ttf - - family: josefin - fonts: - - asset: assets/fonts/josefin.ttf + # - family: mohanad + # fonts: + # - asset: assets/fonts/mohanad.ttf + # - family: josefin + # fonts: + # - asset: assets/fonts/josefin.ttf - family: digit fonts: - asset: assets/fonts/digit.ttf diff --git a/test/widget_test.dart b/test/widget_test.dart index 71168b6..2f45d6b 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:SEFER/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget(MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);