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.
Files changed (176) hide show
  1. pythonnative/__init__.py +6 -4
  2. pythonnative/_ios_log.py +1 -1
  3. pythonnative/alerts.py +34 -88
  4. pythonnative/bootstrap.py +90 -0
  5. pythonnative/bridge/__init__.py +306 -0
  6. pythonnative/bridge/android.py +78 -0
  7. pythonnative/bridge/codec.py +164 -0
  8. pythonnative/bridge/fake.py +236 -0
  9. pythonnative/bridge/ios.py +138 -0
  10. pythonnative/cli/pn.py +24 -6
  11. pythonnative/components/layout.py +65 -3
  12. pythonnative/gestures.py +13 -10
  13. pythonnative/hooks.py +41 -26
  14. pythonnative/hosts/__init__.py +13 -36
  15. pythonnative/hosts/native.py +348 -0
  16. pythonnative/images.py +6 -5
  17. pythonnative/mutations.py +21 -0
  18. pythonnative/native_modules/__init__.py +30 -29
  19. pythonnative/native_modules/app_state.py +10 -6
  20. pythonnative/native_modules/battery.py +20 -84
  21. pythonnative/native_modules/biometrics.py +11 -124
  22. pythonnative/native_modules/camera.py +15 -291
  23. pythonnative/native_modules/clipboard.py +14 -109
  24. pythonnative/native_modules/desktop.py +322 -0
  25. pythonnative/native_modules/file_system.py +15 -21
  26. pythonnative/native_modules/haptics.py +19 -129
  27. pythonnative/native_modules/linking.py +24 -137
  28. pythonnative/native_modules/location.py +28 -180
  29. pythonnative/native_modules/net_info.py +25 -135
  30. pythonnative/native_modules/notifications.py +36 -257
  31. pythonnative/native_modules/permissions.py +20 -184
  32. pythonnative/native_modules/registry.py +513 -0
  33. pythonnative/native_modules/secure_store.py +18 -155
  34. pythonnative/native_modules/share.py +8 -113
  35. pythonnative/native_views/__init__.py +67 -86
  36. pythonnative/native_views/bridge_backend.py +264 -0
  37. pythonnative/platform.py +19 -15
  38. pythonnative/platform_metrics.py +3 -3
  39. pythonnative/project/android.py +0 -3
  40. pythonnative/project/builder.py +38 -16
  41. pythonnative/project/config.py +12 -2
  42. pythonnative/project/plugins.py +366 -0
  43. pythonnative/py.typed +0 -0
  44. pythonnative/runtime.py +35 -203
  45. pythonnative/sdk/__init__.py +48 -33
  46. pythonnative/sdk/_components.py +57 -76
  47. pythonnative/storage.py +18 -243
  48. pythonnative/templates/android_template/app/build.gradle +6 -9
  49. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
  50. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
  51. pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
  52. pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
  53. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
  54. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
  55. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
  56. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
  57. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
  58. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
  59. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
  60. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
  61. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
  62. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
  63. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
  64. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
  65. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
  66. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
  67. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
  68. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
  69. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
  70. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
  71. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
  72. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
  73. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
  74. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
  75. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
  76. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
  77. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
  78. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
  79. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
  80. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
  81. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
  82. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
  83. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
  84. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
  85. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
  86. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
  87. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
  88. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
  89. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
  90. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
  91. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
  92. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
  93. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
  94. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
  95. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
  96. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
  97. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
  98. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
  99. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
  100. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
  101. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
  102. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
  103. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
  104. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
  105. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
  106. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
  107. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
  108. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
  109. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
  110. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
  111. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
  112. pythonnative/templates/android_template/settings.gradle +1 -0
  113. pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
  114. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
  115. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
  116. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
  117. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
  118. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
  119. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
  120. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
  121. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
  122. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
  123. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
  124. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
  125. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
  126. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
  127. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
  128. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
  129. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
  130. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
  131. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
  132. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
  133. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
  134. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
  135. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
  136. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
  137. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
  138. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
  139. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
  140. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
  141. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
  142. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
  143. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
  144. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
  145. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
  146. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
  147. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
  148. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
  149. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
  150. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
  151. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
  152. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
  153. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
  154. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
  155. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
  156. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
  157. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
  158. pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
  159. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
  160. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
  161. pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
  162. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
  163. pythonnative/utils.py +4 -82
  164. pythonnative/virtual_rows.py +3 -3
  165. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
  166. pythonnative-0.37.0.dist-info/RECORD +263 -0
  167. pythonnative/hosts/android.py +0 -269
  168. pythonnative/hosts/ios.py +0 -404
  169. pythonnative/native_views/android.py +0 -3702
  170. pythonnative/native_views/ios.py +0 -5253
  171. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
  172. pythonnative-0.35.0.dist-info/RECORD +0 -155
  173. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
  174. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
  175. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
  176. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,355 @@
