Update codebase

This commit is contained in:
Hamza-Ayed
2026-08-09 17:45:22 +03:00
parent b64debaa88
commit 6726fcd917
1413 changed files with 0 additions and 104728 deletions
-3
View File
@@ -1,3 +0,0 @@
# Tripz — تطبيق السائق (Flutter)
كود موحّد + flavor لكل مستأجر. **OffersBloc** لتدفق العروض. راجع docs/03.
الحالة: هيكل placeholder — يُنشأ فعلياً في P0/P1.
@@ -1,29 +0,0 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
@@ -1,14 +0,0 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
gradle.properties
**/*.keystore
**/*.jks
@@ -1,13 +0,0 @@
arguments=--init-script /var/folders/cn/w9j2wl8d11517_kjx3xk_vc00000gn/T/db3b08fc4a9ef609cb16b96b200fa13e563f396e9bb1ed0905fdab7bc3bc513b.gradle --init-script /var/folders/cn/w9j2wl8d11517_kjx3xk_vc00000gn/T/52cde0cfcf3e28b8b7510e992210d9614505e0911af0c190bd590d7158574963.gradle
auto.sync=true
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
gradle.user.home=
java.home=/Users/hamzaaleghwairyeen/Library/Java/JavaVirtualMachines/openjdk-22.0.2/Contents/Home
jvm.arguments=
offline.mode=false
override.workspace.settings=true
show.console.view=true
show.executions.view=true
@@ -1,130 +0,0 @@
plugins {
id "com.android.application"
// START: FlutterFire Configuration
id 'com.google.gms.google-services'
// END: FlutterFire Configuration
id "org.jetbrains.kotlin.android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystorePropertiesFile.withReader("UTF-8") { reader ->
keystoreProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode') ?: '12'
def flutterVersionName = localProperties.getProperty('flutter.versionName') ?: '1.1.2'
android {
namespace "com.sefer_driver"
compileSdk = 36
ndkVersion "29.0.14033849"
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.22.1"
}
}
//
defaultConfig {
applicationId "com.sefer_driver"
minSdk 29
targetSdk 36
versionCode 152
versionName '2.0.152'
multiDexEnabled true
ndk {
abiFilters "armeabi-v7a", "arm64-v8a"
}
externalNativeBuild {
cmake {
arguments "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
}
}
// استدعاء المفتاح من ملف key.properties
manifestPlaceholders += [mapsApiKey: keystoreProperties['mapsApiKey'] ?: ""]
}
signingConfigs {
release {
// Defensively check if properties exist to avoid NullPointerException in BundleTool
if (keystoreProperties.isEmpty()) {
println "BUILD_LOG: ERROR - No properties found in key.properties. Release build will fail."
} else {
storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword = keystoreProperties['storePassword']
keyAlias = keystoreProperties['keyAlias']
keyPassword = keystoreProperties['keyPassword']
// Final check to ensure no crucial signing variable is null
if (!storeFile || !storePassword || !keyAlias || !keyPassword) {
println "BUILD_LOG: ERROR - One or more signing properties are NULL. Check key.properties."
}
}
}
}
buildTypes {
release {
// Only assign signingConfig if the release config is valid
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
coreLibraryDesugaringEnabled true
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17
}
sourceSets {
main {
java.srcDirs += 'src/main/kotlin'
jniLibs.srcDirs = ['src/main/jniLibs']
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// تمت الترقية لتطابق تطبيق الراكب
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
// تمت الترقية لتطابق تطبيق الراكب (لتجنب ملفات 4KB القديمة)
implementation "com.stripe:stripe-android:21.4.2"
implementation 'com.stripe:paymentsheet:21.4.2'
implementation 'com.scottyab:rootbeer-lib:0.1.0'
// تمت الترقية لتطابق تطبيق الراكب
implementation 'com.google.android.gms:play-services-safetynet:18.1.0'
implementation "androidx.concurrent:concurrent-futures:1.2.0"
}
@@ -1,38 +0,0 @@
# Suppress warnings for specific Google ML Kit and Stripe classes
-dontwarn com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions$Builder
-dontwarn com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions
-dontwarn com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions$Builder
-dontwarn com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions
-dontwarn com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions$Builder
-dontwarn com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions
-dontwarn com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions$Builder
-dontwarn com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions
-dontwarn com.stripe.android.pushProvisioning.PushProvisioningActivity$g
-dontwarn com.stripe.android.pushProvisioning.PushProvisioningActivityStarter$Args
-dontwarn com.stripe.android.pushProvisioning.PushProvisioningActivityStarter$Error
-dontwarn com.stripe.android.pushProvisioning.PushProvisioningActivityStarter
-dontwarn com.stripe.android.pushProvisioning.PushProvisioningEphemeralKeyProvider
# Keep rules for Google ML Kit
-keep class com.google.mlkit.vision.** { *; }
-keep class com.google.mlkit.vision.text.** { *; }
-keep class com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions { *; }
-keep class com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions$Builder { *; }
-keep class com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions { *; }
-keep class com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions$Builder { *; }
-keep class com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions { *; }
-keep class com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions$Builder { *; }
-keep class com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions { *; }
-keep class com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions$Builder { *; }
# Keep rules for Stripe
-keep class com.stripe.android.** { *; }
-keep class com.stripe.android.pushProvisioning.** { *; }
-keep class com.yalantis.ucrop.** { *; }
-keep class com.yalantis.ucrop.util.** { *; }
# منع قراءة كود RootDetection
#-keep class com.sefer_driver.RootDetection { *; }
-keep class com.sefer_driver.RootDetection {
native <methods>;
}
@@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -1,176 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.sefer_driver"> <!-- تأكد من أن الباكيج هنا صحيح حسب مشروعك -->
<!-- الأذونات (Permissions) -->
<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" />
<!-- أذونات الخدمات الأمامية (ضرورية لأندرويد 14+) -->
<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.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<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.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.QUICKBOOT_POWERON" />
<uses-permission android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
<!-- أذونات إضافية لتطابق Intaleq -->
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.PICTURE_IN_PICTURE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<!-- تم إضافة android:name لجعل التطبيق يعمل كـ Flutter Application بشكل صحيح مثل Intaleq -->
<application
android:name="${applicationName}"
android:label="Tripz Driver"
android:icon="@mipmap/launcher_icon"
android:allowBackup="false"
android:fullBackupContent="false"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="false"
android:theme="@style/LaunchTheme"
android:enableOnBackInvokedCallback="true">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize|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" />
</intent-filter>
<!-- Deep Link المضاف مثل Intaleq -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tripzdriver" />
</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="${mapsApiKey}" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- تم تعطيل Impeller مؤقتاً كما في Intaleq لتجنب مشاكل الريندر -->
<meta-data android:name="io.flutter.embedding.android.EnableImpeller" android:value="false" />
<!-- ✅ الخدمة الأساسية للخلفية مع تحديد النوع (حل الكراش) -->
<service
android:name="id.flutter.flutter_background_service.BackgroundService"
android:foregroundServiceType="location"
android:enabled="true"
android:exported="true" />
<!-- الخدمات الأخرى -->
<service
android:name=".MyFirebaseMessagingService"
android:exported="false" />
<service
android:name=".LocationUpdatesService"
android:exported="false"
android:foregroundServiceType="location" />
<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="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>
@@ -1,11 +0,0 @@
cmake_minimum_required(VERSION 3.10.2) # 3.10.2 is fine, but no need to go as high as 3.31.5
project(sefer_driver) # Good
# Add your C++ source file(s) to create a SHARED library.
add_library(native-lib SHARED native-lib.cpp)
# Find the Android log library.
find_library(log-lib log)
# Link your library against the log library. This is essential for debugging.
target_link_libraries(native-lib ${log-lib})
@@ -1,167 +0,0 @@
#include <jni.h>
#include <string>
#include <fstream>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <android/log.h>
#include <pthread.h>
#include <dlfcn.h>
#include <link.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ptrace.h> // Add this line
#define LOG_TAG "NativeLib"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
// Function to check for common root binaries
bool isRooted() {
std::string paths[] = {
"/system/app/Superuser.apk",
"/system/xbin/su",
"/system/bin/su",
"/system/bin/magisk",
"/system/xbin/magisk",
"/sbin/magisk"
};
for (const auto &path : paths) {
std::ifstream file(path);
if (file.good()) {
return true;
}
}
return false;
}
// Function to check for the presence of files or directories commonly associated with Frida.
bool checkFridaFiles() {
std::string fridaFiles[] = {
"/data/local/tmp/re.frida.server", //Common Frida server path
"/data/local/tmp/frida-server",
"/usr/lib/libfrida-gadget.so", // Frida gadget (injected library)
"/usr/lib64/libfrida-gadget.so",
"/data/local/re.frida.server",
};
for (const auto &path : fridaFiles) {
if (access(path.c_str(), F_OK) != -1) {
LOGE("Frida file detected: %s", path.c_str());
return true;
}
}
return false;
}
// Checks for open ports commonly used by Frida. This is less reliable, as ports can be changed.
bool checkFridaPorts() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
return false; // Couldn't create socket, not a strong indicator.
}
sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(27042); // Default Frida port
inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr);
if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) != -1) {
LOGE("Frida default port (27042) is open.");
close(sock);
return true;
}
close(sock);
return false;
}
// Check the maps file of the current process for any suspicious entries.
bool checkMaps() {
std::ifstream mapsFile("/proc/self/maps");
std::string line;
if (mapsFile.is_open()) {
while (std::getline(mapsFile, line)) {
// Look for lines that indicate injected libraries, especially Frida.
if (line.find("frida") != std::string::npos ||
line.find("gum-js-") != std::string::npos) { // Gum is Frida's JavaScript engine
LOGE("Suspicious entry in /proc/self/maps: %s", line.c_str());
return true;
}
}
mapsFile.close();
} else
{
LOGE("Could not open /proc/self/maps");
return false;
}
return false;
}
// Check loaded modules.
bool checkLoadedModules() {
bool found = false;
dl_iterate_phdr([](struct dl_phdr_info *info, size_t size, void *data) {
bool *found_ptr = static_cast<bool *>(data);
if (std::string(info->dlpi_name).find("frida") != std::string::npos) {
LOGE("Frida module detected: %s", info->dlpi_name);
*found_ptr = true;
return 1; // Stop iterating
}
return 0; // Continue iterating
}, &found);
return found;
}
// This is a simple ptrace check. More sophisticated checks are possible (and necessary for robust detection).
//bool checkPtrace() {
// // Attempt to ptrace ourselves. If another process is already tracing us, this will fail.
// if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0) {
// LOGE("ptrace failed. Debugger or tracer detected.");
// return true; // Likely being traced
// }
// // Detach. If attached, need to detach to not interfere.
// ptrace(PTRACE_DETACH, 0, 0, 0);
// return false;
//}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_sefer_1driver_RootDetection_isNativeRooted(JNIEnv *env, jobject /* this */) {
if (isRooted()) {
return JNI_TRUE;
}
if (checkFridaFiles()) {
return JNI_TRUE;
}
if (checkFridaPorts()) {
return JNI_TRUE;
}
if (checkMaps()) {
return JNI_TRUE;
}
if (checkLoadedModules()) {
return JNI_TRUE;
}
// if (checkPtrace()) {
// return JNI_TRUE;
// }
return JNI_FALSE;
}
@@ -1,15 +0,0 @@
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"
@@ -1,190 +0,0 @@
package com.sefer_driver
import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.view.setPadding
import com.scottyab.rootbeer.RootBeer
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import java.io.File
import java.util.Timer
import kotlin.concurrent.schedule
class MainActivity : FlutterFragmentActivity() {
private val SECURITY_CHANNEL = "com.sefer_driver/security"
private val APP_CONTROL_CHANNEL = "com.sefer_driver/app_control"
// 🔴 1. تعريف القناة لتكون متاحة للوصول من الدوال الأخرى
private var appControlChannel: MethodChannel? = null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// 🔴 2. تهيئة القناة هنا
appControlChannel =
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, APP_CONTROL_CHANNEL)
// Channel for security checks (isRooted)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SECURITY_CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"isNativeRooted" -> result.success(isDeviceCompromised())
else -> result.notImplemented()
}
}
// Channel for app control (bringing to foreground & overlay events)
appControlChannel?.setMethodCallHandler { call, result ->
when (call.method) {
"bringToForeground" -> {
Log.d("MainActivity", "Received bringToForeground request")
val intent =
Intent(this, MainActivity::class.java).apply {
action = Intent.ACTION_MAIN
addCategory(Intent.CATEGORY_LAUNCHER)
addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_SINGLE_TOP
)
}
try {
startActivity(intent)
Log.d("MainActivity", "App brought to foreground successfully")
result.success(true)
} catch (e: Exception) {
Log.e("MainActivity", "Error bringing app to foreground", e)
result.error("ACTIVITY_START_FAILED", e.message, e.stackTraceToString())
}
}
else -> result.notImplemented()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("MainActivity", "MainActivity onCreate")
if (isDeviceCompromised()) {
showSecurityWarningDialog()
}
// 🔴 3. فحص نية فتح التطبيق (هل جاء من زر قبول النافذة؟)
checkIntentForOverlayAccept(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
Log.d("MainActivity", "Received new intent: ${intent.action}")
// 🔴 4. فحص نية التطبيق عند الفتح من الخلفية
checkIntentForOverlayAccept(intent)
}
// =========================================================================
// 🔴 5. الدالة السحرية لالتقاط زر القبول وإرساله للـ Flutter 🔴
// =========================================================================
private fun checkIntentForOverlayAccept(intent: Intent) {
val acceptedTripId = intent.getStringExtra("acceptedTripId")
if (acceptedTripId != null) {
Log.d("MainActivity", "✅ Trip accepted via Native Intent: $acceptedTripId")
// إرسال الإشارة عبر القناة لتطبيق فلاتر
appControlChannel?.invokeMethod("onOverlayTripAccepted", acceptedTripId)
// مسح البيانات حتى لا تتكرر العملية إذا أغلق السائق التطبيق وفتحه يدوياً
intent.removeExtra("acceptedTripId")
}
}
// =========================================================================
// --- دوال الحماية الخاصة بك (لم يتم المساس بها) ---
// =========================================================================
private fun isDeviceCompromised(): Boolean {
return try {
val isRootedByRootBeer = RootBeer(this).isRooted
Log.d("MainActivity", "Root check result: $isRootedByRootBeer")
isRootedByRootBeer
} catch (e: Exception) {
Log.e("MainActivity", "Security check error: ${e.message}", e)
true // Fail-safe: assume compromised if check fails
}
}
private fun showSecurityWarningDialog() {
var secondsRemaining = 10
val progressBar =
ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal).apply {
max = 10
progress = 10
}
val textView =
TextView(this).apply {
text = getString(R.string.security_warning_message)
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
}
val layout =
LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(48)
addView(textView)
addView(progressBar)
}
val dialog =
AlertDialog.Builder(this)
.setTitle(getString(R.string.security_warning_title))
.setView(layout)
.setCancelable(false)
.create()
dialog.show()
val timer = Timer()
timer.schedule(0, 1000) {
secondsRemaining--
runOnUiThread {
progressBar.progress = secondsRemaining
if (secondsRemaining <= 0) {
timer.cancel()
dialog.dismiss()
clearAppDataAndExit()
}
}
}
}
private fun clearAppDataAndExit() {
try {
Runtime.getRuntime().exec("pm clear $packageName")
Log.d("MainActivity", "Cleared app data via package manager")
} catch (e: Exception) {
Log.e("MainActivity", "Error clearing app data: ${e.message}", e)
clearCache()
clearAppData()
}
finishAffinity()
System.exit(0)
}
private fun clearCache() {
deleteDir(cacheDir)
deleteDir(externalCacheDir)
Log.d("MainActivity", "Cleared cache directories")
}
private fun clearAppData() {
Log.d("MainActivity", "App data clearing skipped (commented out)")
}
private fun deleteDir(dir: File?): Boolean {
if (dir != null && dir.isDirectory) {
dir.list()?.forEach { deleteDir(File(dir, it)) }
}
val deleted = dir?.delete() ?: false
Log.d("MainActivity", "Deleted directory ${dir?.path}: $deleted")
return deleted
}
}
@@ -1,41 +0,0 @@
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)
}
}
@@ -1,10 +0,0 @@
package com.sefer_driver
object RootDetection {
init {
System.loadLibrary("native-lib") // Load the native library
}
external fun isNativeRooted(): Boolean // Declare the external function
}
@@ -1,109 +0,0 @@
import android.content.Context
import android.util.Base64
import android.util.Log
import com.google.android.gms.safetynet.SafetyNet
import com.google.android.gms.safetynet.SafetyNetApi
import com.google.android.gms.tasks.OnFailureListener
import com.google.android.gms.tasks.OnSuccessListener
import org.json.JSONObject
import java.io.IOException
import java.security.GeneralSecurityException
import java.security.SecureRandom
import java.util.Arrays
object SafetyNetCheck {
private const val TAG = "SafetyNetCheck"
fun checkSafetyNet(context: Context, apiKey: String, callback: (Boolean) -> Unit) {
// Generate a nonce. A good nonce is large, random, and used only once.
val nonce = generateNonce()
SafetyNet.getClient(context).attest(nonce, apiKey)
.addOnSuccessListener { response ->
// Success! Now, *verify* the response.
val jwsResult = response.jwsResult
if (jwsResult != null) {
try {
val isSafe = SafetyNetResponseVerifier.verify(jwsResult)
Log.d(TAG, "SafetyNet verification result: $isSafe")
callback(isSafe) // Now passing a *verified* result.
} catch (e: Exception) {
Log.e(TAG, "Error verifying SafetyNet response: ${e.message}", e)
callback(false) // Treat verification errors as failures.
}
} else {
Log.e(TAG, "SafetyNet jwsResult is null")
callback(false) // Null result is a failure.
}
}
.addOnFailureListener { e ->
Log.e(TAG, "SafetyNet attest API call failed: ${e.message}", e)
callback(false) // API call failure.
}
}
// Helper function to generate a nonce.
private fun generateNonce(): ByteArray {
val byteGenerator = SecureRandom()
val nonce = ByteArray(32)
byteGenerator.nextBytes(nonce)
return nonce
}
}
// Helper class to verify the SafetyNet response.
object SafetyNetResponseVerifier {
private const val TAG = "SafetyNetVerifier"
// This method *must* be implemented on a *backend server* for real security.
// This is just a *simplified example* for demonstration purposes and is
// *not* suitable for production without a backend check.
@Throws(GeneralSecurityException::class, IOException::class)
fun verify(jwsResult: String): Boolean {
// 1. Parse the JWS: Split into header, payload, and signature.
val parts = jwsResult.split(".")
if (parts.size != 3) {
Log.e(TAG, "Invalid JWS format")
return false // Invalid JWS format
}
val header = parts[0]
val payload = parts[1]
val signature = parts[2]
// 2. Decode the payload (it's Base64 encoded).
val decodedPayload = Base64.decode(payload, Base64.DEFAULT)
val payloadJson = JSONObject(String(decodedPayload))
// 3. Check the ctsProfileMatch and basicIntegrity.
val ctsProfileMatch = payloadJson.optBoolean("ctsProfileMatch", false)
val basicIntegrity = payloadJson.optBoolean("basicIntegrity", false)
Log.d(TAG, "ctsProfileMatch: $ctsProfileMatch, basicIntegrity: $basicIntegrity")
// 4. **CRITICAL: In a real application, you *must* send the JWS to your
// backend server for verification. The server should use the
// Google SafetyNet API (or a library that wraps it) to verify
// the signature and check the fields. This prevents attackers
// from tampering with the response on the device.**
//
// // Example (pseudo-code) of what the backend check would do:
// // GoogleCredential credential = ...;
// // SafetyNet safetyNet = new SafetyNet.Builder(httpTransport, jsonFactory)
// // .setApplicationName("YourAppName")
// // .setHttpRequestInitializer(credential)
// // .build();
// // SafetyNetApi.VerifyJwsRequest request = safetyNet.safetynet().verifyJws(jwsResult);
// // SafetyNetApi.VerifyJwsResponse response = request.execute();
// // return response.isValidSignature() && response.getCtsProfileMatch() && response.getBasicIntegrity();
// 5. For this *example* (without a backend), we'll just check the fields.
// This is *NOT SECURE* for production!
return ctsProfileMatch && basicIntegrity
}
}
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

