提交 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
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
# 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 static in.jvapps.system_alert_window.services.WindowServiceNew.INTENT_EXTRA_IS_CLOSE_WINDOW;
import static in.jvapps.system_alert_window.services.WindowServiceNew.INTENT_EXTRA_IS_UPDATE_WINDOW;
import static in.jvapps.system_alert_window.utils.Constants.INTENT_EXTRA_PARAMS_MAP;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.gson.Gson;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.HashMap;
import in.jvapps.system_alert_window.services.WindowServiceNew;
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 in.jvapps.system_alert_window.utils.NotificationHelper;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.JSONMethodCodec;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
public class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler, PluginRegistry.ActivityResultListener {
private static final String TAG = "SAW:MethodCallHandlerImpl";
public int ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE = 1237;
@Nullable
private Activity mActivity;
@Nullable
private MethodChannel channel;
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
try {
LogUtils.getInstance().d(TAG, "On method call " + call.method);
Context mContext = ContextHolder.getApplicationContext();
String prefMode;
JSONArray arguments;
switch (call.method) {
case "getPlatformVersion":
result.success("Android " + Build.VERSION.RELEASE);
break;
case "enableLogs":
assert (call.arguments != null);
arguments = (JSONArray) call.arguments;
LogUtils.getInstance().setLogFileEnabled((boolean) arguments.get(0));
result.success(true);
break;
case "getLogFile":
result.success(LogUtils.getInstance().getLogFilePath());
break;
case "requestPermissions":
assert (call.arguments != null);
arguments = (JSONArray) call.arguments;
prefMode = (String) arguments.get(0);
if (prefMode == null) {
prefMode = "default";
}
if (askPermission(!isBubbleMode(prefMode))) {
result.success(true);
} else {
result.success(false);
}
break;
case "checkPermissions":
arguments = (JSONArray) call.arguments;
prefMode = (String) arguments.get(0);
if (prefMode == null) {
prefMode = "default";
}
if (checkPermission(!isBubbleMode(prefMode))) {
result.success(true);
} else {
result.success(false);
}
break;
case "showSystemWindow":
assert (call.arguments != null);
arguments = (JSONArray) call.arguments;
String title = (String) arguments.get(0);
String body = (String) arguments.get(1);
JSONObject paramObj = (JSONObject) arguments.get(2);
@SuppressWarnings("unchecked")
HashMap<String, Object> params = new Gson().fromJson(paramObj.toString(), HashMap.class);
prefMode = (String) arguments.get(3);
if (prefMode == null) {
prefMode = "default";
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && isBubbleMode(prefMode)) {
if (checkPermission(false)) {
LogUtils.getInstance().d(TAG, "Going to show Bubble");
showBubble(title, body, params);
} else {
Toast.makeText(mContext, "Please enable bubbles", Toast.LENGTH_LONG).show();
result.success(false);
}
} else {
if (checkPermission(true)) {
LogUtils.getInstance().d(TAG, "Going to show System Alert Window");
final Intent i = new Intent(mContext, WindowServiceNew.class);
i.putExtra(INTENT_EXTRA_PARAMS_MAP, params);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
i.putExtra(INTENT_EXTRA_IS_UPDATE_WINDOW, false);
//WindowService.enqueueWork(mContext, i);
mContext.startService(i);
} else {
Toast.makeText(mContext, "Please give draw over other apps permission", Toast.LENGTH_LONG).show();
result.success(false);
}
}
result.success(true);
break;
case "updateSystemWindow":
assert (call.arguments != null);
JSONArray updateArguments = (JSONArray) call.arguments;
String updateTitle = (String) updateArguments.get(0);
String updateBody = (String) updateArguments.get(1);
@SuppressWarnings("unchecked")
HashMap<String, Object> updateParams = new Gson().fromJson(updateArguments.get(2).toString(), HashMap.class);
prefMode = (String) updateArguments.get(3);
if (prefMode == null) {
prefMode = "default";
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && isBubbleMode(prefMode)) {
if (checkPermission(false)) {
LogUtils.getInstance().d(TAG, "Going to update Bubble");
NotificationHelper.getInstance(mContext).dismissNotification();
showBubble(updateTitle, updateBody, updateParams);
} else {
Toast.makeText(mContext, "Please enable bubbles", Toast.LENGTH_LONG).show();
result.success(false);
}
} else {
if (checkPermission(true)) {
LogUtils.getInstance().d(TAG, "Going to update System Alert Window");
final Intent i = new Intent(mContext, WindowServiceNew.class);
i.putExtra(INTENT_EXTRA_PARAMS_MAP, updateParams);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
i.putExtra(INTENT_EXTRA_IS_UPDATE_WINDOW, true);
//WindowService.enqueueWork(mContext, i);
mContext.startService(i);
} else {
Toast.makeText(mContext, "Please give draw over other apps permission", Toast.LENGTH_LONG).show();
result.success(false);
}
}
result.success(true);
break;
case "closeSystemWindow":
arguments = (JSONArray) call.arguments;
prefMode = (String) arguments.get(0);
if (prefMode == null) {
prefMode = "default";
}
if (checkPermission(!isBubbleMode(prefMode))) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && isBubbleMode(prefMode)) {
NotificationHelper.getInstance(mContext).dismissNotification();
} else {
final Intent i = new Intent(mContext, WindowServiceNew.class);
i.putExtra(INTENT_EXTRA_IS_CLOSE_WINDOW, true);
//WindowService.dequeueWork(mContext, i);
mContext.startService(i);
}
result.success(true);
}
break;
case "launchApp":
arguments = (JSONArray) call.arguments;
String packageName = (String) arguments.get(0);
Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(packageName);
if (launchIntent != null) {
mContext.startActivity(launchIntent);
result.success(true);
} else {
result.success(false);
}
break;
default:
result.notImplemented();
}
} catch (Exception ex) {
LogUtils.getInstance().e(TAG, ex.toString());
}
}
/**
* Registers this instance as a method call handler on the given {@code messenger}.
*
* <p>Stops any previously started and unstopped calls.
*
* <p>This should be cleaned with {@link #stopListening} once the messenger is disposed of.
*/
void startListening(BinaryMessenger messenger) {
if (channel != null) {
LogUtils.getInstance().w(TAG, "Setting a method call handler before the last was disposed.");
stopListening();
}
channel = new MethodChannel(messenger, Constants.CHANNEL, JSONMethodCodec.INSTANCE);
channel.setMethodCallHandler(this);
}
/**
* Clears this instance from listening to method calls.
*
* <p>Does nothing if {@link #startListening} hasn't been called, or if we're already stopped.
*/
void stopListening() {
if (channel == null) {
LogUtils.getInstance().d(TAG, "Tried to stop listening when no MethodChannel had been initialized.");
return;
}
channel.setMethodCallHandler(null);
channel = null;
}
void setActivity(@Nullable Activity activity) {
this.mActivity = activity;
}
private boolean isBubbleMode(String prefMode) {
boolean isPreferOverlay = "overlay".equalsIgnoreCase(prefMode);
return Commons.isForceAndroidBubble(ContextHolder.getApplicationContext()) ||
(!isPreferOverlay && ("bubble".equalsIgnoreCase(prefMode) || Build.VERSION.SDK_INT >= Build.VERSION_CODES.R));
}
public boolean askPermission(boolean isOverlay) {
Context mContext = ContextHolder.getApplicationContext();
if (mContext != null) {
if (!isOverlay && (Commons.isForceAndroidBubble(mContext) || Build.VERSION.SDK_INT > Build.VERSION_CODES.Q)) {
return NotificationHelper.getInstance(mContext).areBubblesAllowed();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(mContext)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + mContext.getPackageName()));
if (mActivity == null) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
Toast.makeText(mContext, "Please grant, Can Draw Over Other Apps permission.", Toast.LENGTH_SHORT).show();
LogUtils.getInstance().e(TAG, "Can't detect the permission change, as the mActivity is null");
} else {
mActivity.startActivityForResult(intent, ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE);
}
} else {
return true;
}
}
} else {
LogUtils.getInstance().e(TAG, "'Can Draw Over Other Apps' permission is not requested as context is null");
}
return false;
}
public boolean checkPermission(boolean isOverlay) {
Context mContext = ContextHolder.getApplicationContext();
if (!isOverlay && (Commons.isForceAndroidBubble(mContext) || Build.VERSION.SDK_INT > Build.VERSION_CODES.Q)) {
//return NotificationHelper.getInstance(mContext).areBubblesAllowed();
return true;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return Settings.canDrawOverlays(mContext);
}
return false;
}
@RequiresApi(api = Build.VERSION_CODES.Q)
private void showBubble(String title, String body, HashMap<String, Object> params) {
Context mContext = ContextHolder.getApplicationContext();
Icon icon = Icon.createWithResource(mContext, R.drawable.ic_notification);
NotificationHelper notificationHelper = NotificationHelper.getInstance(mContext);
notificationHelper.showNotification(icon, title, body, params);
}
@TargetApi(Build.VERSION_CODES.M)
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE) {
Context mContext = ContextHolder.getApplicationContext();
if (!Settings.canDrawOverlays(mContext)) {
LogUtils.getInstance().e(TAG, "System Alert Window will not work without 'Can Draw Over Other Apps' permission");
Toast.makeText(mContext, "System Alert Window will not work without 'Can Draw Over Other Apps' permission", Toast.LENGTH_LONG).show();
}
}
return false;
}
}
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.services;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.content.pm.ServiceInfo;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.IBinder;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.RemoteViews;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.view.ViewCompat;
import com.google.gson.Gson;
import java.util.HashMap;
import java.util.Objects;
import in.jvapps.system_alert_window.R;
import in.jvapps.system_alert_window.SystemAlertWindowPlugin;
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 in.jvapps.system_alert_window.utils.NumberUtils;
import io.flutter.embedding.android.FlutterTextureView;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.JSONMessageCodec;
import io.flutter.plugin.common.MethodChannel;
public class WindowServiceNew extends Service implements View.OnTouchListener {
private static final String TAG = WindowServiceNew.class.getSimpleName();
public static final String CHANNEL_ID = "ForegroundServiceChannel";
private static final int NOTIFICATION_ID = 1;
public static final String INTENT_EXTRA_IS_UPDATE_WINDOW = "IsUpdateWindow";
public static final String INTENT_EXTRA_IS_CLOSE_WINDOW = "IsCloseWindow";
private WindowManager windowManager;
private String windowGravity;
private int windowWidth;
private int windowHeight;
private boolean isDisableClicks = false;
private FlutterView flutterView;
private RemoteViews remoteViews;
private Notification notification;
private PendingIntent pendingIntent;
private float offsetY;
private float offsetX;
private boolean moving;
private BasicMessageChannel<Object> overlayMessageChannel = new BasicMessageChannel(FlutterEngineCache.getInstance().get(Constants.FLUTTER_CACHE_ENGINE).getDartExecutor(), Constants.MESSAGE_CHANNEL, JSONMessageCodec.INSTANCE);
@SuppressLint("UnspecifiedImmutableFlag")
@Override
public void onCreate() {
createNotificationChannel();
Intent notificationIntent = new Intent(this, SystemAlertWindowPlugin.class);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, PendingIntent.FLAG_MUTABLE);
} else {
pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
}
remoteViews = new RemoteViews(getPackageName(), R.layout.notification_configuration_layout);
notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("暂无进行中任务")
.setContentText("现在没有正在进行中的拉运任务")
.setSmallIcon(R.drawable.ic_notification_logo)
.setContentIntent(pendingIntent)
.build();
if (Build.VERSION.SDK_INT >= 34) {
startForeground(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
} else {
startForeground(NOTIFICATION_ID, notification);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
if (null != intent && intent.getExtras() != null) {
ContextHolder.setApplicationContext(this);
@SuppressWarnings("unchecked")
HashMap<String, Object> paramsMap = (HashMap<String, Object>) intent.getSerializableExtra(Constants.INTENT_EXTRA_PARAMS_MAP);
boolean isCloseWindow = intent.getBooleanExtra(INTENT_EXTRA_IS_CLOSE_WINDOW, false);
if (!isCloseWindow) {
assert paramsMap != null;
boolean isUpdateWindow = intent.getBooleanExtra(INTENT_EXTRA_IS_UPDATE_WINDOW, false);
if (isUpdateWindow && windowManager != null && flutterView != null) {
if (ViewCompat.isAttachedToWindow(flutterView)) {
updateWindow(paramsMap);
// update the notification
updateNotification(paramsMap);
} else {
createWindow(paramsMap);
}
} else {
createWindow(paramsMap);
}
} else {
closeWindow(true);
}
}
return START_STICKY;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
assert manager != null;
manager.createNotificationChannel(serviceChannel);
}
}
private void setWindowManager() {
if (windowManager == null) {
windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
}
}
private void setWindowLayoutFromMap(HashMap<String, Object> paramsMap) {
isDisableClicks = Commons.getIsClicksDisabled(paramsMap);
LogUtils.getInstance().i(TAG, String.valueOf(isDisableClicks));
windowGravity = (String) paramsMap.get(Constants.KEY_GRAVITY);
windowWidth = NumberUtils.getInt(paramsMap.get(Constants.KEY_WIDTH));
windowHeight = NumberUtils.getInt(paramsMap.get(Constants.KEY_HEIGHT));
}
private WindowManager.LayoutParams getLayoutParams() {
final WindowManager.LayoutParams params;
params = new WindowManager.LayoutParams();
params.width = (windowWidth == 0) ? android.view.WindowManager.LayoutParams.MATCH_PARENT : Commons.getPixelsFromDp(this, windowWidth);
params.height = (windowHeight == 0) ? android.view.WindowManager.LayoutParams.WRAP_CONTENT : Commons.getPixelsFromDp(this, windowHeight);
params.format = PixelFormat.TRANSLUCENT;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
params.type = android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
if (isDisableClicks) {
params.flags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
} else {
params.flags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
}
} else {
params.type = android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
if (isDisableClicks) {
params.flags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
} else {
params.flags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && isDisableClicks) {
params.alpha = 0.8f;
}
params.gravity = Commons.getGravity(windowGravity, Gravity.TOP);
return params;
}
@SuppressLint("ClickableViewAccessibility")
private void createWindow(HashMap<String, Object> paramsMap) {
closeWindow(false);
setWindowManager();
setWindowLayoutFromMap(paramsMap);
WindowManager.LayoutParams params = getLayoutParams();
FlutterEngine engine = FlutterEngineCache.getInstance().get(Constants.FLUTTER_CACHE_ENGINE);
assert engine != null;
engine.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);
overlayMessageChannel.setMessageHandler((message, reply) -> Commons.messenger.send(message));
flutterView.setOnTouchListener(this);
try {
windowManager.addView(flutterView, params);
} catch (Exception ex) {
LogUtils.getInstance().e(TAG, ex.toString());
retryCreateWindow(paramsMap);
}
}
@SuppressLint("ClickableViewAccessibility")
private void retryCreateWindow(HashMap<String, Object> paramsMap) {
try {
LogUtils.getInstance().d(TAG, "Retrying create window");
closeWindow(false);
setWindowManager();
setWindowLayoutFromMap(paramsMap);
WindowManager.LayoutParams params = getLayoutParams();
FlutterEngine engine = FlutterEngineCache.getInstance().get(Constants.FLUTTER_CACHE_ENGINE);
assert engine != null;
engine.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);
flutterView.setOnTouchListener(this);
windowManager.addView(flutterView, params);
} catch (Exception ex) {
LogUtils.getInstance().e(TAG, ex.toString());
}
}
private void updateWindow(HashMap<String, Object> paramsMap) {
setWindowLayoutFromMap(paramsMap);
WindowManager.LayoutParams newParams = getLayoutParams();
WindowManager.LayoutParams previousParams = (WindowManager.LayoutParams) flutterView.getLayoutParams();
previousParams.width = (windowWidth == 0) ? android.view.WindowManager.LayoutParams.MATCH_PARENT : Commons.getPixelsFromDp(this, windowWidth);
previousParams.height = (windowHeight == 0) ? android.view.WindowManager.LayoutParams.WRAP_CONTENT : Commons.getPixelsFromDp(this, windowHeight);
previousParams.flags = newParams.flags;
previousParams.alpha = newParams.alpha;
windowManager.updateViewLayout(flutterView, previousParams);
}
// update the notification
private void updateNotification(HashMap<String, Object> paramsMap) {
String notificationUpdateData = (String) paramsMap.get(Constants.KEY_UPDATE_NOTIFICATION_DATA);
assert notificationUpdateData != null;
HashMap<String, String> notificationData = new Gson().fromJson(notificationUpdateData, HashMap.class);
if (notificationData != null) {
LogUtils.getInstance().d(TAG, "Updating the notification");
LogUtils.getInstance().d(TAG, notificationData.toString());
String receiveAddress = notificationData.get(Constants.KEY_RECEIVE_ADDRESS);
String sendAddress = notificationData.get(Constants.KEY_SEND_ADDRESS);
String lastArriveSendTime = notificationData.get(Constants.KEY_LATEST_SEND_ADDRESS);
String lastArriveReceiveTime = notificationData.get(Constants.KEY_LATEST_RECEIVE_ADDRESS);
String lastLoadTime = notificationData.get(Constants.KEY_LATEST_LOAD_TIME);
if (receiveAddress != null) {
remoteViews.setTextViewText(R.id.tv_receive_address, receiveAddress);
}
if (sendAddress != null) {
remoteViews.setTextViewText(R.id.tv_send_address, sendAddress);
}
if (lastArriveSendTime != null) {
remoteViews.setTextViewText(R.id.tv_last_arrive_send_time, lastArriveSendTime);
}
if (lastArriveReceiveTime != null) {
remoteViews.setTextViewText(R.id.tv_last_arrive_receive_time, lastArriveReceiveTime);
}
if (lastLoadTime != null) {
remoteViews.setTextViewText(R.id.tv_last_load_time, "最晚装货:" + lastLoadTime);
}
notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("任务进行中")
.setContentText("当前有任务正在进行中")
.setSmallIcon(R.drawable.ic_notification_logo)
.setCustomBigContentView(remoteViews)
.setContentIntent(pendingIntent)
.build();
if (Build.VERSION.SDK_INT >= 34) {
startForeground(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
} else {
startForeground(NOTIFICATION_ID, notification);
}
}
}
private void closeWindow(boolean isStopService) {
LogUtils.getInstance().i(TAG, "Closing the overlay window");
try {
if (windowManager != null) {
if (flutterView != null) {
windowManager.removeView(flutterView);
windowManager = null;
flutterView.detachFromFlutterEngine();
}
}
} catch (IllegalArgumentException e) {
LogUtils.getInstance().e(TAG, "view not found");
}
if (isStopService) {
stopSelf();
}
}
@SuppressLint("ClickableViewAccessibility")
public boolean onTouch(View v, MotionEvent event) {
if (windowManager != null) {
WindowManager.LayoutParams params = (WindowManager.LayoutParams) flutterView.getLayoutParams();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
moving = false;
offsetY = event.getRawY();
offsetX = event.getRawX();
break;
case MotionEvent.ACTION_MOVE:
float dy = event.getRawY() - offsetY;
float dx = event.getRawX() - offsetX;
if (!moving && (Math.abs(dy) > 25 || Math.abs(dx) > 25)) {
moving = true;
}
if (moving) {
offsetY = event.getRawY();
offsetX = event.getRawX();
int yy = params.y + (int) dy;
int xx = params.x + (int) dx;
params.y = yy;
params.x = xx;
windowManager.updateViewLayout(flutterView, params);
}
break;
case MotionEvent.ACTION_UP:
default:
return false;
}
return false;
}
return false;
}
@Override
public void onDestroy() {
closeWindow(false);
LogUtils.getInstance().d(TAG, "Destroying the overlay window service");
NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
assert notificationManager != null;
notificationManager.cancel(NOTIFICATION_ID);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
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
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 0910;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = in.jvapps.systemAlertWindowExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 4.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = in.jvapps.systemAlertWindowExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = in.jvapps.systemAlertWindowExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 4.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
<?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,
);
});
}
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig
/Flutter/flutter_export_environment.sh
\ No newline at end of file
import Flutter
import UIKit
public class SwiftSystemAlertWindowPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "system_alert_window", binaryMessenger: registrar.messenger())
let instance = SwiftSystemAlertWindowPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if (call.method == "getPlatformVersion") {
result("iOS " + UIDevice.current.systemVersion)
}
else if (call.method == "showSystemWindow") {
DispatchQueue.main.async {
let alert = UIAlertController(title: "Alert", message: "Hi, My name is flutter", preferredStyle: .alert);
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil);
}
}
}
}
#import <Flutter/Flutter.h>
@interface SystemAlertWindowPlugin : NSObject<FlutterPlugin>
@end
#import "SystemAlertWindowPlugin.h"
#import <system_alert_window/system_alert_window-Swift.h>
@implementation SystemAlertWindowPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
[SwiftSystemAlertWindowPlugin registerWithRegistrar:registrar];
}
@end
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'system_alert_window'
s.version = '0.0.1'
s.summary = 'A flutter plugin to show overlay window on top of everything in Android. This plugin uses &#x27;draw on top&#x27; permission to work on Android platform. For IOS platform, it displays as a notification in the notification center.'
s.description = <<-DESC
A flutter plugin to show overlay window on top of everything in Android. This plugin uses &#x27;draw on top&#x27; permission to work on Android platform. For IOS platform, it displays as a notification in the notification center.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
s.ios.deployment_target = '8.0'
end
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:system_alert_window/utils/commons.dart';
import 'package:system_alert_window/utils/constants.dart';
enum SystemWindowGravity { TOP, BOTTOM, CENTER }
enum SystemWindowPrefMode { DEFAULT, OVERLAY, BUBBLE }
class SystemAlertWindow {
///Channel name to handle the communication between flutter and platform specific code
static const MethodChannel _channel = const MethodChannel(Constants.CHANNEL, JSONMethodCodec());
static const BasicMessageChannel _overlayMessageChannel = BasicMessageChannel(Constants.MESSAGE_CHANNEL, JSONMessageCodec());
static final StreamController _controller = StreamController();
/// Fetches the current platform version
static Future<String?> get platformVersion async {
final String? version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
/// Fetches the generated log file
static Future<String?> get getLogFile async {
return await _channel.invokeMethod('getLogFile');
}
/// Method to enable the logs. By default, logs are disabled
static Future<void> enableLogs(bool flag) async {
await _channel.invokeMethod('enableLogs', [flag]);
}
/// Check if system window permission is granted
static Future<bool?> checkPermissions({SystemWindowPrefMode prefMode = SystemWindowPrefMode.DEFAULT}) async {
return await _channel.invokeMethod('checkPermissions', [Commons.getSystemWindowPrefMode(prefMode)]);
}
/// Request the corresponding system window permission
static Future<bool?> requestPermissions({SystemWindowPrefMode prefMode = SystemWindowPrefMode.DEFAULT}) async {
return await _channel.invokeMethod('requestPermissions', [Commons.getSystemWindowPrefMode(prefMode)]);
}
static Future<bool> removeOnClickListener() async {
return await _channel.invokeMethod("removeCallBackHandler");
}
/// Show System Window
///
/// `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.
static Future<bool?> showSystemWindow(
{SystemWindowGravity gravity = SystemWindowGravity.CENTER,
int? width,
int? height,
String notificationTitle = "Title",
String notificationBody = "Body",
SystemWindowPrefMode prefMode = SystemWindowPrefMode.DEFAULT,
bool isDisableClicks = false}) async {
final Map<String, dynamic> params = <String, dynamic>{
'gravity': Commons.getWindowGravity(gravity),
'width': width ?? Constants.MATCH_PARENT,
'height': height ?? Constants.WRAP_CONTENT,
'isDisableClicks': isDisableClicks
};
return await _channel.invokeMethod('showSystemWindow', [notificationTitle, notificationBody, params, Commons.getSystemWindowPrefMode(prefMode)]);
}
/// Update System Window
///
/// `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.
static Future<bool?> updateSystemWindow(
{SystemWindowGravity gravity = SystemWindowGravity.CENTER,
int? width,
int? height,
String notificationTitle = "Title",
String notificationBody = "Body",
String? notificationUpdateData,
SystemWindowPrefMode prefMode = SystemWindowPrefMode.DEFAULT,
bool isDisableClicks = false}) async {
final Map<String, dynamic> params = <String, dynamic>{
'gravity': Commons.getWindowGravity(gravity),
'width': width ?? Constants.MATCH_PARENT,
'height': height ?? Constants.WRAP_CONTENT,
'notificationUpdateData': notificationUpdateData,
'isDisableClicks': isDisableClicks
};
return await _channel
.invokeMethod('updateSystemWindow', [notificationTitle, notificationBody, params, Commons.getSystemWindowPrefMode(prefMode)]);
}
/// Broadcast data to overlay app
static Future sendMessageToOverlay(dynamic data) async {
return await _overlayMessageChannel.send(data);
}
/// Launch the app
/// `packageName` Package name of the app to be launched
static Future<void> launchApp(String packageName) async {
return await _channel.invokeMethod('launchApp', [packageName]);
}
static Stream<dynamic> get overlayListener {
_overlayMessageChannel.setMessageHandler((message) async {
_controller.add(message);
return message;
});
return _controller.stream;
}
static void disposeOverlayListener() {
_controller.close();
}
/// Closes the system window
static Future<bool?> closeSystemWindow({SystemWindowPrefMode prefMode = SystemWindowPrefMode.DEFAULT}) async {
return await _channel.invokeMethod('closeSystemWindow', [Commons.getSystemWindowPrefMode(prefMode)]);
}
}
extension HexColor on Color {
String _generateAlpha({required int alpha, required bool withAlpha}) {
if (withAlpha) {
return alpha.toRadixString(16).padLeft(2, '0');
} else {
return '';
}
}
/// Extension method for Color to generate Hex code
String toHex({bool leadingHashSign = false, bool withAlpha = false}) => '${leadingHashSign ? '#' : ''}'
'${_generateAlpha(alpha: alpha, withAlpha: withAlpha)}'
'${red.toRadixString(16).padLeft(2, '0')}'
'${green.toRadixString(16).padLeft(2, '0')}'
'${blue.toRadixString(16).padLeft(2, '0')}'
.toUpperCase();
}
import 'package:system_alert_window/system_alert_window.dart';
class Commons {
///Converts SystemWindowGravity to string format
static String getWindowGravity(SystemWindowGravity gravity) {
//if (gravity == null) gravity = SystemWindowGravity.TOP;
switch (gravity) {
case SystemWindowGravity.CENTER:
return "center";
case SystemWindowGravity.BOTTOM:
return "bottom";
case SystemWindowGravity.TOP:
default:
return "top";
}
}
///Converts SystemWindowPrefMode to string format
static String getSystemWindowPrefMode(SystemWindowPrefMode prefMode) {
//if (prefMode == null) prefMode = SystemWindowPrefMode.DEFAULT;
switch (prefMode) {
case SystemWindowPrefMode.OVERLAY:
return "overlay";
case SystemWindowPrefMode.BUBBLE:
return "bubble";
case SystemWindowPrefMode.DEFAULT:
default:
return "default";
}
}
}
class Constants {
static const int MATCH_PARENT = -1;
static const int WRAP_CONTENT = -2;
static const String CHANNEL = "in.jvapps.system_alert_window";
static const String BACKGROUND_CHANNEL =
"in.jvapps.system_alert_window/background";
static const String MESSAGE_CHANNEL =
"in.jvapps.system_alert_window/message";
}
name: system_alert_window
description: A flutter plugin to show Truecaller like overlay window, over all other apps along with callback events.
version: 2.0.0
homepage: https://github.com/pvsvamsi/SystemAlertWindow
environment:
sdk: '>=2.13.0 <4.0.0'
flutter: ">=2.0.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 androidPackage and pluginClass 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: in.jvapps.system_alert_window
pluginClass: SystemAlertWindowPlugin
ios:
package: in.jvapps.system_alert_window
pluginClass: SystemAlertWindowPlugin
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - 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: Montserrat
# fonts:
# - asset: assets/fonts/montserratRegular.ttf
# - asset: assets/fonts/montserratSemiBold.ttf
# weight: 700
# - asset: assets/fonts/montserratMedium.ttf
# weight: 800
# - asset: assets/fonts/montserratBold.ttf
# weight: 900
# For details regarding fonts in packages, see
# https://flutter.dev/custom-fonts/#from-packages
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:system_alert_window/system_alert_window.dart';
void main() {
const MethodChannel channel = MethodChannel('system_alert_window');
setUp(() {
channel.setMockMethodCallHandler((MethodCall methodCall) async {
return '42';
});
});
tearDown(() {
channel.setMockMethodCallHandler(null);
});
test('getPlatformVersion', () async {
expect(await SystemAlertWindow.platformVersion, '42');
});
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论