This commit is contained in:
Hamza-Ayed
2024-07-31 21:19:19 +03:00
parent dea83d970c
commit 2bc71355c3
106 changed files with 4600 additions and 727 deletions

View File

@@ -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'
}

View File

@@ -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

View File

@@ -1,10 +1,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -14,32 +18,48 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<!-- <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" /> -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:icon="@mipmap/launcher_icon"
android:label="Sefer Driver"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon">
android:theme="@style/LaunchTheme">
<service
android:name=".MyFirebaseMessagingService"
android:exported="false"></service>
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTask"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="com.example.action.APP_ACTION" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/api_key" />
@@ -49,28 +69,77 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<!-- <service
android:name=".LocationUpdatesService"
android:exported="false" /> -->
<service
android:name=".LocationUpdatesService"
android:foregroundServiceType="location"
android:exported="false"> <!-- or false, depending on whether it should be accessible by other
apps -->
android:exported="false"
android:foregroundServiceType="location" />
<!-- <service
android:name=".java.MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service> -->
<service
android:name="com.google.firebase.messaging.FirebaseMessagingService"
android:exported="false"
tools:replace="android:exported">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service
android:name="com.phan_tech.flutter_overlay_apps.OverlayService"
android:exported="false" />
<service
android:name="flutter.overlay.window.flutter_overlay_window.OverlayService"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="explanation_for_special_use" />
</service>
<receiver
android:name=".YourBroadcastReceiver"
android:exported="false"> <!-- or false, depending on whether it should be accessible by other
apps -->
<!-- <intent-filter>
<action android:name="com.example.ACTION" />
</intent-filter> -->
android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.sefer_driver" />
</intent-filter>
</receiver>
<service
android:name="com.dsaved.bubblehead.bubble.BubbleHeadService"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="intent.bring.app.to.foreground" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
<receiver
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver"
android:exported="false" />
<receiver
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
<receiver
android:name=".YourBroadcastReceiver"
android:exported="false" />
</application>
</manifest>

View File

@@ -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"

View File

@@ -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() {
}
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)
}
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/notification_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Title" />
<TextView
android:id="@+id/notification_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
</LinearLayout>

View File

@@ -3,4 +3,6 @@
<!-- <string name="default_notification_channel_id">ride_channel</string> -->
<string name="default_notification_channel_id">default_channel</string>
<string name="api_key">AIzaSyCyfwRXTwSTLOFQSQgN5p7QZgGJVZnEKq0</string>
<string name="channel_name">FCM Notifications</string>
<string name="channel_description">Notifications from Firebase Cloud Messaging</string>
</resources>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
assets/images/logo1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

BIN
assets/order.mp3 Normal file

Binary file not shown.

7
bubble-master/.gitignore vendored Executable file
View File

@@ -0,0 +1,7 @@
.DS_Store
.dart_tool/
.packages
.pub/
build/

10
bubble-master/.metadata Executable file
View File

@@ -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

14
bubble-master/CHANGELOG.md Executable file
View File

@@ -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

21
bubble-master/LICENSE Executable file
View File

@@ -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.

128
bubble-master/README.md Executable file
View File

@@ -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
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
```
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
<intent-filter>
<action android:name="intent.bring.app.to.foreground" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
```
Add `service` in application tag
```xml
<service
android:name="com.dsaved.bubblehead.bubble.BubbleHeadService"
android:enabled="true"
android:exported="false"/>
```
### 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<void> startBubbleHead() async {
try {
await _bubble.startBubbleHead();
} on PlatformException {
print('Failed to call startBubbleHead');
}
}
```
**To stop/close bubble**
```dart
Bubble _bubble = new Bubble();
Future<void> 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<void> 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)

8
bubble-master/android/.gitignore vendored Executable file
View File

@@ -0,0 +1,8 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures

View File

@@ -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'
}

View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -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

View File

@@ -0,0 +1 @@
rootProject.name = 'bubble'

View File

@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dsaved.bubblehead.bubble">
</manifest>

View File

@@ -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);
}
}
}

View File