@@ -1,21 +0,0 @@
<?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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

@@ -1,6 +0,0 @@
<resources>
<string name="security_warning_title">تحذير أمني</string>
<string name="security_warning_message">تم اكتشاف مشكلة أمنية أو تعديل على هذا الجهاز. لا يمكن تشغيل التطبيق على هذا الجهاز.</string>
<string name="exit_button">إغلاق التطبيق</string>
<string name="device_secure">الجهاز آمن. الاستمرار بشكل طبيعي.</string>
</resources>
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<!-- <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">-->
<style name="LaunchTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<!-- <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">-->
<style name="NormalTheme" parent="Theme.MaterialComponents">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -1,15 +0,0 @@
<resources>
<string name="app_name">My App</string>
<string name="default_notification_channel_id">high_importance_channel</string>
<string name="api_key">kffk</string>
<string name="api_key_safety">AIzaSyB04YNW3LbvmQ5lX1t2bOwEU18-</string>
<string name="channel_name">FCM Notifications</string>
<string name="channel_description">Notifications from Firebase Cloud Messaging</string>
<string name="security_warning_title">Security Warning</string>
<string name="security_warning_message">A security issue or modification has been detected on
this device. The app cannot run on this device.</string>
<string name="exit_button">Exit App</string>
<string name="device_secure">Device is secure. Proceeding normally.</string>
</resources>
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<!-- <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">-->
<style name="LaunchTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<!-- <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">-->
<style name="NormalTheme" parent="Theme.MaterialComponents">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- منع أي اتصال HTTP غير مشفّر -->
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<!-- الاعتماد على شهادات النظام فقط -->
<certificates src="system" />
</trust-anchors>
</base-config>
<!-- اسمح بالاتصال بدوميناتك فقط -->
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">tripz-egypt.com</domain>
</domain-config>
<!-- في debug فقط: اسمح بشهادات المستخدم لسهولة الاختبار عبر Proxy -->
<debug-overrides>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>
@@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -1,52 +0,0 @@
buildscript {
ext.kotlin_version = '2.3.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.13.2'
classpath 'com.google.gms:google-services:4.3.15'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
project.evaluationDependsOn(':app')
def setupAndroid = {
if (project.plugins.hasPlugin('com.android.library') || project.plugins.hasPlugin('com.android.application')) {
project.android {
if (namespace == null || namespace == "") {
namespace = project.group
}
compileSdk 36
defaultConfig {
targetSdk 36
}
}
}
}
if (project.state.executed) {
setupAndroid()
} else {
project.afterEvaluate {
setupAndroid()
}
}
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
@@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
@@ -1,25 +0,0 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version '8.13.2' apply false
id "com.google.gms.google-services" version "4.3.15" apply false
id "org.jetbrains.kotlin.android" version "2.3.10" apply false
}
include ":app"
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.
Binary file not shown.
@@ -1,7 +0,0 @@
.DS_Store
.dart_tool/
.packages
.pub/
build/
@@ -1,10 +0,0 @@
# 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
@@ -1,14 +0,0 @@
## 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
@@ -1,21 +0,0 @@
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.
@@ -1,128 +0,0 @@
# 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)
@@ -1,8 +0,0 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
@@ -1,35 +0,0 @@
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 36
defaultConfig {
minSdkVersion 16
}
}
dependencies {
implementation 'com.google.android.material:material:1.4.0'
}
@@ -1,3 +0,0 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
@@ -1,5 +0,0 @@
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
@@ -1 +0,0 @@
rootProject.name = 'bubble'
@@ -1,4 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dsaved.bubblehead.bubble">
</manifest>
@@ -1,456 +0,0 @@
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);
}
}
}
@@ -1,95 +0,0 @@
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() {
}
}
@@ -1,6 +0,0 @@
<?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>
@@ -1,9 +0,0 @@
<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>
@@ -1,9 +0,0 @@
<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>
@@ -1,9 +0,0 @@
<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.

Before

Width:  |  Height:  |  Size: 3.3 KiB

@@ -1,9 +0,0 @@
<?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>
@@ -1,16 +0,0 @@
<?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>
@@ -1,45 +0,0 @@
<?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>
@@ -1,16 +0,0 @@
<?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>
@@ -1,41 +0,0 @@
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/s.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');
}
}

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