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,87 @@
|
|
|
1
|
+
package com.pythonnative.runtime.components
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.view.View
|
|
5
|
+
import android.widget.AdapterView
|
|
6
|
+
import android.widget.ArrayAdapter
|
|
7
|
+
import android.widget.Spinner
|
|
8
|
+
import com.pythonnative.runtime.bridge.JsonUtil
|
|
9
|
+
import com.pythonnative.runtime.bridge.str
|
|
10
|
+
import com.pythonnative.runtime.bridge.value
|
|
11
|
+
import org.json.JSONArray
|
|
12
|
+
import org.json.JSONObject
|
|
13
|
+
|
|
14
|
+
/** `Picker` element: a native `Spinner` dropdown over `items` of `{label, value}`. */
|
|
15
|
+
class PickerManager : ComponentManager() {
|
|
16
|
+
override fun createView(context: Context, tag: Long, props: JSONObject): View {
|
|
17
|
+
val spinner = Spinner(context)
|
|
18
|
+
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
|
19
|
+
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
|
|
20
|
+
if (stateOf(parent)["suppress"] == true) return
|
|
21
|
+
val items = propsOf(parent).value("items") as? JSONArray ?: return
|
|
22
|
+
if (position < 0 || position >= items.length()) return
|
|
23
|
+
fire(parent, "on_change", itemValue(items.opt(position)))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override fun onNothingSelected(parent: AdapterView<*>) {}
|
|
27
|
+
}
|
|
28
|
+
return spinner
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private fun itemValue(item: Any?): Any? = when (item) {
|
|
32
|
+
is JSONObject -> item.value("value")
|
|
33
|
+
JSONObject.NULL -> null
|
|
34
|
+
else -> item
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private fun itemLabel(item: Any?): String = when (item) {
|
|
38
|
+
is JSONObject -> item.str("label") ?: item.str("value") ?: ""
|
|
39
|
+
null, JSONObject.NULL -> ""
|
|
40
|
+
else -> item.toString()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
|
|
44
|
+
val spinner = view as Spinner
|
|
45
|
+
val state = stateOf(spinner)
|
|
46
|
+
val merged = propsOf(spinner)
|
|
47
|
+
val items = merged.value("items") as? JSONArray ?: JSONArray()
|
|
48
|
+
if (props.has("items") || initial) {
|
|
49
|
+
val labels = (0 until items.length()).map { itemLabel(items.opt(it)) }
|
|
50
|
+
val adapter = ArrayAdapter(spinner.context, android.R.layout.simple_spinner_item, labels)
|
|
51
|
+
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
|
52
|
+
state["suppress"] = true
|
|
53
|
+
try {
|
|
54
|
+
spinner.adapter = adapter
|
|
55
|
+
} finally {
|
|
56
|
+
state["suppress"] = false
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (props.has("value") || props.has("selected_value") || initial) {
|
|
60
|
+
val value = merged.value("value") ?: merged.value("selected_value")
|
|
61
|
+
var target = -1
|
|
62
|
+
for (i in 0 until items.length()) {
|
|
63
|
+
if (valuesEqual(itemValue(items.opt(i)), value)) {
|
|
64
|
+
target = i
|
|
65
|
+
break
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (target >= 0 && spinner.selectedItemPosition != target) {
|
|
69
|
+
state["suppress"] = true
|
|
70
|
+
try {
|
|
71
|
+
spinner.setSelection(target, false)
|
|
72
|
+
} finally {
|
|
73
|
+
state["suppress"] = false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (props.has("enabled")) spinner.isEnabled = JsonUtil.truthy(props.value("enabled"))
|
|
78
|
+
if (props.has("disabled")) spinner.isEnabled = !JsonUtil.truthy(props.value("disabled"))
|
|
79
|
+
ViewStyler.apply(spinner, props)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private fun valuesEqual(a: Any?, b: Any?): Boolean {
|
|
83
|
+
if (a == b) return true
|
|
84
|
+
if (a is Number && b is Number) return a.toDouble() == b.toDouble()
|
|
85
|
+
return a != null && b != null && a.toString() == b.toString()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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.screens.ScreenRegistry
|
|
8
|
+
import org.json.JSONObject
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `Portal` element: floats its children over the screen in a full-size,
|
|
12
|
+
* non-clickable overlay added directly to the screen's fragment
|
|
13
|
+
* container (the same parent as the screen root, so portal coordinates
|
|
14
|
+
* equal viewport coordinates). The overlay attaches lazily on the first
|
|
15
|
+
* child insert and re-homes itself if the container changed.
|
|
16
|
+
*/
|
|
17
|
+
class PortalManager : ComponentManager() {
|
|
18
|
+
override fun createView(context: Context, tag: Long, props: JSONObject): View {
|
|
19
|
+
val overlay = FrameLayout(context)
|
|
20
|
+
overlay.isClickable = false
|
|
21
|
+
return overlay
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
|
|
25
|
+
ViewStyler.apply(view, props)
|
|
26
|
+
if (!initial) ensureAttached(view)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private fun ensureAttached(overlay: View) {
|
|
30
|
+
val container = ScreenRegistry.activeContainer() ?: return
|
|
31
|
+
val parent = overlay.parent as? ViewGroup
|
|
32
|
+
if (parent === container) {
|
|
33
|
+
overlay.bringToFront()
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
parent?.removeView(overlay)
|
|
37
|
+
container.addView(
|
|
38
|
+
overlay,
|
|
39
|
+
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT),
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override fun insertChild(parent: View, child: View, index: Int) {
|
|
44
|
+
ensureAttached(parent)
|
|
45
|
+
ViewChildren.insert(parent as ViewGroup, child, index)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
override fun setFrame(view: View, x: Double, y: Double, width: Double, height: Double) {
|
|
49
|
+
// The overlay fills its container; the engine frames only the portal's children.
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override fun teardown(view: View) {
|
|
53
|
+
(view.parent as? ViewGroup)?.removeView(view)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
package com.pythonnative.runtime.components
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.res.ColorStateList
|
|
6
|
+
import android.graphics.drawable.RippleDrawable
|
|
7
|
+
import android.view.MotionEvent
|
|
8
|
+
import android.view.View
|
|
9
|
+
import android.view.ViewConfiguration
|
|
10
|
+
import com.pythonnative.runtime.bridge.JsonUtil
|
|
11
|
+
import com.pythonnative.runtime.bridge.MainThread
|
|
12
|
+
import com.pythonnative.runtime.bridge.num
|
|
13
|
+
import com.pythonnative.runtime.bridge.value
|
|
14
|
+
import com.pythonnative.runtime.gestures.GestureCoordinator
|
|
15
|
+
import com.pythonnative.runtime.views.PNFrameLayout
|
|
16
|
+
import org.json.JSONObject
|
|
17
|
+
import kotlin.math.hypot
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `Pressable` element: a container dispatching press events from one
|
|
21
|
+
* touch stream. `on_press_in` at finger-down (plus an opacity dip),
|
|
22
|
+
* `on_long_press` after the long-press timeout, `on_press` on a clean
|
|
23
|
+
* release, and `on_press_out` when the finger lifts or the touch
|
|
24
|
+
* cancels. The same stream feeds the gesture coordinator so press
|
|
25
|
+
* feedback and pan/pinch recognition coexist on one view.
|
|
26
|
+
*/
|
|
27
|
+
class PressableManager : ViewManager() {
|
|
28
|
+
private class Press(val downX: Double, val downY: Double, val seq: Int) {
|
|
29
|
+
var moved = false
|
|
30
|
+
var longFired = false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@SuppressLint("ClickableViewAccessibility")
|
|
34
|
+
override fun createView(context: Context, tag: Long, props: JSONObject): View {
|
|
35
|
+
val view = PNFrameLayout(context)
|
|
36
|
+
view.isClickable = true
|
|
37
|
+
view.isFocusable = true
|
|
38
|
+
view.setOnTouchListener { v, event -> onTouch(v, event) }
|
|
39
|
+
return view
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
|
|
43
|
+
// Press handling owns the touch listener; gesture specs are fed from inside it.
|
|
44
|
+
stateOf(view)["gestures_bound"] = true
|
|
45
|
+
super.applyProps(view, props, initial)
|
|
46
|
+
if (props.has("ripple") || props.has("android_ripple")) applyRipple(view, props)
|
|
47
|
+
if (props.has("disabled")) view.isEnabled = !JsonUtil.truthy(props.value("disabled"))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private fun applyRipple(view: View, props: JSONObject) {
|
|
51
|
+
val spec = props.value("android_ripple") ?: props.value("ripple")
|
|
52
|
+
if (spec == null || spec == false) {
|
|
53
|
+
view.foreground = null
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
val color = when (spec) {
|
|
57
|
+
is JSONObject -> PNColor.parse(spec.value("color"))
|
|
58
|
+
else -> PNColor.parse(spec)
|
|
59
|
+
} ?: 0x33000000
|
|
60
|
+
val borderless = (spec as? JSONObject)?.let { JsonUtil.truthy(it.value("borderless")) } ?: false
|
|
61
|
+
val mask = if (borderless) null else android.graphics.drawable.ColorDrawable(0xFFFFFFFF.toInt())
|
|
62
|
+
view.foreground = RippleDrawable(ColorStateList.valueOf(color), null, mask)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private fun onTouch(view: View, event: MotionEvent): Boolean {
|
|
66
|
+
if (ViewStyler.pointerEventsBlocked(view)) return false
|
|
67
|
+
val record = recordOf(view) ?: return false
|
|
68
|
+
GestureCoordinator.feed(record, event)
|
|
69
|
+
if (!view.isEnabled) return true
|
|
70
|
+
val state = record.state
|
|
71
|
+
val merged = record.props
|
|
72
|
+
val density = view.resources.displayMetrics.density
|
|
73
|
+
val x = event.x / density
|
|
74
|
+
val y = event.y / density
|
|
75
|
+
when (event.actionMasked) {
|
|
76
|
+
MotionEvent.ACTION_DOWN -> {
|
|
77
|
+
val seq = ((state["press_seq"] as? Int) ?: 0) + 1
|
|
78
|
+
val press = Press(x.toDouble(), y.toDouble(), seq)
|
|
79
|
+
state["press"] = press
|
|
80
|
+
state["press_seq"] = seq
|
|
81
|
+
fire(view, "on_press_in")
|
|
82
|
+
val opacity = merged.num("pressed_opacity") ?: merged.num("active_opacity") ?: 0.6
|
|
83
|
+
if (opacity < 1.0) view.animate().alpha(opacity.toFloat()).setDuration(50).start()
|
|
84
|
+
if (hasEvent(view, "on_long_press")) {
|
|
85
|
+
val delay = merged.num("delay_long_press")?.toLong() ?: ViewConfiguration.getLongPressTimeout().toLong().coerceAtLeast(500L)
|
|
86
|
+
MainThread.postDelayed({
|
|
87
|
+
val live = recordOf(view)?.state?.get("press") as? Press ?: return@postDelayed
|
|
88
|
+
if (live.seq != seq || live.moved || live.longFired) return@postDelayed
|
|
89
|
+
live.longFired = true
|
|
90
|
+
fire(view, "on_long_press")
|
|
91
|
+
}, delay)
|
|
92
|
+
}
|
|
93
|
+
view.isPressed = true
|
|
94
|
+
return true
|
|
95
|
+
}
|
|
96
|
+
MotionEvent.ACTION_MOVE -> {
|
|
97
|
+
val press = state["press"] as? Press ?: return true
|
|
98
|
+
val slop = merged.num("tap_slop") ?: TAP_SLOP_DP
|
|
99
|
+
if (hypot(x - press.downX, y - press.downY) > slop) press.moved = true
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
|
103
|
+
val press = state["press"] as? Press
|
|
104
|
+
state["press"] = null
|
|
105
|
+
view.isPressed = false
|
|
106
|
+
if (press == null) return true
|
|
107
|
+
fire(view, "on_press_out")
|
|
108
|
+
view.animate().alpha(1f).setDuration(100).start()
|
|
109
|
+
if (event.actionMasked == MotionEvent.ACTION_UP && !press.moved && !press.longFired) {
|
|
110
|
+
view.performClick()
|
|
111
|
+
fire(view, "on_press")
|
|
112
|
+
}
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return true
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private companion object {
|
|
120
|
+
const val TAP_SLOP_DP = 12.0
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
package com.pythonnative.runtime.components
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.view.MotionEvent
|
|
6
|
+
import android.view.View
|
|
7
|
+
import android.view.ViewGroup
|
|
8
|
+
import android.view.inputmethod.InputMethodManager
|
|
9
|
+
import android.widget.HorizontalScrollView
|
|
10
|
+
import androidx.core.widget.NestedScrollView
|
|
11
|
+
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
12
|
+
import com.pythonnative.runtime.bridge.JsonUtil
|
|
13
|
+
import com.pythonnative.runtime.bridge.MainThread
|
|
14
|
+
import com.pythonnative.runtime.bridge.obj
|
|
15
|
+
import com.pythonnative.runtime.bridge.str
|
|
16
|
+
import com.pythonnative.runtime.bridge.value
|
|
17
|
+
import org.json.JSONObject
|
|
18
|
+
import kotlin.math.max
|
|
19
|
+
import kotlin.math.roundToInt
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* `ScrollView` element: a `NestedScrollView` (vertical, always wrapped in
|
|
23
|
+
* a `SwipeRefreshLayout` that is enabled only while `refresh_control`
|
|
24
|
+
* is set) or a `HorizontalScrollView`.
|
|
25
|
+
*
|
|
26
|
+
* `on_scroll` carries `{x, y, extent, range}` in dp. Commands:
|
|
27
|
+
* `scroll_to_offset`, `scroll_to_end`, `get_scroll_offset`,
|
|
28
|
+
* `flash_scroll_indicators`.
|
|
29
|
+
*/
|
|
30
|
+
class ScrollViewManager : ComponentManager() {
|
|
31
|
+
override fun createView(context: Context, tag: Long, props: JSONObject): View {
|
|
32
|
+
val horizontal = props.str("scroll_axis") == "horizontal" || JsonUtil.truthy(props.value("horizontal"))
|
|
33
|
+
return if (horizontal) {
|
|
34
|
+
val sv = HorizontalScrollView(context)
|
|
35
|
+
bindScrollListener(sv, sv, true)
|
|
36
|
+
bindTouch(sv, sv, true)
|
|
37
|
+
sv
|
|
38
|
+
} else {
|
|
39
|
+
val sv = NestedScrollView(context)
|
|
40
|
+
val wrapper = SwipeRefreshLayout(context)
|
|
41
|
+
wrapper.addView(sv, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
|
|
42
|
+
wrapper.isEnabled = false
|
|
43
|
+
wrapper.setOnRefreshListener { fire(wrapper, "on_refresh") }
|
|
44
|
+
bindScrollListener(wrapper, sv, false)
|
|
45
|
+
bindTouch(wrapper, sv, false)
|
|
46
|
+
wrapper
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private fun inner(outer: View): ViewGroup = when (outer) {
|
|
51
|
+
is SwipeRefreshLayout -> outer.getChildAt(0) as ViewGroup
|
|
52
|
+
else -> outer as ViewGroup
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private fun isHorizontal(outer: View): Boolean = outer is HorizontalScrollView
|
|
56
|
+
|
|
57
|
+
override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
|
|
58
|
+
val sv = inner(view)
|
|
59
|
+
ViewStyler.apply(sv, props)
|
|
60
|
+
if (props.has("shows_scroll_indicator")) {
|
|
61
|
+
val show = props.value("shows_scroll_indicator") != false
|
|
62
|
+
sv.isVerticalScrollBarEnabled = show
|
|
63
|
+
sv.isHorizontalScrollBarEnabled = show
|
|
64
|
+
}
|
|
65
|
+
if (props.has("bounces")) {
|
|
66
|
+
sv.overScrollMode = if (props.value("bounces") == false) View.OVER_SCROLL_NEVER else View.OVER_SCROLL_IF_CONTENT_SCROLLS
|
|
67
|
+
}
|
|
68
|
+
if (props.has("scroll_enabled")) stateOf(view)["scroll_enabled"] = props.value("scroll_enabled") != false
|
|
69
|
+
if (props.has("refresh_control") && view is SwipeRefreshLayout) applyRefresh(view, props.obj("refresh_control"))
|
|
70
|
+
// paging_enabled and keyboard_dismiss_mode are read from merged props at touch time.
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private fun applyRefresh(srl: SwipeRefreshLayout, spec: JSONObject?) {
|
|
74
|
+
if (spec == null) {
|
|
75
|
+
srl.isEnabled = false
|
|
76
|
+
srl.isRefreshing = false
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
srl.isEnabled = true
|
|
80
|
+
PNColor.parse(spec.value("tint_color"))?.let { srl.setColorSchemeColors(it) }
|
|
81
|
+
PNColor.parse(spec.value("background_color"))?.let { srl.setProgressBackgroundColorSchemeColor(it) }
|
|
82
|
+
srl.isRefreshing = JsonUtil.truthy(spec.value("refreshing"))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
override fun insertChild(parent: View, child: View, index: Int) = ViewChildren.insert(inner(parent), child, index)
|
|
86
|
+
|
|
87
|
+
override fun removeChild(parent: View, child: View) {
|
|
88
|
+
inner(parent).removeView(child)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private fun bindScrollListener(outer: View, sv: View, horizontal: Boolean) {
|
|
92
|
+
sv.setOnScrollChangeListener { v, scrollX, scrollY, _, _ ->
|
|
93
|
+
if (!hasEvent(outer, "on_scroll")) return@setOnScrollChangeListener
|
|
94
|
+
fire(outer, "on_scroll", scrollPayload(v as ViewGroup, horizontal, scrollX, scrollY))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private fun scrollPayload(sv: ViewGroup, horizontal: Boolean, scrollX: Int, scrollY: Int): Map<String, Any?> {
|
|
99
|
+
val content = if (sv.childCount > 0) sv.getChildAt(0) else null
|
|
100
|
+
val extent = if (horizontal) sv.width else sv.height
|
|
101
|
+
val range = if (content == null) extent else if (horizontal) content.width else content.height
|
|
102
|
+
return mapOf(
|
|
103
|
+
"x" to dp(scrollX),
|
|
104
|
+
"y" to dp(scrollY),
|
|
105
|
+
"extent" to dp(extent),
|
|
106
|
+
"range" to dp(max(range, extent)),
|
|
107
|
+
"content_width" to dp(content?.width ?: sv.width),
|
|
108
|
+
"content_height" to dp(content?.height ?: sv.height),
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@SuppressLint("ClickableViewAccessibility")
|
|
113
|
+
private fun bindTouch(outer: View, sv: ViewGroup, horizontal: Boolean) {
|
|
114
|
+
sv.setOnTouchListener { v, event ->
|
|
115
|
+
val merged = propsOf(outer)
|
|
116
|
+
if (stateOf(outer)["scroll_enabled"] == false) return@setOnTouchListener true
|
|
117
|
+
when (event.actionMasked) {
|
|
118
|
+
MotionEvent.ACTION_MOVE -> {
|
|
119
|
+
val mode = merged.str("keyboard_dismiss_mode")
|
|
120
|
+
if (mode == "on_drag" || mode == "interactive") dismissKeyboard(v)
|
|
121
|
+
}
|
|
122
|
+
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
|
123
|
+
if (JsonUtil.truthy(merged.value("paging_enabled"))) scheduleSnap(sv, horizontal)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
false
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private fun dismissKeyboard(view: View) {
|
|
131
|
+
val focused = view.rootView.findFocus() ?: return
|
|
132
|
+
focused.clearFocus()
|
|
133
|
+
(view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager)
|
|
134
|
+
?.hideSoftInputFromWindow(view.windowToken, 0)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Wait for the fling to settle (two identical samples), then snap to the nearest page. */
|
|
138
|
+
private fun scheduleSnap(sv: ViewGroup, horizontal: Boolean) {
|
|
139
|
+
val state = stateOf(sv.parent as? View ?: sv)
|
|
140
|
+
val token = ((state["snap_token"] as? Int) ?: 0) + 1
|
|
141
|
+
state["snap_token"] = token
|
|
142
|
+
var last = -1
|
|
143
|
+
val poll = object : Runnable {
|
|
144
|
+
override fun run() {
|
|
145
|
+
if (state["snap_token"] != token) return
|
|
146
|
+
val current = if (horizontal) sv.scrollX else sv.scrollY
|
|
147
|
+
if (current == last) {
|
|
148
|
+
val page = if (horizontal) sv.width else sv.height
|
|
149
|
+
if (page > 0) {
|
|
150
|
+
val target = (current.toDouble() / page).roundToInt() * page
|
|
151
|
+
if (target != current) {
|
|
152
|
+
when (sv) {
|
|
153
|
+
is NestedScrollView -> sv.smoothScrollTo(0, target)
|
|
154
|
+
is HorizontalScrollView -> sv.smoothScrollTo(target, 0)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
last = current
|
|
161
|
+
MainThread.postDelayed(this, 50)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
MainThread.postDelayed(poll, 50)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
override fun command(view: View, name: String, args: JSONObject): Any? {
|
|
168
|
+
val sv = inner(view)
|
|
169
|
+
val horizontal = isHorizontal(view)
|
|
170
|
+
when (name) {
|
|
171
|
+
"scroll_to_offset" -> {
|
|
172
|
+
val x = px(JsonUtil.toDouble(args.opt("x")))
|
|
173
|
+
val y = px(JsonUtil.toDouble(args.opt("y")))
|
|
174
|
+
val animated = args.value("animated") != false
|
|
175
|
+
when (sv) {
|
|
176
|
+
is NestedScrollView -> if (animated) sv.smoothScrollTo(x, y) else sv.scrollTo(x, y)
|
|
177
|
+
is HorizontalScrollView -> if (animated) sv.smoothScrollTo(x, y) else sv.scrollTo(x, y)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
"scroll_to_end" -> {
|
|
181
|
+
val child = if (sv.childCount > 0) sv.getChildAt(0) else return null
|
|
182
|
+
val animated = args.value("animated") != false
|
|
183
|
+
if (horizontal) {
|
|
184
|
+
val target = max(0, child.width - sv.width)
|
|
185
|
+
if (animated) (sv as HorizontalScrollView).smoothScrollTo(target, 0) else sv.scrollTo(target, 0)
|
|
186
|
+
} else {
|
|
187
|
+
val target = max(0, child.height - sv.height)
|
|
188
|
+
if (animated) (sv as NestedScrollView).smoothScrollTo(0, target) else sv.scrollTo(0, target)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
"get_scroll_offset" -> return mapOf("x" to dp(sv.scrollX), "y" to dp(sv.scrollY))
|
|
192
|
+
"flash_scroll_indicators" -> sv.awakenScrollBarsCompat()
|
|
193
|
+
}
|
|
194
|
+
return null
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private fun View.awakenScrollBarsCompat() {
|
|
198
|
+
// awakenScrollBars is protected; toggling the fade re-shows the bars briefly.
|
|
199
|
+
isScrollbarFadingEnabled = false
|
|
200
|
+
MainThread.postDelayed({ isScrollbarFadingEnabled = true }, 600)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
override fun setFrame(view: View, x: Double, y: Double, width: Double, height: Double) {
|
|
204
|
+
super.setFrame(view, x, y, width, height)
|
|
205
|
+
// The inner scroll view fills its refresh wrapper.
|
|
206
|
+
if (view is SwipeRefreshLayout) {
|
|
207
|
+
val sv = view.getChildAt(0)
|
|
208
|
+
val lp = sv.layoutParams
|
|
209
|
+
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
|
|
210
|
+
lp.height = ViewGroup.LayoutParams.MATCH_PARENT
|
|
211
|
+
sv.layoutParams = lp
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
package com.pythonnative.runtime.components
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.drawable.GradientDrawable
|
|
5
|
+
import android.view.View
|
|
6
|
+
import android.widget.Button
|
|
7
|
+
import android.widget.LinearLayout
|
|
8
|
+
import com.pythonnative.runtime.bridge.JsonUtil
|
|
9
|
+
import com.pythonnative.runtime.bridge.arr
|
|
10
|
+
import com.pythonnative.runtime.bridge.value
|
|
11
|
+
import org.json.JSONObject
|
|
12
|
+
import kotlin.math.max
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* `SegmentedControl` element: a horizontal `LinearLayout` of equal-width
|
|
16
|
+
* toggle `Button`s. The selected segment is filled with `tint_color`;
|
|
17
|
+
* the rest are outlined. The control owns its subviews, so child
|
|
18
|
+
* inserts are ignored.
|
|
19
|
+
*/
|
|
20
|
+
class SegmentedControlManager : ComponentManager() {
|
|
21
|
+
override fun createView(context: Context, tag: Long, props: JSONObject): View {
|
|
22
|
+
val ll = LinearLayout(context)
|
|
23
|
+
ll.orientation = LinearLayout.HORIZONTAL
|
|
24
|
+
return ll
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
override fun insertChild(parent: View, child: View, index: Int) {}
|
|
28
|
+
|
|
29
|
+
override fun removeChild(parent: View, child: View) {}
|
|
30
|
+
|
|
31
|
+
override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
|
|
32
|
+
val ll = view as LinearLayout
|
|
33
|
+
val state = stateOf(ll)
|
|
34
|
+
val merged = propsOf(ll)
|
|
35
|
+
var segmentsChanged = false
|
|
36
|
+
if (props.has("segments") || initial) {
|
|
37
|
+
val segments = JsonUtil.toList(merged.arr("segments")).map { it?.toString() ?: "" }
|
|
38
|
+
if (initial || segments != state["segments"]) {
|
|
39
|
+
state["segments"] = segments
|
|
40
|
+
segmentsChanged = true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
props.value("selected_index")?.let { state["selected_index"] = JsonUtil.toInt(it) }
|
|
44
|
+
if (segmentsChanged) rebuild(ll) else restyle(ll)
|
|
45
|
+
ViewStyler.applyAccessibility(ll, props)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private fun rebuild(ll: LinearLayout) {
|
|
49
|
+
val state = stateOf(ll)
|
|
50
|
+
ll.removeAllViews()
|
|
51
|
+
@Suppress("UNCHECKED_CAST")
|
|
52
|
+
val segments = state["segments"] as? List<String> ?: emptyList()
|
|
53
|
+
val enabled = propsOf(ll).value("enabled") != false
|
|
54
|
+
segments.forEachIndexed { index, label ->
|
|
55
|
+
val btn = Button(ll.context)
|
|
56
|
+
btn.text = label
|
|
57
|
+
btn.isAllCaps = false
|
|
58
|
+
btn.layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)
|
|
59
|
+
btn.isEnabled = enabled
|
|
60
|
+
btn.setOnClickListener {
|
|
61
|
+
if (propsOf(ll).value("enabled") == false) return@setOnClickListener
|
|
62
|
+
stateOf(ll)["selected_index"] = index
|
|
63
|
+
restyle(ll)
|
|
64
|
+
fire(ll, "on_change", index)
|
|
65
|
+
}
|
|
66
|
+
ll.addView(btn)
|
|
67
|
+
}
|
|
68
|
+
restyle(ll)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private fun restyle(ll: LinearLayout) {
|
|
72
|
+
val merged = propsOf(ll)
|
|
73
|
+
val accent = PNColor.parseOr(merged.value("tint_color"), DEFAULT_ACCENT)
|
|
74
|
+
val selected = (stateOf(ll)["selected_index"] as? Int) ?: JsonUtil.toInt(merged.value("selected_index"))
|
|
75
|
+
val enabled = merged.value("enabled") != false
|
|
76
|
+
for (i in 0 until ll.childCount) {
|
|
77
|
+
val btn = ll.getChildAt(i) as? Button ?: continue
|
|
78
|
+
val drawable = GradientDrawable()
|
|
79
|
+
drawable.cornerRadius = pxF(6.0)
|
|
80
|
+
drawable.setStroke(px(1.0), accent)
|
|
81
|
+
if (i == selected) {
|
|
82
|
+
drawable.setColor(accent)
|
|
83
|
+
btn.setTextColor(0xFFFFFFFF.toInt())
|
|
84
|
+
} else {
|
|
85
|
+
drawable.setColor(0x00FFFFFF)
|
|
86
|
+
btn.setTextColor(accent)
|
|
87
|
+
}
|
|
88
|
+
btn.background = drawable
|
|
89
|
+
btn.isEnabled = enabled
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
override fun measure(view: View, maxWidth: Double, maxHeight: Double): FloatArray {
|
|
94
|
+
// Weighted children measure to ~0 under an unspecified spec, so
|
|
95
|
+
// size to the sum of the segments' natural widths instead.
|
|
96
|
+
val ll = view as LinearLayout
|
|
97
|
+
if (ll.childCount == 0) return floatArrayOf(0f, 0f)
|
|
98
|
+
val spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
|
99
|
+
var totalW = 0
|
|
100
|
+
var maxH = 0
|
|
101
|
+
for (i in 0 until ll.childCount) {
|
|
102
|
+
val child = ll.getChildAt(i)
|
|
103
|
+
child.measure(spec, spec)
|
|
104
|
+
totalW += child.measuredWidth
|
|
105
|
+
maxH = max(maxH, child.measuredHeight)
|
|
106
|
+
}
|
|
107
|
+
return floatArrayOf(dp(totalW).toFloat(), dp(maxH).toFloat())
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private companion object {
|
|
111
|
+
val DEFAULT_ACCENT = 0xFF007AFF.toInt()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
package com.pythonnative.runtime.components
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.view.View
|
|
6
|
+
import androidx.core.view.WindowCompat
|
|
7
|
+
import androidx.core.view.WindowInsetsCompat
|
|
8
|
+
import androidx.core.view.WindowInsetsControllerCompat
|
|
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.str
|
|
13
|
+
import com.pythonnative.runtime.bridge.value
|
|
14
|
+
import org.json.JSONObject
|
|
15
|
+
|
|
16
|
+
/** `StatusBar` element: applies bar color, style, and visibility to the host window. */
|
|
17
|
+
class StatusBarManager : ComponentManager() {
|
|
18
|
+
override fun createView(context: Context, tag: Long, props: JSONObject): View {
|
|
19
|
+
val v = View(context)
|
|
20
|
+
v.visibility = View.GONE
|
|
21
|
+
return v
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override fun setFrame(view: View, x: Double, y: Double, width: Double, height: Double) {}
|
|
25
|
+
|
|
26
|
+
override fun measure(view: View, maxWidth: Double, maxHeight: Double): FloatArray = floatArrayOf(0f, 0f)
|
|
27
|
+
|
|
28
|
+
override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
|
|
29
|
+
val activity = PNBridge.activity() ?: (view.context as? Activity) ?: return
|
|
30
|
+
val window = activity.window ?: return
|
|
31
|
+
try {
|
|
32
|
+
PNColor.parse(props.value("background_color"))?.let { window.statusBarColor = it }
|
|
33
|
+
val controller: WindowInsetsControllerCompat = WindowCompat.getInsetsController(window, window.decorView)
|
|
34
|
+
props.str("bar_style")?.let { style ->
|
|
35
|
+
// "dark" / "default" mean dark icons (light backgrounds); "light" means light icons.
|
|
36
|
+
controller.isAppearanceLightStatusBars = style == "dark" || style == "default" || style == "dark_content"
|
|
37
|
+
}
|
|
38
|
+
if (props.has("hidden")) {
|
|
39
|
+
if (JsonUtil.truthy(props.value("hidden"))) {
|
|
40
|
+
controller.hide(WindowInsetsCompat.Type.statusBars())
|
|
41
|
+
} else {
|
|
42
|
+
controller.show(WindowInsetsCompat.Type.statusBars())
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (props.has("translucent")) {
|
|
46
|
+
WindowCompat.setDecorFitsSystemWindows(window, !JsonUtil.truthy(props.value("translucent")))
|
|
47
|
+
}
|
|
48
|
+
} catch (e: Exception) {
|
|
49
|
+
PNLog.once("statusbar", "StatusBar: could not apply props on Android: $e")
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|