25-7-28-2

This commit is contained in:
Hamza-Ayed
2025-07-28 12:21:28 +03:00
parent 660d60e1f5
commit 83a97baed1
549 changed files with 109870 additions and 0 deletions

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 33
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,6 @@
#Thu Jul 10 12:15:00 EET 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

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>

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

@@ -0,0 +1,41 @@
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');
}
}

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: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
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: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev"
source: hosted
version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev"
source: hosted
version: "3.0.9"
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: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.16.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev"
source: hosted
version: "0.7.4"
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: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
version: "15.0.0"
sdks:
dart: ">=3.7.0-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