1
+ package com.pythonnative.runtime.components
2
+
3
+ import android.graphics.Rect
4
+ import android.graphics.drawable.GradientDrawable
5
+ import android.os.Build
6
+ import android.view.TouchDelegate
7
+ import android.view.View
8
+ import android.view.ViewGroup
9
+ import com.pythonnative.runtime.PNBridge
10
+ import com.pythonnative.runtime.bridge.JsonUtil
11
+ import com.pythonnative.runtime.bridge.PNLog
12
+ import com.pythonnative.runtime.bridge.ViewRecord
13
+ import com.pythonnative.runtime.bridge.num
14
+ import com.pythonnative.runtime.bridge.obj
15
+ import com.pythonnative.runtime.bridge.str
16
+ import com.pythonnative.runtime.bridge.value
17
+ import com.pythonnative.runtime.views.PNAccessibilityDelegate
18
+ import com.pythonnative.runtime.views.PNBorderDrawable
19
+ import com.pythonnative.runtime.views.PNFrameLayout
20
+ import org.json.JSONArray
21
+ import org.json.JSONObject
22
+ import kotlin.math.roundToInt
23
+
24
+ /**
25
+ * Applies the visual props every element supports: background and
26
+ * borders, opacity, overflow, display, z-order, pointer events, hit
27
+ * slop, shadows, transforms, and accessibility.
28
+ *
29
+ * Border-related props are baked into a single background drawable,
30
+ * so the styler keeps the merged "visual" subset per view and re-bakes
31
+ * it whenever one of those keys changes.
32
+ */
33
+ object ViewStyler {
34
+ private val sideWidthKeys = listOf("border_left_width", "border_top_width", "border_right_width", "border_bottom_width")
35
+ private val sideColorKeys = listOf("border_left_color", "border_top_color", "border_right_color", "border_bottom_color")
36
+ // Order matches GradientDrawable.setCornerRadii: TL, TR, BR, BL.
37
+ private val cornerKeys = listOf(
38
+ "border_top_left_radius", "border_top_right_radius", "border_bottom_right_radius", "border_bottom_left_radius",
39
+ )
40
+ private val drawableKeys: List<String> =
41
+ listOf("background_color", "border_radius", "border_width", "border_color") + cornerKeys + sideWidthKeys + sideColorKeys
42
+
43
+ private fun density(): Float = PNBridge.density()
44
+ private fun px(dp: Double): Float = (dp * density()).toFloat()
45
+ private fun pxI(dp: Double): Int = (dp * density()).roundToInt()
46
+
47
+ private fun record(view: View): ViewRecord? = PNBridge.registry.recordFor(view)
48
+
49
+ /** Apply the common visual props present in `props` to `view`. */
50
+ fun apply(view: View, props: JSONObject) {
51
+ val record = record(view)
52
+ if (drawableKeys.any { props.has(it) }) {
53
+ val visual = visualProps(record)
54
+ for (key in drawableKeys) {
55
+ if (props.has(key)) visual.put(key, props.opt(key) ?: JSONObject.NULL)
56
+ }
57
+ applyBorder(view, visual)
58
+ }
59
+ if (props.has("overflow")) {
60
+ val clip = props.str("overflow") == "hidden"
61
+ if (view is ViewGroup) {
62
+ view.clipChildren = clip
63
+ view.clipToPadding = clip
64
+ }
65
+ }
66
+ if (props.has("display")) {
67
+ view.visibility = if (props.str("display") == "none") View.GONE else View.VISIBLE
68
+ }
69
+ props.num("opacity")?.let { view.alpha = it.toFloat() }
70
+ if (props.has("z_index")) {
71
+ val z = props.num("z_index")
72
+ view.z = if (z != null) px(z) else 0f
73
+ }
74
+ if (props.has("pointer_events")) applyPointerEvents(view, props.str("pointer_events"))
75
+ if (props.has("hit_slop")) updateHitSlop(view)
76
+ applyShadow(view, props)
77
+ applyTransform(view, props)
78
+ applyAccessibility(view, props)
79
+ }
80
+
81
+ private fun visualProps(record: ViewRecord?): JSONObject {
82
+ val existing = record?.state?.get("visual") as? JSONObject
83
+ if (existing != null) return existing
84
+ val fresh = JSONObject()
85
+ record?.state?.put("visual", fresh)
86
+ return fresh
87
+ }
88
+
89
+ // ------------------------------------------------------------------
90
+ // Background and borders
91
+ // ------------------------------------------------------------------
92
+
93
+ /** Re-bake the background drawable from the merged visual props. */
94
+ fun applyBorder(view: View, visual: JSONObject) {
95
+ if (applySideBorder(view, visual)) return
96
+ val hasBorder = listOf("border_radius", "border_width", "border_color").any { visual.has(it) } ||
97
+ cornerKeys.any { visual.has(it) }
98
+ val hasBg = visual.value("background_color") != null
99
+ if (!hasBorder && !hasBg) {
100
+ if (view.background is GradientDrawable || view.background is PNBorderDrawable) view.background = null
101
+ return
102
+ }
103
+ val drawable = GradientDrawable()
104
+ PNColor.parse(visual.value("background_color"))?.let { drawable.setColor(it) }
105
+ if (cornerKeys.any { visual.value(it) != null }) {
106
+ val base = visual.num("border_radius") ?: 0.0
107
+ val radii = FloatArray(8)
108
+ for ((i, key) in cornerKeys.withIndex()) {
109
+ val r = px(visual.num(key) ?: base)
110
+ radii[i * 2] = r
111
+ radii[i * 2 + 1] = r
112
+ }
113
+ drawable.cornerRadii = radii
114
+ } else {
115
+ visual.num("border_radius")?.let { drawable.cornerRadius = px(it) }
116
+ }
117
+ if (visual.value("border_width") != null || visual.value("border_color") != null) {
118
+ val width = visual.num("border_width") ?: 1.0
119
+ val color = PNColor.parseOr(visual.value("border_color"), 0xFF000000.toInt())
120
+ drawable.setStroke(pxI(width), color)
121
+ }
122
+ view.background = drawable
123
+ view.invalidate()
124
+ }
125
+
126
+ private fun applySideBorder(view: View, visual: JSONObject): Boolean {
127
+ if (sideWidthKeys.none { visual.value(it) != null }) return false
128
+ val baseWidth = visual.num("border_width") ?: 0.0
129
+ val baseColor = visual.value("border_color") ?: "#000000"
130
+ val widths = FloatArray(4) { i -> px(visual.num(sideWidthKeys[i]) ?: baseWidth) }
131
+ val colors = IntArray(4) { i -> PNColor.parseOr(visual.value(sideColorKeys[i]) ?: baseColor, 0xFF000000.toInt()) }
132
+ val bg = PNColor.parse(visual.value("background_color"))
133
+ val radius = px(visual.num("border_radius") ?: 0.0)
134
+ view.background = PNBorderDrawable(bg != null, bg ?: 0, radius, widths, colors)
135
+ view.invalidate()
136
+ return true
137
+ }
138
+
139
+ /**
140
+ * Apply one animated `background_color` frame without losing borders:
141
+ * mutate the existing drawable's fill in place when possible.
142
+ */
143
+ fun setAnimatedBackground(view: View, value: Any?) {
144
+ val color = PNColor.parse(value) ?: return
145
+ val record = record(view)
146
+ val visual = record?.state?.get("visual") as? JSONObject
147
+ val hasBorder = visual != null && drawableKeys.any { it != "background_color" && visual.value(it) != null }
148
+ if (hasBorder && visual != null) {
149
+ visual.put("background_color", String.format("#%08X", color))
150
+ when (val bg = view.background) {
151
+ is GradientDrawable -> bg.setColor(color)
152
+ is PNBorderDrawable -> bg.setFillColor(color)
153
+ else -> applyBorder(view, visual)
154
+ }
155
+ return
156
+ }
157
+ view.setBackgroundColor(color)
158
+ }
159
+
160
+ // ------------------------------------------------------------------
161
+ // Shadow / elevation
162
+ // ------------------------------------------------------------------
163
+
164
+ private fun applyShadow(view: View, props: JSONObject) {
165
+ var elevation = props.num("elevation")
166
+ if (elevation == null && props.has("shadow_radius")) elevation = props.num("shadow_radius")
167
+ if (elevation == null && (props.value("shadow_color") != null || props.value("shadow_opacity") != null)) {
168
+ // Shadow requested without an explicit size: Material card-like default.
169
+ elevation = 4.0
170
+ }
171
+ if (elevation == null) return
172
+ view.elevation = px(elevation)
173
+ val color = props.value("shadow_color") ?: return
174
+ if (Build.VERSION.SDK_INT < 28) return
175
+ var argb = PNColor.parse(color) ?: return
176
+ props.num("shadow_opacity")?.let { argb = PNColor.withAlpha(argb, it) }
177
+ view.outlineAmbientShadowColor = argb
178
+ view.outlineSpotShadowColor = argb
179
+ }
180
+
181
+ // ------------------------------------------------------------------
182
+ // Transform
183
+ // ------------------------------------------------------------------
184
+
185
+ private fun applyTransform(view: View, props: JSONObject) {
186
+ if (!props.has("transform")) return
187
+ val spec = props.value("transform")
188
+ if (spec == null) {
189
+ view.rotation = 0f
190
+ view.rotationX = 0f
191
+ view.rotationY = 0f
192
+ view.scaleX = 1f
193
+ view.scaleY = 1f
194
+ view.translationX = 0f
195
+ view.translationY = 0f
196
+ return
197
+ }
198
+ val entries: List<Any?> = when (spec) {
199
+ is JSONArray -> JsonUtil.toList(spec)
200
+ else -> listOf(spec)
201
+ }
202
+ for (entry in entries) {
203
+ val e = entry as? JSONObject ?: continue
204
+ try {
205
+ if (e.has("rotate")) view.rotation = angleDegrees(e.value("rotate"))
206
+ if (e.has("rotate_x")) view.rotationX = angleDegrees(e.value("rotate_x"))
207
+ if (e.has("rotate_y")) view.rotationY = angleDegrees(e.value("rotate_y"))
208
+ if (e.has("rotate_z")) view.rotation = angleDegrees(e.value("rotate_z"))
209
+ e.num("scale")?.let { view.scaleX = it.toFloat(); view.scaleY = it.toFloat() }
210
+ e.num("scale_x")?.let { view.scaleX = it.toFloat() }
211
+ e.num("scale_y")?.let { view.scaleY = it.toFloat() }
212
+ e.num("translate_x")?.let { view.translationX = px(it) }
213
+ e.num("translate_y")?.let { view.translationY = px(it) }
214
+ if (e.has("skew_x") || e.has("skew_y")) {
215
+ // Android views have no skew transform; approximate with rotation around the axis.
216
+ PNLog.once("skew", "transform skew_x/skew_y is not supported on Android and is ignored")
217
+ }
218
+ } catch (ex: Exception) {
219
+ PNLog.swallowed("ViewStyler.applyTransform", ex)
220
+ }
221
+ }
222
+ }
223
+
224
+ /** Parse `"45deg"`, `"0.5rad"`, or a bare number (degrees) into degrees. */
225
+ fun angleDegrees(value: Any?): Float {
226
+ return when (value) {
227
+ is Number -> value.toFloat()
228
+ is String -> {
229
+ val s = value.trim()
230
+ when {
231
+ s.endsWith("deg") -> s.dropLast(3).toFloatOrNull() ?: 0f
232
+ s.endsWith("rad") -> Math.toDegrees((s.dropLast(3).toDoubleOrNull() ?: 0.0)).toFloat()
233
+ else -> s.toFloatOrNull() ?: 0f
234
+ }
235
+ }
236
+ else -> 0f
237
+ }
238
+ }
239
+
240
+ // ------------------------------------------------------------------
241
+ // Accessibility
242
+ // ------------------------------------------------------------------
243
+
244
+ /** Apply accessibility props (label / accessible / state / live region / role / test_id). */
245
+ fun applyAccessibility(view: View, props: JSONObject) {
246
+ if (props.has("accessible")) {
247
+ view.importantForAccessibility = if (JsonUtil.truthy(props.value("accessible"))) {
248
+ View.IMPORTANT_FOR_ACCESSIBILITY_YES
249
+ } else {
250
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO
251
+ }
252
+ }
253
+ if (props.has("accessibility_label")) {
254
+ view.contentDescription = props.str("accessibility_label")
255
+ }
256
+ if (props.has("accessibility_hint") && Build.VERSION.SDK_INT >= 28) {
257
+ view.tooltipText = props.str("accessibility_hint")
258
+ }
259
+ if (props.has("accessibility_live_region")) {
260
+ view.accessibilityLiveRegion = when (props.str("accessibility_live_region")?.lowercase()) {
261
+ "polite" -> View.ACCESSIBILITY_LIVE_REGION_POLITE
262
+ "assertive" -> View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
263
+ else -> View.ACCESSIBILITY_LIVE_REGION_NONE
264
+ }
265
+ }
266
+ val state = props.obj("accessibility_state")
267
+ if (state != null && state.has("selected")) {
268
+ view.isSelected = JsonUtil.truthy(state.value("selected"))
269
+ }
270
+ val testId = props.str("test_id")
271
+ val role = props.str("accessibility_role")
272
+ if (testId == null && state == null && role == null) return
273
+ val record = record(view)
274
+ var delegate = record?.state?.get("a11y_delegate") as? PNAccessibilityDelegate
275
+ if (delegate == null) {
276
+ delegate = PNAccessibilityDelegate()
277
+ view.accessibilityDelegate = delegate
278
+ record?.state?.put("a11y_delegate", delegate)
279
+ }
280
+ if (testId != null) {
281
+ delegate.testId = testId
282
+ view.tag = testId
283
+ }
284
+ if (role != null) delegate.role = role
285
+ if (state != null) {
286
+ delegate.stateDisabled = optBool(state, "disabled")
287
+ delegate.stateSelected = optBool(state, "selected")
288
+ delegate.stateChecked = optBool(state, "checked")
289
+ delegate.stateBusy = optBool(state, "busy")
290
+ delegate.stateExpanded = optBool(state, "expanded")
291
+ }
292
+ }
293
+
294
+ private fun optBool(obj: JSONObject, key: String): Boolean? {
295
+ val v = obj.value(key) ?: return null
296
+ return JsonUtil.truthy(v)
297
+ }
298
+
299
+ // ------------------------------------------------------------------
300
+ // Pointer events and hit slop
301
+ // ------------------------------------------------------------------
302
+
303
+ /** Whether `pointer_events` disables the view's own touch handling. */
304
+ fun pointerEventsBlocked(view: View): Boolean {
305
+ val mode = record(view)?.props?.str("pointer_events") ?: return false
306
+ return mode == "none" || mode == "box_none"
307
+ }
308
+
309
+ private fun applyPointerEvents(view: View, value: String?) {
310
+ val mode = value ?: "auto"
311
+ val state = record(view)?.state
312
+ (view as? PNFrameLayout)?.setPointerEventsMode(mode)
313
+ when (mode) {
314
+ "none", "box_none" -> {
315
+ state?.let { if (!it.containsKey("pe_was_clickable")) it["pe_was_clickable"] = view.isClickable }
316
+ view.isClickable = false
317
+ }
318
+ "box_only" -> {
319
+ state?.let { if (!it.containsKey("pe_was_clickable")) it["pe_was_clickable"] = view.isClickable }
320
+ view.isClickable = true
321
+ }
322
+ else -> {
323
+ val was = state?.remove("pe_was_clickable") as? Boolean
324
+ if (was != null) view.isClickable = was
325
+ }
326
+ }
327
+ }
328
+
329
+ private fun hitSlopInsets(value: Any?): DoubleArray? {
330
+ return when (value) {
331
+ null, JSONObject.NULL -> null
332
+ is JSONObject -> doubleArrayOf(
333
+ value.num("top") ?: 0.0, value.num("left") ?: 0.0, value.num("bottom") ?: 0.0, value.num("right") ?: 0.0,
334
+ )
335
+ else -> JsonUtil.toDoubleOrNull(value)?.let { doubleArrayOf(it, it, it, it) }
336
+ }
337
+ }
338
+
339
+ /** Extend the view's touch target via a `TouchDelegate` on its parent (needs a frame). */
340
+ fun updateHitSlop(view: View) {
341
+ val record = record(view) ?: return
342
+ val frame = record.frame ?: return
343
+ val insets = hitSlopInsets(record.props.value("hit_slop"))
344
+ val parent = view.parent as? ViewGroup ?: return
345
+ if (insets == null) {
346
+ if (record.state.remove("hit_slop_active") != null) parent.touchDelegate = null
347
+ return
348
+ }
349
+ val (top, left, bottom, right) = insets
350
+ val (x, y, w, h) = frame
351
+ val rect = Rect(pxI(x - left), pxI(y - top), pxI(x + w + right), pxI(y + h + bottom))
352
+ parent.touchDelegate = TouchDelegate(rect, view)
353
+ record.state["hit_slop_active"] = true
354
+ }
355
+ }
@@ -0,0 +1,179 @@
1
+ package com.pythonnative.runtime.components
2
+
3
+ import android.content.Context
4
+ import android.view.View
5
+ import android.view.ViewGroup
6
+ import android.widget.FrameLayout
7
+ import com.pythonnative.runtime.PNBridge
8
+ import com.pythonnative.runtime.bridge.JsonUtil
9
+ import com.pythonnative.runtime.bridge.PNLog
10
+ import com.pythonnative.runtime.bridge.arr
11
+ import com.pythonnative.runtime.bridge.num
12
+ import com.pythonnative.runtime.bridge.value
13
+ import com.pythonnative.runtime.views.PNVirtualListView
14
+ import org.json.JSONArray
15
+ import org.json.JSONObject
16
+ import kotlin.math.max
17
+
18
+ /**
19
+ * `VirtualList` element: a `RecyclerView` (via [PNVirtualListView])
20
+ * whose rows are rendered lazily by Python.
21
+ *
22
+ * Binding a row is a synchronous round trip: `on_bind_row` is sent with
23
+ * `{container, index, width, height}` (the container key is its
24
+ * identity hash) and Python answers `{"root": tag}`; the tag's view is
25
+ * detached from any previous parent and added to the row container.
26
+ * Recycling emits `on_unbind_row` with the same key.
27
+ */
28
+ class VirtualListManager : ComponentManager() {
29
+ private class Info {
30
+ var count = 0
31
+ var rowHeight = 44.0
32
+ var rowHeights: JSONArray? = null
33
+ var list: PNVirtualListView? = null
34
+ val mounted = HashMap<Int, View>()
35
+ }
36
+
37
+ override fun createView(context: Context, tag: Long, props: JSONObject): View {
38
+ val info = Info()
39
+ info.count = JsonUtil.toInt(props.value("count"))
40
+ info.rowHeight = props.num("row_height") ?: 44.0
41
+ info.rowHeights = props.arr("row_heights")
42
+ val delegate = object : PNVirtualListView.Delegate {
43
+ override fun getCount(): Int = info.count
44
+
45
+ override fun getRowHeightDp(position: Int): Float {
46
+ val heights = info.rowHeights
47
+ if (heights != null && position in 0 until heights.length()) {
48
+ return JsonUtil.toDouble(heights.opt(position), info.rowHeight).toFloat()
49
+ }
50
+ return info.rowHeight.toFloat()
51
+ }
52
+
53
+ override fun bindRow(position: Int, container: FrameLayout, widthDp: Float, heightDp: Float) {
54
+ val list = info.list ?: return
55
+ val key = System.identityHashCode(container)
56
+ val payload = JSONObject()
57
+ .put("container", key)
58
+ .put("index", position)
59
+ .put("width", widthDp.toDouble())
60
+ .put("height", heightDp.toDouble())
61
+ val reply = fireForResult(list, "on_bind_row", payload) ?: return
62
+ val rootTag = try {
63
+ JSONObject(reply).optLong("root", -1L)
64
+ } catch (e: Exception) {
65
+ PNLog.rateLimited("vlist-bind", "on_bind_row returned invalid JSON: $reply", e)
66
+ return
67
+ }
68
+ if (rootTag < 0) return
69
+ val root = PNBridge.registry.get(rootTag)?.view ?: run {
70
+ PNLog.rateLimited("vlist-root", "on_bind_row root tag $rootTag is unknown")
71
+ return
72
+ }
73
+ (root.parent as? ViewGroup)?.let { if (it !== container) it.removeView(root) }
74
+ if (root.parent !== container) {
75
+ container.addView(root, 0)
76
+ }
77
+ // The layout engine frames only the descendants of a subtree
78
+ // root, so the root itself must fill the cell.
79
+ root.layoutParams = FrameLayout.LayoutParams(
80
+ FrameLayout.LayoutParams.MATCH_PARENT,
81
+ FrameLayout.LayoutParams.MATCH_PARENT,
82
+ )
83
+ info.mounted[key] = root
84
+ }
85
+
86
+ override fun onRowPress(position: Int) {
87
+ info.list?.let { fire(it, "on_row_press", position) }
88
+ }
89
+
90
+ override fun onRowRecycled(container: FrameLayout) {
91
+ val list = info.list ?: return
92
+ val key = System.identityHashCode(container)
93
+ info.mounted.remove(key)
94
+ fire(list, "on_unbind_row", JSONObject().put("container", key))
95
+ }
96
+
97
+ override fun onScrolled(offsetDp: Float, extentDp: Float, rangeDp: Float) {
98
+ val list = info.list ?: return
99
+ if (!hasEvent(list, "on_scroll")) return
100
+ fire(
101
+ list,
102
+ "on_scroll",
103
+ mapOf(
104
+ "x" to 0.0,
105
+ "y" to offsetDp.toDouble(),
106
+ "extent" to extentDp.toDouble(),
107
+ "range" to rangeDp.toDouble(),
108
+ ),
109
+ )
110
+ }
111
+ }
112
+ val list = PNVirtualListView(context, delegate)
113
+ info.list = list
114
+ infos[list] = info
115
+ return list
116
+ }
117
+
118
+ private val infos = java.util.IdentityHashMap<View, Info>()
119
+
120
+ override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
121
+ ViewStyler.apply(view, props)
122
+ val list = view as PNVirtualListView
123
+ val info = infos[list] ?: return
124
+ var dataChanged = false
125
+ if (props.has("count")) {
126
+ info.count = JsonUtil.toInt(props.value("count"))
127
+ dataChanged = true
128
+ }
129
+ props.num("row_height")?.let {
130
+ info.rowHeight = it
131
+ dataChanged = true
132
+ }
133
+ if (props.has("row_heights")) {
134
+ info.rowHeights = props.arr("row_heights")
135
+ dataChanged = true
136
+ }
137
+ if (props.has("shows_scroll_indicator")) {
138
+ list.isVerticalScrollBarEnabled = props.value("shows_scroll_indicator") != false
139
+ }
140
+ if (dataChanged && !initial) list.notifyDataChanged()
141
+ }
142
+
143
+ override fun insertChild(parent: View, child: View, index: Int) {
144
+ // Row roots are attached through the bind protocol, not the tree ops.
145
+ }
146
+
147
+ override fun removeChild(parent: View, child: View) {
148
+ (child.parent as? ViewGroup)?.removeView(child)
149
+ }
150
+
151
+ override fun teardown(view: View) {
152
+ val info = infos.remove(view) ?: return
153
+ info.mounted.clear()
154
+ info.list = null
155
+ }
156
+
157
+ override fun measure(view: View, maxWidth: Double, maxHeight: Double): FloatArray {
158
+ // Fill the available space; collapse to 0 on an unbounded axis
159
+ // (nested inside another scroll view), like React Native.
160
+ val w = if (maxWidth.isFinite() && maxWidth < 1e6) maxWidth else 0.0
161
+ val h = if (maxHeight.isFinite() && maxHeight < 1e6) maxHeight else 0.0
162
+ return floatArrayOf(max(0.0, w).toFloat(), max(0.0, h).toFloat())
163
+ }
164
+
165
+ override fun command(view: View, name: String, args: JSONObject): Any? {
166
+ val list = view as PNVirtualListView
167
+ val animated = args.value("animated") != false
168
+ when (name) {
169
+ "scroll_to_offset" -> list.scrollToOffsetDp(JsonUtil.toDouble(args.opt("y")).toFloat(), animated)
170
+ "scroll_to_index" -> list.scrollToIndex(JsonUtil.toInt(args.opt("index")), animated)
171
+ "scroll_to_end" -> {
172
+ val count = infos[list]?.count ?: 0
173
+ list.scrollToIndex(max(0, count - 1), animated)
174
+ }
175
+ "get_scroll_offset" -> return mapOf("x" to 0.0, "y" to dp(list.computeVerticalScrollOffset()))
176
+ }
177
+ return null
178
+ }
179
+ }
@@ -0,0 +1,149 @@
1
+ package com.pythonnative.runtime.components
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import android.graphics.Bitmap
6
+ import android.view.MotionEvent
7
+ import android.view.View
8
+ import android.webkit.WebResourceError
9
+ import android.webkit.WebResourceRequest
10
+ import android.webkit.WebView
11
+ import android.webkit.WebViewClient
12
+ import com.pythonnative.runtime.bridge.JsonUtil
13
+ import com.pythonnative.runtime.bridge.str
14
+ import com.pythonnative.runtime.bridge.value
15
+ import org.json.JSONArray
16
+ import org.json.JSONObject
17
+ import java.net.URLDecoder
18
+
19
+ /**
20
+ * `WebView` element. Fires `on_navigation_state_change`, `on_load`,
21
+ * `on_error`, and `on_message` (via a `window.pythonnative.postMessage`
22
+ * shim that navigates to a `pythonnative://message/` URL). Commands:
23
+ * `load_url`, `reload`, `go_back`, `go_forward`, `stop_loading`,
24
+ * `inject_javascript`, `post_message`.
25
+ */
26
+ class WebViewManager : ComponentManager() {
27
+ private val scheme = "pythonnative://message/"
28
+
29
+ override fun createView(context: Context, tag: Long, props: JSONObject): View {
30
+ val wv = WebView(context)
31
+ wv.webViewClient = client(wv)
32
+ return wv
33
+ }
34
+
35
+ @SuppressLint("SetJavaScriptEnabled")
36
+ override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
37
+ val wv = view as WebView
38
+ val merged = propsOf(wv)
39
+ val events = merged.value("_pn_events") as? JSONArray
40
+ val needsJs = merged.value("inject_javascript") != null ||
41
+ (events != null && events.length() > 0) ||
42
+ merged.value("javascript_enabled") != false
43
+ if (needsJs) wv.settings.javaScriptEnabled = true
44
+ if (props.has("javascript_enabled")) wv.settings.javaScriptEnabled = JsonUtil.truthy(props.value("javascript_enabled"))
45
+ if (props.has("dom_storage_enabled")) wv.settings.domStorageEnabled = JsonUtil.truthy(props.value("dom_storage_enabled"))
46
+ if (props.has("user_agent")) wv.settings.userAgentString = props.str("user_agent")
47
+
48
+ val html = props.str("html")
49
+ val url = props.str("url") ?: sourceUrl(props.value("source"))
50
+ // `html` takes precedence over `url` when both are present.
51
+ if (!html.isNullOrEmpty()) {
52
+ wv.loadDataWithBaseURL(props.str("base_url"), html, "text/html", "utf-8", null)
53
+ } else if (!url.isNullOrEmpty()) {
54
+ wv.loadUrl(url)
55
+ }
56
+ if (props.has("scroll_enabled")) applyScrollEnabled(wv, props.value("scroll_enabled"))
57
+ ViewStyler.apply(wv, props)
58
+ }
59
+
60
+ private fun sourceUrl(source: Any?): String? = when (source) {
61
+ is JSONObject -> source.str("uri") ?: source.str("url")
62
+ is String -> source
63
+ else -> null
64
+ }
65
+
66
+ @SuppressLint("ClickableViewAccessibility")
67
+ private fun applyScrollEnabled(wv: WebView, enabled: Any?) {
68
+ if (enabled == false) {
69
+ wv.setOnTouchListener { _, event -> event.action == MotionEvent.ACTION_MOVE }
70
+ } else {
71
+ wv.setOnTouchListener(null)
72
+ }
73
+ }
74
+
75
+ private fun client(wv: WebView): WebViewClient = object : WebViewClient() {
76
+ override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
77
+ fire(wv, "on_navigation_state_change", url ?: "")
78
+ fire(wv, "on_load_start", url ?: "")
79
+ }
80
+
81
+ override fun onPageFinished(view: WebView, url: String?) {
82
+ fire(wv, "on_load", url ?: "")
83
+ if (hasEvent(wv, "on_message")) {
84
+ val shim = "(function(){window.pythonnative=window.pythonnative||{};" +
85
+ "window.pythonnative.postMessage=function(m){" +
86
+ "window.location.href='$scheme'+encodeURIComponent(m);};})();"
87
+ view.evaluateJavascript(shim, null)
88
+ }
89
+ propsOf(wv).str("inject_javascript")?.takeIf { it.isNotEmpty() }?.let { view.evaluateJavascript(it, null) }
90
+ }
91
+
92
+ override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceError) {
93
+ if (request.isForMainFrame) {
94
+ fire(wv, "on_error", error.description?.toString() ?: "load error")
95
+ }
96
+ }
97
+
98
+ override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
99
+ val url = request.url?.toString() ?: return false
100
+ return handleUrl(url)
101
+ }
102
+
103
+ @Deprecated("Deprecated in Java")
104
+ override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean = handleUrl(url)
105
+
106
+ private fun handleUrl(url: String): Boolean {
107
+ if (url.startsWith(scheme)) {
108
+ val message = try {
109
+ URLDecoder.decode(url.substring(scheme.length), "UTF-8")
110
+ } catch (e: Exception) {
111
+ url.substring(scheme.length)
112
+ }
113
+ fire(wv, "on_message", message)
114
+ return true
115
+ }
116
+ return false
117
+ }
118
+ }
119
+
120
+ override fun command(view: View, name: String, args: JSONObject): Any? {
121
+ val wv = view as? WebView ?: return null
122
+ when (name) {
123
+ "load_url" -> args.str("url")?.let { wv.loadUrl(it) }
124
+ "reload" -> wv.reload()
125
+ "go_back" -> if (wv.canGoBack()) wv.goBack()
126
+ "go_forward" -> if (wv.canGoForward()) wv.goForward()
127
+ "stop_loading" -> wv.stopLoading()
128
+ "inject_javascript", "evaluate_javascript" -> {
129
+ val script = args.str("script") ?: args.str("javascript") ?: return null
130
+ wv.evaluateJavascript(script, null)
131
+ }
132
+ "post_message" -> {
133
+ val message = JSONObject.quote(args.str("message") ?: "")
134
+ wv.evaluateJavascript("window.dispatchEvent(new MessageEvent('message',{data:$message}));", null)
135
+ }
136
+ "can_go_back" -> return wv.canGoBack()
137
+ "can_go_forward" -> return wv.canGoForward()
138
+ "get_url" -> return wv.url
139
+ }
140
+ return null
141
+ }
142
+
143
+ override fun teardown(view: View) {
144
+ (view as? WebView)?.let {
145
+ it.stopLoading()
146
+ it.destroy()
147
+ }
148
+ }
149
+ }