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,116 @@
|
|
|
1
|
+
package com.pythonnative.runtime.screens
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle
|
|
4
|
+
import androidx.fragment.app.Fragment
|
|
5
|
+
import androidx.fragment.app.FragmentActivity
|
|
6
|
+
import androidx.fragment.app.FragmentManager
|
|
7
|
+
import androidx.navigation.NavController
|
|
8
|
+
import androidx.navigation.NavOptions
|
|
9
|
+
import androidx.navigation.fragment.NavHostFragment
|
|
10
|
+
import com.pythonnative.runtime.bridge.PNLog
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Stack navigation over the app's `NavHostFragment`. The nav graph is
|
|
14
|
+
* expected to contain a single fragment destination (the app's
|
|
15
|
+
* `PNScreenFragment` subclass) taking `screen_path` / `args_json` /
|
|
16
|
+
* `title` arguments; every push navigates to that destination.
|
|
17
|
+
*/
|
|
18
|
+
object Navigator {
|
|
19
|
+
/** Locate the `NavHostFragment` anywhere in the activity's fragment tree. */
|
|
20
|
+
fun navController(activity: FragmentActivity): NavController? {
|
|
21
|
+
return findNavHost(activity.supportFragmentManager)?.navController
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private fun findNavHost(fm: FragmentManager): NavHostFragment? {
|
|
25
|
+
for (fragment in fm.fragments) {
|
|
26
|
+
if (fragment is NavHostFragment) return fragment
|
|
27
|
+
val nested = findNavHost(fragment.childFragmentManager)
|
|
28
|
+
if (nested != null) return nested
|
|
29
|
+
}
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private fun args(screenPath: String, argsJson: String?, title: String?): Bundle {
|
|
34
|
+
val bundle = Bundle()
|
|
35
|
+
bundle.putString(PNScreenFragment.ARG_PATH, screenPath)
|
|
36
|
+
if (argsJson != null) bundle.putString(PNScreenFragment.ARG_ARGS, argsJson)
|
|
37
|
+
if (title != null) bundle.putString(PNScreenFragment.ARG_TITLE, title)
|
|
38
|
+
return bundle
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@JvmStatic
|
|
42
|
+
fun push(activity: FragmentActivity, screenPath: String, argsJson: String?, title: String? = null): Boolean {
|
|
43
|
+
val nav = navController(activity) ?: return false
|
|
44
|
+
return try {
|
|
45
|
+
nav.navigate(nav.graph.startDestinationId, args(screenPath, argsJson, title))
|
|
46
|
+
true
|
|
47
|
+
} catch (e: Exception) {
|
|
48
|
+
PNLog.rateLimited("nav-push", "push failed", e)
|
|
49
|
+
false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@JvmStatic
|
|
54
|
+
fun pop(activity: FragmentActivity, count: Int = 1): Boolean {
|
|
55
|
+
val nav = navController(activity) ?: run {
|
|
56
|
+
activity.finish()
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
var popped = false
|
|
60
|
+
for (i in 0 until count) {
|
|
61
|
+
if (!nav.popBackStack()) {
|
|
62
|
+
if (!popped) activity.finish()
|
|
63
|
+
break
|
|
64
|
+
}
|
|
65
|
+
popped = true
|
|
66
|
+
}
|
|
67
|
+
return popped
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Pop everything above the start destination. */
|
|
71
|
+
@JvmStatic
|
|
72
|
+
fun popToRoot(activity: FragmentActivity): Boolean {
|
|
73
|
+
val nav = navController(activity) ?: return false
|
|
74
|
+
return nav.popBackStack(nav.graph.startDestinationId, false)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Replace the current screen with a new one (pop current, push new). */
|
|
78
|
+
@JvmStatic
|
|
79
|
+
fun replace(activity: FragmentActivity, screenPath: String, argsJson: String?, title: String? = null): Boolean {
|
|
80
|
+
val nav = navController(activity) ?: return false
|
|
81
|
+
val current = nav.currentBackStackEntry?.destination?.id ?: nav.graph.startDestinationId
|
|
82
|
+
return try {
|
|
83
|
+
val options = NavOptions.Builder().setPopUpTo(current, true).build()
|
|
84
|
+
nav.navigate(nav.graph.startDestinationId, args(screenPath, argsJson, title), options)
|
|
85
|
+
true
|
|
86
|
+
} catch (e: Exception) {
|
|
87
|
+
PNLog.rateLimited("nav-replace", "replace failed", e)
|
|
88
|
+
false
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Rebuild the whole stack from `screens` (`Triple(path, argsJson, title)`). */
|
|
93
|
+
@JvmStatic
|
|
94
|
+
fun reset(activity: FragmentActivity, screens: List<Triple<String, String?, String?>>): Boolean {
|
|
95
|
+
val nav = navController(activity) ?: return false
|
|
96
|
+
if (screens.isEmpty()) return popToRoot(activity)
|
|
97
|
+
return try {
|
|
98
|
+
val root = nav.graph.startDestinationId
|
|
99
|
+
val (firstPath, firstArgs, firstTitle) = screens[0]
|
|
100
|
+
val options = NavOptions.Builder().setPopUpTo(root, true).build()
|
|
101
|
+
nav.navigate(root, args(firstPath, firstArgs, firstTitle), options)
|
|
102
|
+
for (i in 1 until screens.size) {
|
|
103
|
+
val (path, argsJson, title) = screens[i]
|
|
104
|
+
nav.navigate(root, args(path, argsJson, title))
|
|
105
|
+
}
|
|
106
|
+
true
|
|
107
|
+
} catch (e: Exception) {
|
|
108
|
+
PNLog.rateLimited("nav-reset", "reset failed", e)
|
|
109
|
+
false
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The fragment currently shown by the nav host, if any. */
|
|
114
|
+
fun currentFragment(activity: FragmentActivity): Fragment? =
|
|
115
|
+
findNavHost(activity.supportFragmentManager)?.childFragmentManager?.primaryNavigationFragment
|
|
116
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
package com.pythonnative.runtime.screens
|
|
2
|
+
|
|
3
|
+
import android.content.pm.ApplicationInfo
|
|
4
|
+
import android.os.Bundle
|
|
5
|
+
import android.view.LayoutInflater
|
|
6
|
+
import android.view.View
|
|
7
|
+
import android.view.ViewGroup
|
|
8
|
+
import android.widget.FrameLayout
|
|
9
|
+
import androidx.activity.OnBackPressedCallback
|
|
10
|
+
import androidx.core.os.bundleOf
|
|
11
|
+
import androidx.core.view.ViewCompat
|
|
12
|
+
import androidx.fragment.app.Fragment
|
|
13
|
+
import com.pythonnative.runtime.PNBridge
|
|
14
|
+
import com.pythonnative.runtime.bridge.MainThread
|
|
15
|
+
import com.pythonnative.runtime.bridge.PNLog
|
|
16
|
+
import org.json.JSONObject
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The Fragment hosting one PythonNative screen.
|
|
20
|
+
*
|
|
21
|
+
* On view creation it asks Python to create the screen
|
|
22
|
+
* (`callback("host", screenId, "create", {...})`) and afterwards
|
|
23
|
+
* forwards lifecycle and layout: `start`, `layout`, `resume`, `pause`,
|
|
24
|
+
* `stop`, `destroy`, `back_pressed`, `save_state`, and
|
|
25
|
+
* `restore_state`. Python attaches the screen's root view through the
|
|
26
|
+
* `Host.attach_root` module call, which lands in [attachRoot].
|
|
27
|
+
*
|
|
28
|
+
* Apps subclass this (the nav graph names the subclass) and override
|
|
29
|
+
* [defaultPath] / [defaultDevRoot] to supply the entry module and the
|
|
30
|
+
* hot-reload root when the fragment arguments don't carry them.
|
|
31
|
+
*/
|
|
32
|
+
open class PNScreenFragment : Fragment() {
|
|
33
|
+
/** The screen id assigned by [ScreenRegistry]. */
|
|
34
|
+
var screenId: Int = 0
|
|
35
|
+
private set
|
|
36
|
+
|
|
37
|
+
/** The container the root view is attached into (valid between `onCreateView` and `onDestroyView`). */
|
|
38
|
+
var container: FrameLayout? = null
|
|
39
|
+
private set
|
|
40
|
+
|
|
41
|
+
private var created = false
|
|
42
|
+
private var pendingRestore: String? = null
|
|
43
|
+
private var lastLayout: String? = null
|
|
44
|
+
private var hotReloadRunning = false
|
|
45
|
+
|
|
46
|
+
private val hotReloadRunnable = object : Runnable {
|
|
47
|
+
override fun run() {
|
|
48
|
+
if (!hotReloadRunning) return
|
|
49
|
+
host("hot_reload_tick", "{}")
|
|
50
|
+
MainThread.postDelayed(this, 500)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Screen path when the arguments carry none (apps return the entry module). */
|
|
55
|
+
protected open fun defaultPath(): String? = null
|
|
56
|
+
|
|
57
|
+
/** Hot-reload source root when the arguments carry none (debug builds only). */
|
|
58
|
+
protected open fun defaultDevRoot(): String? = null
|
|
59
|
+
|
|
60
|
+
/** The path this fragment shows. */
|
|
61
|
+
fun screenPath(): String? = arguments?.getString(ARG_PATH) ?: defaultPath()
|
|
62
|
+
|
|
63
|
+
private fun argsJson(): String? = arguments?.getString(ARG_ARGS)
|
|
64
|
+
|
|
65
|
+
private fun devRoot(): String? {
|
|
66
|
+
val explicit = arguments?.getString(ARG_DEV_ROOT)
|
|
67
|
+
if (explicit != null) return explicit
|
|
68
|
+
return if (isDebuggable()) defaultDevRoot() else null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private fun isDebuggable(): Boolean {
|
|
72
|
+
val ctx = context ?: return false
|
|
73
|
+
return (ctx.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
77
|
+
super.onCreate(savedInstanceState)
|
|
78
|
+
screenId = ScreenRegistry.register(this)
|
|
79
|
+
pendingRestore = savedInstanceState?.getString(STATE_KEY)
|
|
80
|
+
requireActivity().onBackPressedDispatcher.addCallback(
|
|
81
|
+
this,
|
|
82
|
+
object : OnBackPressedCallback(true) {
|
|
83
|
+
override fun handleOnBackPressed() {
|
|
84
|
+
val consumed = created && host("back_pressed", "{}") == "true"
|
|
85
|
+
if (!consumed) {
|
|
86
|
+
isEnabled = false
|
|
87
|
+
requireActivity().onBackPressedDispatcher.onBackPressed()
|
|
88
|
+
isEnabled = true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
|
96
|
+
val frame = FrameLayout(requireContext())
|
|
97
|
+
frame.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
|
98
|
+
this.container = frame
|
|
99
|
+
return frame
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
103
|
+
super.onViewCreated(view, savedInstanceState)
|
|
104
|
+
arguments?.getString(ARG_TITLE)?.let { activity?.title = it }
|
|
105
|
+
val payload = JSONObject()
|
|
106
|
+
.put("path", screenPath() ?: JSONObject.NULL)
|
|
107
|
+
.put("args", argsJson() ?: JSONObject.NULL)
|
|
108
|
+
.put("dev_root", devRoot() ?: JSONObject.NULL)
|
|
109
|
+
host("create", payload.toString())
|
|
110
|
+
created = true
|
|
111
|
+
pendingRestore?.let {
|
|
112
|
+
host("restore_state", JSONObject().put("state", it).toString())
|
|
113
|
+
pendingRestore = null
|
|
114
|
+
}
|
|
115
|
+
view.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> publishLayout(v) }
|
|
116
|
+
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
|
|
117
|
+
publishLayout(v, force = true)
|
|
118
|
+
insets
|
|
119
|
+
}
|
|
120
|
+
publishLayout(view, force = true)
|
|
121
|
+
if (devRoot() != null) {
|
|
122
|
+
hotReloadRunning = true
|
|
123
|
+
MainThread.remove(hotReloadRunnable)
|
|
124
|
+
MainThread.postDelayed(hotReloadRunnable, 500)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private fun publishLayout(view: View, force: Boolean = false) {
|
|
129
|
+
if (!created) return
|
|
130
|
+
val payload = Viewport.describe(view).toString()
|
|
131
|
+
if (!force && payload == lastLayout) return
|
|
132
|
+
lastLayout = payload
|
|
133
|
+
host("layout", payload)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Current viewport payload for this screen. */
|
|
137
|
+
fun viewport(): JSONObject {
|
|
138
|
+
val v = container ?: view
|
|
139
|
+
return if (v != null) Viewport.describe(v) else JSONObject()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Attach `root` (MATCH_PARENT) into the fragment container, replacing any previous root. */
|
|
143
|
+
fun attachRoot(root: View) {
|
|
144
|
+
val target = container ?: return
|
|
145
|
+
target.removeAllViews()
|
|
146
|
+
(root.parent as? ViewGroup)?.removeView(root)
|
|
147
|
+
target.addView(root, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
|
|
148
|
+
publishLayout(target, force = true)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Detach `root` from wherever it is parented (never clears the shared container). */
|
|
152
|
+
fun detachRoot(root: View) {
|
|
153
|
+
(root.parent as? ViewGroup)?.removeView(root)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
override fun onStart() {
|
|
157
|
+
super.onStart()
|
|
158
|
+
host("start", "{}")
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
override fun onResume() {
|
|
162
|
+
super.onResume()
|
|
163
|
+
ScreenRegistry.setActive(screenId)
|
|
164
|
+
host("resume", viewport().toString())
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
override fun onPause() {
|
|
168
|
+
super.onPause()
|
|
169
|
+
host("pause", "{}")
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
override fun onStop() {
|
|
173
|
+
super.onStop()
|
|
174
|
+
host("stop", "{}")
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
override fun onSaveInstanceState(outState: Bundle) {
|
|
178
|
+
super.onSaveInstanceState(outState)
|
|
179
|
+
if (!created) return
|
|
180
|
+
host("save_state", "{}")?.let { outState.putString(STATE_KEY, it) }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
override fun onDestroyView() {
|
|
184
|
+
super.onDestroyView()
|
|
185
|
+
hotReloadRunning = false
|
|
186
|
+
MainThread.remove(hotReloadRunnable)
|
|
187
|
+
container = null
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
override fun onDestroy() {
|
|
191
|
+
super.onDestroy()
|
|
192
|
+
hotReloadRunning = false
|
|
193
|
+
MainThread.remove(hotReloadRunnable)
|
|
194
|
+
if (created) host("destroy", "{}")
|
|
195
|
+
created = false
|
|
196
|
+
ScreenRegistry.unregister(screenId)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private fun host(name: String, payloadJson: String): String? {
|
|
200
|
+
return try {
|
|
201
|
+
PNBridge.callPython("host", screenId.toLong(), name, payloadJson)
|
|
202
|
+
} catch (e: Exception) {
|
|
203
|
+
PNLog.rateLimited("host:$name", "host callback '$name' failed", e)
|
|
204
|
+
null
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
companion object {
|
|
209
|
+
const val ARG_PATH = "screen_path"
|
|
210
|
+
const val ARG_ARGS = "args_json"
|
|
211
|
+
const val ARG_TITLE = "title"
|
|
212
|
+
const val ARG_DEV_ROOT = "dev_root"
|
|
213
|
+
private const val STATE_KEY = "pn_screen_state"
|
|
214
|
+
|
|
215
|
+
/** Build a fragment for `path` with optional JSON `args` and hot-reload `devRoot`. */
|
|
216
|
+
@JvmStatic
|
|
217
|
+
fun newInstance(path: String, argsJson: String?, devRoot: String? = null): PNScreenFragment {
|
|
218
|
+
val f = PNScreenFragment()
|
|
219
|
+
f.arguments = bundleOf(ARG_PATH to path, ARG_ARGS to argsJson, ARG_DEV_ROOT to devRoot)
|
|
220
|
+
return f
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
package com.pythonnative.runtime.screens
|
|
2
|
+
|
|
3
|
+
import android.content.res.Configuration
|
|
4
|
+
import android.view.View
|
|
5
|
+
import android.view.ViewGroup
|
|
6
|
+
import androidx.core.view.ViewCompat
|
|
7
|
+
import androidx.core.view.WindowInsetsCompat
|
|
8
|
+
import com.pythonnative.runtime.PNBridge
|
|
9
|
+
import org.json.JSONObject
|
|
10
|
+
import kotlin.math.max
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Integer ids for live [PNScreenFragment]s. Python addresses screens
|
|
14
|
+
* by these ids in `Host` module calls and receives them as the `tag`
|
|
15
|
+
* of `host` callbacks.
|
|
16
|
+
*/
|
|
17
|
+
object ScreenRegistry {
|
|
18
|
+
private val screens = LinkedHashMap<Int, PNScreenFragment>()
|
|
19
|
+
private var nextId = 1
|
|
20
|
+
private var activeId = 0
|
|
21
|
+
|
|
22
|
+
/** Assign an id to `fragment`. */
|
|
23
|
+
fun register(fragment: PNScreenFragment): Int {
|
|
24
|
+
val id = nextId++
|
|
25
|
+
screens[id] = fragment
|
|
26
|
+
return id
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fun unregister(id: Int) {
|
|
30
|
+
screens.remove(id)
|
|
31
|
+
if (activeId == id) activeId = screens.keys.lastOrNull() ?: 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fun get(id: Int): PNScreenFragment? = screens[id]
|
|
35
|
+
|
|
36
|
+
/** Mark `id` as the screen the user currently sees (called on resume). */
|
|
37
|
+
fun setActive(id: Int) {
|
|
38
|
+
activeId = id
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The visible screen's id, or `0`. */
|
|
42
|
+
fun activeId(): Int = activeId
|
|
43
|
+
|
|
44
|
+
/** The fragment container of the visible screen (overlay host for `Portal`). */
|
|
45
|
+
fun activeContainer(): ViewGroup? = screens[activeId]?.container ?: screens.values.lastOrNull()?.container
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Builds the viewport payload shared by `layout`, `resume`, and `Host.viewport`. */
|
|
49
|
+
object Viewport {
|
|
50
|
+
/**
|
|
51
|
+
* `{width, height, insets{top,left,bottom,right}, color_scheme, keyboard_height}`
|
|
52
|
+
* in dp, measured from `view` (falling back to display metrics when
|
|
53
|
+
* the view has not been laid out yet).
|
|
54
|
+
*/
|
|
55
|
+
fun describe(view: View): JSONObject {
|
|
56
|
+
val density = PNBridge.density()
|
|
57
|
+
val metrics = view.resources.displayMetrics
|
|
58
|
+
val w = if (view.width > 0) view.width else metrics.widthPixels
|
|
59
|
+
val h = if (view.height > 0) view.height else metrics.heightPixels
|
|
60
|
+
val insets = ViewCompat.getRootWindowInsets(view)
|
|
61
|
+
val bars = insets?.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout())
|
|
62
|
+
val ime = insets?.getInsets(WindowInsetsCompat.Type.ime())
|
|
63
|
+
val bottomBar = bars?.bottom ?: 0
|
|
64
|
+
val keyboard = max(0, (ime?.bottom ?: 0) - bottomBar) / density
|
|
65
|
+
val night = view.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
|
66
|
+
return JSONObject()
|
|
67
|
+
.put("width", w / density.toDouble())
|
|
68
|
+
.put("height", h / density.toDouble())
|
|
69
|
+
.put(
|
|
70
|
+
"insets",
|
|
71
|
+
JSONObject()
|
|
72
|
+
.put("top", (bars?.top ?: 0) / density.toDouble())
|
|
73
|
+
.put("left", (bars?.left ?: 0) / density.toDouble())
|
|
74
|
+
.put("bottom", bottomBar / density.toDouble())
|
|
75
|
+
.put("right", (bars?.right ?: 0) / density.toDouble()),
|
|
76
|
+
)
|
|
77
|
+
.put("color_scheme", if (night == Configuration.UI_MODE_NIGHT_YES) "dark" else "light")
|
|
78
|
+
.put("keyboard_height", keyboard.toDouble())
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
package com.pythonnative.
|
|
1
|
+
package com.pythonnative.runtime.views
|
|
2
2
|
|
|
3
3
|
import android.os.Build
|
|
4
4
|
import android.view.View
|
|
5
5
|
import android.view.accessibility.AccessibilityNodeInfo
|
|
6
|
+
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Accessibility delegate that exposes PythonNative's `test_id` and
|
|
@@ -12,9 +13,7 @@ import android.view.accessibility.AccessibilityNodeInfo
|
|
|
12
13
|
* UI Automator-based tools (Maestro, UiAutomator2, Appium) match as
|
|
13
14
|
* `resource-id`. State flags map onto the closest
|
|
14
15
|
* [AccessibilityNodeInfo] equivalents so TalkBack announces them.
|
|
15
|
-
*
|
|
16
|
-
* Instances are created and configured from Python (the Android view
|
|
17
|
-
* handlers); fields left null are not applied.
|
|
16
|
+
* Fields left null are not applied.
|
|
18
17
|
*/
|
|
19
18
|
class PNAccessibilityDelegate : View.AccessibilityDelegate() {
|
|
20
19
|
var testId: String? = null
|
|
@@ -23,6 +22,7 @@ class PNAccessibilityDelegate : View.AccessibilityDelegate() {
|
|
|
23
22
|
var stateChecked: Boolean? = null
|
|
24
23
|
var stateBusy: Boolean? = null
|
|
25
24
|
var stateExpanded: Boolean? = null
|
|
25
|
+
var role: String? = null
|
|
26
26
|
|
|
27
27
|
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
|
|
28
28
|
super.onInitializeAccessibilityNodeInfo(host, info)
|
|
@@ -33,6 +33,7 @@ class PNAccessibilityDelegate : View.AccessibilityDelegate() {
|
|
|
33
33
|
info.isCheckable = true
|
|
34
34
|
info.isChecked = it
|
|
35
35
|
}
|
|
36
|
+
role?.let { AccessibilityNodeInfoCompat.wrap(info).roleDescription = it }
|
|
36
37
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
37
38
|
val states = mutableListOf<String>()
|
|
38
39
|
if (stateBusy == true) states.add("busy")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.pythonnative.
|
|
1
|
+
package com.pythonnative.runtime.views
|
|
2
2
|
|
|
3
3
|
import android.graphics.Canvas
|
|
4
4
|
import android.graphics.ColorFilter
|
|
@@ -14,16 +14,25 @@ import android.graphics.drawable.Drawable
|
|
|
14
14
|
* `GradientDrawable`'s single uniform stroke cannot express.
|
|
15
15
|
*
|
|
16
16
|
* Widths are in pixels; sides are ordered left, top, right, bottom.
|
|
17
|
+
* The fill color is mutable so animated `background_color` frames can
|
|
18
|
+
* update it in place without allocating a new drawable.
|
|
17
19
|
*/
|
|
18
20
|
class PNBorderDrawable(
|
|
19
|
-
private
|
|
20
|
-
private
|
|
21
|
+
private var hasBackground: Boolean,
|
|
22
|
+
private var backgroundColor: Int,
|
|
21
23
|
private val cornerRadius: Float,
|
|
22
24
|
private val widths: FloatArray,
|
|
23
25
|
private val colors: IntArray,
|
|
24
26
|
) : Drawable() {
|
|
25
27
|
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
26
28
|
|
|
29
|
+
/** Replace the fill color (used by animated background frames). */
|
|
30
|
+
fun setFillColor(color: Int) {
|
|
31
|
+
hasBackground = true
|
|
32
|
+
backgroundColor = color
|
|
33
|
+
invalidateSelf()
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
override fun draw(canvas: Canvas) {
|
|
28
37
|
val b = RectF(bounds)
|
|
29
38
|
if (hasBackground) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
package com.pythonnative.runtime.views
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import androidx.appcompat.widget.AppCompatEditText
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `EditText` that reports selection changes, which the platform only
|
|
8
|
+
* exposes through a protected override.
|
|
9
|
+
*/
|
|
10
|
+
class PNEditText(context: Context) : AppCompatEditText(context) {
|
|
11
|
+
/** Called with `(start, end)` whenever the selection or cursor moves. */
|
|
12
|
+
var onSelectionChangedListener: ((Int, Int) -> Unit)? = null
|
|
13
|
+
|
|
14
|
+
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
|
|
15
|
+
super.onSelectionChanged(selStart, selEnd)
|
|
16
|
+
onSelectionChangedListener?.invoke(selStart, selEnd)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.pythonnative.
|
|
1
|
+
package com.pythonnative.runtime.views
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.view.MotionEvent
|
|
@@ -6,24 +6,20 @@ import android.widget.FrameLayout
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Container `FrameLayout` with React Native-style `pointer_events`
|
|
9
|
-
* semantics.
|
|
10
|
-
*
|
|
9
|
+
* semantics. Component managers create these for flex containers and
|
|
10
|
+
* drive the mode via [setPointerEventsMode]:
|
|
11
11
|
*
|
|
12
12
|
* - `"auto"` (default): normal dispatch; the view and its children
|
|
13
13
|
* both receive touches.
|
|
14
14
|
* - `"none"`: neither the view nor its children receive touches; the
|
|
15
15
|
* event falls through to whatever sits underneath.
|
|
16
16
|
* - `"box_none"`: children receive touches, but the view itself never
|
|
17
|
-
* intercepts or consumes them (the
|
|
18
|
-
*
|
|
17
|
+
* intercepts or consumes them (the styler also mutes the view's own
|
|
18
|
+
* touch listeners).
|
|
19
19
|
* - `"box_only"`: the view receives touches and its children don't;
|
|
20
20
|
* every event in the subtree is intercepted at this level.
|
|
21
|
-
*
|
|
22
|
-
* Interception can't be expressed from Python because Chaquopy can't
|
|
23
|
-
* subclass `ViewGroup`, so this class owns the two overrides and
|
|
24
|
-
* exposes the mode as a plain setter.
|
|
25
21
|
*/
|
|
26
|
-
class PNFrameLayout(context: Context) : FrameLayout(context) {
|
|
22
|
+
open class PNFrameLayout(context: Context) : FrameLayout(context) {
|
|
27
23
|
private var pointerEventsMode: String = "auto"
|
|
28
24
|
|
|
29
25
|
/** Set the pointer-events mode; `null` restores `"auto"`. */
|
|
@@ -31,6 +27,9 @@ class PNFrameLayout(context: Context) : FrameLayout(context) {
|
|
|
31
27
|
pointerEventsMode = mode ?: "auto"
|
|
32
28
|
}
|
|
33
29
|
|
|
30
|
+
/** The current pointer-events mode. */
|
|
31
|
+
fun getPointerEventsMode(): String = pointerEventsMode
|
|
32
|
+
|
|
34
33
|
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
35
34
|
if (pointerEventsMode == "none") {
|
|
36
35
|
// Declining the whole dispatch lets the event pass through
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.pythonnative.
|
|
1
|
+
package com.pythonnative.runtime.views;
|
|
2
2
|
|
|
3
3
|
import android.content.Context;
|
|
4
4
|
import android.util.TypedValue;
|
|
@@ -10,17 +10,15 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|
|
10
10
|
import androidx.recyclerview.widget.RecyclerView;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* RecyclerView-backed virtualized list used by PythonNative's
|
|
13
|
+
* RecyclerView-backed virtualized list used by PythonNative's VirtualList manager.
|
|
14
14
|
*
|
|
15
|
-
* <p>
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* content back to Python through the small Delegate interface below.</p>
|
|
15
|
+
* <p>The adapter and view holders live here; row content is delegated to the
|
|
16
|
+
* {@link Delegate}, which the VirtualList manager implements by asking Python
|
|
17
|
+
* for a row subtree through the synchronous {@code on_bind_row} protocol.</p>
|
|
19
18
|
*
|
|
20
19
|
* <p>Rows may have per-position heights (section lists interleave headers and
|
|
21
20
|
* items); the delegate reports each row's height in dp. Scroll changes are
|
|
22
|
-
* forwarded to the delegate in dp
|
|
23
|
-
* on_end_reached, and viewability callbacks with one code path per platform.</p>
|
|
21
|
+
* forwarded to the delegate in dp.</p>
|
|
24
22
|
*/
|
|
25
23
|
public class PNVirtualListView extends RecyclerView {
|
|
26
24
|
public interface Delegate {
|
|
@@ -28,7 +26,7 @@ public class PNVirtualListView extends RecyclerView {
|
|
|
28
26
|
|
|
29
27
|
float getRowHeightDp(int position);
|
|
30
28
|
|
|
31
|
-
void
|
|
29
|
+
void bindRow(int position, FrameLayout container, float widthDp, float heightDp);
|
|
32
30
|
|
|
33
31
|
void onRowPress(int position);
|
|
34
32
|
|
|
@@ -146,14 +144,13 @@ public class PNVirtualListView extends RecyclerView {
|
|
|
146
144
|
params.height = heightPx;
|
|
147
145
|
holder.container.setLayoutParams(params);
|
|
148
146
|
}
|
|
149
|
-
holder.container.removeAllViews();
|
|
150
147
|
holder.container.setOnClickListener(v -> {
|
|
151
148
|
int pos = holder.getBindingAdapterPosition();
|
|
152
149
|
if (pos != RecyclerView.NO_POSITION) {
|
|
153
150
|
delegate.onRowPress(pos);
|
|
154
151
|
}
|
|
155
152
|
});
|
|
156
|
-
delegate.
|
|
153
|
+
delegate.bindRow(
|
|
157
154
|
position,
|
|
158
155
|
holder.container,
|
|
159
156
|
currentWidthDp(holder.container),
|
|
@@ -163,9 +160,9 @@ public class PNVirtualListView extends RecyclerView {
|
|
|
163
160
|
|
|
164
161
|
@Override
|
|
165
162
|
public void onViewRecycled(@NonNull RowHolder holder) {
|
|
166
|
-
holder.container.removeAllViews();
|
|
167
163
|
holder.container.setOnClickListener(null);
|
|
168
164
|
delegate.onRowRecycled(holder.container);
|
|
165
|
+
holder.container.removeAllViews();
|
|
169
166
|
super.onViewRecycled(holder);
|
|
170
167
|
}
|
|
171
168
|
}
|