@@ -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() {
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/black" />
</shape>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="1dp"
android:color="@android:color/white" />
</shape>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/remove_relativelayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<ImageView
android:id="@+id/remove_img"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/white_circle_shape"
android:padding="10dp"
android:src="@drawable/ic_close_white_24dp" />
</RelativeLayout>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- Root container of Floating Widget View -->
<RelativeLayout
android:id="@+id/root_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- View while view is collapsed -->
<RelativeLayout
android:id="@+id/collapse_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="visible">
<!-- ImageView of floating widget -->
<ImageView
android:id="@+id/chat_head_profile_iv"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginTop="8dp"
android:src="@drawable/ic_launcher"
tools:ignore="ContentDescription" />
<!-- Close button to close Floating Widget View -->
<ImageView
android:id="@+id/close_bubble_head"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
android:layout_marginTop="5dp"
android:padding="1dp"
android:background="@drawable/circle_shape"
android:src="@drawable/ic_close_white_24dp"
tools:ignore="ContentDescription" />
</RelativeLayout>
</RelativeLayout>
</FrameLayout>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="ShapeAppearance.Image.PILL" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">50%</item>
</style>
<style name="ShapeAppearance.Image.Top.PILL" parent="">
<item name="cornerSizeTopLeft">6dp</item>
<item name="cornerFamilyTopLeft">rounded</item>
<item name="cornerSizeTopRight">6dp</item>
<item name="cornerFamilyTopRight">rounded</item>
</style>
</resources>

39
bubble-master/lib/bubble.dart Executable file
View File

@@ -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<void> 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<void> stopBubbleHead() async {
await _channel.invokeMethod('stopBubbleHead');
}
}

189
bubble-master/pubspec.lock Executable file
View File

@@ -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"

63
bubble-master/pubspec.yaml Executable file
View File

@@ -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

View File

@@ -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"}}}}}}
{"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"}}}}}

29
flutter_overlay_apps-main/.gitignore vendored Normal file
View File

@@ -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/

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application>
...
<service
android:name="com.phan_tech.flutter_overlay_apps.OverlayService"
android:exported="false" />
</application>
```
### 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(<data>);
```
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<MyOverlaContent> createState() => _MyOverlaContentState();
}
class _MyOverlaContentState extends State<MyOverlaContent> {
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 <a href="https://www.buymeacoffee.com/EddieGenius" target="_blank"><img src="https://i.imgur.com/aV6DDA7.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" > </a>

View File

@@ -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

View File

@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

View File

@@ -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"
}

Binary file not shown.

View File

@@ -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

234
flutter_overlay_apps-main/android/gradlew vendored Executable file
View File

@@ -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" "$@"

View File

@@ -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

View File

@@ -0,0 +1 @@
rootProject.name = 'flutter_overlay_apps'

View File

@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.phan_tech.flutter_overlay_apps"
>
</manifest>

View File

@@ -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<Any?> {
private lateinit var channel: MethodChannel
private lateinit var messenger: BasicMessageChannel<Any?>
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<Int>("height")
val width = call.argument<Int>("width")
val alignment = call.argument<String>("alignment")
val closeOnBackButton = call.argument<Boolean>("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<Any?>) {
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}")
)
)
}
}
}

View File

@@ -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<Any?>{
override fun onMessage(message: Any?, reply: BasicMessageChannel.Reply<Any?>) {
WindowSetup.messenger!!.send(message)
}
}

View File

@@ -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<Any?>? = 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
}
}
}
}

View File

@@ -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<bool?> 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<bool?> 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
}

View File

@@ -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

View File

@@ -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);
});
}

View File

@@ -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

View File

@@ -6,6 +6,8 @@
<string>594687661098-9fnj82nef9oagl98prigdf8qne3ddbto.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.594687661098-9fnj82nef9oagl98prigdf8qne3ddbto</string>
<key>ANDROID_CLIENT_ID</key>
<string>594687661098-2dhoogl7be9phobfbu8bbg1sj567iv88.apps.googleusercontent.com</string>
<key>API_KEY</key>
<string>AIzaSyCf2mW2h0HD8ZYjwh4VOa2ladw6MJkCDTM</string>
<key>GCM_SENDER_ID</key>

View File

@@ -2,6 +2,18 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>location</string>
<string>remote-notification</string>
</array>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
<key>NSContactsUsageDescription</key>
<string>This app requires contacts access to function properly.</string>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
@@ -21,7 +33,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>51</string>
<string>52</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@@ -36,7 +48,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>4.0.51</string>
<string>4.0.52</string>
<key>FirebaseAppDelegateProxyEnabled</key>
<string>NO</string>
<key>GMSApiKey</key>
@@ -51,8 +63,8 @@
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>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.</string>
<string>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.</string>
<key>NSFaceIDUsageDescription</key>
<string>Use Face ID to securely authenticate payment accounts.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
@@ -72,12 +84,6 @@
<string>Explanation of why your app needs access to the photo library.</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>location</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>

View File

@@ -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";

View File

