@parastud/floating-bubble 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/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @parastud/floating-bubble
2
+
3
+ A draggable, circular **"back to Rastaa"** bubble that floats on top of other apps
4
+ (Ola/Porter style), so a driver can tap it to jump straight back into the app while
5
+ a navigation app (e.g. Google Maps) is in the foreground.
6
+
7
+ - **Android** — a real overlay drawn with `WindowManager`, gated on the
8
+ `SYSTEM_ALERT_WINDOW` ("Display over other apps") permission.
9
+ - **iOS** — there is no public API to draw over other apps, so the native module is
10
+ a **no-op stub**. Every method resolves to a safe default; nothing crashes.
11
+
12
+ This is a native Expo module: it ships native Android/iOS code and **requires a
13
+ native rebuild** (dev client or EAS build). It cannot be delivered as an OTA update.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @parastud/floating-bubble
19
+ ```
20
+
21
+ Then rebuild the native app (autolinking picks the module up automatically):
22
+
23
+ ```bash
24
+ npx expo run:android # or: eas build
25
+ ```
26
+
27
+ The Android `SYSTEM_ALERT_WINDOW` permission is merged into the host app's manifest
28
+ automatically via this module's `AndroidManifest.xml`.
29
+
30
+ ## Usage
31
+
32
+ ```ts
33
+ import { FloatingBubble } from '@parastud/floating-bubble';
34
+
35
+ // Is the bubble actually usable here? (Android + native module present)
36
+ if (FloatingBubble.isSupported) {
37
+ // Has the user granted "Display over other apps"?
38
+ if (!FloatingBubble.canDrawOverlays()) {
39
+ FloatingBubble.requestPermission(); // opens the system settings screen
40
+ }
41
+
42
+ FloatingBubble.show(); // float the bubble over other apps
43
+ FloatingBubble.hide(); // remove it
44
+ FloatingBubble.isVisible();
45
+ }
46
+ ```
47
+
48
+ Every method is safe to call on any platform — on iOS, or on a build without the
49
+ native module, calls short-circuit to safe defaults instead of throwing.
50
+
51
+ ## API
52
+
53
+ | Member | Returns | Description |
54
+ | --- | --- | --- |
55
+ | `isSupported` | `boolean` | `true` only on Android with the native module present. |
56
+ | `canDrawOverlays()` | `boolean` | Whether "Display over other apps" is granted. |
57
+ | `requestPermission()` | `void` | Opens the system settings screen to grant the permission. |
58
+ | `isVisible()` | `boolean` | Whether the bubble is currently on screen. |
59
+ | `show()` | `void` | Show the bubble (no-op without permission / if already visible). |
60
+ | `hide()` | `void` | Remove the bubble. |
61
+
62
+ The raw native surface is also exported as the `FloatingBubbleNativeModule` type for
63
+ advanced use.
64
+
65
+ ## License
66
+
67
+ UNLICENSED — internal Rastaa package.
@@ -0,0 +1,19 @@
1
+ apply plugin: 'com.android.library'
2
+ apply plugin: 'org.jetbrains.kotlin.android'
3
+
4
+ group = 'ai.rastaa.floatingbubble'
5
+ version = '0.1.0'
6
+
7
+ def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
8
+ apply from: expoModulesCorePlugin
9
+ applyKotlinExpoModulesCorePlugin()
10
+ useCoreDependencies()
11
+ useDefaultAndroidSdkVersions()
12
+ useExpoPublishing()
13
+
14
+ android {
15
+ namespace "ai.rastaa.floatingbubble"
16
+ defaultConfig {
17
+ minSdkVersion 24
18
+ }
19
+ }
@@ -0,0 +1,4 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <!-- Lets the app draw the "back to Rastaa" bubble over other apps (e.g. Google Maps). -->
3
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
4
+ </manifest>
@@ -0,0 +1,206 @@
1
+ package ai.rastaa.floatingbubble
2
+
3
+ import android.content.Context
4
+ import android.content.Intent
5
+ import android.graphics.Color
6
+ import android.graphics.Outline
7
+ import android.graphics.PixelFormat
8
+ import android.graphics.drawable.GradientDrawable
9
+ import android.net.Uri
10
+ import android.os.Build
11
+ import android.os.Handler
12
+ import android.os.Looper
13
+ import android.provider.Settings
14
+ import android.view.Gravity
15
+ import android.view.MotionEvent
16
+ import android.view.View
17
+ import android.view.ViewOutlineProvider
18
+ import android.view.WindowManager
19
+ import android.widget.FrameLayout
20
+ import android.widget.ImageView
21
+ import expo.modules.kotlin.modules.Module
22
+ import expo.modules.kotlin.modules.ModuleDefinition
23
+ import kotlin.math.abs
24
+
25
+ // Renders a draggable, circular "back to Rastaa" bubble on top of every other app
26
+ // (Ola/Porter style). Android only — iOS has no API to draw over other apps.
27
+ class FloatingBubbleModule : Module() {
28
+
29
+ private var bubbleView: View? = null
30
+ private var windowManager: WindowManager? = null
31
+ private val mainHandler = Handler(Looper.getMainLooper())
32
+
33
+ private val appContextOrNull: Context?
34
+ get() = appContext.reactContext?.applicationContext
35
+
36
+ override fun definition() = ModuleDefinition {
37
+ Name("FloatingBubble")
38
+
39
+ // Has the user granted "Display over other apps"?
40
+ Function("canDrawOverlays") {
41
+ val ctx = appContextOrNull ?: return@Function false
42
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) Settings.canDrawOverlays(ctx) else true
43
+ }
44
+
45
+ // Send the user to the system settings screen to grant the overlay permission.
46
+ Function("requestOverlayPermission") {
47
+ val ctx = appContextOrNull
48
+ if (
49
+ ctx != null &&
50
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
51
+ !Settings.canDrawOverlays(ctx)
52
+ ) {
53
+ val intent = Intent(
54
+ Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
55
+ Uri.parse("package:${ctx.packageName}"),
56
+ ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
57
+ ctx.startActivity(intent)
58
+ }
59
+ }
60
+
61
+ Function("isVisible") { bubbleView != null }
62
+
63
+ Function("show") {
64
+ mainHandler.post { showBubble() }
65
+ }
66
+
67
+ Function("hide") {
68
+ mainHandler.post { hideBubble() }
69
+ }
70
+
71
+ OnDestroy {
72
+ mainHandler.post { hideBubble() }
73
+ }
74
+ }
75
+
76
+ private fun showBubble() {
77
+ val ctx = appContextOrNull ?: return
78
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(ctx)) return
79
+ if (bubbleView != null) return
80
+
81
+ val wm = ctx.getSystemService(Context.WINDOW_SERVICE) as WindowManager
82
+ windowManager = wm
83
+
84
+ val density = ctx.resources.displayMetrics.density
85
+ val sizePx = (60 * density).toInt()
86
+ val padPx = (8 * density).toInt()
87
+
88
+ // Black circle with the app icon centered inside it.
89
+ val container = FrameLayout(ctx).apply {
90
+ background = GradientDrawable().apply {
91
+ shape = GradientDrawable.OVAL
92
+ setColor(Color.BLACK)
93
+ }
94
+ elevation = 12 * density
95
+ outlineProvider = object : ViewOutlineProvider() {
96
+ override fun getOutline(view: View, outline: Outline) {
97
+ outline.setOval(0, 0, view.width, view.height)
98
+ }
99
+ }
100
+ clipToOutline = true
101
+ }
102
+
103
+ val icon = ImageView(ctx).apply {
104
+ scaleType = ImageView.ScaleType.CENTER_CROP
105
+ try {
106
+ setImageDrawable(ctx.packageManager.getApplicationIcon(ctx.packageName))
107
+ } catch (_: Exception) {
108
+ }
109
+ }
110
+ container.addView(
111
+ icon,
112
+ FrameLayout.LayoutParams(sizePx - padPx, sizePx - padPx, Gravity.CENTER),
113
+ )
114
+
115
+ val windowType =
116
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
117
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
118
+ else
119
+ @Suppress("DEPRECATION")
120
+ WindowManager.LayoutParams.TYPE_PHONE
121
+
122
+ val params = WindowManager.LayoutParams(
123
+ sizePx,
124
+ sizePx,
125
+ windowType,
126
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
127
+ PixelFormat.TRANSLUCENT,
128
+ ).apply {
129
+ gravity = Gravity.TOP or Gravity.START
130
+ x = (16 * density).toInt()
131
+ y = (140 * density).toInt()
132
+ }
133
+
134
+ container.setOnTouchListener(object : View.OnTouchListener {
135
+ private var startX = 0
136
+ private var startY = 0
137
+ private var touchDownX = 0f
138
+ private var touchDownY = 0f
139
+ private var dragging = false
140
+ private val touchSlop = 8 * density
141
+
142
+ override fun onTouch(v: View, e: MotionEvent): Boolean {
143
+ when (e.action) {
144
+ MotionEvent.ACTION_DOWN -> {
145
+ startX = params.x
146
+ startY = params.y
147
+ touchDownX = e.rawX
148
+ touchDownY = e.rawY
149
+ dragging = false
150
+ return true
151
+ }
152
+ MotionEvent.ACTION_MOVE -> {
153
+ val dx = e.rawX - touchDownX
154
+ val dy = e.rawY - touchDownY
155
+ if (abs(dx) > touchSlop || abs(dy) > touchSlop) dragging = true
156
+ params.x = startX + dx.toInt()
157
+ params.y = startY + dy.toInt()
158
+ try {
159
+ wm.updateViewLayout(v, params)
160
+ } catch (_: Exception) {
161
+ }
162
+ return true
163
+ }
164
+ MotionEvent.ACTION_UP -> {
165
+ if (!dragging) bringAppToFront()
166
+ return true
167
+ }
168
+ }
169
+ return false
170
+ }
171
+ })
172
+
173
+ try {
174
+ wm.addView(container, params)
175
+ bubbleView = container
176
+ } catch (_: Exception) {
177
+ bubbleView = null
178
+ }
179
+ }
180
+
181
+ private fun bringAppToFront() {
182
+ val ctx = appContextOrNull ?: return
183
+ val launchIntent = ctx.packageManager.getLaunchIntentForPackage(ctx.packageName)
184
+ launchIntent?.addFlags(
185
+ Intent.FLAG_ACTIVITY_NEW_TASK or
186
+ Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or
187
+ Intent.FLAG_ACTIVITY_SINGLE_TOP,
188
+ )
189
+ if (launchIntent != null) {
190
+ try {
191
+ ctx.startActivity(launchIntent)
192
+ } catch (_: Exception) {
193
+ }
194
+ }
195
+ hideBubble()
196
+ }
197
+
198
+ private fun hideBubble() {
199
+ val view = bubbleView ?: return
200
+ try {
201
+ windowManager?.removeView(view)
202
+ } catch (_: Exception) {
203
+ }
204
+ bubbleView = null
205
+ }
206
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The raw native surface registered by the module on each platform.
3
+ *
4
+ * On Android these are backed by a real `WindowManager` overlay drawn with the
5
+ * SYSTEM_ALERT_WINDOW ("Display over other apps") permission. iOS has no public
6
+ * API to draw over other apps, so the native module there is a no-op stub and
7
+ * every method returns a safe default.
8
+ *
9
+ * Prefer the {@link FloatingBubble} façade below — it guards every call so it is
10
+ * safe to invoke on any platform.
11
+ */
12
+ export type FloatingBubbleNativeModule = {
13
+ /** Whether the user has granted "Display over other apps" (SYSTEM_ALERT_WINDOW). */
14
+ canDrawOverlays(): boolean;
15
+ /** Opens the system settings screen to grant the overlay permission. */
16
+ requestOverlayPermission(): void;
17
+ /** Whether the bubble is currently on screen. */
18
+ isVisible(): boolean;
19
+ /** Show the bubble (no-op without permission, or if already visible). */
20
+ show(): void;
21
+ /** Remove the bubble. */
22
+ hide(): void;
23
+ };
24
+ /**
25
+ * True only where the bubble can actually render: Android, with the native
26
+ * module present in the running binary. Everywhere else every call below is a
27
+ * safe no-op.
28
+ */
29
+ export declare const isSupported: boolean;
30
+ /**
31
+ * Cross-platform façade over the native module. Safe to call on any platform —
32
+ * on iOS, or on a build without the native module, methods short-circuit to safe
33
+ * defaults instead of throwing.
34
+ */
35
+ export declare const FloatingBubble: {
36
+ /** See {@link isSupported}. */
37
+ isSupported: boolean;
38
+ /** Whether the user has granted "Display over other apps". `false` if unsupported. */
39
+ canDrawOverlays(): boolean;
40
+ /** Send the user to the system settings screen to grant the overlay permission. */
41
+ requestPermission(): void;
42
+ /** Whether the bubble is currently on screen. `false` if unsupported. */
43
+ isVisible(): boolean;
44
+ /** Show the bubble. No-op without permission, if already visible, or if unsupported. */
45
+ show(): void;
46
+ /** Remove the bubble. No-op if unsupported. */
47
+ hide(): void;
48
+ };
49
+ export default FloatingBubble;
50
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,oFAAoF;IACpF,eAAe,IAAI,OAAO,CAAC;IAC3B,wEAAwE;IACxE,wBAAwB,IAAI,IAAI,CAAC;IACjC,iDAAiD;IACjD,SAAS,IAAI,OAAO,CAAC;IACrB,yEAAyE;IACzE,IAAI,IAAI,IAAI,CAAC;IACb,yBAAyB;IACzB,IAAI,IAAI,IAAI,CAAC;CACd,CAAC;AAWF;;;;GAIG;AACH,eAAO,MAAM,WAAW,SAA8B,CAAC;AAEvD;;;;GAIG;AACH,eAAO,MAAM,cAAc;IACzB,+BAA+B;;IAG/B,sFAAsF;uBACnE,OAAO;IAI1B,mFAAmF;yBAC9D,IAAI;IAIzB,yEAAyE;iBAC5D,OAAO;IAIpB,wFAAwF;YAChF,IAAI;IAIZ,+CAA+C;YACvC,IAAI;CAGb,CAAC;AAEF,eAAe,cAAc,CAAC"}
package/build/index.js ADDED
@@ -0,0 +1,48 @@
1
+ import { requireOptionalNativeModule } from 'expo';
2
+ import { Platform } from 'react-native';
3
+ const isAndroid = Platform.OS === 'android';
4
+ // `requireOptionalNativeModule` returns null instead of throwing when the native
5
+ // module isn't in the running binary. That keeps a JS-only/OTA bundle from
6
+ // crashing if it ever lands on an older build that predates this native module —
7
+ // the bubble simply does nothing there. iOS ships a no-op stub of the same name.
8
+ const native = requireOptionalNativeModule('FloatingBubble');
9
+ /**
10
+ * True only where the bubble can actually render: Android, with the native
11
+ * module present in the running binary. Everywhere else every call below is a
12
+ * safe no-op.
13
+ */
14
+ export const isSupported = isAndroid && native != null;
15
+ /**
16
+ * Cross-platform façade over the native module. Safe to call on any platform —
17
+ * on iOS, or on a build without the native module, methods short-circuit to safe
18
+ * defaults instead of throwing.
19
+ */
20
+ export const FloatingBubble = {
21
+ /** See {@link isSupported}. */
22
+ isSupported,
23
+ /** Whether the user has granted "Display over other apps". `false` if unsupported. */
24
+ canDrawOverlays() {
25
+ return isSupported ? native.canDrawOverlays() : false;
26
+ },
27
+ /** Send the user to the system settings screen to grant the overlay permission. */
28
+ requestPermission() {
29
+ if (isSupported)
30
+ native.requestOverlayPermission();
31
+ },
32
+ /** Whether the bubble is currently on screen. `false` if unsupported. */
33
+ isVisible() {
34
+ return isSupported ? native.isVisible() : false;
35
+ },
36
+ /** Show the bubble. No-op without permission, if already visible, or if unsupported. */
37
+ show() {
38
+ if (isSupported)
39
+ native.show();
40
+ },
41
+ /** Remove the bubble. No-op if unsupported. */
42
+ hide() {
43
+ if (isSupported)
44
+ native.hide();
45
+ },
46
+ };
47
+ export default FloatingBubble;
48
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,MAAM,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AA0BxC,MAAM,SAAS,GAAG,QAAQ,CAAC,EAAE,KAAK,SAAS,CAAC;AAE5C,iFAAiF;AACjF,2EAA2E;AAC3E,iFAAiF;AACjF,iFAAiF;AACjF,MAAM,MAAM,GACV,2BAA2B,CAA6B,gBAAgB,CAAC,CAAC;AAE5E;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,IAAI,MAAM,IAAI,IAAI,CAAC;AAEvD;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,+BAA+B;IAC/B,WAAW;IAEX,sFAAsF;IACtF,eAAe;QACb,OAAO,WAAW,CAAC,CAAC,CAAC,MAAO,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IACzD,CAAC;IAED,mFAAmF;IACnF,iBAAiB;QACf,IAAI,WAAW;YAAE,MAAO,CAAC,wBAAwB,EAAE,CAAC;IACtD,CAAC;IAED,yEAAyE;IACzE,SAAS;QACP,OAAO,WAAW,CAAC,CAAC,CAAC,MAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IACnD,CAAC;IAED,wFAAwF;IACxF,IAAI;QACF,IAAI,WAAW;YAAE,MAAO,CAAC,IAAI,EAAE,CAAC;IAClC,CAAC;IAED,+CAA+C;IAC/C,IAAI;QACF,IAAI,WAAW;YAAE,MAAO,CAAC,IAAI,EAAE,CAAC;IAClC,CAAC;CACF,CAAC;AAEF,eAAe,cAAc,CAAC","sourcesContent":["import { requireOptionalNativeModule } from 'expo';\nimport { Platform } from 'react-native';\n\n/**\n * The raw native surface registered by the module on each platform.\n *\n * On Android these are backed by a real `WindowManager` overlay drawn with the\n * SYSTEM_ALERT_WINDOW (\"Display over other apps\") permission. iOS has no public\n * API to draw over other apps, so the native module there is a no-op stub and\n * every method returns a safe default.\n *\n * Prefer the {@link FloatingBubble} façade below — it guards every call so it is\n * safe to invoke on any platform.\n */\nexport type FloatingBubbleNativeModule = {\n /** Whether the user has granted \"Display over other apps\" (SYSTEM_ALERT_WINDOW). */\n canDrawOverlays(): boolean;\n /** Opens the system settings screen to grant the overlay permission. */\n requestOverlayPermission(): void;\n /** Whether the bubble is currently on screen. */\n isVisible(): boolean;\n /** Show the bubble (no-op without permission, or if already visible). */\n show(): void;\n /** Remove the bubble. */\n hide(): void;\n};\n\nconst isAndroid = Platform.OS === 'android';\n\n// `requireOptionalNativeModule` returns null instead of throwing when the native\n// module isn't in the running binary. That keeps a JS-only/OTA bundle from\n// crashing if it ever lands on an older build that predates this native module —\n// the bubble simply does nothing there. iOS ships a no-op stub of the same name.\nconst native =\n requireOptionalNativeModule<FloatingBubbleNativeModule>('FloatingBubble');\n\n/**\n * True only where the bubble can actually render: Android, with the native\n * module present in the running binary. Everywhere else every call below is a\n * safe no-op.\n */\nexport const isSupported = isAndroid && native != null;\n\n/**\n * Cross-platform façade over the native module. Safe to call on any platform —\n * on iOS, or on a build without the native module, methods short-circuit to safe\n * defaults instead of throwing.\n */\nexport const FloatingBubble = {\n /** See {@link isSupported}. */\n isSupported,\n\n /** Whether the user has granted \"Display over other apps\". `false` if unsupported. */\n canDrawOverlays(): boolean {\n return isSupported ? native!.canDrawOverlays() : false;\n },\n\n /** Send the user to the system settings screen to grant the overlay permission. */\n requestPermission(): void {\n if (isSupported) native!.requestOverlayPermission();\n },\n\n /** Whether the bubble is currently on screen. `false` if unsupported. */\n isVisible(): boolean {\n return isSupported ? native!.isVisible() : false;\n },\n\n /** Show the bubble. No-op without permission, if already visible, or if unsupported. */\n show(): void {\n if (isSupported) native!.show();\n },\n\n /** Remove the bubble. No-op if unsupported. */\n hide(): void {\n if (isSupported) native!.hide();\n },\n};\n\nexport default FloatingBubble;\n"]}
@@ -0,0 +1,9 @@
1
+ {
2
+ "platforms": ["apple", "android"],
3
+ "apple": {
4
+ "modules": ["FloatingBubbleModule"]
5
+ },
6
+ "android": {
7
+ "modules": ["ai.rastaa.floatingbubble.FloatingBubbleModule"]
8
+ }
9
+ }
@@ -0,0 +1,20 @@
1
+ Pod::Spec.new do |s|
2
+ s.name = 'FloatingBubble'
3
+ s.version = '0.1.0'
4
+ s.summary = 'Back-to-Rastaa floating bubble (Android-only; iOS no-op stub).'
5
+ s.description = 'Expo module that draws a return-to-app bubble over other apps on Android.'
6
+ s.author = 'Rastaa <tech@rastaa.ai>'
7
+ s.homepage = 'https://github.com/rastaa/floating-bubble'
8
+ s.platforms = { :ios => '15.1', :tvos => '15.1' }
9
+ s.source = { git: 'https://github.com/rastaa/floating-bubble.git', tag: "v#{s.version}" }
10
+ s.static_framework = true
11
+
12
+ s.dependency 'ExpoModulesCore'
13
+
14
+ s.pod_target_xcconfig = {
15
+ 'DEFINES_MODULE' => 'YES',
16
+ 'SWIFT_COMPILATION_MODE' => 'wholemodule'
17
+ }
18
+
19
+ s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
20
+ end
@@ -0,0 +1,30 @@
1
+ import ExpoModulesCore
2
+
3
+ // iOS cannot draw a widget over other apps (no public API), so this is a no-op
4
+ // stub. It exists only so `requireNativeModule('FloatingBubble')` resolves and
5
+ // the JS layer can call the same API on both platforms without crashing.
6
+ public class FloatingBubbleModule: Module {
7
+ public func definition() -> ModuleDefinition {
8
+ Name("FloatingBubble")
9
+
10
+ Function("canDrawOverlays") { () -> Bool in
11
+ false
12
+ }
13
+
14
+ Function("requestOverlayPermission") {
15
+ // No-op on iOS.
16
+ }
17
+
18
+ Function("isVisible") { () -> Bool in
19
+ false
20
+ }
21
+
22
+ Function("show") {
23
+ // No-op on iOS.
24
+ }
25
+
26
+ Function("hide") {
27
+ // No-op on iOS.
28
+ }
29
+ }
30
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@parastud/floating-bubble",
3
+ "version": "0.1.0",
4
+ "description": "Back-to-Rastaa floating bubble — a draggable circular overlay to jump back into the app from a navigation app. Android only; iOS is a safe no-op stub.",
5
+ "main": "build/index.js",
6
+ "types": "build/index.d.ts",
7
+ "scripts": {
8
+ "build": "expo-module build",
9
+ "clean": "expo-module clean",
10
+ "lint": "expo-module lint",
11
+ "test": "expo-module test",
12
+ "prepare": "expo-module prepare",
13
+ "prepublishOnly": "expo-module prepublishOnly",
14
+ "expo-module": "expo-module"
15
+ },
16
+ "keywords": [
17
+ "react-native",
18
+ "expo",
19
+ "expo-module",
20
+ "floating-bubble",
21
+ "overlay",
22
+ "system-alert-window",
23
+ "android"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/parastud/floating-bubble.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/parastud/floating-bubble/issues"
31
+ },
32
+ "author": "Rastaa <tech@rastaa.ai>",
33
+ "license": "UNLICENSED",
34
+ "homepage": "https://github.com/parastud/floating-bubble#readme",
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "files": [
39
+ "build",
40
+ "android",
41
+ "ios",
42
+ "expo-module.config.json",
43
+ "README.md"
44
+ ],
45
+ "peerDependencies": {
46
+ "expo": "*",
47
+ "react": "*",
48
+ "react-native": "*"
49
+ },
50
+ "devDependencies": {
51
+ "@types/react": "~19.2.10",
52
+ "expo": "~55.0.26",
53
+ "expo-module-scripts": "^55.0.0",
54
+ "expo-modules-core": "~55.0.25",
55
+ "react": "19.2.0",
56
+ "react-native": "0.83.6",
57
+ "typescript": "~5.9.2"
58
+ }
59
+ }