vaderjs-native 1.0.10 → 1.0.11

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.
@@ -1,24 +1,34 @@
1
1
  package com.example.myapplication
2
- import androidx.activity.OnBackPressedCallback
3
2
 
3
+ import android.app.AlertDialog
4
4
  import android.annotation.SuppressLint
5
5
  import android.content.Context
6
+ import android.content.pm.PackageManager
6
7
  import android.os.Bundle
7
8
  import android.os.Message
8
9
  import android.view.KeyEvent
9
10
  import android.webkit.JavascriptInterface
11
+ import android.webkit.WebChromeClient
10
12
  import android.webkit.WebView
11
13
  import android.webkit.WebViewClient
12
- import android.webkit.WebChromeClient
13
14
  import android.widget.Toast
14
15
  import androidx.activity.ComponentActivity
16
+ import androidx.activity.OnBackPressedCallback
17
+ import androidx.core.app.ActivityCompat
18
+ import androidx.core.content.ContextCompat
19
+ import org.json.JSONArray
20
+ import java.io.FileNotFoundException
15
21
  import java.net.HttpURLConnection
16
22
  import java.net.URL
17
23
 
18
24
  class MainActivity : ComponentActivity() {
25
+
19
26
  lateinit var webView: WebView
27
+ lateinit var androidBridge: AndroidBridge
20
28
 
21
- private var baseUrl = "file:///android_asset/myapp/"
29
+ private val baseUrl = "file:///android_asset/myapp/index.html"
30
+
31
+ @SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface")
22
32
  override fun onCreate(savedInstanceState: Bundle?) {
23
33
  super.onCreate(savedInstanceState)
24
34
 
@@ -26,63 +36,95 @@ class MainActivity : ComponentActivity() {
26
36
  setContentView(webView)
27
37
 
28
38
  // --- WebView Settings ---
29
- val settings = webView.settings
30
- settings.javaScriptEnabled = true
31
- settings.allowFileAccess = true
32
- settings.allowContentAccess = true
33
- settings.allowFileAccessFromFileURLs = true
34
- settings.allowUniversalAccessFromFileURLs = true
35
- settings.domStorageEnabled = true
36
- settings.mediaPlaybackRequiresUserGesture = false
39
+ webView.settings.apply {
40
+ javaScriptEnabled = true
41
+ allowFileAccess = true
42
+ allowContentAccess = true
43
+ allowFileAccessFromFileURLs = true
44
+ allowUniversalAccessFromFileURLs = true
45
+ domStorageEnabled = true
46
+ databaseEnabled = true
47
+ mediaPlaybackRequiresUserGesture = false
48
+
49
+ // Basic compatibility settings
50
+ setSupportMultipleWindows(false)
51
+ loadWithOverviewMode = true
52
+ useWideViewPort = true
53
+ builtInZoomControls = true
54
+ displayZoomControls = false
55
+ setSupportZoom(true)
56
+
57
+ // Performance optimizations
58
+ javaScriptCanOpenWindowsAutomatically = false
59
+ loadsImagesAutomatically = true
60
+ }
61
+
37
62
  webView.isFocusable = true
38
63
  webView.isFocusableInTouchMode = true
39
64
  webView.requestFocus()
40
- // --- WebViewClient to block external URLs ---
65
+
66
+ // --- JS Bridge ---
67
+ androidBridge = AndroidBridge(this, webView, baseUrl)
68
+ webView.addJavascriptInterface(androidBridge, "Android")
69
+
70
+ // --- WebViewClient ---
41
71
  webView.webViewClient = object : WebViewClient() {
72
+
42
73
  override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
43
- if (url != null && url.startsWith("file:///android_asset/myapp/")) {
44
- return false
74
+ return if (url != null && url.startsWith(baseUrl)) {
75
+ false
76
+ } else {
77
+ Toast.makeText(
78
+ this@MainActivity,
79
+ "Blocked external navigation",
80
+ Toast.LENGTH_SHORT
81
+ ).show()
82
+ true
45
83
  }
46
- Toast.makeText(this@MainActivity, "Blocked external navigation", Toast.LENGTH_SHORT)
47
- .show()
48
- return true
84
+ }
85
+
86
+ override fun onPageFinished(view: WebView?, url: String?) {
87
+ view?.evaluateJavascript(
88
+ "console.log('Android bridge ready:', !!window.Android)",
89
+ null
90
+ )
49
91
  }
50
92
  }
51
93
 
52
- // --- Optional WebChromeClient for popups ---
94
+ // --- Block popups ---
53
95
  webView.webChromeClient = object : WebChromeClient() {
54
96
  override fun onCreateWindow(
55
97
  view: WebView?,
56
98
  isDialog: Boolean,
57
99
  isUserGesture: Boolean,
58
100
  resultMsg: Message?
59
- ): Boolean {
60
- // Block popups completely
61
- return false
62
- }
101
+ ): Boolean = false
63
102
  }
64
103
 
65
- // --- Add JS bridge ---
66
- webView.addJavascriptInterface(AndroidBridge(this, webView, baseUrl), "Android")
104
+ // --- Back button → JS ---
67
105
  onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
68
106
  override fun handleOnBackPressed() {
69
- // Forward back key to JS
70
107
  webView.evaluateJavascript(
71
- "(function(){if(window.onNativeKey){window.onNativeKey(4);}})()",
108
+ "window.onNativeKey && window.onNativeKey(4)",
72
109
  null
73
110
  )
74
- // Do NOT call super, JS will handle closing modals/player
75
111
  }
76
112
  })