@@ -21,8 +21,9 @@ class AppColor {
// For dynamic elements like gradients
static List<Color> 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<Color> secondaryGradientStartEnd = [

View File

@@ -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///////////

View File

@@ -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(

View File

@@ -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";
}

View File

@@ -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<void> 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');

View File

@@ -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());

View File

@@ -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);
}
}
}

View File

@@ -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<GoogleSignInAccount?> signInFromLogin() async {
// // try {
// // final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
// // if (googleUser != null) {
// // await _handleSignUp(googleUser);
// // // if (box.read(BoxName.countryCode) == 'Egypt') {
// // await Get.find<LoginDriverController>().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<LoginDriverController>()
// .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<GoogleSignInAccount?> signInFromLogin() async {
try {
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
if (googleUser != null) {
await _handleSignUp(googleUser);
// if (box.read(BoxName.countryCode) == 'Egypt') {
await Get.find<LoginDriverController>().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;
}
}

View File

@@ -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<void> 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");
}
}
}

View File

@@ -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<dynamic>;
// var myPoints = jsonDecode(points) as List<dynamic>;
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<void> 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<dynamic>;
// 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<dynamic>;
// 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<dynamic>;
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),
),
],
),
),
);
}
}

View File

@@ -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<void> 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)}');
}
}
}
}

View File

@@ -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());
}
},
),
),
);
}
}

View File

@@ -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']);

View File

@@ -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

View File

@@ -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<CaptainWalletController>().totalPoints;
totalPoints =
Get.find<CaptainWalletController>().totalPoints.toString();
isActive = Get.find<HomeCaptainController>().isActive;
if (isActive) {
if (double.parse(totalPoints) > -3000) {

View File

@@ -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<void> 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<void> 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<void> 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);
}

View File

@@ -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<String> 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<dynamic> 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<RegisterCaptainController>().phoneController.text}'
});
box.write(BoxName.phoneDriver,
'+2${Get.find<RegisterCaptainController>().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: '',

View File

@@ -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();

View File

@@ -229,7 +229,7 @@ class HomeCaptainController extends GetxController {
getAllPayment();
startPeriodicExecution();
onMapCreated(mapHomeCaptainController!);
totalPoints = Get.find<CaptainWalletController>().totalPoints;
totalPoints = Get.find<CaptainWalletController>().totalPoints.toString();
getRefusedOrderByCaptain();
// LocationController().getLocation();
super.onInit();

View File

@@ -26,8 +26,10 @@ class MapDriverController extends GetxController {
bool isLoading = true;
final formKey1 = GlobalKey<FormState>();
final formKey2 = GlobalKey<FormState>();
final formKeyCancel = GlobalKey<FormState>();
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;

View File

@@ -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

View File

@@ -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']);

View File

@@ -10,7 +10,7 @@ class LocaleController extends GetxController {
String countryCode = '';
void restartApp() {
// Get.offAll(MyApp);
runApp(const MyApp());
runApp(MyApp());
}
ThemeData appTheme = themeEnglish;

View File

@@ -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":
"التقاط صورة للجهة الخلفية من وثيقة الهوية الخاصة بك",

View File

@@ -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<String, dynamic> 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(

View File

@@ -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',
);
}

View File

@@ -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<void> 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<dynamic>;
} 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<void> handleBackgroundNotificationClick(RemoteMessage message) async {
await Firebase.initializeApp();
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
var myListString = message.data['DriverList'];
var myList = jsonDecode(myListString) as List<dynamic>;
@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<Future> 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)),
],
);
}
}

View File

@@ -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
)
''');
},
);
}

View File

@@ -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<InviteController>(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}',
),
],
),
),
);
},

View File

@@ -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,

View File

@@ -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');
}

View File

@@ -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<HomeCaptainController> leftMainMenuCaptainIcons() {
return GetBuilder<HomeCaptainController>(
@@ -168,16 +169,125 @@ GetBuilder<HomeCaptainController> 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<String> 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<void> 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,
// ),
// );
// }),
// ),
],
)),

View File

@@ -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 =

View File

@@ -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<OrderOverlay> createState() => _OrderOverlayState();
}
class _OrderOverlayState extends State<OrderOverlay>
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<Color>(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();
}
}
}

View File

@@ -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<LatLng> 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<HomeCaptainController>().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<HomeCaptainController>().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<String> 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<OrderRequestController>(
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<String> 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<OrderRequestController>(
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<String> 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,
),
],
),
)
],
),
),
)
],

View File

@@ -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(

Some files were not shown because too many files have changed in this diff Show More