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,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.android_template
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.android_template
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 val hasBackground: Boolean,
20
- private val backgroundColor: Int,
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.android_template
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. Python's Android view handlers create these for flex
10
- * containers and drive the mode via [setPointerEventsMode]:
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 Python side also mutes the
18
- * view's own touch listeners).
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.android_template;
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 Android bridge.
13
+ * RecyclerView-backed virtualized list used by PythonNative's VirtualList manager.
14
14
  *
15
- * <p>Chaquopy can proxy Java interfaces from Python, but it can't subclass Java
16
- * abstract classes such as RecyclerView.Adapter or RecyclerView.ViewHolder. This
17
- * helper owns those abstract-class implementations in Java and delegates row
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 so the Python side can drive on_scroll,
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 mountRow(int position, FrameLayout container, float widthDp, float heightDp);
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.mountRow(
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
  }