77
- webView.webViewClient = object : WebViewClient() {
78
- override fun onPageFinished(view: WebView?, url: String?) {
79
- view?.evaluateJavascript("console.log('JS ready')", null)
80
- }
81
- }
82
- // --- Load local HTML ---
113
+
83
114
  webView.loadUrl(baseUrl)
84
115
  }
85
116
 
117
+ // --- Permission result forwarding ---
118
+ override fun onRequestPermissionsResult(
119
+ requestCode: Int,
120
+ permissions: Array<out String>,
121
+ grantResults: IntArray
122
+ ) {
123
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
124
+ androidBridge.onPermissionResult(requestCode, grantResults)
125
+ }
126
+
127
+ // --- DPAD / media keys ---
86
128
  @SuppressLint("RestrictedApi")
87
129
  override fun dispatchKeyEvent(event: KeyEvent): Boolean {
88
130
  if (event.action == KeyEvent.ACTION_DOWN) {
@@ -102,7 +144,6 @@ class MainActivity : ComponentActivity() {
102
144
  }
103
145
  true
104
146
  }
105
-
106
147
  else -> false
107
148
  }
108
149
  if (handled) return true
@@ -111,13 +152,164 @@ class MainActivity : ComponentActivity() {
111
152
  }
112
153
  }
113
154
 
