pythonnative 0.35.0__py3-none-any.whl → 0.37.0__py3-none-any.whl
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.
- pythonnative/__init__.py +6 -4
- pythonnative/_ios_log.py +1 -1
- pythonnative/alerts.py +34 -88
- pythonnative/bootstrap.py +90 -0
- pythonnative/bridge/__init__.py +306 -0
- pythonnative/bridge/android.py +78 -0
- pythonnative/bridge/codec.py +164 -0
- pythonnative/bridge/fake.py +236 -0
- pythonnative/bridge/ios.py +138 -0
- pythonnative/cli/pn.py +24 -6
- pythonnative/components/layout.py +65 -3
- pythonnative/gestures.py +13 -10
- pythonnative/hooks.py +41 -26
- pythonnative/hosts/__init__.py +13 -36
- pythonnative/hosts/native.py +348 -0
- pythonnative/images.py +6 -5
- pythonnative/mutations.py +21 -0
- pythonnative/native_modules/__init__.py +30 -29
- pythonnative/native_modules/app_state.py +10 -6
- pythonnative/native_modules/battery.py +20 -84
- pythonnative/native_modules/biometrics.py +11 -124
- pythonnative/native_modules/camera.py +15 -291
- pythonnative/native_modules/clipboard.py +14 -109
- pythonnative/native_modules/desktop.py +322 -0
- pythonnative/native_modules/file_system.py +15 -21
- pythonnative/native_modules/haptics.py +19 -129
- pythonnative/native_modules/linking.py +24 -137
- pythonnative/native_modules/location.py +28 -180
- pythonnative/native_modules/net_info.py +25 -135
- pythonnative/native_modules/notifications.py +36 -257
- pythonnative/native_modules/permissions.py +20 -184
- pythonnative/native_modules/registry.py +513 -0
- pythonnative/native_modules/secure_store.py +18 -155
- pythonnative/native_modules/share.py +8 -113
- pythonnative/native_views/__init__.py +67 -86
- pythonnative/native_views/bridge_backend.py +264 -0
- pythonnative/platform.py +19 -15
- pythonnative/platform_metrics.py +3 -3
- pythonnative/project/android.py +0 -3
- pythonnative/project/builder.py +38 -16
- pythonnative/project/config.py +12 -2
- pythonnative/project/plugins.py +366 -0
- pythonnative/py.typed +0 -0
- pythonnative/runtime.py +35 -203
- pythonnative/sdk/__init__.py +48 -33
- pythonnative/sdk/_components.py +57 -76
- pythonnative/storage.py +18 -243
- pythonnative/templates/android_template/app/build.gradle +6 -9
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
- pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
- pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
- pythonnative/templates/android_template/settings.gradle +1 -0
- pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
- pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
- pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
- pythonnative/utils.py +4 -82
- pythonnative/virtual_rows.py +3 -3
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
- pythonnative-0.37.0.dist-info/RECORD +263 -0
- pythonnative/hosts/android.py +0 -269
- pythonnative/hosts/ios.py +0 -404
- pythonnative/native_views/android.py +0 -3702
- pythonnative/native_views/ios.py +0 -5253
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
- pythonnative-0.35.0.dist-info/RECORD +0 -155
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
package com.pythonnative.runtime.modules
|
|
2
|
+
|
|
3
|
+
import android.app.AlertDialog
|
|
4
|
+
import android.content.ClipData
|
|
5
|
+
import android.content.ClipboardManager
|
|
6
|
+
import android.content.Context
|
|
7
|
+
import android.content.Intent
|
|
8
|
+
import android.net.Uri
|
|
9
|
+
import android.os.Build
|
|
10
|
+
import android.os.VibrationEffect
|
|
11
|
+
import android.os.Vibrator
|
|
12
|
+
import android.os.VibratorManager
|
|
13
|
+
import android.provider.Settings
|
|
14
|
+
import com.pythonnative.runtime.PNBridge
|
|
15
|
+
import com.pythonnative.runtime.bridge.JsonUtil
|
|
16
|
+
import com.pythonnative.runtime.bridge.PNLog
|
|
17
|
+
import com.pythonnative.runtime.bridge.str
|
|
18
|
+
import com.pythonnative.runtime.bridge.value
|
|
19
|
+
import org.json.JSONArray
|
|
20
|
+
import org.json.JSONObject
|
|
21
|
+
import java.util.Locale
|
|
22
|
+
|
|
23
|
+
/** `Device.info()`: static facts about the OS, hardware, and app directories. */
|
|
24
|
+
class DeviceModule : NativeModule {
|
|
25
|
+
override val name = "Device"
|
|
26
|
+
|
|
27
|
+
override fun call(method: String, args: JSONObject, promise: Promise) {
|
|
28
|
+
when (method) {
|
|
29
|
+
"info" -> {
|
|
30
|
+
val ctx = PNBridge.context()
|
|
31
|
+
val locale = Locale.getDefault()
|
|
32
|
+
promise.resolve(
|
|
33
|
+
mapOf(
|
|
34
|
+
"os" to "android",
|
|
35
|
+
"os_version" to Build.VERSION.RELEASE,
|
|
36
|
+
"sdk_int" to Build.VERSION.SDK_INT,
|
|
37
|
+
"model" to Build.MODEL,
|
|
38
|
+
"manufacturer" to Build.MANUFACTURER,
|
|
39
|
+
"app_dir" to ctx.filesDir.absolutePath,
|
|
40
|
+
"cache_dir" to ctx.cacheDir.absolutePath,
|
|
41
|
+
"locale" to locale.toLanguageTag(),
|
|
42
|
+
"density" to PNBridge.density().toDouble(),
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
else -> promise.rejectUnknownMethod(method)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* `Alert.show({title, message, buttons:[{label, style}], style})`:
|
|
53
|
+
* an `AlertDialog` resolving to the selected button index, or `-1`
|
|
54
|
+
* on dismiss. `AlertDialog` has three slots; the first `default`
|
|
55
|
+
* button takes positive, the first `cancel` negative, the first
|
|
56
|
+
* `destructive` neutral, and leftovers spill into free slots.
|
|
57
|
+
*/
|
|
58
|
+
class AlertModule : NativeModule {
|
|
59
|
+
override val name = "Alert"
|
|
60
|
+
|
|
61
|
+
override fun call(method: String, args: JSONObject, promise: Promise) {
|
|
62
|
+
when (method) {
|
|
63
|
+
"present" -> present(args, promise, fireAndForget = false)
|
|
64
|
+
"show" -> present(args, promise, fireAndForget = true)
|
|
65
|
+
else -> promise.rejectUnknownMethod(method)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Build and show the dialog. `present` resolves with the tapped button
|
|
71
|
+
* index (or -1 on dismiss); `show` resolves immediately so the Python
|
|
72
|
+
* side's synchronous `call("show")` returns before the dialog closes.
|
|
73
|
+
*/
|
|
74
|
+
private fun present(args: JSONObject, promise: Promise, fireAndForget: Boolean) {
|
|
75
|
+
val activity = PNBridge.activity()
|
|
76
|
+
if (activity == null) {
|
|
77
|
+
if (fireAndForget) promise.resolve(null) else promise.reject("no activity", "no_activity")
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
if (fireAndForget) promise.resolve(null)
|
|
81
|
+
val builder = AlertDialog.Builder(activity)
|
|
82
|
+
builder.setTitle(args.str("title") ?: "")
|
|
83
|
+
args.str("message")?.let { builder.setMessage(it) }
|
|
84
|
+
val buttons = args.value("buttons") as? JSONArray
|
|
85
|
+
val specs = ArrayList<JSONObject>()
|
|
86
|
+
if (buttons != null) for (i in 0 until buttons.length()) buttons.optJSONObject(i)?.let { specs.add(it) }
|
|
87
|
+
if (specs.isEmpty()) specs.add(JSONObject().put("label", "OK").put("style", "default"))
|
|
88
|
+
|
|
89
|
+
val slotFor = HashMap<Int, String>()
|
|
90
|
+
val free = arrayListOf("positive", "negative", "neutral")
|
|
91
|
+
specs.forEachIndexed { i, spec ->
|
|
92
|
+
val preferred = when (spec.str("style") ?: "default") {
|
|
93
|
+
"cancel" -> "negative"
|
|
94
|
+
"destructive" -> "neutral"
|
|
95
|
+
else -> "positive"
|
|
96
|
+
}
|
|
97
|
+
if (free.remove(preferred)) slotFor[i] = preferred
|
|
98
|
+
}
|
|
99
|
+
specs.forEachIndexed { i, _ ->
|
|
100
|
+
if (!slotFor.containsKey(i) && free.isNotEmpty()) slotFor[i] = free.removeAt(0)
|
|
101
|
+
}
|
|
102
|
+
var delivered = false
|
|
103
|
+
fun deliver(index: Int) {
|
|
104
|
+
if (delivered) return
|
|
105
|
+
delivered = true
|
|
106
|
+
if (!fireAndForget) promise.resolve(index)
|
|
107
|
+
}
|
|
108
|
+
specs.forEachIndexed { i, spec ->
|
|
109
|
+
val label = spec.str("label") ?: "OK"
|
|
110
|
+
when (slotFor[i]) {
|
|
111
|
+
"positive" -> builder.setPositiveButton(label) { _, _ -> deliver(i) }
|
|
112
|
+
"negative" -> builder.setNegativeButton(label) { _, _ -> deliver(i) }
|
|
113
|
+
"neutral" -> builder.setNeutralButton(label) { _, _ -> deliver(i) }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
builder.setOnCancelListener { deliver(-1) }
|
|
117
|
+
builder.setOnDismissListener { deliver(-1) }
|
|
118
|
+
builder.show()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** `Clipboard.set_string(value)` / `get_string()`. */
|
|
123
|
+
class ClipboardModule : NativeModule {
|
|
124
|
+
override val name = "Clipboard"
|
|
125
|
+
|
|
126
|
+
private fun manager(): ClipboardManager? =
|
|
127
|
+
PNBridge.context().getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
|
|
128
|
+
|
|
129
|
+
override fun call(method: String, args: JSONObject, promise: Promise) {
|
|
130
|
+
when (method) {
|
|
131
|
+
"set_string" -> {
|
|
132
|
+
manager()?.setPrimaryClip(ClipData.newPlainText("pythonnative", args.str("value") ?: ""))
|
|
133
|
+
promise.resolve(null)
|
|
134
|
+
}
|
|
135
|
+
"get_string" -> {
|
|
136
|
+
val clip = manager()?.primaryClip
|
|
137
|
+
val text = if (clip != null && clip.itemCount > 0) clip.getItemAt(0).text?.toString() else null
|
|
138
|
+
promise.resolve(text ?: "")
|
|
139
|
+
}
|
|
140
|
+
"has_string" -> {
|
|
141
|
+
val clip = manager()?.primaryClip
|
|
142
|
+
promise.resolve(clip != null && clip.itemCount > 0 && !clip.getItemAt(0).text.isNullOrEmpty())
|
|
143
|
+
}
|
|
144
|
+
else -> promise.rejectUnknownMethod(method)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** `Share.share({message, url, title})`: an `ACTION_SEND` chooser resolving `true` when it returns. */
|
|
150
|
+
class ShareModule : NativeModule {
|
|
151
|
+
override val name = "Share"
|
|
152
|
+
private var pending: Promise? = null
|
|
153
|
+
private var pendingCode = 0
|
|
154
|
+
|
|
155
|
+
override fun call(method: String, args: JSONObject, promise: Promise) {
|
|
156
|
+
when (method) {
|
|
157
|
+
"share" -> share(args, promise)
|
|
158
|
+
else -> promise.rejectUnknownMethod(method)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private fun share(args: JSONObject, promise: Promise) {
|
|
163
|
+
val activity = PNBridge.activity() ?: return promise.reject("no activity", "no_activity")
|
|
164
|
+
val message = args.str("message")
|
|
165
|
+
val url = args.str("url")
|
|
166
|
+
val body = listOfNotNull(message, url).joinToString("\n")
|
|
167
|
+
if (body.isEmpty()) return promise.resolve(false)
|
|
168
|
+
val intent = Intent(Intent.ACTION_SEND)
|
|
169
|
+
intent.type = "text/plain"
|
|
170
|
+
intent.putExtra(Intent.EXTRA_TEXT, body)
|
|
171
|
+
args.str("title")?.let { intent.putExtra(Intent.EXTRA_SUBJECT, it) }
|
|
172
|
+
try {
|
|
173
|
+
pending?.resolve(true)
|
|
174
|
+
pending = promise
|
|
175
|
+
pendingCode = RequestCodes.next()
|
|
176
|
+
@Suppress("DEPRECATION")
|
|
177
|
+
activity.startActivityForResult(Intent.createChooser(intent, args.str("title")), pendingCode)
|
|
178
|
+
} catch (e: Exception) {
|
|
179
|
+
pending = null
|
|
180
|
+
promise.resolve(false)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Resolve the pending share when the chooser activity returns. */
|
|
185
|
+
fun onActivityResult(requestCode: Int): Boolean {
|
|
186
|
+
if (requestCode != pendingCode || pending == null) return false
|
|
187
|
+
val p = pending
|
|
188
|
+
pending = null
|
|
189
|
+
// The chooser gives no success signal; returning to the app counts as shared.
|
|
190
|
+
p?.resolve(true)
|
|
191
|
+
return true
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* `Linking`: `open_url`, `can_open_url`, `open_settings` (sync bools)
|
|
197
|
+
* plus `url` events for deep links, buffered until a `PythonHost` is
|
|
198
|
+
* installed.
|
|
199
|
+
*/
|
|
200
|
+
class LinkingModule : NativeModule {
|
|
201
|
+
override val name = "Linking"
|
|
202
|
+
private val buffered = ArrayList<String>()
|
|
203
|
+
private var initialUrl: String? = null
|
|
204
|
+
|
|
205
|
+
override fun call(method: String, args: JSONObject, promise: Promise) {
|
|
206
|
+
when (method) {
|
|
207
|
+
"open_url" -> promise.resolve(openUrl(args.str("url") ?: ""))
|
|
208
|
+
"can_open_url" -> promise.resolve(canOpen(args.str("url") ?: ""))
|
|
209
|
+
"open_settings" -> promise.resolve(openSettings())
|
|
210
|
+
"get_initial_url" -> promise.resolve(initialUrl)
|
|
211
|
+
else -> promise.rejectUnknownMethod(method)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Record a deep link and emit it once Python can receive it. */
|
|
216
|
+
fun onDeepLink(url: String) {
|
|
217
|
+
if (initialUrl == null) initialUrl = url
|
|
218
|
+
buffered.add(url)
|
|
219
|
+
PNBridge.runWhenHostReady { flush() }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private fun flush() {
|
|
223
|
+
val urls = ArrayList(buffered)
|
|
224
|
+
buffered.clear()
|
|
225
|
+
for (url in urls) ModuleEvents.emit(name, "url", JSONObject().put("url", url))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private fun openUrl(url: String): Boolean {
|
|
229
|
+
if (url.isEmpty()) return false
|
|
230
|
+
return try {
|
|
231
|
+
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
|
232
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
233
|
+
PNBridge.context().startActivity(intent)
|
|
234
|
+
true
|
|
235
|
+
} catch (e: Exception) {
|
|
236
|
+
PNLog.swallowed("Linking.open_url", e)
|
|
237
|
+
false
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private fun canOpen(url: String): Boolean {
|
|
242
|
+
if (url.isEmpty()) return false
|
|
243
|
+
return try {
|
|
244
|
+
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
|
245
|
+
intent.resolveActivity(PNBridge.context().packageManager) != null
|
|
246
|
+
} catch (e: Exception) {
|
|
247
|
+
false
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private fun openSettings(): Boolean {
|
|
252
|
+
return try {
|
|
253
|
+
val ctx = PNBridge.context()
|
|
254
|
+
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
|
255
|
+
intent.data = Uri.fromParts("package", ctx.packageName, null)
|
|
256
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
257
|
+
ctx.startActivity(intent)
|
|
258
|
+
true
|
|
259
|
+
} catch (e: Exception) {
|
|
260
|
+
false
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** `Haptics`: `impact(style)`, `notification(type)`, `selection()`, `vibrate(duration_ms)`, `cancel()`. */
|
|
266
|
+
class HapticsModule : NativeModule {
|
|
267
|
+
override val name = "Haptics"
|
|
268
|
+
|
|
269
|
+
override fun call(method: String, args: JSONObject, promise: Promise) {
|
|
270
|
+
when (method) {
|
|
271
|
+
"impact" -> buzz(IMPACT_MS[args.str("style") ?: "medium"] ?: 20L)
|
|
272
|
+
"notification" -> buzz(NOTIFICATION_MS[args.str("type") ?: args.str("type_") ?: "success"] ?: 30L)
|
|
273
|
+
"selection" -> buzz(10L)
|
|
274
|
+
"vibrate" -> buzz(JsonUtil.toLong(args.opt("duration_ms"), 400L))
|
|
275
|
+
"cancel" -> vibrator()?.cancel()
|
|
276
|
+
else -> return promise.rejectUnknownMethod(method)
|
|
277
|
+
}
|
|
278
|
+
promise.resolve(null)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private fun vibrator(): Vibrator? {
|
|
282
|
+
val ctx = PNBridge.context()
|
|
283
|
+
return if (Build.VERSION.SDK_INT >= 31) {
|
|
284
|
+
(ctx.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as? VibratorManager)?.defaultVibrator
|
|
285
|
+
} else {
|
|
286
|
+
@Suppress("DEPRECATION")
|
|
287
|
+
ctx.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private fun buzz(durationMs: Long) {
|
|
292
|
+
val v = vibrator() ?: return
|
|
293
|
+
try {
|
|
294
|
+
if (Build.VERSION.SDK_INT >= 26) {
|
|
295
|
+
v.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE))
|
|
296
|
+
} else {
|
|
297
|
+
@Suppress("DEPRECATION")
|
|
298
|
+
v.vibrate(durationMs)
|
|
299
|
+
}
|
|
300
|
+
} catch (e: Exception) {
|
|
301
|
+
PNLog.swallowed("Haptics.buzz", e)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private companion object {
|
|
306
|
+
val IMPACT_MS = mapOf("light" to 10L, "medium" to 20L, "heavy" to 40L, "soft" to 15L, "rigid" to 30L)
|
|
307
|
+
val NOTIFICATION_MS = mapOf("success" to 30L, "warning" to 50L, "error" to 70L)
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Monotonic request codes for `startActivityForResult` / `requestPermissions`. */
|
|
312
|
+
object RequestCodes {
|
|
313
|
+
private var next = 50001
|
|
314
|
+
|
|
315
|
+
fun next(): Int = next++
|
|
316
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
package com.pythonnative.runtime.modules
|
|
2
|
+
|
|
3
|
+
import androidx.fragment.app.FragmentActivity
|
|
4
|
+
import com.pythonnative.runtime.PNBridge
|
|
5
|
+
import com.pythonnative.runtime.bridge.JsonUtil
|
|
6
|
+
import com.pythonnative.runtime.bridge.MainThread
|
|
7
|
+
import com.pythonnative.runtime.bridge.str
|
|
8
|
+
import com.pythonnative.runtime.bridge.value
|
|
9
|
+
import com.pythonnative.runtime.screens.Navigator
|
|
10
|
+
import com.pythonnative.runtime.screens.PNScreenFragment
|
|
11
|
+
import com.pythonnative.runtime.screens.ScreenRegistry
|
|
12
|
+
import org.json.JSONArray
|
|
13
|
+
import org.json.JSONObject
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `Host`: screen hosting and navigation. Screens are the integer ids
|
|
17
|
+
* assigned by [ScreenRegistry] when a [PNScreenFragment] is created.
|
|
18
|
+
*/
|
|
19
|
+
class HostModule : NativeModule {
|
|
20
|
+
override val name = "Host"
|
|
21
|
+
|
|
22
|
+
private fun screen(args: JSONObject): PNScreenFragment? = ScreenRegistry.get(JsonUtil.toInt(args.opt("screen")))
|
|
23
|
+
|
|
24
|
+
private fun activity(): FragmentActivity? = PNBridge.activity() as? FragmentActivity
|
|
25
|
+
|
|
26
|
+
private fun argsJson(args: JSONObject): String? {
|
|
27
|
+
val value = args.value("args") ?: return null
|
|
28
|
+
return if (value is String) value else value.toString()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
override fun call(method: String, args: JSONObject, promise: Promise) {
|
|
32
|
+
when (method) {
|
|
33
|
+
"attach_root" -> {
|
|
34
|
+
val fragment = screen(args) ?: return promise.reject("unknown screen", "unknown_screen")
|
|
35
|
+
val record = PNBridge.registry.get(JsonUtil.toLong(args.opt("tag")))
|
|
36
|
+
?: return promise.reject("unknown tag", "unknown_tag")
|
|
37
|
+
fragment.attachRoot(record.view)
|
|
38
|
+
promise.resolve(null)
|
|
39
|
+
}
|
|
40
|
+
"detach_root" -> {
|
|
41
|
+
val fragment = screen(args)
|
|
42
|
+
val record = PNBridge.registry.get(JsonUtil.toLong(args.opt("tag")))
|
|
43
|
+
if (record != null) {
|
|
44
|
+
if (fragment != null) fragment.detachRoot(record.view) else (record.view.parent as? android.view.ViewGroup)?.removeView(record.view)
|
|
45
|
+
}
|
|
46
|
+
promise.resolve(null)
|
|
47
|
+
}
|
|
48
|
+
"push" -> {
|
|
49
|
+
val activity = activity() ?: return promise.resolve(false)
|
|
50
|
+
promise.resolve(Navigator.push(activity, args.str("path") ?: "", argsJson(args), args.str("title")))
|
|
51
|
+
}
|
|
52
|
+
"pop" -> {
|
|
53
|
+
val activity = activity() ?: return promise.resolve(false)
|
|
54
|
+
promise.resolve(Navigator.pop(activity, JsonUtil.toInt(args.opt("count"), 1).coerceAtLeast(1)))
|
|
55
|
+
}
|
|
56
|
+
"pop_to_root" -> {
|
|
57
|
+
val activity = activity() ?: return promise.resolve(false)
|
|
58
|
+
promise.resolve(Navigator.popToRoot(activity))
|
|
59
|
+
}
|
|
60
|
+
"replace" -> {
|
|
61
|
+
val activity = activity() ?: return promise.resolve(false)
|
|
62
|
+
promise.resolve(Navigator.replace(activity, args.str("path") ?: "", argsJson(args), args.str("title")))
|
|
63
|
+
}
|
|
64
|
+
"reset" -> {
|
|
65
|
+
val activity = activity() ?: return promise.resolve(false)
|
|
66
|
+
val screens = ArrayList<Triple<String, String?, String?>>()
|
|
67
|
+
val list = args.value("screens") as? JSONArray
|
|
68
|
+
if (list != null) {
|
|
69
|
+
for (i in 0 until list.length()) {
|
|
70
|
+
val entry = list.optJSONObject(i) ?: continue
|
|
71
|
+
screens.add(Triple(entry.str("path") ?: "", argsJson(entry), entry.str("title")))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
promise.resolve(Navigator.reset(activity, screens))
|
|
75
|
+
}
|
|
76
|
+
"set_options" -> {
|
|
77
|
+
val options = args.value("options") as? JSONObject ?: JSONObject()
|
|
78
|
+
options.str("title")?.let { title -> activity()?.title = title }
|
|
79
|
+
promise.resolve(null)
|
|
80
|
+
}
|
|
81
|
+
"viewport" -> {
|
|
82
|
+
val fragment = screen(args) ?: ScreenRegistry.get(ScreenRegistry.activeId())
|
|
83
|
+
val payload = fragment?.viewport() ?: activity()?.window?.decorView?.let {
|
|
84
|
+
com.pythonnative.runtime.screens.Viewport.describe(it)
|
|
85
|
+
} ?: JSONObject()
|
|
86
|
+
promise.resolve(payload)
|
|
87
|
+
}
|
|
88
|
+
"post" -> {
|
|
89
|
+
MainThread.post { PNBridge.callPython("pump", 0, "", "") }
|
|
90
|
+
promise.resolve(null)
|
|
91
|
+
}
|
|
92
|
+
"is_main_thread" -> promise.resolve(MainThread.isMain())
|
|
93
|
+
"active_screen" -> promise.resolve(ScreenRegistry.activeId())
|
|
94
|
+
else -> promise.rejectUnknownMethod(method)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
package com.pythonnative.runtime.modules
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.app.Activity
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.content.Intent
|
|
7
|
+
import android.graphics.Bitmap
|
|
8
|
+
import android.location.Location
|
|
9
|
+
import android.location.LocationListener
|
|
10
|
+
import android.location.LocationManager
|
|
11
|
+
import android.os.Bundle
|
|
12
|
+
import android.provider.MediaStore
|
|
13
|
+
import androidx.biometric.BiometricManager
|
|
14
|
+
import androidx.biometric.BiometricPrompt
|
|
15
|
+
import androidx.core.content.ContextCompat
|
|
16
|
+
import androidx.fragment.app.FragmentActivity
|
|
17
|
+
import com.pythonnative.runtime.PNBridge
|
|
18
|
+
import com.pythonnative.runtime.bridge.MainThread
|
|
19
|
+
import com.pythonnative.runtime.bridge.PNLog
|
|
20
|
+
import com.pythonnative.runtime.bridge.str
|
|
21
|
+
import org.json.JSONObject
|
|
22
|
+
import java.io.File
|
|
23
|
+
import java.io.FileOutputStream
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `Camera.take_photo()` / `pick_from_gallery()`: launch the system
|
|
27
|
+
* capture or pick intent and resolve to the resulting path (a content
|
|
28
|
+
* URI string, or a JPEG written to the cache dir for thumbnails) or
|
|
29
|
+
* `null` when cancelled.
|
|
30
|
+
*/
|
|
31
|
+
class CameraModule : NativeModule {
|
|
32
|
+
override val name = "Camera"
|
|
33
|
+
private val pending = HashMap<Int, Promise>()
|
|
34
|
+
|
|
35
|
+
override fun call(method: String, args: JSONObject, promise: Promise) {
|
|
36
|
+
when (method) {
|
|
37
|
+
"take_photo" -> launch(Intent(MediaStore.ACTION_IMAGE_CAPTURE), promise)
|
|
38
|
+
"pick_from_gallery" -> launch(Intent(Intent.ACTION_PICK).apply { type = "image/*" }, promise)
|
|
39
|
+
else -> promise.rejectUnknownMethod(method)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private fun launch(intent: Intent, promise: Promise) {
|
|
44
|
+
val activity = PNBridge.activity() ?: return promise.resolve(null)
|
|
45
|
+
val code = RequestCodes.next()
|
|
46
|
+
pending[code] = promise
|
|
47
|
+
try {
|
|
48
|
+
@Suppress("DEPRECATION")
|
|
49
|
+
activity.startActivityForResult(intent, code)
|
|
50
|
+
} catch (e: Exception) {
|
|
51
|
+
pending.remove(code)
|
|
52
|
+
PNLog.swallowed("Camera.launch", e)
|
|
53
|
+
promise.resolve(null)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Route `Activity.onActivityResult`; `true` when a pending picker matched. */
|
|
58
|
+
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
|
|
59
|
+
val promise = pending.remove(requestCode) ?: return false
|
|
60
|
+
var path: String? = null
|
|
61
|
+
if (resultCode == Activity.RESULT_OK && data != null) {
|
|
62
|
+
val uri = data.data
|
|
63
|
+
if (uri != null) {
|
|
64
|
+
path = uri.toString()
|
|
65
|
+
} else {
|
|
66
|
+
@Suppress("DEPRECATION")
|
|
67
|
+
val thumb = data.extras?.get("data") as? Bitmap
|
|
68
|
+
if (thumb != null) path = writeBitmapToCache(thumb)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
promise.resolve(path)
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private fun writeBitmapToCache(bitmap: Bitmap): String? {
|
|
76
|
+
return try {
|
|
77
|
+
val target = File(PNBridge.context().cacheDir, "pn-camera-${System.currentTimeMillis()}.jpg")
|
|
78
|
+
FileOutputStream(target).use { bitmap.compress(Bitmap.CompressFormat.JPEG, 85, it) }
|
|
79
|
+
target.absolutePath
|
|
80
|
+
} catch (e: Exception) {
|
|
81
|
+
PNLog.swallowed("Camera.writeBitmapToCache", e)
|
|
82
|
+
null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* `Location.get_current()`: the last known fix if fresh enough,
|
|
89
|
+
* otherwise a single `network`/`gps` update with a 15 s timeout.
|
|
90
|
+
* Resolves to `{latitude, longitude}` or `null`.
|
|
91
|
+
*/
|
|
92
|
+
class LocationModule : NativeModule {
|
|
93
|
+
override val name = "Location"
|
|
94
|
+
|
|
95
|
+
override fun call(method: String, args: JSONObject, promise: Promise) {
|
|
96
|
+
when (method) {
|
|
97
|
+
"get_current" -> getCurrent(promise)
|
|
98
|
+
else -> promise.rejectUnknownMethod(method)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@SuppressLint("MissingPermission")
|
|
103
|
+
private fun getCurrent(promise: Promise) {
|
|
104
|
+
val ctx = PNBridge.context()
|
|
105
|
+
val lm = ctx.getSystemService(Context.LOCATION_SERVICE) as? LocationManager ?: return promise.resolve(null)
|
|
106
|
+
try {
|
|
107
|
+
for (provider in listOf(LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER)) {
|
|
108
|
+
val last = lm.getLastKnownLocation(provider) ?: continue
|
|
109
|
+
if (System.currentTimeMillis() - last.time < 60_000) return promise.resolve(coords(last))
|
|
110
|
+
}
|
|
111
|
+
} catch (e: SecurityException) {
|
|
112
|
+
return promise.resolve(null)
|
|
113
|
+
} catch (e: Exception) {
|
|
114
|
+
PNLog.swallowed("Location.lastKnown", e)
|
|
115
|
+
}
|
|
116
|
+
var settled = false
|
|
117
|
+
val listener = object : LocationListener {
|
|
118
|
+
override fun onLocationChanged(location: Location) {
|
|
119
|
+
if (settled) return
|
|
120
|
+
settled = true
|
|
121
|
+
lm.removeUpdates(this)
|
|
122
|
+
promise.resolve(coords(location))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@Deprecated("Deprecated in Java")
|
|
126
|
+
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
|
|
127
|
+
override fun onProviderEnabled(provider: String) {}
|
|
128
|
+
override fun onProviderDisabled(provider: String) {}
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
val provider = when {
|
|
132
|
+
lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER) -> LocationManager.NETWORK_PROVIDER
|
|
133
|
+
lm.isProviderEnabled(LocationManager.GPS_PROVIDER) -> LocationManager.GPS_PROVIDER
|
|
134
|
+
else -> return promise.resolve(null)
|
|
135
|
+
}
|
|
136
|
+
lm.requestLocationUpdates(provider, 1000L, 0f, listener)
|
|
137
|
+
MainThread.postDelayed({
|
|
138
|
+
if (settled) return@postDelayed
|
|
139
|
+
settled = true
|
|
140
|
+
lm.removeUpdates(listener)
|
|
141
|
+
promise.resolve(null)
|
|
142
|
+
}, 15_000L)
|
|
143
|
+
} catch (e: Exception) {
|
|
144
|
+
PNLog.swallowed("Location.requestUpdates", e)
|
|
145
|
+
promise.resolve(null)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private fun coords(location: Location): Map<String, Any?> = mapOf(
|
|
150
|
+
"latitude" to location.latitude,
|
|
151
|
+
"longitude" to location.longitude,
|
|
152
|
+
"accuracy" to location.accuracy.toDouble(),
|
|
153
|
+
"altitude" to if (location.hasAltitude()) location.altitude else null,
|
|
154
|
+
"timestamp" to location.time,
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** `Biometrics.is_available()` (sync bool) and `authenticate(reason)` (async bool via `BiometricPrompt`). */
|
|
159
|
+
class BiometricsModule : NativeModule {
|
|
160
|
+
override val name = "Biometrics"
|
|
161
|
+
|
|
162
|
+
override fun call(method: String, args: JSONObject, promise: Promise) {
|
|
163
|
+
when (method) {
|
|
164
|
+
"is_available" -> promise.resolve(isAvailable())
|
|
165
|
+
"authenticate" -> authenticate(args.str("reason") ?: "Authenticate", promise)
|
|
166
|
+
else -> promise.rejectUnknownMethod(method)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private fun isAvailable(): Boolean {
|
|
171
|
+
return try {
|
|
172
|
+
val manager = BiometricManager.from(PNBridge.context())
|
|
173
|
+
manager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS
|
|
174
|
+
} catch (e: Exception) {
|
|
175
|
+
false
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private fun authenticate(reason: String, promise: Promise) {
|
|
180
|
+
val activity = PNBridge.activity() as? FragmentActivity ?: return promise.resolve(false)
|
|
181
|
+
if (!isAvailable()) return promise.resolve(false)
|
|
182
|
+
try {
|
|
183
|
+
val executor = ContextCompat.getMainExecutor(activity)
|
|
184
|
+
val prompt = BiometricPrompt(activity, executor, object : BiometricPrompt.AuthenticationCallback() {
|
|
185
|
+
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) = promise.resolve(true)
|
|
186
|
+
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) = promise.resolve(false)
|
|
187
|
+
override fun onAuthenticationFailed() {}
|
|
188
|
+
})
|
|
189
|
+
val info = BiometricPrompt.PromptInfo.Builder()
|
|
190
|
+
.setTitle(reason)
|
|
191
|
+
.setNegativeButtonText("Cancel")
|
|
192
|
+
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK)
|
|
193
|
+
.build()
|
|
194
|
+
prompt.authenticate(info)
|
|
195
|
+
} catch (e: Exception) {
|
|
196
|
+
PNLog.swallowed("Biometrics.authenticate", e)
|
|
197
|
+
promise.resolve(false)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|