提交 c1c22ea2 authored 作者: 张国庆's avatar 张国庆

Initial commit

上级
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/*
**/.idea/*
# Visual Studio Code related
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.packages
.pub-cache/
.pub/
build/*
pubscpec.lock
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
**/android/localLib/
**/build/*
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
**/ios/Flutter/flutter_export_environment.sh
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
pubspec.lock
example/.flutter-plugins-dependencies
android/localLib/flutter.jar
# 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: 012ff65745a39be3460c1bc2b37f07113d0fa282
channel: stable
project_type: plugin
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/*
**/.idea/*
# Visual Studio Code related
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.packages
.pub-cache/
.pub/
build/*
/assets/images/
pubscpec.lock
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/build/*
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
**/android/localLib/
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
**/ios/Flutter/flutter_export_environment.sh
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
pubspec.lock
example/.flutter-plugins-dependencies
android/localLib/flutter.jar
## 2.0.0
* Added support for Flutter UI within the overlay window
## 1.3.0
* Added support for Android 14
* Uses new permission FOREGROUND_SERVICE_SPECIAL_USE
## 1.2.2
* Updated Gradle, min SDK is now set as 21
* Added new notifications permission for Android 13
* Added removeOnClickListener
## 1.2.1
* Fixed an issue in invoking the callbacks, while running the app in release mode.
* Added comments for the methods
## 1.2.0
* Added support for background color across the system alert window through the param 'bgColor'. This will be used as a default background color
* Added support for disabling clicks across the system alert window through the param 'isDisableClicks'. This is not applicable for bubbles.
* Added comments for the methods
## 1.1.2
* Fixed a crash while updating the window layout
## 1.1.1
* Fixed Android Bubble related issue in Android 12 + Fixed log file crash related issue
## 1.1.0+1
* Changed the base folder of the logs
## 1.1.0
* Added provision to save and fetch logs + Fixed run time errors
## 1.0.1+3
* Removed unused fonts + updated pubignore
## 1.0.1+2
* Updated the gradle + removed unused dependencies
## 1.0.1+1
* Added try catch to fix runtime exceptions
## 1.0.1
* Fixed issue with casting
## 1.0.0
* Supporting latest flutter version.
* Fixed issues related to deprecated embedded versions
## 0.5.0+1
* Fixed issues in prefMode not being honored by the permissions and close layout calls
## 0.5.0
* Migrated to Null safety + Updated the readme
## 0.4.4 (Latest Non null safety version)
* Fixed issues in prefMode not being honored by the permissions and close layout calls
## 0.4.3+1
* Fixed prefMode crash
## 0.4.3
* Added support for prefMode to SystemAlertWindow. Using this users can force SystemAlertWindow to show overlay on Android 11 (prefMode = OVERLAY) and Bubble in Android 10 (prefMode = BUBBLE)
## 0.4.2+2
* Fixed the return types of request/check permission methods
## 0.4.2+1
* Fixed the return types of request/check permission methods
## 0.4.2 - Breaking Changes
* Separated request permissions and check permissions
## 0.4.1+1
* Fixed a crash in system alert window while closing the window
## 0.4.1
* Fixed a crash in system alert window if permission is not given
## 0.4.0+1
* Updated the readme + Fixed issues in pubspec file
## 0.4.0
* Added bubble support for Android 11 and Android Go versions
## 0.3.2+2
* Fixed a crash while checking app permissions
## 0.3.2+1
* Updated the readme file, as gifs are not displaying on pub packages
## 0.3.2
* Updated the readme file. Now system alert window is draggable irrespective of it's width
## 0.3.1
* Fixed a rare crash, while updating system alert window. Now, update window method will consider the given width and height
## 0.3.0
* Migrated Overlay window to a new service and added notification to prevent it from getting unresponsive during long runtime
## 0.2.2+3
* Fixed a rare crash, while updating system alert window
## 0.2.2+2
* Added retries for callback events + added logs to identify errors (if any)
## 0.2.2+1
* Fixed a rare issue, window is not updating, Removed focus to the window, to allow keyboard interactions behind it
## 0.2.2
* Fixed a rare issue, callback of button click are not working
## 0.2.1+4
* Fixed a rare crash, while opening system alert window
## 0.2.1+3
* Fixed a crash, while closing system alert window
## 0.2.1+2
* Added logs to debug click call back events.
## 0.2.1+1
* Updated readme. Added debug logs to identify the crash while invoking callback.
## 0.2.1
* Added support for background dispatch of click events. So that the click events are not missed, in case the app is destroyed while displaying the overlay window.
## 0.2.0 - Breaking Changes
* Added support for multiple buttons in the footer. Please refer to the updated example
## 0.1.5+1
* Fixed the casting issue in onClick callback.
## 0.1.5 - Breaking Changes
* Refactored the code and replaced registerCallBack with registerOnClickListener. Please refer to the updated example.
## 0.1.4+6
* Now body and footer are optional
## 0.1.4+5
* Fixed issue with ButtonPosition.TRAILING alignment
## 0.1.4+4
* Fixed issue with bubble configuration on Android Q
## 0.1.4+3
* Fixed minimum required API version for bubbles + Optimized the exports of systemAlertWindow models
## 0.1.4+2
* Fixed an issue where overlay window is crashing during update overlay window call
## 0.1.4+1
* Fixed an issue where multiple overlay windows are creating during update overlay window call
## 0.1.4
* Fixed overlay window issue with older Android versions
## 0.1.3+1
* Fixed a crash with updateSystemWindow method for android 9 and below
## 0.1.3
* Fixed a bug in closeSystemWindow method
* Improved permissions checking logic
* Added support for updating the system alert window
## 0.1.2
* Moved bubble notification to foreground service, to allow the bubble to display in all cases
* Fixed the launcher icons of the example
## 0.1.1+1
* Added logging + Made minor changes to fix crashes with bubbles
## 0.1.1 - Breaking Changes
* Renamed the models to avoid conflicts with the native libraries
## 0.1.0
* Added support to close the system alert window
* Improved the permissions API to include support for Android 10
## 0.0.1
* Initial release (Working only for Android)
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at pvsvamsi2009@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
差异被折叠。
# system_alert_window
[![Pub](https://img.shields.io/pub/v/system_alert_window.svg)](https://pub.dartlang.org/packages/system_alert_window)
A flutter plugin to show Truecaller like overlay window, over all other apps along with callback events. Android Go or Android 11 & above, this plugin shows notification bubble, in other android versions, it shows an overlay window.
## Android
### Demo
###### 1. Clip of example app and 2. Working of button click in the background
<img src="https://github.com/pvsvamsi/SystemAlertWindow/raw/master/assets/images/example%20demo.gif" width="300" height="570">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src="https://github.com/pvsvamsi/SystemAlertWindow/raw/master/assets/images/background%20button%20click.gif" width="300" height="570">
### Manifest
//Permissions
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE " />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
//Linking the previously added application class
android:name=".Application"
android:label="system_alert_window_example"
android:icon="@mipmap/ic_launcher">
#### Android 10 & below, Android GO (API 27)
Uses &#x27;draw on top&#x27; permission and displays it as a overlay window
#### Android 11 & above
With SystemWindowPrefMode.OVERLAY, system alert windows uses 'overlay functionality' wherever it's supported. In that mode, it will show as bubbles if the 'display over other apps' is not supported.
With SystemWindowPrefMode.DEFAULT/SystemWindowPrefMode.BUBBLE, User has to allow 'All conversations can bubble' in the notification settings of the app. Uses Android Bubble APIs to show the overlay window inside a notification bubble.
#### Android GO (API 29)
User has to manually enable bubbles from the developer options. Uses Android Bubble APIs to show the overlay window inside a notification bubble.
## IOS
Displays as a notification in the notification center [Help Needed]
## Example
#### Show Overlay
#### Request overlay permission
await SystemAlertWindow.requestPermissions;
### Inside `main.dart` create an entry point for your Overlay widget;
```dart
// overlay entry point
@pragma("vm:entry-point")
void overlayMain() {
runApp(const MaterialApp(
debugShowCheckedModeBanner: false,
home: Material(child: Text("My overlay"))
));
}
//Open overLay content
// - Optional arguments:
/// `gravity` Position of the window and default is [SystemWindowGravity.CENTER]
/// `width` Width of the window and default is [Constants.MATCH_PARENT]
/// `height` Height of the window and default is [Constants.WRAP_CONTENT]
/// `notificationTitle` Notification title, applicable in case of bubble
/// `notificationBody` Notification body, applicable in case of bubble
/// `prefMode` Preference for the system window. Default is [SystemWindowPrefMode.DEFAULT]
/// `isDisableClicks` Disables the clicks across the system window. Default is false. This is not applicable for bubbles.
await SystemAlertWindow.showSystemWindow();
/// update the overlay flag while the overlay in action
/// - Optional arguments:
/// `gravity` Position of the window and default is [SystemWindowGravity.CENTER]
/// `width` Width of the window and default is [Constants.MATCH_PARENT]
/// `height` Height of the window and default is [Constants.WRAP_CONTENT]
/// `notificationTitle` Notification title, applicable in case of bubble
/// `notificationBody` Notification body, applicable in case of bubble
/// `prefMode` Preference for the system window. Default is [SystemWindowPrefMode.DEFAULT]
/// `isDisableClicks` Disables the clicks across the system window. Default is false. This is not applicable for bubbles.
await FlutterOverlayWindow.updateSystemWindow();
// closes overlay if open
await SystemAlertWindow.closeSystemWindow();
// broadcast data to overlay app from main app
await SystemAlertWindow.sendMessageToOverlay("Hello from the other side");
//streams message from main app to overlay.
SystemAlertWindow.overlayListener.listen((event) {
log("Current Event: $event");
});
```
#### Isolate communication
###### Use this snippet, if you want the callbacks on your main thread
###### Create an isolate_manager.dart
```dart
import 'dart:isolate';
import 'dart:ui';
class IsolateManager{
static const FOREGROUND_PORT_NAME = "foreground_port";
static SendPort lookupPortByName() {
return IsolateNameServer.lookupPortByName(FOREGROUND_PORT_NAME);
}
static bool registerPortWithName(SendPort port) {
assert(port != null, "'port' cannot be null.");
removePortNameMapping(FOREGROUND_PORT_NAME);
return IsolateNameServer.registerPortWithName(port, FOREGROUND_PORT_NAME);
}
static bool removePortNameMapping(String name) {
assert(name != null, "'name' cannot be null.");
return IsolateNameServer.removePortNameMapping(name);
}
}
```
###### While initializing system alert window in your code
```dart
await SystemAlertWindow.checkPermissions;
ReceivePort _port = ReceivePort();
IsolateManager.registerPortWithName(_port.sendPort);
_port.listen((message) {
log("message from OVERLAY: $message");
print("Do what ever you want here. This is inside your application scope");
});
```
###### Use this to send data to isolate mentioned above
```dart
void callBackFunction(String tag) {
print("Got tag " + tag);
SendPort port = IsolateManager.lookupPortByName();
port.send(tag);
}
```
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
/gradle
/gradlew
/gradlew.bat
\ No newline at end of file
group 'in.jvapps.system_alert_window'
version '1.0-SNAPSHOT'
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.1'
}
}
rootProject.allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 34
defaultConfig {
minSdkVersion 21
targetSdkVersion 34
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
disable 'InvalidPackage'
}
}
dependencies {
//implementation files('localLib/flutter.jar')
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.code.gson:gson:2.8.9'
}
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
rootProject.name = 'system_alert_window'
<?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="in.jvapps.system_alert_window">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<application>
<activity
android:name=".BubbleActivity"
android:documentLaunchMode="always"
android:label="@string/bubbles_activity"
android:allowEmbedded="true"
android:resizeableActivity="true"
android:showOnLockScreen="true"
android:hardwareAccelerated="true"
android:theme="@style/AppTheme.NoActionBar"
tools:ignore="UnusedAttribute" />
<service
android:name=".services.WindowServiceNew"
android:foregroundServiceType="specialUse"
android:enabled="true"
android:exported="false"
android:permission="android.permission.FOREGROUND_SERVICE"><property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="@string/foreground_service_special_use"/>
</service>
</application>
</manifest>
\ No newline at end of file
package in.jvapps.system_alert_window;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Bundle;
import android.view.Gravity;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.appcompat.app.AppCompatActivity;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import in.jvapps.system_alert_window.utils.Commons;
import in.jvapps.system_alert_window.utils.Constants;
import in.jvapps.system_alert_window.utils.NumberUtils;
import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.FlutterEngineGroup;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.android.FlutterFragment;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.android.FlutterTextureView;
import static in.jvapps.system_alert_window.utils.Constants.INTENT_EXTRA_PARAMS_MAP;
public class BubbleActivity extends AppCompatActivity {
private LinearLayout bubbleLayout;
private HashMap<String, Object> paramsMap;
private FlutterView flutterView;
private FlutterEngine flutterEngine;
private Context mContext;
@SuppressWarnings("unchecked")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bubble);
mContext = this;
Intent intent = getIntent();
if (intent != null && intent.getExtras() != null) {
paramsMap = (HashMap<String, Object>) intent.getSerializableExtra(INTENT_EXTRA_PARAMS_MAP);
FlutterEngineGroup enn = new FlutterEngineGroup(mContext);
DartExecutor.DartEntrypoint dEntry = new DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
"overlayMain");
flutterEngine = enn.createAndRunEngine(mContext, dEntry);
FlutterEngineCache.getInstance().put(Constants.FLUTTER_CACHE_ENGINE, flutterEngine);
configureUI();
}
}
protected void onResume() {
super.onResume();
flutterEngine.getLifecycleChannel().appIsResumed();
}
@Override
protected void onPause() {
super.onPause();
flutterEngine.getLifecycleChannel().appIsInactive();
}
@Override
protected void onStop() {
super.onStop();
flutterEngine.getLifecycleChannel().appIsPaused();
}
@Override
protected void onDestroy() {
super.onDestroy();
flutterEngine.getLifecycleChannel().appIsDetached();
}
void configureUI(){
LinearLayout linearLayout = new LinearLayout(mContext);
linearLayout.setOrientation(LinearLayout.VERTICAL); // Set the orientation if needed
flutterEngine.getLifecycleChannel().appIsResumed();
flutterView = new FlutterView(getApplicationContext(), new FlutterTextureView(getApplicationContext()));
flutterView.attachToFlutterEngine(Objects.requireNonNull(FlutterEngineCache.getInstance().get(Constants.FLUTTER_CACHE_ENGINE)));
flutterView.setFitsSystemWindows(true);
flutterView.setFocusable(true);
flutterView.setFocusableInTouchMode(true);
flutterView.setBackgroundColor(Color.TRANSPARENT);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
flutterView.setLayoutParams(params);
linearLayout.addView(flutterView);
setContentView(linearLayout);
}
}
package in.jvapps.system_alert_window;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import in.jvapps.system_alert_window.utils.Commons;
import in.jvapps.system_alert_window.utils.Constants;
import in.jvapps.system_alert_window.utils.ContextHolder;
import in.jvapps.system_alert_window.utils.LogUtils;
import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.FlutterEngineGroup;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.JSONMessageCodec;
public class SystemAlertWindowPlugin implements FlutterPlugin, ActivityAware,BasicMessageChannel.MessageHandler {
private boolean isInitialized;
@Nullable
private ActivityPluginBinding pluginBinding;
MethodCallHandlerImpl methodCallHandler;
private final String TAG = "SAW:Plugin";
private Context context;
private BasicMessageChannel<Object> messenger;
public SystemAlertWindowPlugin() {
LogUtils.getInstance().d(TAG, "Initializing the constructor");
isInitialized = false;
}
private void initialize(FlutterPluginBinding binding) {
ContextHolder.setApplicationContext(binding.getApplicationContext());
if (!isInitialized) {
isInitialized = true;
LogUtils.getInstance().d(TAG, "Initializing on attached to engine");
if (methodCallHandler == null) {
methodCallHandler = new MethodCallHandlerImpl();
methodCallHandler.startListening(binding.getBinaryMessenger());
}
LogUtils.getInstance().d(TAG, "onAttachedToEngine");
}
}
private void registerListeners() {
if (pluginBinding != null) {
pluginBinding.addActivityResultListener(methodCallHandler);
}
}
private void deregisterListeners() {
if (pluginBinding != null) {
pluginBinding.removeActivityResultListener(methodCallHandler);
}
}
private void dispose() {
LogUtils.getInstance().d(TAG, "Disposing call track plugin class");
if (methodCallHandler != null) {
methodCallHandler.stopListening();
methodCallHandler.setActivity(null);
methodCallHandler = null;
}
isInitialized = false;
}
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
this.context = flutterPluginBinding.getApplicationContext();
initialize(flutterPluginBinding);
messenger = new BasicMessageChannel<>(flutterPluginBinding.getBinaryMessenger(), Constants.MESSAGE_CHANNEL,
JSONMessageCodec.INSTANCE);
messenger.setMessageHandler(this);
Commons.messenger = messenger;
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
if (!isInitialized) {
LogUtils.getInstance().d(TAG, "Already detached from the engine.");
return;
}
LogUtils.getInstance().d(TAG, "On detached from engine");
dispose();
}
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) {
LogUtils.getInstance().d(TAG, "Initializing on attached to activity");
if (methodCallHandler != null) {
methodCallHandler.setActivity(activityPluginBinding.getActivity());
FlutterEngineGroup enn = new FlutterEngineGroup(context);
DartExecutor.DartEntrypoint dEntry = new DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
"overlayMain");
FlutterEngine engine = enn.createAndRunEngine(context, dEntry);
FlutterEngineCache.getInstance().put(Constants.FLUTTER_CACHE_ENGINE, engine);
}
this.pluginBinding = activityPluginBinding;
registerListeners();
}
@Override
public void onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity();
}
@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding activityPluginBinding) {
onAttachedToActivity(activityPluginBinding);
}
@Override
public void onDetachedFromActivity() {
LogUtils.getInstance().d(TAG, "On detached from activity");
if (methodCallHandler != null) {
methodCallHandler.setActivity(null);
}
deregisterListeners();
}
@Override
public void onMessage(@Nullable Object message, @NonNull BasicMessageChannel.Reply reply) {
BasicMessageChannel overlayMessageChannel = new BasicMessageChannel(
FlutterEngineCache.getInstance().get(Constants.FLUTTER_CACHE_ENGINE)
.getDartExecutor(),
Constants.MESSAGE_CHANNEL, JSONMessageCodec.INSTANCE);
overlayMessageChannel.send(message, reply);
}
}
package in.jvapps.system_alert_window.utils;
import static android.content.Context.ACTIVITY_SERVICE;
import static in.jvapps.system_alert_window.utils.Constants.KEY_BACKGROUND_COLOR;
import static in.jvapps.system_alert_window.utils.Constants.KEY_IS_DISABLE_CLICKS;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Build;
import android.util.TypedValue;
import android.view.Gravity;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.BasicMessageChannel;
public class Commons {
@SuppressWarnings("unchecked")
public static Map<String, Object> getMapFromObject(@NonNull Map<String, Object> map, String key) {
return (Map<String, Object>) map.get(key);
}
@SuppressWarnings("unchecked")
public static List<Map<String, Object>> getMapListFromObject(@NonNull Map<String, Object> map, String key) {
return (List<Map<String, Object>>) map.get(key);
}
public static BasicMessageChannel<Object> messenger = null;
public static boolean getIsClicksDisabled(@NonNull Map<String, Object> paramsMap) {
Object isDisableClicksObj = paramsMap.get(KEY_IS_DISABLE_CLICKS);
if(isDisableClicksObj != null){
return (Boolean) isDisableClicksObj;
}
return false;
}
public static int getPixelsFromDp(@NonNull Context context, int dp) {
if (dp == -1) return -1;
return (int) (TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()));
}
public static int getGravity(@Nullable String gravityStr, int defVal) {
int gravity = defVal;
if (gravityStr != null) {
switch (gravityStr) {
case "top":
gravity = Gravity.TOP;
break;
case "center":
gravity = Gravity.CENTER;
break;
case "bottom":
gravity = Gravity.BOTTOM;
break;
case "leading":
gravity = Gravity.START;
break;
case "trailing":
gravity = Gravity.END;
break;
}
}
return gravity;
}
public static boolean isForceAndroidBubble(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
if (activityManager != null) {
PackageManager pm = context.getPackageManager();
return !pm.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) || pm.hasSystemFeature(PackageManager.FEATURE_RAM_LOW) || activityManager.isLowRamDevice();
} else {
LogUtils.getInstance().i("SAW:Commons", "Marking force android bubble as false");
}
}
return false;
}
}
package in.jvapps.system_alert_window.utils;
public class Constants {
public static final String CHANNEL = "in.jvapps.system_alert_window";
public static final String BACKGROUND_CHANNEL = "in.jvapps.system_alert_window/background";
public static final String MESSAGE_CHANNEL = "in.jvapps.system_alert_window/message";
public static final String FLUTTER_CACHE_ENGINE = "in.jvapps.flutter_cache_engine";
public static final String SHARED_PREF_SYSTEM_ALERT_WINDOW = "in.jvapps.system_alert_window";
public static final String CALLBACK_HANDLE_KEY = "callback_handler";
public static final String CODE_CALLBACK_HANDLE_KEY = "code_callback_handler";
public static final String INTENT_EXTRA_PARAMS_MAP = "intent_params_map";
//Internal plugin param map keys
public static final String KEY_BACKGROUND_COLOR = "bgColor";
public static final String KEY_HEADER = "header";
public static final String KEY_BODY = "body";
public static final String KEY_FOOTER = "footer";
public static final String KEY_IS_SHOW_FOOTER = "isShowFooter";
public static final String KEY_IS_DISABLE_CLICKS = "isDisableClicks";
public static final String KEY_GRAVITY = "gravity";
public static final String KEY_WIDTH = "width";
public static final String KEY_HEIGHT = "height";
// 更新通知栏数据
public static final String KEY_UPDATE_NOTIFICATION_DATA = "notificationUpdateData";
// 发货地址
public static final String KEY_RECEIVE_ADDRESS= "receiveAddress";
//收货地址
public static final String KEY_SEND_ADDRESS= "sendAddress";
//最晚发货地
public static final String KEY_LATEST_SEND_ADDRESS= "lastArriveSendTime";
//最晚收货地
public static final String KEY_LATEST_RECEIVE_ADDRESS= "lastArriveReceiveTime";
//最晚装货时间
public static final String KEY_LATEST_LOAD_TIME= "lastLoadTime";
}
package in.jvapps.system_alert_window.utils;
import android.content.Context;
public class ContextHolder {
private static Context applicationContext;
public static Context getApplicationContext() {
return applicationContext;
}
public static void setApplicationContext(Context context) {
if (applicationContext == null) {
applicationContext = context;
LogUtils.getInstance().d("SAW:ContextHolder", "received application context");
}
}
}
\ No newline at end of file
package in.jvapps.system_alert_window.utils;
import android.content.Context;
import android.util.Log;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;
public class LogUtils {
//private int debugMode = 0;
private static LogUtils _instance;
private final String TAG = "SAW:LogUtils";
private boolean isLogFileEnabled = false;
private File sawFolder;
private LogUtils() {
}
public static LogUtils getInstance() {
if (_instance == null) {
_instance = new LogUtils();
}
return _instance;
}
public void setLogFileEnabled(boolean logFileEnabled) {
this.isLogFileEnabled = logFileEnabled;
}
public void i(String TAG, String text) {
Log.i(TAG, text);
appendLog(text);
}
public void d(String TAG, String text) {
Log.d(TAG, text);
appendLog(text);
}
public void w(String TAG, String text) {
Log.w(TAG, text);
appendLog(text);
}
public void e(String TAG, String text) {
Log.e(TAG, text);
appendLog(text);
}
private void appendLog(String text) {
try {
Context context = ContextHolder.getApplicationContext();
if (isLogFileEnabled && context != null) {
if (sawFolder == null) {
Log.d(TAG, "sawFolder is null");
sawFolder = new File(context.getApplicationContext().getExternalFilesDir(null), "Logs" + File.separator + "SAW");
Log.d(TAG, sawFolder.getAbsolutePath());
if (!sawFolder.exists()) {
if (!sawFolder.mkdirs()) {
Log.e(TAG, "sawFolder to create the SAW directory");
sawFolder = null;
return;
}
} else {
Log.d(TAG, "sawFolder is already created");
}
}
Date now = new Date();
String today = new SimpleDateFormat("ddMMyyyy", Locale.getDefault()).format(now);
File logFile = new File(sawFolder.getAbsolutePath() + File.separator + today + ".log");
if (!logFile.exists()) {
deleteRecursive(sawFolder);
try {
if (!logFile.createNewFile()) {
Log.e(TAG, "Unable to create the log file");
return;
}
} catch (IOException e) {
Log.e(TAG, e.getMessage());
e.printStackTrace();
}
}
try {
String timeStamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(now);
text = timeStamp + " - " + text;
//BufferedWriter for performance, true to set append to file flag
BufferedWriter buf = new BufferedWriter(new FileWriter(logFile, true));
buf.append(text);
buf.newLine();
buf.close();
} catch (IOException e) {
Log.e(TAG, e.getMessage());
e.printStackTrace();
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
public String getLogFilePath() {
if (sawFolder != null) {
Date now = new Date();
String today = new SimpleDateFormat("ddMMyyyy", Locale.getDefault()).format(now);
File logFile = new File(sawFolder.getAbsolutePath() + File.separator + today + ".log");
if (logFile.exists()) {
return logFile.getAbsolutePath();
}
}
return null;
}
void deleteRecursive(File fileOrDirectory) {
try {
if (fileOrDirectory.isDirectory()) {
for (File child : Objects.requireNonNull(fileOrDirectory.listFiles()))
deleteRecursive(child);
} else {
//noinspection ResultOfMethodCallIgnored
fileOrDirectory.delete();
}
} catch (Exception ex) {
ex.printStackTrace();
Log.e(TAG, ex.toString());
}
}
}
package in.jvapps.system_alert_window.utils;
import static java.lang.System.currentTimeMillis;
import static in.jvapps.system_alert_window.utils.Constants.INTENT_EXTRA_PARAMS_MAP;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
import android.content.Context;
import android.content.Intent;
import android.content.LocusId;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.provider.Settings;
import android.widget.Toast;
import androidx.annotation.RequiresApi;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Set;
import in.jvapps.system_alert_window.BubbleActivity;
public class NotificationHelper {
private static final String CHANNEL_ID = "bubble_notification_channel";
private static final String CHANNEL_NAME = "Incoming notification";
private static final String CHANNEL_DESCRIPTION = "Incoming notification description";
private static final String SHORTCUT_LABEL = "Notification";
private static final int BUBBLE_NOTIFICATION_ID = 1237;
private static final String BUBBLE_SHORTCUT_ID = "bubble_shortcut";
private static final int REQUEST_CONTENT = 1;
private static final int REQUEST_BUBBLE = 2;
private static NotificationManager notificationManager;
private static final String TAG = "NotificationHelper";
private final WeakReference<Context> mContext;
private static NotificationHelper mInstance;
private NotificationHelper(Context context) {
this.mContext = new WeakReference<>(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
initNotificationManager();
}
public static NotificationHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new NotificationHelper(context);
}
return mInstance;
}
@SuppressLint("AnnotateVersionCheck")
private boolean isMinAndroidR() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
}
@RequiresApi(api = Build.VERSION_CODES.Q)
private void initNotificationManager() {
if (notificationManager == null) {
if (mContext == null) {
LogUtils.getInstance().e(TAG, "Context is null. Can't show the System Alert Window");
return;
}
notificationManager = mContext.get().getSystemService(NotificationManager.class);
setUpNotificationChannels();
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void setUpNotificationChannels() {
if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
notificationChannel.setDescription(CHANNEL_DESCRIPTION);
notificationManager.createNotificationChannel(notificationChannel);
}
}
@RequiresApi(api = Build.VERSION_CODES.R)
private void updateShortcuts(Icon icon) {
Set<String> categories = new LinkedHashSet<>();
categories.add("com.example.android.bubbles.category.TEXT_SHARE_TARGET");
ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext.get(), BUBBLE_SHORTCUT_ID)
.setLocusId(new LocusId(BUBBLE_SHORTCUT_ID))
//.setActivity(new ComponentName(mContext.get(), BubbleActivity.class))
.setShortLabel(SHORTCUT_LABEL)
.setIcon(icon)
.setLongLived(true)
.setCategories(categories)
.setIntent(new Intent(mContext.get(), BubbleActivity.class).setAction(Intent.ACTION_VIEW))
.setPerson(new Person.Builder()
.setName(SHORTCUT_LABEL)
.setIcon(icon)
.build())
.build();
ShortcutManager shortcutManager = (ShortcutManager) mContext.get().getSystemService(Context.SHORTCUT_SERVICE);
shortcutManager.pushDynamicShortcut(shortcutInfo);
}
@RequiresApi(api = Build.VERSION_CODES.Q)
private Notification.BubbleMetadata createBubbleMetadata(Icon icon, PendingIntent intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return new Notification.BubbleMetadata.Builder(intent, icon)
.setDesiredHeight(250)
.setAutoExpandBubble(true)
.setSuppressNotification(true)
.build();
} else {
//noinspection deprecation
return new Notification.BubbleMetadata.Builder()
.setDesiredHeight(250)
.setIcon(icon)
.setIntent(intent)
.setAutoExpandBubble(true)
.setSuppressNotification(true)
.build();
}
}
@RequiresApi(api = Build.VERSION_CODES.Q)
public void showNotification(Icon icon, String notificationTitle, String notificationBody, HashMap<String, Object> params) {
if (isMinAndroidR())
updateShortcuts(icon);
Person user = new Person.Builder().setName("You").build();
Person person = new Person.Builder().setName(notificationTitle).setIcon(icon).build();
Intent bubbleIntent = new Intent(mContext.get(), BubbleActivity.class);
bubbleIntent.setAction(Intent.ACTION_VIEW);
bubbleIntent.putExtra(INTENT_EXTRA_PARAMS_MAP, params);
@SuppressLint("UnspecifiedImmutableFlag")
PendingIntent pendingIntent = PendingIntent.getActivity(mContext.get(), REQUEST_BUBBLE, bubbleIntent,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? (PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE)
: PendingIntent.FLAG_UPDATE_CURRENT);
long now = currentTimeMillis() - 100;
@SuppressLint("UnspecifiedImmutableFlag")
Notification.Builder builder = new Notification.Builder(mContext.get(), CHANNEL_ID)
.setBubbleMetadata(createBubbleMetadata(icon, pendingIntent))
.setContentTitle(notificationTitle)
.setSmallIcon(icon)
.setCategory(Notification.CATEGORY_MESSAGE)
.setShortcutId(BUBBLE_SHORTCUT_ID)
.setLocusId(new LocusId(BUBBLE_SHORTCUT_ID))
.addPerson(person)
.setShowWhen(true)
.setContentIntent(PendingIntent.getActivity(mContext.get(), REQUEST_CONTENT, bubbleIntent,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? (PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE)
: PendingIntent.FLAG_UPDATE_CURRENT))
.setStyle(new Notification.MessagingStyle(user)
.addMessage(new Notification.MessagingStyle.Message(notificationBody, now, person))
.setGroupConversation(false))
.setWhen(now);
if (isMinAndroidR()) {
builder.addAction(new Notification.Action.Builder(null, "Click the icon in the end ->", null).build());
}
notificationManager.notify(BUBBLE_NOTIFICATION_ID, builder.build());
}
public void dismissNotification() {
notificationManager.cancel(BUBBLE_NOTIFICATION_ID);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public boolean areBubblesAllowed() {
if (isMinAndroidR()) {
NotificationChannel notificationChannel = notificationManager.getNotificationChannel(CHANNEL_ID, BUBBLE_SHORTCUT_ID);
assert notificationChannel != null;
return notificationManager.areBubblesAllowed() || notificationChannel.canBubble();
} else {
int devOptions = Settings.Secure.getInt(mContext.get().getContentResolver(), Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
if (devOptions == 1) {
LogUtils.getInstance().d(TAG, "Android bubbles are enabled");
return true;
} else {
LogUtils.getInstance().e(TAG, "System Alert Window will not work without enabling the android bubbles");
Toast.makeText(mContext.get(), "Enable android bubbles in the developer options, for System Alert Window to work", Toast.LENGTH_LONG).show();
return false;
}
}
}
}
package in.jvapps.system_alert_window.utils;
public class NumberUtils {
private static final String TAG = "NumberUtils";
public static float getFloat(Object object) {
return getNumber(object).floatValue();
}
public static int getInt(Object object) {
return getNumber(object).intValue();
}
private static Number getNumber(Object object) {
Number val = 0;
if (object != null) {
try {
val = ((Number) object);
} catch (Exception ex) {
LogUtils.getInstance().e(TAG, ex.toString());
}
}
return val;
}
}
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:width="22dp" android:height="22dp">
<shape android:shape="rectangle">
<solid android:color="#ffea5529" />
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp" android:bottomLeftRadius="4dp" android:bottomRightRadius="4dp" />
</shape>
</item>
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:width="22dp" android:height="22dp">
<shape android:shape="rectangle">
<solid android:color="#ff00b578" />
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp" android:bottomLeftRadius="4dp" android:bottomRightRadius="4dp" />
</shape>
</item>
</selector>
\ No newline at end of file
<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="M21,2L3,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h7v2L8,20v2h8v-2h-2v-2h7c1.1,0 2,-0.9 2,-2L23,4c0,-1.1 -0.9,-2 -2,-2zM21,16L3,16L3,4h18v12z"/>
</vector>
<?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>
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M17,10L7,10v2h10v-2zM19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,8h14v11zM14,14L7,14v2h7v-2z"/>
</vector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".BubbleActivity">
<LinearLayout
android:id="@+id/bubbleLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</ScrollView>
\ No newline at end of file
<?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="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_marginTop="8dp"
android:layout_width="28dp"
android:layout_height="28dp"
android:src="@drawable/ic_notification_logo" />
<LinearLayout
android:layout_marginTop="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="任务进行中"
android:textColor="#333C4C"
android:textFontWeight="500"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text=""
android:textColor="#333C4C"
android:textSize="13sp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_tx_marking_s"
android:gravity="center"
android:paddingHorizontal="5dp"
android:paddingVertical="2dp"
android:text="发"
android:textColor="@android:color/white" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_send_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="#ff344254"
android:textFontWeight="500"
android:textSize="15sp" />
<TextView
android:id="@+id/tv_last_arrive_send_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:text=""
android:textColor="#ff86909c"
android:textSize="13sp" />
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="10dp"
android:background="#C5C7D3" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_tx_marking_r"
android:gravity="center"
android:paddingHorizontal="5dp"
android:paddingVertical="2dp"
android:text="收"
android:textColor="@android:color/white" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_receive_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="#ff344254"
android:textFontWeight="500"
android:textSize="15sp" />
<TextView
android:id="@+id/tv_last_arrive_receive_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:text=""
android:textColor="#ff86909c"
android:textSize="13sp" />
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="10dp"
android:background="#C5C7D3" />
<TextView
android:id="@+id/tv_last_load_time"
android:layout_marginTop="10dp"
android:layout_width="175dp"
android:layout_height="18dp"
android:text=""
android:textColor="#ff344254"
android:textSize="13sp" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>
\ No newline at end of file
<resources>
<string name="action_settings">Settings</string>
<string name="bubbles_activity">Notification</string>
<string name="channel_name">System Alert Window Channel</string>
<string name="channel_description">This is a channel for system alert window</string>
<string name="foreground_service_special_use">This service helps this app to show overlay window</string>
</resources>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>
\ No newline at end of file
差异被折叠。
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.packages
.pub-cache/
.pub/
/build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
# 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: 012ff65745a39be3460c1bc2b37f07113d0fa282
channel: stable
project_type: app
# system_alert_window_example
A flutter plugin to show Truecaller like overlay window, over all other apps along with callback events.
## Android
### Permissions
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE " />
<uses-permission android:name="android.permission.WAKE_LOCK" />
#### Android 9 and below
Uses &#x27;draw on top&#x27; permission and displays it as a overlay window
#### Android 10 and above
Uses Android Bubble APIs to show the overlay window.
## IOS
Displays as a notification in the notification center [Help Needed]
\ No newline at end of file
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
//apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 34
/*sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}*/
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "in.jvapps.system_alert_window_example"
minSdkVersion 21
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
//implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="in.jvapps.system_alert_window_example">
<!-- Flutter 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>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="in.jvapps.system_alert_window_example">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<uses-feature
android:name="android.hardware.ram.low"
android:required="true" />
<application
android:hardwareAccelerated="false"
android:icon="@mipmap/ic_launcher"
android:label="system_alert_window_example">
<activity
android:name="system_alert_window_example.MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
package system_alert_window_example;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
}
<?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>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="in.jvapps.system_alert_window_example">
<!-- Flutter 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>
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.1'
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
#Fri Oct 11 19:24:10 IST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
# Uncomment this line to define a global platform for your project
# platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
差异被折叠。
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>system_alert_window_example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
#import "GeneratedPluginRegistrant.h"
\ No newline at end of file
import 'dart:developer';
import 'dart:isolate';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:system_alert_window/system_alert_window.dart';
class CustomOverlay extends StatefulWidget {
@override
State<CustomOverlay> createState() => _CustomOverlayState();
}
class _CustomOverlayState extends State<CustomOverlay> {
static const String _mainAppPort = 'MainApp';
SendPort? mainAppPort;
bool update = false;
@override
void initState() {
// TODO: implement initState
super.initState();
SystemAlertWindow.overlayListener.listen((event) {
log("$event in overlay");
if (event is bool) {
setState(() {
update = event;
});
}
});
}
void callBackFunction(String tag) {
print("Got tag " + tag);
mainAppPort ??= IsolateNameServer.lookupPortByName(
_mainAppPort,
);
mainAppPort?.send('Date: ${DateTime.now()}');
mainAppPort?.send(tag);
}
Widget overlay() {
return Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
height: 60,
color: Colors.grey[100],
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
children: [
Text(update ? "outgoing" : "Incoming", style: TextStyle(fontSize: 10, color: Colors.black45)),
Text(
"123456",
style: TextStyle(fontSize: 14, color: Colors.black87, fontWeight: FontWeight.bold),
),
],
),
),
TextButton(
style: ButtonStyle(
overlayColor: MaterialStateProperty.all(Colors.transparent),
),
onPressed: () {
callBackFunction("Close");
SystemAlertWindow.closeSystemWindow(prefMode: prefMode);
},
child: Container(
width: MediaQuery.of(context).size.width / 2.3,
margin: EdgeInsets.only(left: 30),
padding: EdgeInsets.all(10),
decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(30)), color: update ? Colors.grey : Colors.deepOrange),
child: Center(
child: Text(
"Close",
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
),
)
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20),
child: Text(
update ? "clicks Disabled" : "Body",
style: TextStyle(fontSize: 16, color: Colors.black54),
),
),
TextButton(
style: ButtonStyle(
overlayColor: MaterialStateProperty.all(Colors.transparent),
),
onPressed: () {
callBackFunction("Action");
},
child: Container(
padding: EdgeInsets.all(12),
height: (MediaQuery.of(context).size.height) / 3.5,
width: MediaQuery.of(context).size.width / 1.05,
decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(5)), color: update ? Colors.grey : Colors.deepOrange),
child: Center(
child: Text(
"Action",
style: TextStyle(fontSize: 14, color: Colors.white),
),
),
),
)
],
),
);
}
SystemWindowPrefMode prefMode = SystemWindowPrefMode.OVERLAY;
@override
Widget build(BuildContext context) {
return Scaffold(
body: overlay(),
);
}
}
import 'dart:async';
import 'dart:developer';
import 'dart:isolate';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:share_plus/share_plus.dart';
import 'package:system_alert_window/system_alert_window.dart';
import 'custom_overlay.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
@pragma("vm:entry-point")
void overlayMain() {
WidgetsFlutterBinding.ensureInitialized();
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
home: CustomOverlay(),
),
);
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
bool _isShowingWindow = false;
bool _isUpdatedWindow = false;
SystemWindowPrefMode prefMode = SystemWindowPrefMode.OVERLAY;
static const String _mainAppPort = 'MainApp';
final _receivePort = ReceivePort();
SendPort? homePort;
String? latestMessageFromOverlay;
@override
void initState() {
super.initState();
_initPlatformState();
_requestPermissions();
if (homePort != null) return;
final res = IsolateNameServer.registerPortWithName(
_receivePort.sendPort,
_mainAppPort,
);
log("$res: OVERLAY");
_receivePort.listen((message) {
log("message from OVERLAY: $message");
});
}
@override
void dispose() {
SystemAlertWindow.removeOnClickListener();
super.dispose();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> _initPlatformState() async {
await SystemAlertWindow.enableLogs(true);
String? platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
platformVersion = await SystemAlertWindow.platformVersion;
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
if (platformVersion != null)
setState(() {
_platformVersion = platformVersion!;
});
}
Future<void> _requestPermissions() async {
await SystemAlertWindow.requestPermissions(prefMode: prefMode);
}
void _showOverlayWindow() async {
if (!_isShowingWindow) {
await SystemAlertWindow.sendMessageToOverlay('show system window');
SystemAlertWindow.showSystemWindow(
height: 200,
width: MediaQuery.of(context).size.width.floor(),
gravity: SystemWindowGravity.CENTER,
prefMode: prefMode,
);
setState(() {
_isShowingWindow = true;
});
} else if (!_isUpdatedWindow) {
await SystemAlertWindow.sendMessageToOverlay('update system window');
SystemAlertWindow.updateSystemWindow(
height: 200,
width: MediaQuery.of(context).size.width.floor(),
gravity: SystemWindowGravity.CENTER,
prefMode: prefMode,
isDisableClicks: true);
setState(() {
_isUpdatedWindow = true;
SystemAlertWindow.sendMessageToOverlay(_isUpdatedWindow);
});
} else {
setState(() {
_isShowingWindow = false;
_isUpdatedWindow = false;
SystemAlertWindow.sendMessageToOverlay(_isUpdatedWindow);
});
SystemAlertWindow.closeSystemWindow(prefMode: prefMode);
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('System Alert Window Example App \n with flutterview'),
),
body: Center(
child: Column(
children: <Widget>[
Text('Running on: $_platformVersion\n'),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: MaterialButton(
onPressed: _showOverlayWindow,
textColor: Colors.white,
child: !_isShowingWindow
? Text("Show system alert window")
: !_isUpdatedWindow
? Text("Update system alert window")
: Text("Close system alert window"),
color: Colors.deepOrange,
padding: const EdgeInsets.symmetric(vertical: 8.0),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: MaterialButton(
onPressed: () => SystemAlertWindow.sendMessageToOverlay("message from main"),
textColor: Colors.white,
child: Text("send message to overlay"),
color: Colors.deepOrange,
padding: const EdgeInsets.symmetric(vertical: 8.0),
),
),
TextButton(
onPressed: () async {
String? logFilePath = await SystemAlertWindow.getLogFile;
if (logFilePath != null && logFilePath.isNotEmpty) {
Share.shareFiles([logFilePath]);
} else {
print("Path is empty");
}
},
child: Text("Share Log file"))
],
),
),
),
);
}
}
name: system_alert_window_example
description: Demonstrates how to use the system_alert_window plugin.
publish_to: 'none'
environment:
sdk: '>=2.13.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.5
dev_dependencies:
flutter_test:
sdk: flutter
system_alert_window:
path: ../
share_plus: ^5.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:system_alert_window_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) =>
widget is Text && widget.data!.startsWith('Running on:'),
),
findsOneWidget,
);
});
}
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论