114
- class AndroidBridge(private val context: Context, private val webView: WebView, private val baseUrl: String) {
155
+ // ---------------- JS BRIDGE ----------------
156
+
157
+ class AndroidBridge(
158
+ private val activity: ComponentActivity,
159
+ private val webView: WebView,
160
+ private val baseUrl: String
161
+ ) {
162
+
163
+ private val PERMISSION_REQUEST_CODE = 9001
115
164
 
165
+ // ---- Toast ----
116
166
  @JavascriptInterface
117
167
  fun showToast(message: String) {
118
- Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
168
+ activity.runOnUiThread {
169
+ Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
170
+ }
119
171
  }
120
172
 
173
+ // ---- File System Methods ----
174
+ @JavascriptInterface
175
+ fun writeFile(path: String, content: String): Boolean {
176
+ return try {
177
+ // Create directories if needed
178
+ val file = File(activity.filesDir, path)
179
+ file.parentFile?.mkdirs()
180
+
181
+ file.bufferedWriter().use { writer ->
182
+ writer.write(content)
183
+ }
184
+ true
185
+ } catch (e: Exception) {
186
+ e.printStackTrace()
187
+ false
188
+ }
189
+ }
190
+
191
+ @JavascriptInterface
192
+ fun readFile(path: String): String {
193
+ return try {
194
+ val file = File(activity.filesDir, path)
195
+ if (!file.exists()) {
196
+ return "{\"error\":\"File not found\"}"
197
+ }
198
+ file.bufferedReader().use { it.readText() }
199
+ } catch (e: Exception) {
200
+ e.printStackTrace()
201
+ "{\"error\":\"${e.message}\"}"
202
+ }
203
+ }
204
+
205
+ @JavascriptInterface
206
+ fun deleteFile(path: String): Boolean {
207
+ return try {
208
+ val file = File(activity.filesDir, path)
209
+ file.delete()
210
+ } catch (e: Exception) {
211
+ e.printStackTrace()
212
+ false
213
+ }
214
+ }
215
+
216
+ @JavascriptInterface
217
+ fun listFiles(path: String = ""): String {
218
+ return try {
219
+ val dir = File(activity.filesDir, path)
220
+ val files = if (dir.exists() && dir.isDirectory) {
221
+ dir.list()?.toList() ?: emptyList()
222
+ } else {
223
+ emptyList()
224
+ }
225
+ JSONArray(files).toString()
226
+ } catch (e: Exception) {
227
+ e.printStackTrace()
228
+ "[]"
229
+ }
230
+ }
231
+
232
+ // ---- Permissions ----
233
+ @JavascriptInterface
234
+ fun hasPermission(name: String): Boolean {
235
+ val permissions = mapPermission(name)
236
+ return permissions.all {
237
+ ContextCompat.checkSelfPermission(activity, it) ==
238
+ PackageManager.PERMISSION_GRANTED
239
+ }
240
+ }
241
+
242
+ @JavascriptInterface
243
+ fun requestPermission(name: String) {
244
+ val permissions = mapPermission(name)
245
+
246
+ if (permissions.isEmpty()) {
247
+ sendPermissionResult(true)
248
+ return
249
+ }
250
+
251
+ val granted = permissions.all {
252
+ ContextCompat.checkSelfPermission(activity, it) ==
253
+ PackageManager.PERMISSION_GRANTED
254
+ }
255
+
256
+ if (granted) {
257
+ sendPermissionResult(true)
258
+ return
259
+ }
260
+
261
+ ActivityCompat.requestPermissions(
262
+ activity,
263
+ permissions,
264
+ PERMISSION_REQUEST_CODE
265
+ )
266
+ }
267
+
268
+ fun onPermissionResult(requestCode: Int, grantResults: IntArray) {
269
+ if (requestCode != PERMISSION_REQUEST_CODE) return
270
+ val granted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
271
+ sendPermissionResult(granted)
272
+ }
273
+
274
+ private fun sendPermissionResult(granted: Boolean) {
275
+ webView.post {
276
+ webView.evaluateJavascript(
277
+ "window.onNativePermissionResult && window.onNativePermissionResult($granted)",
278
+ null
279
+ )
280
+ }
281
+ }
282
+
283
+ // ---- Dialog ----
284
+ @JavascriptInterface
285
+ fun showDialog(
286
+ title: String,
287
+ message: String,
288
+ okText: String = "OK",
289
+ cancelText: String = "Cancel"
290
+ ) {
291
+ activity.runOnUiThread {
292
+ AlertDialog.Builder(activity)
293
+ .setTitle(title)
294
+ .setMessage(message)
295
+ .setPositiveButton(okText) { _, _ ->
296
+ webView.evaluateJavascript(
297
+ "window.onNativeDialogResult && window.onNativeDialogResult(true)",
298
+ null
299
+ )
300
+ }
301
+ .setNegativeButton(cancelText) { _, _ ->
302
+ webView.evaluateJavascript(
303
+ "window.onNativeDialogResult && window.onNativeDialogResult(false)",
304
+ null
305
+ )
306
+ }
307
+ .setCancelable(false)
308
+ .show()
309
+ }
310
+ }
311
+
312
+ // ---- Native fetch ----
121
313
  @JavascriptInterface
122
314
  fun nativeFetch(url: String, method: String): String {
123
315
  return try {
@@ -125,26 +317,35 @@ class MainActivity : ComponentActivity() {
125
317
  connection.requestMethod = method
126
318
  connection.connectTimeout = 5000
127
319
  connection.readTimeout = 5000
128
-
129
- val responseText = connection.inputStream.bufferedReader().use { it.readText() }
320
+ val response = connection.inputStream.bufferedReader().use { it.readText() }
130
321
  connection.disconnect()
131
- responseText
322
+ response
132
323
  } catch (e: Exception) {
133
- "{\"error\": \"${e.message}\"}"
324
+ "{\"error\":\"${e.message}\"}"
134
325
  }
135
326
  }
136
327
 
137
- @JavascriptInterface
138
- fun navigate(path: String?) {
139
- // Ensure path starts with a clean structure
140
- val cleanPath = path?.trimStart('/') ?: ""
328
+ // ---- Navigation ----
329
+ @JavascriptInterface
330
+ fun navigate(path: String?) {
331
+ val clean = path?.trimStart('/') ?: ""
332
+ webView.post {
333
+ val finalUrl = "$baseUrl$clean/index.html".replace("//index", "/index")
334
+ webView.loadUrl(finalUrl)
335
+ }
336
+ }
141
337
 
142
- webView.post {
143
- // Concatenate the base (Dev or Prod) with the path
144
- // Example Dev: http://192.168.1.x:3000/settings/index.html
145
- // Example Prod: file:///android_asset/myapp/settings/index.html
146
- val finalUrl = "${baseUrl}${cleanPath}/index.html".replace("//index", "/index")
147
- webView.loadUrl(finalUrl)
148
- }
338
+ // ---- Permission map ----
339
+ private fun mapPermission(name: String): Array<String> {
340
+ return when (name) {
341
+ "storage" -> arrayOf(
342
+ android.Manifest.permission.READ_EXTERNAL_STORAGE,
343
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE
344
+ )
345
+ "camera" -> arrayOf(android.Manifest.permission.CAMERA)
346
+ "microphone" -> arrayOf(android.Manifest.permission.RECORD_AUDIO)
347
+ "notifications" -> arrayOf(android.Manifest.permission.POST_NOTIFICATIONS)
348
+ else -> emptyArray()
149
349
  }
150
- }
350
+ }
351
+ }
package/index.ts CHANGED
@@ -1,4 +1,19 @@
1
- // Android key code to key mapping
1
+ ;(window as any).onNativeDialogResult = function (confirmed: boolean) {
2
+ if (dialogResolver) {
3
+ dialogResolver(confirmed);
4
+ dialogResolver = null;
5
+ }
6
+ };
7
+
8
+ /**
9
+ * Called by Android
10
+ */
11
+ ;(window as any).onNativePermissionResult = function (granted: boolean) {
12
+ if (permissionResolver) {
13
+ permissionResolver(granted);
14
+ permissionResolver = null;
15
+ }
16
+ };
2
17
  const ANDROID_KEY_MAP: Record<number, string> = {
3
18
  19: "ArrowUp", // DPAD_UP
4
19
  20: "ArrowDown", // DPAD_DOWN
@@ -970,16 +985,335 @@ export function Show({ when, children }: { when: boolean, children: VNode[] }):
970
985
  //@ts-ignore
971
986
  return when ? children : null;
972
987
  }
973
- export function showToast (message: string, duration = 3000) {
974
- //@ts-ignore
975
- if (window.Android && typeof window.Android.showToast === "function") {
976
- //@ts-ignore
977
- window.Android.showToast(message, duration);
978
- } else {
979
- console.log(`[showToast] ${message}`);
988
+ /**
989
+ * @description Show toast allows you to invoke system level toast api to show data to user
990
+ * @param message
991
+ * @param duration
992
+ */
993
+ export function showToast(message: string, duration = 3000) {
994
+ if (typeof window !== "undefined" && (window as any).Android?.showToast) {
995
+ console.log("[Vader] Android Toast");
996
+ (window as any).Android.showToast(message);
997
+ return;
998
+ }
999
+
1000
+ // Web fallback
1001
+ console.log("[Toast]", message);
1002
+
1003
+ const toast = document.createElement("div");
1004
+ toast.textContent = message;
1005
+ Object.assign(toast.style, {
1006
+ position: "fixed",
1007
+ bottom: "24px",
1008
+ left: "50%",
1009
+ transform: "translateX(-50%)",
1010
+ background: "rgba(0,0,0,0.85)",
1011
+ color: "white",
1012
+ padding: "10px 14px",
1013
+ borderRadius: "8px",
1014
+ zIndex: 9999,
1015
+ fontSize: "14px",
1016
+ });
1017
+
1018
+ document.body.appendChild(toast);
1019
+ setTimeout(() => toast.remove(), duration);
1020
+ }
1021
+
1022
+ type PermissionName =
1023
+ | "storage"
1024
+ | "internet"
1025
+ | "camera"
1026
+ | "microphone"
1027
+ | "notifications";
1028
+
1029
+ let permissionResolver: ((granted: boolean) => void) | null = null;
1030
+
1031
+ export function usePermission() {
1032
+ const isAndroid =
1033
+ typeof window !== "undefined" &&
1034
+ (window as any).Android?.requestPermission;
1035
+
1036
+ function request(name: PermissionName): Promise<boolean> {
1037
+ if (isAndroid) {
1038
+ return new Promise<boolean>((resolve) => {
1039
+ permissionResolver = resolve;
1040
+ (window as any).Android.requestPermission(name);
1041
+ });
1042
+ }
1043
+
1044
+ // ---- Web fallback ----
1045
+ console.warn(`[Permission] ${name} auto-granted on web`);
1046
+ return Promise.resolve(true);
1047
+ }
1048
+
1049
+ function has(name: PermissionName): Promise<boolean> {
1050
+ if (isAndroid && (window as any).Android.hasPermission) {
1051
+ return Promise.resolve(
1052
+ (window as any).Android.hasPermission(name)
1053
+ );
1054
+ }
1055
+ return Promise.resolve(true);
1056
+ }
1057
+
1058
+ return {
1059
+ request,
1060
+ has,
1061
+
1062
+ // ergonomic helpers
1063
+ storage: () => request("storage"),
1064
+ camera: () => request("camera"),
1065
+ microphone: () => request("microphone"),
1066
+ notifications: () => request("notifications"),
1067
+ internet: () => request("internet")
1068
+ };
1069
+ }
1070
+
1071
+ type FS = {
1072
+ readFile(path: string): Promise<string>
1073
+ writeFile(path: string, content: string): Promise<boolean>
1074
+ deleteFile(path: string): Promise<boolean>
1075
+ listDir(path: string): Promise<string[]>
1076
+ }
1077
+
1078
+ export const FS: FS = {
1079
+ async writeFile(path: string, content: string): Promise<boolean> {
1080
+ try {
1081
+ if (!window.Android) {
1082
+ console.error('Android bridge not available')
1083
+ return false
1084
+ }
1085
+
1086
+ // Call Android bridge method
1087
+ const result = window.Android.writeFile(path, content)
1088
+ return result === true || result === 'true'
1089
+ } catch (error) {
1090
+ console.error('Error writing file:', error)
1091
+ return false
1092
+ }
1093
+ },
1094
+
1095
+ async readFile(path: string): Promise<string> {
1096
+ try {
1097
+ if (!window.Android) {
1098
+ return JSON.stringify({ error: 'Android bridge not available' })
1099
+ }
1100
+
1101
+ const result = window.Android.readFile(path)
1102
+
1103
+ // Handle both string and boolean returns
1104
+ if (typeof result === 'boolean') {
1105
+ return result ? 'true' : 'false'
1106
+ }
1107
+
1108
+ return result || ''
1109
+ } catch (error) {
1110
+ console.error('Error reading file:', error)
1111
+ return JSON.stringify({ error: error.message })
1112
+ }
1113
+ },
1114
+
1115
+ async deleteFile(path: string): Promise<boolean> {
1116
+ try {
1117
+ if (!window.Android) {
1118
+ console.error('Android bridge not available')
1119
+ return false
1120
+ }
1121
+
1122
+ // Check if deleteFile method exists on Android bridge
1123
+ if (typeof window.Android.deleteFile === 'function') {
1124
+ const result = window.Android.deleteFile(path)
1125
+ return result === true || result === 'true'
1126
+ } else {
1127
+ // Fallback: Try to write empty content
1128
+ console.warn('deleteFile not available, using writeFile fallback')
1129
+ return await this.writeFile(path, '')
1130
+ }
1131
+ } catch (error) {
1132
+ console.error('Error deleting file:', error)
1133
+ return false
1134
+ }
1135
+ },
1136
+
1137
+ async listDir(path: string = ''): Promise<string[]> {
1138
+ try {
1139
+ if (!window.Android) {
1140
+ console.error('Android bridge not available')
1141
+ return []
1142
+ }
1143
+
1144
+ // Check if listFiles method exists on Android bridge
1145
+ if (typeof window.Android.listFiles === 'function') {
1146
+ const result = window.Android.listFiles(path)
1147
+
1148
+ // Parse JSON array from string
1149
+ if (typeof result === 'string') {
1150
+ try {
1151
+ const parsed = JSON.parse(result)
1152
+ return Array.isArray(parsed) ? parsed : []
1153
+ } catch {
1154
+ // If not JSON, return as single item array or empty
1155
+ return result ? [result] : []
1156
+ }
1157
+ }
1158
+
1159
+ // If result is already an array
1160
+ if (Array.isArray(result)) {
1161
+ return result
1162
+ }
1163
+
1164
+ return []
1165
+ } else {
1166
+ console.warn('listFiles not available on Android bridge')
1167
+ return []
1168
+ }
1169
+ } catch (error) {
1170
+ console.error('Error listing directory:', error)
1171
+ return []
1172
+ }
1173
+ },
1174
+
1175
+ // Alias for backward compatibility
1176
+ write(path: string, data: string): Promise<boolean> {
1177
+ return this.writeFile(path, data)
1178
+ },
1179
+
1180
+ read(path: string): Promise<string> {
1181
+ return this.readFile(path)
980
1182
  }
1183
+ }
1184
+
1185
+ // TypeScript declarations for Android bridge
1186
+ declare global {
1187
+ interface Window {
1188
+ Android?: {
1189
+ writeFile?: (path: string, content: string) => boolean | string
1190
+ readFile?: (path: string) => string
1191
+ deleteFile?: (path: string) => boolean | string
1192
+ listFiles?: (path?: string) => string[] | string
1193
+ // Other Android bridge methods...
1194
+ showToast?: (message: string) => void
1195
+ hasPermission?: (name: string) => boolean
1196
+ requestPermission?: (name: string) => void
1197
+ showDialog?: (title: string, message: string, okText?: string, cancelText?: string) => void
1198
+ nativeFetch?: (url: string, method: string) => string
1199
+ navigate?: (path: string) => void
1200
+ }
1201
+ }
1202
+ }
1203
+
1204
+ // Utility functions for common operations
1205
+ export const FileSystem = {
1206
+ // Save JSON data
1207
+ async saveJSON(path: string, data: any): Promise<boolean> {
1208
+ return await FS.writeFile(path, JSON.stringify(data, null, 2))
1209
+ },
1210
+
1211
+ // Load JSON data
1212
+ async loadJSON<T = any>(path: string): Promise<T | null> {
1213
+ try {
1214
+ const content = await FS.readFile(path)
1215
+ if (!content || content.includes('error')) {
1216
+ return null
1217
+ }
1218
+ return JSON.parse(content)
1219
+ } catch (error) {
1220
+ console.error('Error parsing JSON:', error)
1221
+ return null
1222
+ }
1223
+ },
1224
+
1225
+ // Check if file exists
1226
+ async exists(path: string): Promise<boolean> {
1227
+ try {
1228
+ const content = await FS.readFile(path)
1229
+ return !content.includes('File not found') && !content.includes('error')
1230
+ } catch {
1231
+ return false
1232
+ }
1233
+ },
1234
+
1235
+ // Append to file
1236
+ async appendFile(path: string, content: string): Promise<boolean> {
1237
+ try {
1238
+ const existing = await FS.readFile(path)
1239
+ const newContent = existing + content
1240
+ return await FS.writeFile(path, newContent)
1241
+ } catch (error) {
1242
+ console.error('Error appending to file:', error)
1243
+ return false
1244
+ }
1245
+ },
1246
+
1247
+ // Create directory (by creating a dummy file)
1248
+ async createDirectory(path: string): Promise<boolean> {
1249
+ // Create a .nomedia file in the directory
1250
+ const dirPath = path.endsWith('/') ? path : path + '/'
1251
+ return await FS.writeFile(dirPath + '.nomedia', '')
1252
+ }
1253
+ }
1254
+ type DialogOptions = {
1255
+ title?: string;
1256
+ message: string;
1257
+ okText?: string;
1258
+ cancelText?: string;
981
1259
  };
982
1260
 
1261
+ let dialogResolver: ((value: boolean) => void) | null = null;
1262
+
1263
+ export function useDialog() {
1264
+ // ---- ANDROID IMPLEMENTATION ----
1265
+ if (typeof window !== "undefined" && (window as any).Android?.showDialog) {
1266
+ return {
1267
+ alert({ title = "", message, okText = "OK" }: DialogOptions) {
1268
+ return new Promise<void>((resolve) => {
1269
+ dialogResolver = () => resolve();
1270
+
1271
+ (window as any).Android.showDialog(
1272
+ title,
1273
+ message,
1274
+ okText,
1275
+ "" // no cancel
1276
+ );
1277
+ });
1278
+ },
1279
+
1280
+ confirm({
1281
+ title = "",
1282
+ message,
1283
+ okText = "OK",
1284
+ cancelText = "Cancel",
1285
+ }: DialogOptions) {
1286
+ return new Promise<boolean>((resolve) => {
1287
+ dialogResolver = resolve;
1288
+
1289
+ (window as any).Android.showDialog(
1290
+ title,
1291
+ message,
1292
+ okText,
1293
+ cancelText
1294
+ );
1295
+ });
1296
+ },
1297
+ };
1298
+ }
1299
+
1300
+ // ---- WEB FALLBACK ----
1301
+ return {
1302
+ alert({ title = "", message }: DialogOptions) {
1303
+ window.alert(title ? `${title}\n\n${message}` : message);
1304
+ return Promise.resolve();
1305
+ },
1306
+
1307
+ confirm({ title = "", message }: DialogOptions) {
1308
+ const result = window.confirm(
1309
+ title ? `${title}\n\n${message}` : message
1310
+ );
1311
+ return Promise.resolve(result);
1312
+ },
1313
+ };
1314
+ }
1315
+
1316
+
983
1317
  /**
984
1318
  * A React-like useRef hook for mutable references.
985
1319
  * @template T
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vaderjs-native",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "Build Native Applications using Vaderjs framework.",
5
5
  "bin": {
6
6
  "vaderjs": "./main.ts"