react-native-android-overlay 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/android/build.gradle +54 -0
- package/android/settings.gradle +9 -0
- package/android/src/main/AndroidManifest.xml +19 -0
- package/android/src/main/java/tech/zmario/androidoverlay/OverlayPackage.java +52 -0
- package/android/src/main/java/tech/zmario/androidoverlay/module/OverlayModule.java +133 -0
- package/android/src/main/java/tech/zmario/androidoverlay/service/OverlayInstance.java +41 -0
- package/android/src/main/java/tech/zmario/androidoverlay/service/OverlayLayoutParamsBuilder.java +57 -0
- package/android/src/main/java/tech/zmario/androidoverlay/service/OverlayService.java +414 -0
- package/android/src/main/java/tech/zmario/androidoverlay/view/OverlayContainerView.java +137 -0
- package/lib/module/NativeAndroidOverlay.js +5 -0
- package/lib/module/NativeAndroidOverlay.js.map +1 -0
- package/lib/module/index.js +30 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeAndroidOverlay.d.ts +29 -0
- package/lib/typescript/src/NativeAndroidOverlay.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +27 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +179 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mario D.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# react-native-android-overlay
|
|
2
|
+
|
|
3
|
+
A lightweight and performant React Native library for Android that allows you to render registered React Native
|
|
4
|
+
components inside a floating system overlay window on top of other applications.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- Render React Native components as floating overlays above other apps
|
|
11
|
+
- Built on top of the new React Native architecture
|
|
12
|
+
- Automatically tracks the application activity lifecycle
|
|
13
|
+
- Keeps the JavaScript runtime active when the app is minimized, allowing animations, timers and JS logic to continue
|
|
14
|
+
running
|
|
15
|
+
- Supports drag and move gestures with touch interception
|
|
16
|
+
- Fully configurable Android foreground service notification:
|
|
17
|
+
- Title
|
|
18
|
+
- Description
|
|
19
|
+
- Icon
|
|
20
|
+
- Notification channel
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- React Native >= 0.76
|
|
27
|
+
- Android >= 8.0 (API 26)
|
|
28
|
+
- New Architecture enabled
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install react-native-android-overlay
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
or:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
yarn add react-native-android-overlay
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
or:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pnpm add react-native-android-overlay
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Android information
|
|
53
|
+
|
|
54
|
+
The library automatically merges the required permissions into your Android manifest:
|
|
55
|
+
|
|
56
|
+
```xml
|
|
57
|
+
|
|
58
|
+
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
|
59
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
|
60
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Permissions
|
|
64
|
+
|
|
65
|
+
- `SYSTEM_ALERT_WINDOW`
|
|
66
|
+
- Allows displaying overlays above other applications
|
|
67
|
+
|
|
68
|
+
- `FOREGROUND_SERVICE`
|
|
69
|
+
- Required to keep the overlay service alive in background
|
|
70
|
+
|
|
71
|
+
- `FOREGROUND_SERVICE_SPECIAL_USE`
|
|
72
|
+
- Required on Android 14+
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Foreground service
|
|
77
|
+
|
|
78
|
+
This library uses an Android foreground service to keep the React Native runtime alive while your application is
|
|
79
|
+
minimized.
|
|
80
|
+
|
|
81
|
+
When `foreground: true`:
|
|
82
|
+
|
|
83
|
+
- Android displays a persistent foreground service notification
|
|
84
|
+
- JavaScript logic continues running
|
|
85
|
+
|
|
86
|
+
> NOTE: Android requires a foreground service notification to keep the service alive.
|
|
87
|
+
> On Android 13+, the notification visibility depends on the user's notification permission settings.
|
|
88
|
+
> By default,
|
|
89
|
+
> the notification may not be visible unless the app has requested and been granted notification permission.
|
|
90
|
+
|
|
91
|
+
When `foreground: false`:
|
|
92
|
+
|
|
93
|
+
- Android may stop the process at any time depending on system policies
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
# Usage
|
|
98
|
+
|
|
99
|
+
## 1. Register your overlay component
|
|
100
|
+
|
|
101
|
+
In your root entry file (`index.js` / `index.ts`):
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
import { AppRegistry } from 'react-native';
|
|
105
|
+
import App from './App';
|
|
106
|
+
import MyOverlayComponent from './MyOverlayComponent';
|
|
107
|
+
|
|
108
|
+
AppRegistry.registerComponent(
|
|
109
|
+
'MainApp',
|
|
110
|
+
() => App
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Register component used by the overlay
|
|
114
|
+
AppRegistry.registerComponent(
|
|
115
|
+
'OverlayView',
|
|
116
|
+
() => MyOverlayComponent
|
|
117
|
+
);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 2. Start the overlay
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { OverlayManager } from 'react-native-android-overlay';
|
|
126
|
+
|
|
127
|
+
const startOverlay = async () => {
|
|
128
|
+
const hasPermission = await OverlayManager.hasPermission();
|
|
129
|
+
|
|
130
|
+
if (!hasPermission) {
|
|
131
|
+
OverlayManager.requestPermission();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
OverlayManager.startOverlay('OverlayView', {
|
|
136
|
+
width: 300,
|
|
137
|
+
height: 150,
|
|
138
|
+
|
|
139
|
+
x: 0,
|
|
140
|
+
y: 150,
|
|
141
|
+
|
|
142
|
+
gravity: 'bottom',
|
|
143
|
+
|
|
144
|
+
draggable: true,
|
|
145
|
+
touchable: true,
|
|
146
|
+
focusable: false,
|
|
147
|
+
|
|
148
|
+
foreground: true,
|
|
149
|
+
|
|
150
|
+
notificationTitle: 'Overlay Active',
|
|
151
|
+
notificationText: 'Overlay is running in background',
|
|
152
|
+
notificationIcon: 'ic_launcher',
|
|
153
|
+
|
|
154
|
+
channelId: 'overlay_channel',
|
|
155
|
+
channelName: 'Overlay Service'
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 3. Stop the overlay
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
const stopOverlay = () => {
|
|
166
|
+
OverlayManager.stopOverlay('OverlayView');
|
|
167
|
+
};
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Example overlay component
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
import { View, Text } from 'react-native';
|
|
176
|
+
|
|
177
|
+
export default function MyOverlayComponent() {
|
|
178
|
+
return (
|
|
179
|
+
<View>
|
|
180
|
+
<Text>
|
|
181
|
+
Hello from overlay
|
|
182
|
+
</Text>
|
|
183
|
+
</View>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Overlay resizing
|
|
191
|
+
|
|
192
|
+
Resize the overlay container at runtime:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
OverlayManager.resizeOverlay(
|
|
196
|
+
320,
|
|
197
|
+
200,
|
|
198
|
+
'OverlayView'
|
|
199
|
+
);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Overlay movement
|
|
205
|
+
|
|
206
|
+
Move the overlay window manually from JavaScript:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
OverlayManager.startMove('OverlayView');
|
|
210
|
+
|
|
211
|
+
OverlayManager.moveOverlay(
|
|
212
|
+
dx,
|
|
213
|
+
dy,
|
|
214
|
+
'OverlayView'
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
OverlayManager.commitMove('OverlayView');
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
# API reference
|
|
223
|
+
|
|
224
|
+
## OverlayManager
|
|
225
|
+
|
|
226
|
+
| Method | Arguments | Returns | Description |
|
|
227
|
+
|-----------------------|---------------------------------------------------------|--------------------|---------------------------------------------|
|
|
228
|
+
| `hasPermission()` | None | `Promise<boolean>` | Checks if overlay permission is granted |
|
|
229
|
+
| `requestPermission()` | None | `void` | Opens Android overlay permission settings |
|
|
230
|
+
| `startOverlay()` | `componentName: string, options?: OverlayOptions` | `void` | Starts the overlay service |
|
|
231
|
+
| `stopOverlay()` | `componentName?: string` | `void` | Removes overlay and stops service if unused |
|
|
232
|
+
| `resizeOverlay()` | `width: number, height: number, componentName?: string` | `void` | Changes overlay window size |
|
|
233
|
+
| `startMove()` | `componentName?: string` | `void` | Prepares overlay movement |
|
|
234
|
+
| `moveOverlay()` | `dx: number, dy: number, componentName?: string` | `void` | Moves overlay by offset |
|
|
235
|
+
| `commitMove()` | `componentName?: string` | `void` | Saves final overlay position |
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## OverlayOptions
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
interface OverlayOptions {
|
|
243
|
+
width?: number; // Width of the overlay window in DP
|
|
244
|
+
height?: number; // Height of the overlay window in DP
|
|
245
|
+
|
|
246
|
+
x?: number; // Horizontal coordinates offset in DP. Defaults to 0
|
|
247
|
+
y?: number; // Vertical coordinates offset in DP. Defaults to 150dp
|
|
248
|
+
|
|
249
|
+
gravity?:
|
|
250
|
+
'top' |
|
|
251
|
+
'bottom' |
|
|
252
|
+
'center'; // Gravity anchor for overlay positioning. Defaults to 'bottom'
|
|
253
|
+
|
|
254
|
+
draggable?: boolean; // Enable/disable dragging gestures to move overlay (defaults to true)
|
|
255
|
+
touchable?: boolean; // If false, touch events pass through to windows beneath (defaults to true)
|
|
256
|
+
focusable?: boolean; // If true, overlay can receive keyboard/input focus (defaults to false)
|
|
257
|
+
|
|
258
|
+
foreground?: boolean; // Runs overlay inside a persistent foreground service (defaults to true) so that JS logic continues running when app is minimized
|
|
259
|
+
|
|
260
|
+
notificationTitle?: string; // Custom title for foreground service notification
|
|
261
|
+
notificationText?: string; // Custom message text for foreground service notification
|
|
262
|
+
notificationIcon?: string; // Resdrawable name of the notification icon (e.g. "ic_launcher")
|
|
263
|
+
|
|
264
|
+
channelId?: string; // Notification channel ID (defaults to "OverlayServiceChannel")
|
|
265
|
+
channelName?: string; // Notification channel name (defaults to "Overlay Service")
|
|
266
|
+
}
|
|
267
|
+
```
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
}
|
|
6
|
+
dependencies {
|
|
7
|
+
classpath 'com.android.tools.build:gradle:8.10.0'
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
apply plugin: 'com.android.library'
|
|
12
|
+
|
|
13
|
+
def isNewArchitectureEnabled() {
|
|
14
|
+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (isNewArchitectureEnabled()) {
|
|
18
|
+
apply plugin: "com.facebook.react"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def safeExtGet(prop, fallback) {
|
|
23
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
android {
|
|
27
|
+
namespace "tech.zmario.androidoverlay"
|
|
28
|
+
compileSdk safeExtGet('compileSdkVersion', 36)
|
|
29
|
+
|
|
30
|
+
defaultConfig {
|
|
31
|
+
minSdk safeExtGet('minSdkVersion', 24)
|
|
32
|
+
targetSdk safeExtGet('targetSdkVersion', 36)
|
|
33
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
buildTypes {
|
|
37
|
+
release {
|
|
38
|
+
minifyEnabled false
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
repositories {
|
|
44
|
+
google()
|
|
45
|
+
mavenCentral()
|
|
46
|
+
maven {
|
|
47
|
+
url "$rootDir/../node_modules/react-native/android"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
dependencies {
|
|
52
|
+
implementation("com.facebook.react:react-android")
|
|
53
|
+
implementation("androidx.core:core:1.12.0")
|
|
54
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<manifest xmlns:tools="http://schemas.android.com/tools"
|
|
2
|
+
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
|
4
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"
|
|
5
|
+
tools:ignore="ForegroundServicesPolicy" />
|
|
6
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
|
7
|
+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
8
|
+
|
|
9
|
+
<application>
|
|
10
|
+
<service
|
|
11
|
+
android:name="tech.zmario.androidoverlay.service.OverlayService"
|
|
12
|
+
android:foregroundServiceType="specialUse"
|
|
13
|
+
android:enabled="true"
|
|
14
|
+
android:exported="false">
|
|
15
|
+
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
|
16
|
+
android:value="Overlay service to display floating view over other applications."/>
|
|
17
|
+
</service>
|
|
18
|
+
</application>
|
|
19
|
+
</manifest>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
package tech.zmario.androidoverlay;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import com.facebook.react.BaseReactPackage;
|
|
5
|
+
import com.facebook.react.bridge.NativeModule;
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfo;
|
|
8
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider;
|
|
9
|
+
import com.facebook.react.uimanager.ViewManager;
|
|
10
|
+
import java.util.Collections;
|
|
11
|
+
import java.util.HashMap;
|
|
12
|
+
import java.util.List;
|
|
13
|
+
import java.util.Map;
|
|
14
|
+
import tech.zmario.androidoverlay.module.OverlayModule;
|
|
15
|
+
|
|
16
|
+
public class OverlayPackage extends BaseReactPackage {
|
|
17
|
+
|
|
18
|
+
@Override
|
|
19
|
+
public NativeModule getModule(String name, @NonNull ReactApplicationContext reactContext) {
|
|
20
|
+
if (name.equals(NativeAndroidOverlaySpec.NAME)) {
|
|
21
|
+
return new OverlayModule(reactContext);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@NonNull
|
|
28
|
+
@Override
|
|
29
|
+
public ReactModuleInfoProvider getReactModuleInfoProvider() {
|
|
30
|
+
return () -> {
|
|
31
|
+
Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
|
|
32
|
+
boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
|
|
33
|
+
|
|
34
|
+
moduleInfos.put(
|
|
35
|
+
NativeAndroidOverlaySpec.NAME,
|
|
36
|
+
new ReactModuleInfo(
|
|
37
|
+
NativeAndroidOverlaySpec.NAME,
|
|
38
|
+
NativeAndroidOverlaySpec.NAME,
|
|
39
|
+
false,
|
|
40
|
+
false,
|
|
41
|
+
false,
|
|
42
|
+
isTurboModule));
|
|
43
|
+
return moduleInfos;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@NonNull
|
|
48
|
+
@Override
|
|
49
|
+
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
|
|
50
|
+
return Collections.emptyList();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
package tech.zmario.androidoverlay.module;
|
|
2
|
+
|
|
3
|
+
import android.content.Intent;
|
|
4
|
+
import android.net.Uri;
|
|
5
|
+
import android.os.Build;
|
|
6
|
+
import android.provider.Settings;
|
|
7
|
+
import com.facebook.react.bridge.Promise;
|
|
8
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
9
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
10
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
11
|
+
import tech.zmario.androidoverlay.NativeAndroidOverlaySpec;
|
|
12
|
+
import tech.zmario.androidoverlay.service.OverlayService;
|
|
13
|
+
|
|
14
|
+
public class OverlayModule extends NativeAndroidOverlaySpec {
|
|
15
|
+
|
|
16
|
+
public OverlayModule(ReactApplicationContext reactContext) {
|
|
17
|
+
super(reactContext);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@ReactMethod
|
|
21
|
+
public void hasPermission(Promise promise) {
|
|
22
|
+
promise.resolve(Settings.canDrawOverlays(getReactApplicationContext()));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@ReactMethod
|
|
26
|
+
public void requestPermission() {
|
|
27
|
+
if (!Settings.canDrawOverlays(getReactApplicationContext())) {
|
|
28
|
+
Intent intent =
|
|
29
|
+
new Intent(
|
|
30
|
+
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
|
31
|
+
Uri.parse("package:" + getReactApplicationContext().getPackageName()));
|
|
32
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
33
|
+
getReactApplicationContext().startActivity(intent);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@ReactMethod
|
|
38
|
+
public void startOverlay(String componentName, ReadableMap options) {
|
|
39
|
+
if (!Settings.canDrawOverlays(getReactApplicationContext())) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
Intent intent = new Intent(getReactApplicationContext(), OverlayService.class);
|
|
43
|
+
intent.putExtra("componentName", componentName);
|
|
44
|
+
|
|
45
|
+
if (options != null) {
|
|
46
|
+
if (options.hasKey("width")) intent.putExtra("width", options.getDouble("width"));
|
|
47
|
+
if (options.hasKey("height")) intent.putExtra("height", options.getDouble("height"));
|
|
48
|
+
if (options.hasKey("x")) intent.putExtra("x", options.getDouble("x"));
|
|
49
|
+
if (options.hasKey("y")) intent.putExtra("y", options.getDouble("y"));
|
|
50
|
+
if (options.hasKey("gravity")) intent.putExtra("gravity", options.getString("gravity"));
|
|
51
|
+
|
|
52
|
+
if (options.hasKey("draggable"))
|
|
53
|
+
intent.putExtra("draggable", options.getBoolean("draggable"));
|
|
54
|
+
|
|
55
|
+
if (options.hasKey("touchable"))
|
|
56
|
+
intent.putExtra("touchable", options.getBoolean("touchable"));
|
|
57
|
+
|
|
58
|
+
if (options.hasKey("focusable"))
|
|
59
|
+
intent.putExtra("focusable", options.getBoolean("focusable"));
|
|
60
|
+
|
|
61
|
+
if (options.hasKey("notificationTitle"))
|
|
62
|
+
intent.putExtra("notificationTitle", options.getString("notificationTitle"));
|
|
63
|
+
|
|
64
|
+
if (options.hasKey("notificationText"))
|
|
65
|
+
intent.putExtra("notificationText", options.getString("notificationText"));
|
|
66
|
+
|
|
67
|
+
if (options.hasKey("notificationIcon"))
|
|
68
|
+
intent.putExtra("notificationIcon", options.getString("notificationIcon"));
|
|
69
|
+
|
|
70
|
+
if (options.hasKey("channelId")) intent.putExtra("channelId", options.getString("channelId"));
|
|
71
|
+
|
|
72
|
+
if (options.hasKey("channelName"))
|
|
73
|
+
intent.putExtra("channelName", options.getString("channelName"));
|
|
74
|
+
|
|
75
|
+
if (options.hasKey("foreground"))
|
|
76
|
+
intent.putExtra("foreground", options.getBoolean("foreground"));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
boolean foreground = true;
|
|
80
|
+
|
|
81
|
+
if (options != null && options.hasKey("foreground")) {
|
|
82
|
+
foreground = options.getBoolean("foreground");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && foreground) {
|
|
86
|
+
getReactApplicationContext().startForegroundService(intent);
|
|
87
|
+
} else {
|
|
88
|
+
getReactApplicationContext().startService(intent);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@ReactMethod
|
|
93
|
+
public void stopOverlay(String componentName) {
|
|
94
|
+
if (OverlayService.getInstance() != null) {
|
|
95
|
+
OverlayService.getInstance().stopOverlayInstance(componentName);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@ReactMethod
|
|
100
|
+
public void resizeOverlay(double width, double height, String componentName) {
|
|
101
|
+
float density = getReactApplicationContext().getResources().getDisplayMetrics().density;
|
|
102
|
+
int pxWidth = (int) (width * density);
|
|
103
|
+
int pxHeight = (int) (height * density);
|
|
104
|
+
|
|
105
|
+
if (OverlayService.getInstance() != null) {
|
|
106
|
+
OverlayService.getInstance().resizeOverlay(componentName, pxWidth, pxHeight);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@ReactMethod
|
|
111
|
+
public void startMove(String componentName) {
|
|
112
|
+
if (OverlayService.getInstance() != null) {
|
|
113
|
+
OverlayService.getInstance().startMove(componentName);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@ReactMethod
|
|
118
|
+
public void moveOverlay(double dx, double dy, String componentName) {
|
|
119
|
+
float density = getReactApplicationContext().getResources().getDisplayMetrics().density;
|
|
120
|
+
|
|
121
|
+
if (OverlayService.getInstance() != null) {
|
|
122
|
+
OverlayService.getInstance()
|
|
123
|
+
.moveOverlay(componentName, (int) (dx * density), (int) (dy * density));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@ReactMethod
|
|
128
|
+
public void commitMove(String componentName) {
|
|
129
|
+
if (OverlayService.getInstance() != null) {
|
|
130
|
+
OverlayService.getInstance().commitMove(componentName);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
package tech.zmario.androidoverlay.service;
|
|
2
|
+
|
|
3
|
+
import android.widget.FrameLayout;
|
|
4
|
+
import com.facebook.react.interfaces.fabric.ReactSurface;
|
|
5
|
+
|
|
6
|
+
public class OverlayInstance {
|
|
7
|
+
|
|
8
|
+
private final FrameLayout overlayView;
|
|
9
|
+
private final ReactSurface reactSurface;
|
|
10
|
+
private int initialX = 0;
|
|
11
|
+
private int initialY = 0;
|
|
12
|
+
|
|
13
|
+
public OverlayInstance(FrameLayout overlayView, ReactSurface reactSurface) {
|
|
14
|
+
this.overlayView = overlayView;
|
|
15
|
+
this.reactSurface = reactSurface;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public FrameLayout getOverlayView() {
|
|
19
|
+
return overlayView;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public ReactSurface getReactSurface() {
|
|
23
|
+
return reactSurface;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public int getInitialX() {
|
|
27
|
+
return initialX;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public void setInitialX(int initialX) {
|
|
31
|
+
this.initialX = initialX;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public int getInitialY() {
|
|
35
|
+
return initialY;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public void setInitialY(int initialY) {
|
|
39
|
+
this.initialY = initialY;
|
|
40
|
+
}
|
|
41
|
+
}
|
package/android/src/main/java/tech/zmario/androidoverlay/service/OverlayLayoutParamsBuilder.java
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package tech.zmario.androidoverlay.service;
|
|
2
|
+
|
|
3
|
+
import android.graphics.PixelFormat;
|
|
4
|
+
import android.os.Build;
|
|
5
|
+
import android.view.Gravity;
|
|
6
|
+
import android.view.WindowManager;
|
|
7
|
+
|
|
8
|
+
public class OverlayLayoutParamsBuilder {
|
|
9
|
+
|
|
10
|
+
private OverlayLayoutParamsBuilder() {}
|
|
11
|
+
|
|
12
|
+
public static WindowManager.LayoutParams build(
|
|
13
|
+
double width,
|
|
14
|
+
double height,
|
|
15
|
+
double x,
|
|
16
|
+
double y,
|
|
17
|
+
String gravity,
|
|
18
|
+
boolean focusable,
|
|
19
|
+
boolean touchable,
|
|
20
|
+
float density,
|
|
21
|
+
int screenWidth) {
|
|
22
|
+
int layoutFlag =
|
|
23
|
+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
|
24
|
+
? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
|
|
25
|
+
: WindowManager.LayoutParams.TYPE_PHONE;
|
|
26
|
+
int widthPx = width > 0 ? (int) (width * density) : (int) (screenWidth * 0.95);
|
|
27
|
+
int heightPx = height > 0 ? (int) (height * density) : (int) (140f * density);
|
|
28
|
+
|
|
29
|
+
int flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
|
|
30
|
+
|
|
31
|
+
if (focusable) {
|
|
32
|
+
flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
|
|
33
|
+
} else {
|
|
34
|
+
flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
|
35
|
+
}
|
|
36
|
+
if (!touchable) {
|
|
37
|
+
flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
WindowManager.LayoutParams params =
|
|
41
|
+
new WindowManager.LayoutParams(
|
|
42
|
+
widthPx, heightPx, layoutFlag, flags, PixelFormat.TRANSLUCENT);
|
|
43
|
+
|
|
44
|
+
int gravityFlag = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
|
|
45
|
+
if ("top".equalsIgnoreCase(gravity)) {
|
|
46
|
+
gravityFlag = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
|
|
47
|
+
} else if ("center".equalsIgnoreCase(gravity)) {
|
|
48
|
+
gravityFlag = Gravity.CENTER;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
params.gravity = gravityFlag;
|
|
52
|
+
params.x = (int) (x * density);
|
|
53
|
+
params.y = (int) (y * density);
|
|
54
|
+
|
|
55
|
+
return params;
|
|
56
|
+
}
|
|
57
|
+
}
|