pythonnative 0.25.0__tar.gz → 0.26.0__tar.gz

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 (173) hide show
  1. {pythonnative-0.25.0/src/pythonnative.egg-info → pythonnative-0.26.0}/PKG-INFO +1 -1
  2. {pythonnative-0.25.0 → pythonnative-0.26.0}/pyproject.toml +1 -1
  3. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/__init__.py +1 -1
  4. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/cli/pn.py +172 -12
  5. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/diagnostics.py +27 -0
  6. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/app_state.py +2 -1
  7. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/battery.py +3 -2
  8. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/biometrics.py +2 -1
  9. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/camera.py +16 -7
  10. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/clipboard.py +3 -2
  11. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/file_system.py +3 -2
  12. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/haptics.py +7 -6
  13. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/linking.py +62 -6
  14. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/location.py +11 -8
  15. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/net_info.py +2 -1
  16. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/notifications.py +97 -6
  17. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/permissions.py +3 -2
  18. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/share.py +2 -1
  19. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_views/android.py +142 -122
  20. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_views/desktop.py +50 -49
  21. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_views/ios.py +228 -160
  22. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/project/android.py +15 -3
  23. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/project/builder.py +172 -70
  24. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/project/config.py +28 -6
  25. pythonnative-0.26.0/src/pythonnative/project/devices.py +291 -0
  26. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/project/doctor.py +5 -4
  27. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/project/ios.py +51 -77
  28. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/project/permissions.py +19 -1
  29. pythonnative-0.26.0/src/pythonnative/project/runtime_assets.py +184 -0
  30. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/build.gradle +5 -0
  31. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +1 -0
  32. pythonnative-0.26.0/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +115 -0
  33. pythonnative-0.26.0/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +60 -0
  34. pythonnative-0.26.0/src/pythonnative/templates/ios_template/ios_template/BridgingHeader.h +14 -0
  35. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_template/Info.plist +0 -2
  36. pythonnative-0.26.0/src/pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +397 -0
  37. pythonnative-0.26.0/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +66 -0
  38. pythonnative-0.26.0/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +157 -0
  39. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +72 -47
  40. {pythonnative-0.25.0 → pythonnative-0.26.0/src/pythonnative.egg-info}/PKG-INFO +1 -1
  41. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative.egg-info/SOURCES.txt +3 -1
  42. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_native_modules.py +79 -0
  43. pythonnative-0.25.0/src/pythonnative/project/runtime_assets.py +0 -272
  44. pythonnative-0.25.0/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +0 -39
  45. pythonnative-0.25.0/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +0 -36
  46. pythonnative-0.25.0/src/pythonnative/templates/ios_template/ios_template/Base.lproj/Main.storyboard +0 -24
  47. pythonnative-0.25.0/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +0 -55
  48. pythonnative-0.25.0/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +0 -278
  49. {pythonnative-0.25.0 → pythonnative-0.26.0}/LICENSE +0 -0
  50. {pythonnative-0.25.0 → pythonnative-0.26.0}/README.md +0 -0
  51. {pythonnative-0.25.0 → pythonnative-0.26.0}/setup.cfg +0 -0
  52. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/_ios_log.py +0 -0
  53. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/alerts.py +0 -0
  54. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/animated.py +0 -0
  55. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/appearance.py +0 -0
  56. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/cli/__init__.py +0 -0
  57. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/components.py +0 -0
  58. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/element.py +0 -0
  59. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/events.py +0 -0
  60. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/gestures.py +0 -0
  61. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/hooks.py +0 -0
  62. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/hot_reload.py +0 -0
  63. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/images.py +0 -0
  64. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/layout.py +0 -0
  65. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/mutations.py +0 -0
  66. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/__init__.py +0 -0
  67. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_modules/secure_store.py +0 -0
  68. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_views/__init__.py +0 -0
  69. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/native_views/base.py +0 -0
  70. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/navigation.py +0 -0
  71. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/net.py +0 -0
  72. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/platform.py +0 -0
  73. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/platform_metrics.py +0 -0
  74. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/preview.py +0 -0
  75. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/project/__init__.py +0 -0
  76. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/project/icons.py +0 -0
  77. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/reconciler.py +0 -0
  78. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/runtime.py +0 -0
  79. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/screen.py +0 -0
  80. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/sdk/__init__.py +0 -0
  81. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/sdk/_components.py +0 -0
  82. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/storage.py +0 -0
  83. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/style.py +0 -0
  84. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/suspense.py +0 -0
  85. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/proguard-rules.pro +0 -0
  86. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +0 -0
  87. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -0
  88. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNAccessibilityDelegate.kt +0 -0
  89. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNBorderDrawable.kt +0 -0
  90. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNVirtualListView.java +0 -0
  91. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +0 -0
  92. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +0 -0
  93. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -0
  94. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +0 -0
  95. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -0
  96. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -0
  97. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
  98. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
  99. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
  100. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
  101. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
  102. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
  103. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
  104. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
  105. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
  106. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
  107. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +0 -0
  108. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/values/colors.xml +0 -0
  109. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/values/strings.xml +0 -0
  110. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/values/themes.xml +0 -0
  111. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +0 -0
  112. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +0 -0
  113. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +0 -0
  114. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +0 -0
  115. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/build.gradle +0 -0
  116. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
  117. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +0 -0
  118. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/gradle.properties +0 -0
  119. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/gradlew +0 -0
  120. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/gradlew.bat +0 -0
  121. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/android_template/settings.gradle +0 -0
  122. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
  123. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
  124. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +0 -0
  125. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +0 -0
  126. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
  127. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +0 -0
  128. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +0 -0
  129. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +0 -0
  130. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/utils.py +0 -0
  131. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative/virtual_rows.py +0 -0
  132. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative.egg-info/dependency_links.txt +0 -0
  133. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative.egg-info/entry_points.txt +0 -0
  134. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative.egg-info/requires.txt +0 -0
  135. {pythonnative-0.25.0 → pythonnative-0.26.0}/src/pythonnative.egg-info/top_level.txt +0 -0
  136. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_alert.py +0 -0
  137. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_animated.py +0 -0
  138. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_appearance.py +0 -0
  139. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_async_hooks.py +0 -0
  140. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_cli.py +0 -0
  141. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_components.py +0 -0
  142. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_core_semantics.py +0 -0
  143. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_desktop_backend.py +0 -0
  144. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_element.py +0 -0
  145. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_events.py +0 -0
  146. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_extended_components.py +0 -0
  147. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_gestures.py +0 -0
  148. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_hooks.py +0 -0
  149. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_hot_reload.py +0 -0
  150. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_images.py +0 -0
  151. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_incremental_render.py +0 -0
  152. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_ios_log.py +0 -0
  153. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_layout.py +0 -0
  154. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_metric_hooks.py +0 -0
  155. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_mutations.py +0 -0
  156. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_native_lists.py +0 -0
  157. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_native_views.py +0 -0
  158. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_navigation.py +0 -0
  159. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_net.py +0 -0
  160. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_new_components.py +0 -0
  161. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_platform.py +0 -0
  162. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_platform_metrics.py +0 -0
  163. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_reconciler.py +0 -0
  164. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_ref.py +0 -0
  165. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_runtime.py +0 -0
  166. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_screen.py +0 -0
  167. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_sdk.py +0 -0
  168. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_smoke.py +0 -0
  169. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_storage.py +0 -0
  170. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_style.py +0 -0
  171. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_suspense.py +0 -0
  172. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_transitions.py +0 -0
  173. {pythonnative-0.25.0 → pythonnative-0.26.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonnative
3
- Version: 0.25.0
3
+ Version: 0.26.0
4
4
  Summary: Cross-platform native UI toolkit for Android and iOS
5
5
  Author: Owen Carey
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pythonnative"
7
- version = "0.25.0"
7
+ version = "0.26.0"
8
8
  description = "Cross-platform native UI toolkit for Android and iOS"
9
9
  authors = [
10
10
  { name = "Owen Carey" }
@@ -60,7 +60,7 @@ Example:
60
60
  ```
61
61
  """
62
62
 
63
- __version__ = "0.25.0"
63
+ __version__ = "0.26.0"
64
64
 
65
65
  from . import appearance, diagnostics, gestures, images, runtime, sdk
66
66
  from .alerts import Alert
@@ -6,10 +6,14 @@ The console script `pn` (declared in `pyproject.toml`) dispatches to:
6
6
  - `pn doctor [platform]`: diagnose the local toolchain and config.
7
7
  - `pn preview [component]`: render the app in a desktop (Tkinter) window
8
8
  with Fast Refresh, the fast inner dev loop, no device required.
9
- - `pn run android|ios`: stage + build + install + launch on a device or
10
- simulator, with optional on-device hot reload.
9
+ - `pn devices [platform]`: list connected devices, emulators, and
10
+ simulators.
11
+ - `pn run android|ios [--device D]`: stage + build + install + launch on
12
+ a device, emulator, or simulator, with optional on-device hot reload.
13
+ - `pn logs android|ios`: stream logs from the running app without
14
+ rebuilding.
11
15
  - `pn build android|ios`: produce standalone artifacts (signed APK/AAB,
12
- or an iOS archive/IPA).
16
+ or an iOS archive/IPA, optionally uploaded to App Store Connect).
13
17
  - `pn app-id android|ios`: print the resolved application/bundle id
14
18
  (handy for scripts and CI).
15
19
  - `pn clean`: remove the local `build/` directory.
@@ -34,6 +38,7 @@ from pathlib import Path
34
38
  from typing import Any, Dict, List, Optional
35
39
 
36
40
  from ..project import builder as builder_mod
41
+ from ..project import devices as devices_mod
37
42
  from ..project import doctor as doctor_mod
38
43
  from ..project.android import collect_logcat_filters
39
44
  from ..project.config import CONFIG_FILENAME, AppConfig, ConfigError, render_default_toml
@@ -241,6 +246,41 @@ def _preview_entry(project_dir: Path) -> str:
241
246
  return "app.main"
242
247
 
243
248
 
249
+ # ======================================================================
250
+ # devices
251
+ # ======================================================================
252
+
253
+
254
+ def devices_command(args: argparse.Namespace) -> None:
255
+ """List connected devices, emulators, and simulators.
256
+
257
+ Args:
258
+ args: Parsed namespace with optional ``platform``.
259
+ """
260
+ platform: Optional[str] = getattr(args, "platform", None)
261
+ devices = devices_mod.list_devices(platform)
262
+ if not devices:
263
+ print("No devices found.")
264
+ print("Android: start an emulator or connect a device with USB debugging enabled.")
265
+ print("iOS: open Xcode once to install Simulators, or plug in a device.")
266
+ sys.exit(1)
267
+ print(f" {'IDENTIFIER':<40} {'KIND':<10} {'STATE':<10} NAME")
268
+ for device in devices:
269
+ print(device.format())
270
+ print("\nTarget one with: pn run <platform> --device <identifier or name>")
271
+
272
+
273
+ def _resolve_device(platform: str, query: Optional[str]) -> Optional[devices_mod.Device]:
274
+ """Resolve ``--device`` to a concrete target, exiting on a bad query."""
275
+ if not query:
276
+ return None
277
+ device = devices_mod.find_device(devices_mod.list_devices(platform), query)
278
+ if device is None:
279
+ print(f"Error: no {platform} device matches {query!r}. Run 'pn devices {platform}' to list targets.")
280
+ sys.exit(1)
281
+ return device
282
+
283
+
244
284
  # ======================================================================
245
285
  # run
246
286
  # ======================================================================
@@ -250,13 +290,14 @@ def run_project(args: argparse.Namespace) -> None:
250
290
  """Stage, build, install, and launch the app on a device/simulator.
251
291
 
252
292
  Args:
253
- args: Parsed namespace (``platform``, ``prepare_only``,
254
- ``hot_reload``, ``no_logs``).
293
+ args: Parsed namespace (``platform``, ``device``,
294
+ ``prepare_only``, ``hot_reload``, ``no_logs``).
255
295
  """
256
296
  platform: str = args.platform
257
297
  prepare_only: bool = getattr(args, "prepare_only", False)
258
298
  hot_reload: bool = getattr(args, "hot_reload", False)
259
299
  show_logs: bool = not getattr(args, "no_logs", False)
300
+ device = _resolve_device(platform, getattr(args, "device", None))
260
301
 
261
302
  config = _load_config_or_exit()
262
303
  builder = builder_mod.Builder(config, log=print)
@@ -273,9 +314,14 @@ def run_project(args: argparse.Namespace) -> None:
273
314
 
274
315
  try:
275
316
  if platform == "android":
276
- _run_android(builder, prepared, hot_reload=hot_reload, show_logs=show_logs)
317
+ _run_android(builder, prepared, device=device, hot_reload=hot_reload, show_logs=show_logs)
318
+ elif device is not None and device.kind == "device":
319
+ _run_ios_device(builder, prepared, device=device)
320
+ if hot_reload:
321
+ print("Note: hot reload targets the iOS Simulator; skipping it for a physical device.")
322
+ return
277
323
  else:
278
- _run_ios(builder, prepared, hot_reload=hot_reload, show_logs=show_logs)
324
+ _run_ios(builder, prepared, device=device, hot_reload=hot_reload, show_logs=show_logs)
279
325
  except builder_mod.BuildError as exc:
280
326
  print(f"Error: {exc}")
281
327
  sys.exit(1)
@@ -295,9 +341,14 @@ def _run_android(
295
341
  builder: builder_mod.Builder,
296
342
  prepared: builder_mod.PreparedProject,
297
343
  *,
344
+ device: Optional[devices_mod.Device],
298
345
  hot_reload: bool,
299
346
  show_logs: bool,
300
347
  ) -> None:
348
+ if device is not None:
349
+ # Both Gradle's install task and every adb call below honor
350
+ # ANDROID_SERIAL, so exporting it targets the whole run.
351
+ os.environ["ANDROID_SERIAL"] = device.identifier
301
352
  builder.install_android_debug(prepared)
302
353
  _clear_android_hot_reload_overlay(prepared.app_id)
303
354
  subprocess.run(
@@ -319,11 +370,12 @@ def _run_ios(
319
370
  builder: builder_mod.Builder,
320
371
  prepared: builder_mod.PreparedProject,
321
372
  *,
373
+ device: Optional[devices_mod.Device],
322
374
  hot_reload: bool,
323
375
  show_logs: bool,
324
376
  ) -> None:
325
377
  app_path = builder.build_ios_simulator(prepared)
326
- udid = _select_ios_simulator()
378
+ udid = device.identifier if device is not None else _select_ios_simulator()
327
379
  if udid is None:
328
380
  print("No available iOS Simulators found; open the project in Xcode to run.")
329
381
  return
@@ -352,6 +404,45 @@ def _run_ios(
352
404
  print("Launched iOS app on Simulator.")
353
405
 
354
406
 
407
+ def _run_ios_device(
408
+ builder: builder_mod.Builder,
409
+ prepared: builder_mod.PreparedProject,
410
+ *,
411
+ device: devices_mod.Device,
412
+ ) -> None:
413
+ """Build, install, and launch on a physical iOS device via devicectl."""
414
+ app_path = builder.build_ios_device(prepared)
415
+ print(f"Installing on {device.name}...")
416
+ install = subprocess.run(
417
+ ["xcrun", "devicectl", "device", "install", "app", "--device", device.identifier, str(app_path)],
418
+ check=False,
419
+ )
420
+ if install.returncode != 0:
421
+ print(
422
+ "Error: install failed. Make sure the device is unlocked, paired with this Mac, "
423
+ "and has Developer Mode enabled (Settings > Privacy & Security > Developer Mode)."
424
+ )
425
+ sys.exit(1)
426
+ launch = subprocess.run(
427
+ [
428
+ "xcrun",
429
+ "devicectl",
430
+ "device",
431
+ "process",
432
+ "launch",
433
+ "--terminate-existing",
434
+ "--device",
435
+ device.identifier,
436
+ prepared.app_id,
437
+ ],
438
+ check=False,
439
+ )
440
+ if launch.returncode != 0:
441
+ print("Error: launch failed. Launch the app from the home screen to see details.")
442
+ sys.exit(1)
443
+ print(f"Launched on {device.name}. Use Console.app (or Xcode > Devices) for device logs.")
444
+
445
+
355
446
  # ======================================================================
356
447
  # build
357
448
  # ======================================================================
@@ -361,16 +452,24 @@ def build_project(args: argparse.Namespace) -> None:
361
452
  """Build standalone, distributable artifacts for ``platform``.
362
453
 
363
454
  Args:
364
- args: Parsed namespace (``platform``, ``debug``).
455
+ args: Parsed namespace (``platform``, ``debug``, ``upload``).
365
456
  """
366
457
  platform: str = args.platform
367
458
  debug: bool = getattr(args, "debug", False)
459
+ upload: bool = getattr(args, "upload", False)
368
460
 
369
461
  config = _load_config_or_exit()
370
462
  builder = builder_mod.Builder(config, log=print)
371
463
 
464
+ if upload and (platform != "ios" or debug):
465
+ print("Error: --upload applies to 'pn build ios' release builds only.")
466
+ sys.exit(1)
467
+ if upload and config.ios.signing.export_method != "app-store":
468
+ print('Error: --upload requires [ios.signing] export_method = "app-store" in pythonnative.toml.')
469
+ sys.exit(1)
470
+
372
471
  try:
373
- prepared = builder.prepare(platform)
472
+ prepared = builder.prepare(platform, release=not debug)
374
473
  if platform == "android":
375
474
  artifacts = builder.build_android(prepared, debug=debug)
376
475
  else:
@@ -378,19 +477,61 @@ def build_project(args: argparse.Namespace) -> None:
378
477
  app_path = builder.build_ios_simulator(prepared)
379
478
  artifacts = builder_mod.BuildArtifacts(paths=[app_path])
380
479
  else:
381
- artifacts = builder.build_ios_archive(prepared)
480
+ artifacts = builder.build_ios_archive(prepared, upload=upload)
382
481
  except builder_mod.BuildError as exc:
383
482
  print(f"Error: {exc}")
384
483
  sys.exit(1)
385
484
 
485
+ if upload:
486
+ print("\nUploaded to App Store Connect. Check the build's status at appstoreconnect.apple.com.")
386
487
  if not artifacts.paths:
387
- print("Build completed, but no artifacts were found. Check the build output above.")
488
+ if not upload:
489
+ print("Build completed, but no artifacts were found. Check the build output above.")
388
490
  return
389
491
  print("\nBuilt artifacts:")
390
492
  for path in artifacts.paths:
391
493
  print(f" {path}")
392
494
 
393
495
 
496
+ # ======================================================================
497
+ # logs
498
+ # ======================================================================
499
+
500
+
501
+ def logs_command(args: argparse.Namespace) -> None:
502
+ """Stream logs from the running app without rebuilding.
503
+
504
+ Args:
505
+ args: Parsed namespace (``platform``).
506
+ """
507
+ platform: str = args.platform
508
+ if platform == "android":
509
+ proc = _start_android_log_stream()
510
+ if proc is None:
511
+ sys.exit(1)
512
+ try:
513
+ proc.wait()
514
+ except KeyboardInterrupt:
515
+ print()
516
+ _terminate_subprocess(proc)
517
+ print("Stopped log streaming.")
518
+ return
519
+
520
+ # iOS: relaunch the app on the booted simulator with a console PTY so
521
+ # Python's stdout/stderr stream to this terminal.
522
+ config = _load_config_or_exit()
523
+ proc = _start_ios_log_stream(config.bundle_id)
524
+ if proc is None:
525
+ print("For a physical device, use Console.app or Xcode > Devices and Simulators.")
526
+ sys.exit(1)
527
+ try:
528
+ proc.wait()
529
+ except KeyboardInterrupt:
530
+ print()
531
+ _terminate_subprocess(proc)
532
+ print("Stopped log streaming.")
533
+
534
+
394
535
  # ======================================================================
395
536
  # clean
396
537
  # ======================================================================
@@ -747,16 +888,35 @@ def _build_parser() -> argparse.ArgumentParser:
747
888
  parser_preview.add_argument("--no-hot-reload", action="store_true", help="Disable file watching / Fast Refresh")
748
889
  parser_preview.set_defaults(func=preview_project)
749
890
 
891
+ parser_devices = subparsers.add_parser("devices", help="List devices, emulators, and simulators")
892
+ parser_devices.add_argument("platform", nargs="?", choices=["android", "ios"], help="Restrict to a platform")
893
+ parser_devices.set_defaults(func=devices_command)
894
+
750
895
  parser_run = subparsers.add_parser("run", help="Build, install, and launch on a device/simulator")
751
896
  parser_run.add_argument("platform", choices=["android", "ios"])
897
+ parser_run.add_argument(
898
+ "--device",
899
+ "-d",
900
+ help="Target device: an identifier or name from 'pn devices' "
901
+ "(physical iOS devices need [ios].development_team)",
902
+ )
752
903
  parser_run.add_argument("--prepare-only", action="store_true", help="Stage + configure without building")
753
904
  parser_run.add_argument("--hot-reload", action="store_true", help="Watch app/ and push updates to the running app")
754
905
  parser_run.add_argument("--no-logs", action="store_true", help="Don't stream device logs after launch")
755
906
  parser_run.set_defaults(func=run_project)
756
907
 
908
+ parser_logs = subparsers.add_parser("logs", help="Stream logs from the running app")
909
+ parser_logs.add_argument("platform", choices=["android", "ios"])
910
+ parser_logs.set_defaults(func=logs_command)
911
+
757
912
  parser_build = subparsers.add_parser("build", help="Build distributable artifacts")
758
913
  parser_build.add_argument("platform", choices=["android", "ios"])
759
914
  parser_build.add_argument("--debug", action="store_true", help="Build the debug variant instead of release")
915
+ parser_build.add_argument(
916
+ "--upload",
917
+ action="store_true",
918
+ help='Upload the iOS release build to App Store Connect (needs export_method = "app-store")',
919
+ )
760
920
  parser_build.set_defaults(func=build_project)
761
921
 
762
922
  parser_app_id = subparsers.add_parser("app-id", help="Print the resolved application/bundle id")
@@ -37,6 +37,7 @@ __all__ = [
37
37
  "is_dev",
38
38
  "warn",
39
39
  "warn_once",
40
+ "swallowed",
40
41
  "get_warnings",
41
42
  "clear_warnings",
42
43
  "set_error_reporter",
@@ -143,6 +144,32 @@ def warn_once(message: str, key: Optional[str] = None) -> None:
143
144
  warn(message)
144
145
 
145
146
 
147
+ def swallowed(context: str) -> None:
148
+ """Surface a suppressed native-backend exception (dev mode only).
149
+
150
+ The native view/module backends intentionally degrade gracefully in
151
+ production: a failed style application or a missing OS API must not
152
+ crash the app. Call this from the ``except`` block instead of
153
+ ``pass`` so that, in dev mode, each suppression is reported once per
154
+ call site instead of disappearing.
155
+
156
+ Args:
157
+ context: Where the suppression happened, e.g.
158
+ ``"ios.TextHandler._apply"``.
159
+ """
160
+ if not is_dev():
161
+ return
162
+ exc = sys.exc_info()[1]
163
+ if exc is None:
164
+ return
165
+ frame = sys._getframe(1)
166
+ location = f"{os.path.basename(frame.f_code.co_filename)}:{frame.f_lineno}"
167
+ warn_once(
168
+ f"{context} suppressed {type(exc).__name__}: {exc} ({location})",
169
+ key=f"swallowed:{context}:{location}",
170
+ )
171
+
172
+
146
173
  def get_warnings() -> List[str]:
147
174
  """Return a snapshot of the recorded warnings (oldest first)."""
148
175
  with _warn_lock:
@@ -25,6 +25,7 @@ from __future__ import annotations
25
25
 
26
26
  from typing import Callable, List
27
27
 
28
+ from .. import diagnostics
28
29
  from ..hooks import use_effect, use_state
29
30
 
30
31
  AppStateStatus = str # "active" | "inactive" | "background"
@@ -74,7 +75,7 @@ def dispatch_app_state(state: AppStateStatus) -> None:
74
75
  try:
75
76
  listener(state)
76
77
  except Exception:
77
- pass
78
+ diagnostics.swallowed("app_state.dispatch_app_state")
78
79
 
79
80
 
80
81
  def use_app_state() -> AppStateStatus:
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  from typing import Callable, Dict, List
12
12
 
13
+ from .. import diagnostics
13
14
  from ..utils import IS_ANDROID, IS_IOS
14
15
 
15
16
  BatteryState = str # "unknown" | "unplugged" | "charging" | "full"
@@ -62,7 +63,7 @@ def dispatch_battery(level: float, state: BatteryState) -> None:
62
63
  try:
63
64
  listener(dict(payload))
64
65
  except Exception:
65
- pass
66
+ diagnostics.swallowed("battery.dispatch_battery")
66
67
 
67
68
 
68
69
  # ======================================================================
@@ -77,7 +78,7 @@ def _ios_device() -> object:
77
78
  try:
78
79
  device.setBatteryMonitoringEnabled_(True)
79
80
  except Exception:
80
- pass
81
+ diagnostics.swallowed("battery._ios_device")
81
82
  return device
82
83
 
83
84
 
@@ -23,6 +23,7 @@ from __future__ import annotations
23
23
  import asyncio
24
24
  from typing import Any, Callable, Dict
25
25
 
26
+ from .. import diagnostics
26
27
  from ..runtime import resolve_future
27
28
  from ..utils import IS_ANDROID, IS_IOS
28
29
 
@@ -93,7 +94,7 @@ def _ios_authenticate(reason: str, on_done: Callable[[bool], None]) -> None:
93
94
  try:
94
95
  on_done(bool(success))
95
96
  except Exception:
96
- pass
97
+ diagnostics.swallowed("biometrics._ios_authenticate._reply")
97
98
 
98
99
  context.evaluatePolicy_localizedReason_reply_(1, reason, Block(_reply, None, bool, ObjCClass("NSError")))
99
100
  except Exception:
@@ -25,6 +25,7 @@ from __future__ import annotations
25
25
  import asyncio
26
26
  from typing import Any, Callable, Dict, Optional
27
27
 
28
+ from .. import diagnostics
28
29
  from ..runtime import resolve_future
29
30
  from ..utils import IS_ANDROID, IS_IOS
30
31
 
@@ -109,7 +110,9 @@ def _ios_launch_picker(on_result: Callable[[Optional[str]], None], source: str)
109
110
  try:
110
111
  picker.dismissViewControllerAnimated_completion_(True, None)
111
112
  except Exception:
112
- pass
113
+ diagnostics.swallowed(
114
+ "camera._ios_launch_picker._PNImagePickerDelegate.imagePickerController_didFinishPickingMediaWithInfo_"
115
+ )
113
116
  cb = self._callback
114
117
  self._callback = None
115
118
  _pending_delegates.pop(id(self), None)
@@ -117,14 +120,18 @@ def _ios_launch_picker(on_result: Callable[[Optional[str]], None], source: str)
117
120
  try:
118
121
  cb(path)
119
122
  except Exception:
120
- pass
123
+ diagnostics.swallowed(
124
+ "camera._ios_launch_picker._PNImagePickerDelegate.imagePickerController_didFinishPickingMediaWithInfo_"
125
+ )
121
126
 
122
127
  @objc_method
123
128
  def imagePickerControllerDidCancel_(self, picker: Any) -> None:
124
129
  try:
125
130
  picker.dismissViewControllerAnimated_completion_(True, None)
126
131
  except Exception:
127
- pass
132
+ diagnostics.swallowed(
133
+ "camera._ios_launch_picker._PNImagePickerDelegate.imagePickerControllerDidCancel_"
134
+ )
128
135
  cb = self._callback
129
136
  self._callback = None
130
137
  _pending_delegates.pop(id(self), None)
@@ -132,7 +139,9 @@ def _ios_launch_picker(on_result: Callable[[Optional[str]], None], source: str)
132
139
  try:
133
140
  cb(None)
134
141
  except Exception:
135
- pass
142
+ diagnostics.swallowed(
143
+ "camera._ios_launch_picker._PNImagePickerDelegate.imagePickerControllerDidCancel_"
144
+ )
136
145
 
137
146
  delegate = _PNImagePickerDelegate.new()
138
147
  delegate._callback = on_result
@@ -170,7 +179,7 @@ def _ios_extract_path(info: Any) -> Optional[str]:
170
179
  try:
171
180
  return str(url.path)
172
181
  except Exception:
173
- pass
182
+ diagnostics.swallowed("camera._ios_extract_path")
174
183
  image = info.objectForKey_("UIImagePickerControllerOriginalImage")
175
184
  if image is not None:
176
185
  return _ios_write_image_to_tmp(image)
@@ -293,13 +302,13 @@ def deliver_android_activity_result(request_code: int, result_code: int, data: A
293
302
  if thumb is not None:
294
303
  path = _android_write_bitmap_to_cache(thumb)
295
304
  except Exception:
296
- pass
305
+ diagnostics.swallowed("camera.deliver_android_activity_result")
297
306
  except Exception:
298
307
  path = None
299
308
  try:
300
309
  cb(path)
301
310
  except Exception:
302
- pass
311
+ diagnostics.swallowed("camera.deliver_android_activity_result")
303
312
  return True
304
313
 
305
314
 
@@ -22,6 +22,7 @@ from __future__ import annotations
22
22
 
23
23
  from typing import Optional
24
24
 
25
+ from .. import diagnostics
25
26
  from ..utils import IS_ANDROID, IS_IOS
26
27
 
27
28
  # Desktop fallback buffer so the API is usable off-device.
@@ -79,7 +80,7 @@ def _ios_set(text: str) -> None:
79
80
  try:
80
81
  pb.string = text
81
82
  except Exception:
82
- pass
83
+ diagnostics.swallowed("clipboard._ios_set")
83
84
 
84
85
 
85
86
  def _ios_get() -> str:
@@ -122,7 +123,7 @@ def _android_set(text: str) -> None:
122
123
  clip = ClipData.newPlainText("pythonnative", text)
123
124
  manager.setPrimaryClip(clip)
124
125
  except Exception:
125
- pass
126
+ diagnostics.swallowed("clipboard._android_set")
126
127
 
127
128
 
128
129
  def _android_get() -> str:
@@ -17,6 +17,7 @@ Example:
17
17
  import os
18
18
  from typing import Any, Optional
19
19
 
20
+ from .. import diagnostics
20
21
  from ..utils import IS_ANDROID
21
22
 
22
23
 
@@ -49,7 +50,7 @@ class FileSystem:
49
50
 
50
51
  return str(get_android_context().getFilesDir().getAbsolutePath())
51
52
  except Exception:
52
- pass
53
+ diagnostics.swallowed("file_system.FileSystem.app_dir")
53
54
  else:
54
55
  try:
55
56
  from rubicon.objc import ObjCClass
@@ -61,7 +62,7 @@ class FileSystem:
61
62
  if docs and docs.count > 0:
62
63
  return str(docs.objectAtIndex_(0).path)
63
64
  except Exception:
64
- pass
65
+ diagnostics.swallowed("file_system.FileSystem.app_dir")
65
66
  return os.path.join(os.path.expanduser("~"), ".pythonnative_data")
66
67
 
67
68
  @staticmethod
@@ -19,6 +19,7 @@ from __future__ import annotations
19
19
 
20
20
  from typing import Any, Optional
21
21
 
22
+ from .. import diagnostics
22
23
  from ..utils import IS_ANDROID, IS_IOS
23
24
 
24
25
  ImpactStyle = str # "light" | "medium" | "heavy" | "soft" | "rigid"
@@ -86,7 +87,7 @@ class Vibration:
86
87
  try:
87
88
  vibrator.cancel()
88
89
  except Exception:
89
- pass
90
+ diagnostics.swallowed("haptics.Vibration.cancel")
90
91
 
91
92
 
92
93
  # ======================================================================
@@ -102,7 +103,7 @@ def _ios_impact(style: str) -> None:
102
103
  generator.prepare()
103
104
  generator.impactOccurred()
104
105
  except Exception:
105
- pass
106
+ diagnostics.swallowed("haptics._ios_impact")
106
107
 
107
108
 
108
109
  def _ios_notification(type_: str) -> None:
@@ -113,7 +114,7 @@ def _ios_notification(type_: str) -> None:
113
114
  generator.prepare()
114
115
  generator.notificationOccurred_(_IOS_NOTIFICATION_TYPE.get(type_, 0))
115
116
  except Exception:
116
- pass
117
+ diagnostics.swallowed("haptics._ios_notification")
117
118
 
118
119
 
119
120
  def _ios_selection() -> None:
@@ -124,7 +125,7 @@ def _ios_selection() -> None:
124
125
  generator.prepare()
125
126
  generator.selectionChanged()
126
127
  except Exception:
127
- pass
128
+ diagnostics.swallowed("haptics._ios_selection")
128
129
 
129
130
 
130
131
  def _ios_legacy_vibrate() -> None:
@@ -134,7 +135,7 @@ def _ios_legacy_vibrate() -> None:
134
135
  audio = CDLL(util.find_library("AudioToolbox"))
135
136
  audio.AudioServicesPlaySystemSound(4095) # kSystemSoundID_Vibrate
136
137
  except Exception:
137
- pass
138
+ diagnostics.swallowed("haptics._ios_legacy_vibrate")
138
139
 
139
140
 
140
141
  # ======================================================================
@@ -177,4 +178,4 @@ def _android_buzz(duration_ms: int) -> None:
177
178
  else:
178
179
  vibrator.vibrate(int(duration_ms))
179
180
  except Exception:
180
- pass
181
+ diagnostics.swallowed("haptics._android_buzz")