ruflet 0.0.6 → 0.0.8

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 (238) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ruflet/version.rb +1 -1
  3. data/lib/ruflet_ui/ruflet/app.rb +10 -14
  4. data/lib/ruflet_ui/ruflet/control.rb +120 -7
  5. data/lib/ruflet_ui/ruflet/dsl.rb +68 -4
  6. data/lib/ruflet_ui/ruflet/event.rb +15 -1
  7. data/lib/ruflet_ui/ruflet/events/gesture_events.rb +552 -0
  8. data/lib/ruflet_ui/ruflet/page.rb +600 -73
  9. data/lib/ruflet_ui/ruflet/types/geometry.rb +65 -0
  10. data/lib/ruflet_ui/ruflet/types/text_style.rb +191 -0
  11. data/lib/ruflet_ui/ruflet/ui/control_factory.rb +35 -5
  12. data/lib/ruflet_ui/ruflet/ui/control_methods.rb +1 -0
  13. data/lib/ruflet_ui/ruflet/ui/control_registry.rb +22 -2
  14. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoactionsheet_control.rb +57 -0
  15. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoactionsheetaction_control.rb +58 -0
  16. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoactivityindicator_control.rb +57 -0
  17. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoalertdialog_control.rb +39 -0
  18. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoappbar_control.rb +44 -0
  19. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinobottomsheet_control.rb +38 -0
  20. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinobutton_control.rb +73 -0
  21. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinocheckbox_control.rb +70 -0
  22. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinocontextmenu_control.rb +34 -0
  23. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinocontextmenuaction_control.rb +36 -0
  24. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinodatepicker_control.rb +67 -0
  25. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinodialogaction_control.rb +35 -0
  26. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinofilledbutton_control.rb +73 -0
  27. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinolisttile_control.rb +67 -0
  28. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinonavigationbar_control.rb +61 -0
  29. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinopicker_control.rb +66 -0
  30. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoradio_control.rb +66 -0
  31. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinosegmentedbutton_control.rb +63 -0
  32. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoslider_control.rb +64 -0
  33. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoslidingsegmentedbutton_control.rb +60 -0
  34. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoswitch_control.rb +73 -0
  35. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinotextfield_control.rb +159 -0
  36. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinotimerpicker_control.rb +61 -0
  37. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinotintedbutton_control.rb +73 -0
  38. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/ruflet_controls.rb +89 -0
  39. data/lib/ruflet_ui/ruflet/ui/controls/materials/alertdialog_control.rb +58 -0
  40. data/lib/ruflet_ui/ruflet/ui/controls/materials/appbar_control.rb +53 -0
  41. data/lib/ruflet_ui/ruflet/ui/controls/materials/audio_control.rb +36 -0
  42. data/lib/ruflet_ui/ruflet/ui/controls/materials/autocomplete_control.rb +58 -0
  43. data/lib/ruflet_ui/ruflet/ui/controls/materials/badge_control.rb +31 -0
  44. data/lib/ruflet_ui/ruflet/ui/controls/materials/banner_control.rb +47 -0
  45. data/lib/ruflet_ui/ruflet/ui/controls/materials/bottomappbar_control.rb +62 -0
  46. data/lib/ruflet_ui/ruflet/ui/controls/materials/bottomsheet_control.rb +48 -0
  47. data/lib/ruflet_ui/ruflet/ui/controls/materials/button_control.rb +69 -0
  48. data/lib/ruflet_ui/ruflet/ui/controls/materials/card_control.rb +63 -0
  49. data/lib/ruflet_ui/ruflet/ui/controls/materials/chart_controls.rb +321 -0
  50. data/lib/ruflet_ui/ruflet/ui/controls/materials/checkbox_control.rb +76 -0
  51. data/lib/ruflet_ui/ruflet/ui/controls/materials/chip_control.rb +88 -0
  52. data/lib/ruflet_ui/ruflet/ui/controls/materials/circleavatar_control.rb +62 -0
  53. data/lib/ruflet_ui/ruflet/ui/controls/materials/container_control.rb +81 -0
  54. data/lib/ruflet_ui/ruflet/ui/controls/materials/contextmenu_control.rb +63 -0
  55. data/lib/ruflet_ui/ruflet/ui/controls/materials/datacell_control.rb +38 -0
  56. data/lib/ruflet_ui/ruflet/ui/controls/materials/datacolumn_control.rb +34 -0
  57. data/lib/ruflet_ui/ruflet/ui/controls/materials/datarow_control.rb +35 -0
  58. data/lib/ruflet_ui/ruflet/ui/controls/materials/datatable_control.rb +78 -0
  59. data/lib/ruflet_ui/ruflet/ui/controls/materials/datepicker_control.rb +55 -0
  60. data/lib/ruflet_ui/ruflet/ui/controls/materials/daterangepicker_control.rb +57 -0
  61. data/lib/ruflet_ui/ruflet/ui/controls/materials/divider_control.rb +36 -0
  62. data/lib/ruflet_ui/ruflet/ui/controls/materials/dropdown_control.rb +99 -0
  63. data/lib/ruflet_ui/ruflet/ui/controls/materials/dropdownm2_control.rb +119 -0
  64. data/lib/ruflet_ui/ruflet/ui/controls/materials/dropdownoption_control.rb +35 -0
  65. data/lib/ruflet_ui/ruflet/ui/controls/materials/expansionpanel_control.rb +61 -0
  66. data/lib/ruflet_ui/ruflet/ui/controls/materials/expansionpanellist_control.rb +60 -0
  67. data/lib/ruflet_ui/ruflet/ui/controls/materials/expansiontile_control.rb +82 -0
  68. data/lib/ruflet_ui/ruflet/ui/controls/materials/filledbutton_control.rb +69 -0
  69. data/lib/ruflet_ui/ruflet/ui/controls/materials/fillediconbutton_control.rb +81 -0
  70. data/lib/ruflet_ui/ruflet/ui/controls/materials/filledtonalbutton_control.rb +69 -0
  71. data/lib/ruflet_ui/ruflet/ui/controls/materials/filledtonaliconbutton_control.rb +81 -0
  72. data/lib/ruflet_ui/ruflet/ui/controls/materials/floatingactionbutton_control.rb +73 -0
  73. data/lib/ruflet_ui/ruflet/ui/controls/materials/iconbutton_control.rb +81 -0
  74. data/lib/ruflet_ui/ruflet/ui/controls/materials/listtile_control.rb +87 -0
  75. data/lib/ruflet_ui/ruflet/ui/controls/materials/menubar_control.rb +33 -0
  76. data/lib/ruflet_ui/ruflet/ui/controls/materials/menuitembutton_control.rb +67 -0
  77. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationbar_control.rb +67 -0
  78. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationbardestination_control.rb +35 -0
  79. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationdrawer_control.rb +41 -0
  80. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationdrawerdestination_control.rb +34 -0
  81. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationrail_control.rb +70 -0
  82. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationraildestination_control.rb +36 -0
  83. data/lib/ruflet_ui/ruflet/ui/controls/materials/option_control.rb +35 -0
  84. data/lib/ruflet_ui/ruflet/ui/controls/materials/outlinedbutton_control.rb +66 -0
  85. data/lib/ruflet_ui/ruflet/ui/controls/materials/outlinediconbutton_control.rb +81 -0
  86. data/lib/ruflet_ui/ruflet/ui/controls/materials/popupmenubutton_control.rb +74 -0
  87. data/lib/ruflet_ui/ruflet/ui/controls/materials/popupmenuitem_control.rb +38 -0
  88. data/lib/ruflet_ui/ruflet/ui/controls/materials/progressbar_control.rb +64 -0
  89. data/lib/ruflet_ui/ruflet/ui/controls/materials/progressring_control.rb +65 -0
  90. data/lib/ruflet_ui/ruflet/ui/controls/materials/radio_control.rb +70 -0
  91. data/lib/ruflet_ui/ruflet/ui/controls/materials/radiogroup_control.rb +33 -0
  92. data/lib/ruflet_ui/ruflet/ui/controls/materials/rangeslider_control.rb +67 -0
  93. data/lib/ruflet_ui/ruflet/ui/controls/materials/reorderablelistview_control.rb +80 -0
  94. data/lib/ruflet_ui/ruflet/ui/controls/materials/ruflet_controls.rb +252 -0
  95. data/lib/ruflet_ui/ruflet/ui/controls/materials/searchbar_control.rb +94 -0
  96. data/lib/ruflet_ui/ruflet/ui/controls/materials/segment_control.rb +33 -0
  97. data/lib/ruflet_ui/ruflet/ui/controls/materials/segmentedbutton_control.rb +63 -0
  98. data/lib/ruflet_ui/ruflet/ui/controls/materials/selectionarea_control.rb +32 -0
  99. data/lib/ruflet_ui/ruflet/ui/controls/materials/slider_control.rb +76 -0
  100. data/lib/ruflet_ui/ruflet/ui/controls/materials/snackbar_control.rb +51 -0
  101. data/lib/ruflet_ui/ruflet/ui/controls/materials/submenubutton_control.rb +66 -0
  102. data/lib/ruflet_ui/ruflet/ui/controls/materials/switch_control.rb +77 -0
  103. data/lib/ruflet_ui/ruflet/ui/controls/materials/tab_control.rb +35 -0
  104. data/lib/ruflet_ui/ruflet/ui/controls/materials/tabbar_control.rb +77 -0
  105. data/lib/ruflet_ui/ruflet/ui/controls/materials/tabbarview_control.rb +57 -0
  106. data/lib/ruflet_ui/ruflet/ui/controls/materials/tabs_control.rb +59 -0
  107. data/lib/ruflet_ui/ruflet/ui/controls/materials/textbutton_control.rb +66 -0
  108. data/lib/ruflet_ui/ruflet/ui/controls/materials/textfield_control.rb +148 -0
  109. data/lib/ruflet_ui/ruflet/ui/controls/materials/timepicker_control.rb +50 -0
  110. data/lib/ruflet_ui/ruflet/ui/controls/materials/verticaldivider_control.rb +36 -0
  111. data/lib/ruflet_ui/ruflet/ui/controls/materials/video_control.rb +39 -0
  112. data/lib/ruflet_ui/ruflet/ui/controls/materials/webview_control.rb +35 -0
  113. data/lib/ruflet_ui/ruflet/ui/controls/ruflet_controls.rb +447 -0
  114. data/lib/ruflet_ui/ruflet/ui/controls/shared/animatedswitcher_control.rb +59 -0
  115. data/lib/ruflet_ui/ruflet/ui/controls/shared/arc_control.rb +29 -0
  116. data/lib/ruflet_ui/ruflet/ui/controls/shared/autofillgroup_control.rb +32 -0
  117. data/lib/ruflet_ui/ruflet/ui/controls/shared/basepage_control.rb +44 -0
  118. data/lib/ruflet_ui/ruflet/ui/controls/shared/browsercontextmenu_control.rb +21 -0
  119. data/lib/ruflet_ui/ruflet/ui/controls/shared/canvas_control.rb +57 -0
  120. data/lib/ruflet_ui/ruflet/ui/controls/shared/circle_control.rb +25 -0
  121. data/lib/ruflet_ui/ruflet/ui/controls/shared/color_control.rb +23 -0
  122. data/lib/ruflet_ui/ruflet/ui/controls/shared/column_control.rb +67 -0
  123. data/lib/ruflet_ui/ruflet/ui/controls/shared/dialogs_control.rb +22 -0
  124. data/lib/ruflet_ui/ruflet/ui/controls/shared/dismissible_control.rb +66 -0
  125. data/lib/ruflet_ui/ruflet/ui/controls/shared/draggable_control.rb +39 -0
  126. data/lib/ruflet_ui/ruflet/ui/controls/shared/dragtarget_control.rb +36 -0
  127. data/lib/ruflet_ui/ruflet/ui/controls/shared/fill_control.rb +22 -0
  128. data/lib/ruflet_ui/ruflet/ui/controls/shared/fletapp_control.rb +62 -0
  129. data/lib/ruflet_ui/ruflet/ui/controls/shared/gesturedetector_control.rb +129 -0
  130. data/lib/ruflet_ui/ruflet/ui/controls/shared/gridview_control.rb +71 -0
  131. data/lib/ruflet_ui/ruflet/ui/controls/shared/hero_control.rb +56 -0
  132. data/lib/ruflet_ui/ruflet/ui/controls/shared/icon_control.rb +64 -0
  133. data/lib/ruflet_ui/ruflet/ui/controls/shared/image_control.rb +27 -0
  134. data/lib/ruflet_ui/ruflet/ui/controls/shared/interactiveviewer_control.rb +69 -0
  135. data/lib/ruflet_ui/ruflet/ui/controls/shared/keyboardlistener_control.rb +36 -0
  136. data/lib/ruflet_ui/ruflet/ui/controls/shared/line_control.rb +26 -0
  137. data/lib/ruflet_ui/ruflet/ui/controls/shared/listview_control.rb +71 -0
  138. data/lib/ruflet_ui/ruflet/ui/controls/shared/markdown_control.rb +70 -0
  139. data/lib/ruflet_ui/ruflet/ui/controls/shared/mergesemantics_control.rb +31 -0
  140. data/lib/ruflet_ui/ruflet/ui/controls/shared/oval_control.rb +26 -0
  141. data/lib/ruflet_ui/ruflet/ui/controls/shared/overlay_control.rb +22 -0
  142. data/lib/ruflet_ui/ruflet/ui/controls/shared/page_control.rb +74 -0
  143. data/lib/ruflet_ui/ruflet/ui/controls/shared/pagelet_control.rb +64 -0
  144. data/lib/ruflet_ui/ruflet/ui/controls/shared/pageview_control.rb +64 -0
  145. data/lib/ruflet_ui/ruflet/ui/controls/shared/path_control.rb +23 -0
  146. data/lib/ruflet_ui/ruflet/ui/controls/shared/placeholder_control.rb +58 -0
  147. data/lib/ruflet_ui/ruflet/ui/controls/shared/points_control.rb +24 -0
  148. data/lib/ruflet_ui/ruflet/ui/controls/shared/rect_control.rb +27 -0
  149. data/lib/ruflet_ui/ruflet/ui/controls/shared/reorderabledraghandle_control.rb +56 -0
  150. data/lib/ruflet_ui/ruflet/ui/controls/shared/responsiverow_control.rb +61 -0
  151. data/lib/ruflet_ui/ruflet/ui/controls/shared/row_control.rb +67 -0
  152. data/lib/ruflet_ui/ruflet/ui/controls/shared/ruflet_controls.rb +140 -0
  153. data/lib/ruflet_ui/ruflet/ui/controls/shared/safearea_control.rb +61 -0
  154. data/lib/ruflet_ui/ruflet/ui/controls/shared/semantics_control.rb +79 -0
  155. data/lib/ruflet_ui/ruflet/ui/controls/shared/serviceregistry_control.rb +21 -0
  156. data/lib/ruflet_ui/ruflet/ui/controls/shared/shadermask_control.rb +57 -0
  157. data/lib/ruflet_ui/ruflet/ui/controls/shared/shadow_control.rb +25 -0
  158. data/lib/ruflet_ui/ruflet/ui/controls/shared/shimmer_control.rb +60 -0
  159. data/lib/ruflet_ui/ruflet/ui/controls/shared/stack_control.rb +58 -0
  160. data/lib/ruflet_ui/ruflet/ui/controls/shared/text_control.rb +32 -0
  161. data/lib/ruflet_ui/ruflet/ui/controls/shared/textspan_control.rb +39 -0
  162. data/lib/ruflet_ui/ruflet/ui/controls/shared/transparentpointer_control.rb +54 -0
  163. data/lib/ruflet_ui/ruflet/ui/controls/shared/view_control.rb +77 -0
  164. data/lib/ruflet_ui/ruflet/ui/controls/shared/window_control.rb +56 -0
  165. data/lib/ruflet_ui/ruflet/ui/controls/shared/windowdragarea_control.rb +58 -0
  166. data/lib/ruflet_ui/ruflet/ui/cupertino_control_factory.rb +3 -30
  167. data/lib/ruflet_ui/ruflet/ui/material_control_factory.rb +3 -87
  168. data/lib/ruflet_ui/ruflet/ui/material_control_methods.rb +44 -2
  169. data/lib/ruflet_ui/ruflet/ui/material_control_registry.rb +26 -12
  170. data/lib/ruflet_ui/ruflet/ui/services/ruflet/accelerometer_control.rb +26 -0
  171. data/lib/ruflet_ui/ruflet/ui/services/ruflet/barometer_control.rb +26 -0
  172. data/lib/ruflet_ui/ruflet/ui/services/ruflet/battery_control.rb +22 -0
  173. data/lib/ruflet_ui/ruflet/ui/services/ruflet/camera_control.rb +58 -0
  174. data/lib/ruflet_ui/ruflet/ui/services/ruflet/clipboard_control.rb +21 -0
  175. data/lib/ruflet_ui/ruflet/ui/services/ruflet/connectivity_control.rb +22 -0
  176. data/lib/ruflet_ui/ruflet/ui/services/ruflet/filepicker_control.rb +23 -0
  177. data/lib/ruflet_ui/ruflet/ui/services/ruflet/flashlight_control.rb +22 -0
  178. data/lib/ruflet_ui/ruflet/ui/services/ruflet/gyroscope_control.rb +26 -0
  179. data/lib/ruflet_ui/ruflet/ui/services/ruflet/hapticfeedback_control.rb +21 -0
  180. data/lib/ruflet_ui/ruflet/ui/services/ruflet/magnetometer_control.rb +26 -0
  181. data/lib/ruflet_ui/ruflet/ui/services/ruflet/screenbrightness_control.rb +23 -0
  182. data/lib/ruflet_ui/ruflet/ui/services/ruflet/screenshot_control.rb +31 -0
  183. data/lib/ruflet_ui/ruflet/ui/services/ruflet/semanticsservice_control.rb +21 -0
  184. data/lib/ruflet_ui/ruflet/ui/services/ruflet/shakedetector_control.rb +26 -0
  185. data/lib/ruflet_ui/ruflet/ui/services/ruflet/share_control.rb +21 -0
  186. data/lib/ruflet_ui/ruflet/ui/services/ruflet/sharedpreferences_control.rb +21 -0
  187. data/lib/ruflet_ui/ruflet/ui/services/ruflet/storagepaths_control.rb +21 -0
  188. data/lib/ruflet_ui/ruflet/ui/services/ruflet/urllauncher_control.rb +21 -0
  189. data/lib/ruflet_ui/ruflet/ui/services/ruflet/useraccelerometer_control.rb +26 -0
  190. data/lib/ruflet_ui/ruflet/ui/services/ruflet/wakelock_control.rb +21 -0
  191. data/lib/ruflet_ui/ruflet/ui/services/ruflet_services.rb +66 -0
  192. data/lib/ruflet_ui/ruflet/ui/shared_control_forwarders.rb +41 -1
  193. data/lib/ruflet_ui/ruflet/ui/widget_builder.rb +9 -0
  194. data/lib/ruflet_ui.rb +14 -1
  195. metadata +178 -44
  196. data/lib/ruflet/manifest_compiler.rb +0 -62
  197. data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_action_sheet_control.rb +0 -13
  198. data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_alert_dialog_control.rb +0 -13
  199. data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_button_control.rb +0 -13
  200. data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_dialog_action_control.rb +0 -24
  201. data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_filled_button_control.rb +0 -13
  202. data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_navigation_bar_control.rb +0 -13
  203. data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_slider_control.rb +0 -13
  204. data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_switch_control.rb +0 -13
  205. data/lib/ruflet_ui/ruflet/ui/controls/cupertino/cupertino_text_field_control.rb +0 -13
  206. data/lib/ruflet_ui/ruflet/ui/controls/material/alert_dialog_control.rb +0 -13
  207. data/lib/ruflet_ui/ruflet/ui/controls/material/app_bar_control.rb +0 -13
  208. data/lib/ruflet_ui/ruflet/ui/controls/material/bottom_sheet_control.rb +0 -13
  209. data/lib/ruflet_ui/ruflet/ui/controls/material/button_control.rb +0 -24
  210. data/lib/ruflet_ui/ruflet/ui/controls/material/checkbox_control.rb +0 -13
  211. data/lib/ruflet_ui/ruflet/ui/controls/material/column_control.rb +0 -13
  212. data/lib/ruflet_ui/ruflet/ui/controls/material/container_control.rb +0 -13
  213. data/lib/ruflet_ui/ruflet/ui/controls/material/drag_target_control.rb +0 -13
  214. data/lib/ruflet_ui/ruflet/ui/controls/material/draggable_control.rb +0 -13
  215. data/lib/ruflet_ui/ruflet/ui/controls/material/elevated_button_control.rb +0 -13
  216. data/lib/ruflet_ui/ruflet/ui/controls/material/filled_button_control.rb +0 -13
  217. data/lib/ruflet_ui/ruflet/ui/controls/material/floating_action_button_control.rb +0 -28
  218. data/lib/ruflet_ui/ruflet/ui/controls/material/gesture_detector_control.rb +0 -13
  219. data/lib/ruflet_ui/ruflet/ui/controls/material/grid_view_control.rb +0 -13
  220. data/lib/ruflet_ui/ruflet/ui/controls/material/icon_button_control.rb +0 -13
  221. data/lib/ruflet_ui/ruflet/ui/controls/material/icon_control.rb +0 -24
  222. data/lib/ruflet_ui/ruflet/ui/controls/material/image_control.rb +0 -13
  223. data/lib/ruflet_ui/ruflet/ui/controls/material/markdown_control.rb +0 -13
  224. data/lib/ruflet_ui/ruflet/ui/controls/material/navigation_bar_control.rb +0 -13
  225. data/lib/ruflet_ui/ruflet/ui/controls/material/navigation_bar_destination_control.rb +0 -13
  226. data/lib/ruflet_ui/ruflet/ui/controls/material/radio_control.rb +0 -13
  227. data/lib/ruflet_ui/ruflet/ui/controls/material/radio_group_control.rb +0 -13
  228. data/lib/ruflet_ui/ruflet/ui/controls/material/row_control.rb +0 -13
  229. data/lib/ruflet_ui/ruflet/ui/controls/material/snack_bar_control.rb +0 -68
  230. data/lib/ruflet_ui/ruflet/ui/controls/material/stack_control.rb +0 -13
  231. data/lib/ruflet_ui/ruflet/ui/controls/material/tab_bar_control.rb +0 -13
  232. data/lib/ruflet_ui/ruflet/ui/controls/material/tab_bar_view_control.rb +0 -13
  233. data/lib/ruflet_ui/ruflet/ui/controls/material/tab_control.rb +0 -13
  234. data/lib/ruflet_ui/ruflet/ui/controls/material/tabs_control.rb +0 -63
  235. data/lib/ruflet_ui/ruflet/ui/controls/material/text_button_control.rb +0 -13
  236. data/lib/ruflet_ui/ruflet/ui/controls/material/text_control.rb +0 -13
  237. data/lib/ruflet_ui/ruflet/ui/controls/material/text_field_control.rb +0 -13
  238. data/lib/ruflet_ui/ruflet/ui/controls/material/view_control.rb +0 -13
@@ -4,28 +4,23 @@ require_relative "event"
4
4
  require "ruflet_protocol"
5
5
  require_relative "control"
6
6
  require_relative "ui/widget_builder"
7
+ require_relative "ui/control_factory"
7
8
  require_relative "icons/material_icon_lookup"
8
9
  require_relative "icons/cupertino_icon_lookup"
9
10
  require "set"
10
11
  require "cgi"
12
+ require "thread"
13
+ require "timeout"
11
14
 
12
15
  module Ruflet
13
16
  class Page
14
17
  PAGE_PROP_KEYS = %w[route title vertical_alignment horizontal_alignment scroll].freeze
15
18
  DIALOG_PROP_KEYS = %w[dialog snack_bar bottom_sheet].freeze
16
- BUTTON_TEXT_TYPES = %w[button elevatedbutton textbutton filledbutton].freeze
17
- DEPRECATED_PAGE_WIDGET_METHODS = %i[
18
- control widget view column center row stack container gesture_detector gesturedetector draggable
19
- drag_target dragtarget text button elevated_button elevatedbutton text_button textbutton filled_button
20
- filledbutton icon_button iconbutton text_field textfield checkbox radio radio_group radiogroup
21
- alert_dialog alertdialog markdown icon image app_bar appbar floating_action_button snack_bar snackbar
22
- bottom_sheet bottomsheet tabs tab tab_bar tabbar tab_bar_view tabbarview navigation_bar navigationbar
23
- navigation_bar_destination navigationbardestination fab cupertino_button
24
- cupertinobutton cupertino_filled_button cupertinofilledbutton cupertino_text_field cupertinotextfield
25
- cupertino_switch cupertinoswitch cupertino_slider cupertinoslider cupertino_alert_dialog
26
- cupertinoalertdialog cupertino_action_sheet cupertinoactionsheet cupertino_dialog_action
27
- cupertinodialogaction cupertino_navigation_bar cupertinonavigationbar
28
- ].freeze
19
+ WIDGET_HELPER_METHODS = (
20
+ Ruflet::UI::MaterialControlMethods.instance_methods(false) +
21
+ Ruflet::UI::CupertinoControlMethods.instance_methods(false) +
22
+ %i[control widget]
23
+ ).map(&:to_s).to_set.freeze
29
24
 
30
25
  attr_reader :session_id, :client_details, :views
31
26
 
@@ -51,13 +46,17 @@ module Ruflet
51
46
  @services_container = Ruflet::Control.new(
52
47
  type: "service_registry",
53
48
  id: "_services",
54
- "_services": []
49
+ "_services": [],
50
+ "_internals": { "uid" => Ruflet::Control.generate_id }
55
51
  )
56
52
  @dialogs_container = Ruflet::Control.new(
57
53
  type: "dialogs",
58
54
  id: "_dialogs",
59
55
  controls: []
60
56
  )
57
+ @invoke_waiters = {}
58
+ @invoke_callbacks = {}
59
+ @invoke_waiters_mutex = Mutex.new
61
60
  refresh_overlay_container!
62
61
  refresh_services_container!
63
62
  refresh_dialogs_container!
@@ -156,6 +155,45 @@ module Ruflet
156
155
  self
157
156
  end
158
157
 
158
+ def remove_service(*value)
159
+ targets = value.flatten.compact
160
+ return self if targets.empty?
161
+
162
+ @services_container.props["_services"] = services.reject do |service|
163
+ targets.any? do |target|
164
+ case target
165
+ when Control
166
+ service.equal?(target) || (!target.id.nil? && service.id.to_s == target.id.to_s)
167
+ else
168
+ needle = target.to_s
169
+ service.id.to_s == needle || service.type.to_s.downcase == needle.downcase
170
+ end
171
+ end
172
+ end
173
+
174
+ refresh_services_container!
175
+ push_services_update!
176
+ self
177
+ end
178
+
179
+ def service(type, **props)
180
+ mapped_props = normalize_props(props || {})
181
+ id = mapped_props.delete("id")
182
+ normalized_type = type.to_s.downcase
183
+
184
+ existing =
185
+ if id
186
+ services.find { |s| s.is_a?(Control) && s.id.to_s == id.to_s }
187
+ else
188
+ services.find { |s| s.is_a?(Control) && s.type.to_s.downcase == normalized_type }
189
+ end
190
+ return existing if existing
191
+
192
+ svc = Ruflet::UI::ControlFactory.build(type.to_s, id: id&.to_s, **mapped_props)
193
+ add_service(svc) unless services.include?(svc)
194
+ svc
195
+ end
196
+
159
197
  def go(route, **query_params)
160
198
  @page_props["route"] = build_route(route, query_params)
161
199
  dispatch_page_event(name: "route_change", data: @page_props["route"])
@@ -228,71 +266,327 @@ module Ruflet
228
266
  self
229
267
  end
230
268
 
231
- def invoke(control_or_id, method_name, args: nil, timeout: 10)
232
- control = resolve_control(control_or_id)
233
- return nil unless control
269
+ def invoke(control_or_id, method_name, args: nil, timeout: 10, on_result: nil)
270
+ control_id =
271
+ if page_control_target?(control_or_id)
272
+ 1
273
+ else
274
+ control = resolve_control(control_or_id)
275
+ return nil unless control
276
+ control.wire_id
277
+ end
234
278
 
235
279
  call_id = "call_#{Ruflet::Control.generate_id}"
236
- send_message(Protocol::ACTIONS[:invoke_control_method], {
237
- "control_id" => control.wire_id,
280
+ if on_result.respond_to?(:call)
281
+ @invoke_waiters_mutex.synchronize { @invoke_callbacks[call_id] = on_result }
282
+ unless timeout.nil?
283
+ Thread.new(call_id, timeout.to_f) do |pending_call_id, invoke_timeout|
284
+ sleep([invoke_timeout, 0.0].max + 0.1)
285
+ callback = @invoke_waiters_mutex.synchronize { @invoke_callbacks.delete(pending_call_id) }
286
+ callback&.call(nil, "execution expired")
287
+ rescue StandardError => e
288
+ Kernel.warn("invoke timeout callback error: #{e.class}: #{e.message}")
289
+ end
290
+ end
291
+ end
292
+ payload = {
293
+ "control_id" => control_id,
238
294
  "call_id" => call_id,
239
295
  "name" => method_name.to_s,
240
- "args" => args,
241
- "timeout" => timeout
242
- })
296
+ "args" => args
297
+ }
298
+ payload["timeout"] = timeout unless timeout.nil?
299
+ send_message(Protocol::ACTIONS[:invoke_control_method], payload)
243
300
 
244
301
  call_id
245
302
  end
246
303
 
247
- def launch_url(url, mode: "external_application", web_view_configuration: nil, browser_configuration: nil, web_only_window_name: nil, timeout: 10)
248
- launcher = ensure_url_launcher_service
304
+ # Synchronous invoke for controls/services that must return a value
305
+ # before continuing (e.g. picker selection, camera discovery/init).
306
+ def invoke_sync(control_or_id, method_name, args: nil, timeout: 10)
307
+ invoke_and_wait(control_or_id, method_name, args: args, timeout: timeout)
308
+ end
309
+
310
+ def launch_url(url, mode: nil, web_view_configuration: nil, browser_configuration: nil, web_only_window_name: nil, timeout: 10, on_result: nil)
311
+ url_launcher = ensure_url_launcher_service
312
+ args = { "url" => url }
313
+ args["mode"] = mode unless mode.nil?
314
+ args["web_view_configuration"] = web_view_configuration unless web_view_configuration.nil?
315
+ args["browser_configuration"] = browser_configuration unless browser_configuration.nil?
316
+ args["web_only_window_name"] = web_only_window_name unless web_only_window_name.nil?
249
317
  invoke(
250
- launcher,
318
+ url_launcher,
251
319
  "launch_url",
252
- args: {
253
- "url" => url,
254
- "mode" => mode,
255
- "web_view_configuration" => web_view_configuration,
256
- "browser_configuration" => browser_configuration,
257
- "web_only_window_name" => web_only_window_name
258
- }.compact,
259
- timeout: timeout
320
+ args: args,
321
+ timeout: timeout,
322
+ on_result: on_result
260
323
  )
261
324
  end
262
325
 
263
326
  def can_launch_url(url, timeout: 10)
264
- launcher = ensure_url_launcher_service
265
- invoke(launcher, "can_launch_url", args: { "url" => url }, timeout: timeout)
327
+ url_launcher = ensure_url_launcher_service
328
+ invoke(url_launcher, "can_launch_url", args: { "url" => url }, timeout: timeout)
329
+ end
330
+
331
+ # File picker helpers: create an ephemeral service, invoke method, and dispose it.
332
+ def pick_files(
333
+ dialog_title: nil,
334
+ initial_directory: nil,
335
+ file_type: "any",
336
+ allowed_extensions: nil,
337
+ allow_multiple: false,
338
+ with_data: false,
339
+ timeout: nil,
340
+ on_result: nil
341
+ )
342
+ invoke_file_picker(
343
+ "pick_files",
344
+ {
345
+ "dialog_title" => dialog_title,
346
+ "initial_directory" => initial_directory,
347
+ "file_type" => file_type,
348
+ "allowed_extensions" => allowed_extensions,
349
+ "allow_multiple" => allow_multiple,
350
+ "with_data" => with_data
351
+ },
352
+ timeout: timeout,
353
+ on_result: on_result
354
+ )
355
+ end
356
+
357
+ def save_file(
358
+ dialog_title: nil,
359
+ file_name: nil,
360
+ initial_directory: nil,
361
+ file_type: "any",
362
+ allowed_extensions: nil,
363
+ src_bytes: nil,
364
+ timeout: nil,
365
+ on_result: nil
366
+ )
367
+ invoke_file_picker(
368
+ "save_file",
369
+ {
370
+ "dialog_title" => dialog_title,
371
+ "file_name" => file_name,
372
+ "initial_directory" => initial_directory,
373
+ "file_type" => file_type,
374
+ "allowed_extensions" => allowed_extensions,
375
+ "src_bytes" => src_bytes
376
+ },
377
+ timeout: timeout,
378
+ on_result: on_result
379
+ )
380
+ end
381
+
382
+ def get_directory_path(dialog_title: nil, initial_directory: nil, timeout: nil, on_result: nil)
383
+ invoke_file_picker(
384
+ "get_directory_path",
385
+ {
386
+ "dialog_title" => dialog_title,
387
+ "initial_directory" => initial_directory
388
+ },
389
+ timeout: timeout,
390
+ on_result: on_result
391
+ )
392
+ end
393
+
394
+ def set_clipboard(value, timeout: nil, on_result: nil)
395
+ invoke_clipboard_method(
396
+ "set_data",
397
+ args: { "data" => value.to_s },
398
+ timeout: timeout,
399
+ on_result: on_result
400
+ )
401
+ end
402
+
403
+ def get_clipboard(timeout: nil, on_result: nil)
404
+ invoke_clipboard_method("get_data", timeout: timeout, on_result: on_result)
405
+ end
406
+
407
+ def set_clipboard_files(files, timeout: nil, on_result: nil)
408
+ invoke_clipboard_method(
409
+ "set_files",
410
+ args: { "files" => Array(files).map(&:to_s) },
411
+ timeout: timeout,
412
+ on_result: on_result
413
+ )
414
+ end
415
+
416
+ def get_clipboard_files(timeout: nil, on_result: nil)
417
+ invoke_clipboard_method("get_files", timeout: timeout, on_result: on_result)
418
+ end
419
+
420
+ def set_clipboard_image(value, timeout: nil, on_result: nil)
421
+ invoke_clipboard_method(
422
+ "set_image",
423
+ args: { "data" => value },
424
+ timeout: timeout,
425
+ on_result: on_result
426
+ )
427
+ end
428
+
429
+ def get_clipboard_image(timeout: nil, on_result: nil)
430
+ invoke_clipboard_method("get_image", timeout: timeout, on_result: on_result)
431
+ end
432
+
433
+ def get_connectivity(timeout: nil, on_result: nil)
434
+ invoke_connectivity_method("get_connectivity", timeout: timeout, on_result: on_result)
435
+ end
436
+
437
+ def get_battery_level(timeout: nil, on_result: nil)
438
+ invoke_battery_method("get_battery_level", timeout: timeout, on_result: on_result)
439
+ end
440
+
441
+ def get_battery_state(timeout: nil, on_result: nil)
442
+ invoke_battery_method("get_battery_state", timeout: timeout, on_result: on_result)
443
+ end
444
+
445
+ def battery_save_mode?(timeout: nil, on_result: nil)
446
+ invoke_battery_method("is_in_battery_save_mode", timeout: timeout, on_result: on_result)
266
447
  end
267
448
 
268
- def set_clipboard(value, timeout: 10)
269
- clipboard = ensure_clipboard_service
270
- invoke(clipboard, "set", args: { "data" => value.to_s }, timeout: timeout)
449
+ def get_application_cache_directory(timeout: nil, on_result: nil)
450
+ invoke_storage_paths("get_application_cache_directory", timeout: timeout, on_result: on_result)
271
451
  end
272
452
 
273
- def get_clipboard(timeout: 10)
274
- clipboard = ensure_clipboard_service
275
- invoke(clipboard, "get", timeout: timeout)
453
+ def get_application_documents_directory(timeout: nil, on_result: nil)
454
+ invoke_storage_paths("get_application_documents_directory", timeout: timeout, on_result: on_result)
276
455
  end
277
456
 
278
- def set_clipboard_files(files, timeout: 10)
279
- clipboard = ensure_clipboard_service
280
- invoke(clipboard, "set_files", args: { "files" => Array(files).map(&:to_s) }, timeout: timeout)
457
+ def get_application_support_directory(timeout: nil, on_result: nil)
458
+ invoke_storage_paths("get_application_support_directory", timeout: timeout, on_result: on_result)
459
+ end
460
+
461
+ def get_downloads_directory(timeout: nil, on_result: nil)
462
+ invoke_storage_paths("get_downloads_directory", timeout: timeout, on_result: on_result)
463
+ end
464
+
465
+ def get_external_cache_directories(timeout: nil, on_result: nil)
466
+ invoke_storage_paths("get_external_cache_directories", timeout: timeout, on_result: on_result)
467
+ end
468
+
469
+ def get_external_storage_directories(timeout: nil, on_result: nil)
470
+ invoke_storage_paths("get_external_storage_directories", timeout: timeout, on_result: on_result)
471
+ end
472
+
473
+ def get_library_directory(timeout: nil, on_result: nil)
474
+ invoke_storage_paths("get_library_directory", timeout: timeout, on_result: on_result)
475
+ end
476
+
477
+ def get_external_storage_directory(timeout: nil, on_result: nil)
478
+ invoke_storage_paths("get_external_storage_directory", timeout: timeout, on_result: on_result)
479
+ end
480
+
481
+ def get_temporary_directory(timeout: nil, on_result: nil)
482
+ invoke_storage_paths("get_temporary_directory", timeout: timeout, on_result: on_result)
483
+ end
484
+
485
+ def get_console_log_filename(timeout: nil, on_result: nil)
486
+ invoke_storage_paths("get_console_log_filename", timeout: timeout, on_result: on_result)
487
+ end
488
+
489
+ def share_text(
490
+ text:,
491
+ title: nil,
492
+ subject: nil,
493
+ preview_thumbnail: nil,
494
+ share_position_origin: nil,
495
+ download_fallback_enabled: true,
496
+ mail_to_fallback_enabled: true,
497
+ excluded_cupertino_activities: nil,
498
+ timeout: nil,
499
+ on_result: nil
500
+ )
501
+ share = ensure_share_service
502
+ invoke(
503
+ share,
504
+ "share_text",
505
+ args: {
506
+ "text" => text,
507
+ "title" => title,
508
+ "subject" => subject,
509
+ "preview_thumbnail" => preview_thumbnail,
510
+ "share_position_origin" => share_position_origin,
511
+ "download_fallback_enabled" => download_fallback_enabled,
512
+ "mail_to_fallback_enabled" => mail_to_fallback_enabled,
513
+ "excluded_cupertino_activities" => excluded_cupertino_activities
514
+ },
515
+ timeout: timeout,
516
+ on_result: on_result
517
+ )
281
518
  end
282
519
 
283
- def get_clipboard_files(timeout: 10)
284
- clipboard = ensure_clipboard_service
285
- invoke(clipboard, "get_files", timeout: timeout)
520
+ def share_uri(
521
+ uri:,
522
+ share_position_origin: nil,
523
+ excluded_cupertino_activities: nil,
524
+ timeout: nil,
525
+ on_result: nil
526
+ )
527
+ share = ensure_share_service
528
+ invoke(
529
+ share,
530
+ "share_uri",
531
+ args: {
532
+ "uri" => uri,
533
+ "share_position_origin" => share_position_origin,
534
+ "excluded_cupertino_activities" => excluded_cupertino_activities
535
+ },
536
+ timeout: timeout,
537
+ on_result: on_result
538
+ )
286
539
  end
287
540
 
288
- def set_clipboard_image(value, timeout: 10)
289
- clipboard = ensure_clipboard_service
290
- invoke(clipboard, "set_image", args: { "data" => value }, timeout: timeout)
541
+ def share_files(
542
+ files:,
543
+ text: nil,
544
+ title: nil,
545
+ subject: nil,
546
+ preview_thumbnail: nil,
547
+ share_position_origin: nil,
548
+ download_fallback_enabled: true,
549
+ mail_to_fallback_enabled: true,
550
+ excluded_cupertino_activities: nil,
551
+ timeout: nil,
552
+ on_result: nil
553
+ )
554
+ share = ensure_share_service
555
+ invoke(
556
+ share,
557
+ "share_files",
558
+ args: {
559
+ "files" => files,
560
+ "text" => text,
561
+ "title" => title,
562
+ "subject" => subject,
563
+ "preview_thumbnail" => preview_thumbnail,
564
+ "share_position_origin" => share_position_origin,
565
+ "download_fallback_enabled" => download_fallback_enabled,
566
+ "mail_to_fallback_enabled" => mail_to_fallback_enabled,
567
+ "excluded_cupertino_activities" => excluded_cupertino_activities
568
+ },
569
+ timeout: timeout,
570
+ on_result: on_result
571
+ )
291
572
  end
292
573
 
293
- def get_clipboard_image(timeout: 10)
294
- clipboard = ensure_clipboard_service
295
- invoke(clipboard, "get_image", timeout: timeout)
574
+ def handle_invoke_method_result(payload)
575
+ call_id = payload["call_id"].to_s
576
+ waiter = @invoke_waiters_mutex.synchronize { @invoke_waiters[call_id] }
577
+ if waiter
578
+ waiter << payload
579
+ return true
580
+ end
581
+
582
+ callback = @invoke_waiters_mutex.synchronize { @invoke_callbacks.delete(call_id) }
583
+ return false unless callback
584
+
585
+ callback.call(payload["result"], payload["error"])
586
+ true
587
+ rescue StandardError => e
588
+ Kernel.warn("invoke callback error: #{e.class}: #{e.message}")
589
+ false
296
590
  end
297
591
 
298
592
  def pop_dialog
@@ -319,19 +613,32 @@ module Ruflet
319
613
 
320
614
  control = resolve_control(control_or_id)
321
615
  return self unless control
616
+ wire_id = control.wire_id
617
+ if wire_id.nil?
618
+ # Events can race with navigation/disposal; never emit patch_control with nil id.
619
+ refresh_control_indexes!
620
+ wire_id = control.wire_id
621
+ end
622
+ return self if wire_id.nil?
322
623
 
323
624
  patch = normalize_props(props)
324
- if BUTTON_TEXT_TYPES.include?(control.type) && patch.key?("text")
625
+ if text_maps_to_content?(control, patch)
325
626
  patch["content"] = patch.delete("text")
326
627
  end
327
628
 
629
+ # Keep runtime control tree aligned with incremental patches.
630
+ if patch.key?("controls")
631
+ control.children.clear
632
+ Array(patch["controls"]).each { |child| control.children << child if child.is_a?(Control) }
633
+ end
634
+
328
635
  visited = Set.new
329
636
  patch.each_value { |value| register_embedded_value(value, visited) }
330
637
 
331
638
  patch_ops = patch.map { |k, v| [0, 0, k, serialize_patch_value(v)] }
332
639
 
333
640
  send_message(Protocol::ACTIONS[:patch_control], {
334
- "id" => control.wire_id,
641
+ "id" => wire_id,
335
642
  "patch" => [[0], *patch_ops]
336
643
  })
337
644
 
@@ -380,8 +687,7 @@ module Ruflet
380
687
  prop_name = method_name.delete_suffix("=")
381
688
 
382
689
  if method_name.end_with?("=")
383
- if DEPRECATED_PAGE_WIDGET_METHODS.include?(prop_name.to_sym)
384
- Kernel.warn("[DEPRECATION] `page.#{prop_name}(...)` is no longer supported.")
690
+ if widget_helper_method?(prop_name)
385
691
  raise NoMethodError, "Use `#{prop_name}(...)` as a free widget helper, then attach with `page.add(...)`."
386
692
  end
387
693
  assign_split_prop(prop_name, normalize_value(prop_name, args.first))
@@ -394,8 +700,7 @@ module Ruflet
394
700
  return instance_variable_get("@#{method_name}") if DIALOG_PROP_KEYS.include?(method_name)
395
701
  end
396
702
 
397
- if DEPRECATED_PAGE_WIDGET_METHODS.include?(name.to_sym)
398
- Kernel.warn("[DEPRECATION] `page.#{name}(...)` is no longer supported.")
703
+ if widget_helper_method?(name)
399
704
  raise NoMethodError, "Use `#{name}(...)` as a free widget helper, then attach with `page.add(...)`."
400
705
  end
401
706
 
@@ -405,8 +710,8 @@ module Ruflet
405
710
  def respond_to_missing?(name, include_private = false)
406
711
  method_name = name.to_s
407
712
  prop_name = method_name.delete_suffix("=")
408
- DEPRECATED_PAGE_WIDGET_METHODS.include?(name.to_sym) ||
409
- DEPRECATED_PAGE_WIDGET_METHODS.include?(prop_name.to_sym) ||
713
+ widget_helper_method?(name) ||
714
+ widget_helper_method?(prop_name) ||
410
715
  method_name.end_with?("=") ||
411
716
  @page_props.key?(method_name) ||
412
717
  @view_props.key?(method_name) ||
@@ -416,8 +721,47 @@ module Ruflet
416
721
 
417
722
  private
418
723
 
724
+ def invoke_and_wait(control_or_id, method_name, args: nil, timeout: 10)
725
+ control_id =
726
+ if page_control_target?(control_or_id)
727
+ 1
728
+ else
729
+ control = resolve_control(control_or_id)
730
+ return nil unless control
731
+ control.wire_id
732
+ end
733
+
734
+ call_id = "call_#{Ruflet::Control.generate_id}"
735
+ waiter = Queue.new
736
+ @invoke_waiters_mutex.synchronize { @invoke_waiters[call_id] = waiter }
737
+
738
+ send_message(Protocol::ACTIONS[:invoke_control_method], {
739
+ "control_id" => control_id,
740
+ "call_id" => call_id,
741
+ "name" => method_name.to_s,
742
+ "args" => args,
743
+ "timeout" => timeout
744
+ })
745
+
746
+ response = Timeout.timeout(timeout.to_f) { waiter.pop }
747
+ error = response["error"]
748
+ raise RuntimeError, error if error && !error.to_s.empty?
749
+
750
+ response["result"]
751
+ ensure
752
+ @invoke_waiters_mutex.synchronize { @invoke_waiters.delete(call_id) } if call_id
753
+ end
754
+
419
755
  def build_widget(type, **props, &block) = WidgetBuilder.new.control(type, **props, &block)
420
756
 
757
+ def widget_helper_method?(name)
758
+ WIDGET_HELPER_METHODS.include?(name.to_s)
759
+ end
760
+
761
+ def text_maps_to_content?(control, patch)
762
+ patch.key?("text") && control.type.end_with?("button")
763
+ end
764
+
421
765
  def split_props(props)
422
766
  props.each do |k, v|
423
767
  assign_split_prop(k, v)
@@ -515,6 +859,7 @@ module Ruflet
515
859
  def normalize_props(hash)
516
860
  hash.each_with_object({}) do |(k, v), result|
517
861
  key = k.to_s
862
+ key = "controls" if key == "children"
518
863
  result[key] = normalize_value(key, v)
519
864
  end
520
865
  end
@@ -663,7 +1008,15 @@ module Ruflet
663
1008
  end
664
1009
 
665
1010
  def build_page_patch_ops
666
- @page_props.map { |k, v| [0, 0, k, serialize_patch_value(v)] }
1011
+ @page_props.filter_map do |k, v|
1012
+ # Keep internal containers stable after initial mount.
1013
+ # Re-sending them as full objects can replace Control instances with
1014
+ # same IDs and detach service invoke listeners on the Flutter side.
1015
+ next nil if k == "_overlay" && @overlay_container.wire_id
1016
+ next nil if k == "_dialogs" && @dialogs_container.wire_id
1017
+
1018
+ [0, 0, k, serialize_patch_value(v)]
1019
+ end
667
1020
  end
668
1021
 
669
1022
  def resolve_icon_codepoint(value)
@@ -674,22 +1027,196 @@ module Ruflet
674
1027
  codepoint
675
1028
  end
676
1029
 
677
- def ensure_url_launcher_service
678
- launcher = services.find { |service| service.is_a?(Control) && service.type == "url_launcher" }
679
- return launcher if launcher
680
-
681
- launcher = build_widget(:url_launcher)
682
- add_service(launcher)
683
- launcher
684
- end
685
-
686
1030
  def ensure_clipboard_service
687
1031
  clipboard = services.find { |service| service.is_a?(Control) && service.type == "clipboard" }
688
- return clipboard if clipboard
1032
+ return [clipboard, false] if clipboard
689
1033
 
690
1034
  clipboard = build_widget(:clipboard)
691
1035
  add_service(clipboard)
692
- clipboard
1036
+ [clipboard, true]
1037
+ end
1038
+
1039
+ def invoke_clipboard_method(method_name, args: nil, timeout:, on_result:)
1040
+ clipboard, created = ensure_clipboard_service
1041
+ send_view_patch if created
1042
+ sleep(0.05) if created
1043
+ invoke(
1044
+ clipboard,
1045
+ method_name,
1046
+ args: args,
1047
+ timeout: timeout,
1048
+ on_result: lambda { |result, error|
1049
+ message = error.to_s
1050
+ if message.include?("inexistent control")
1051
+ remove_service(clipboard)
1052
+ fresh_clipboard, = ensure_clipboard_service
1053
+ sleep(0.08)
1054
+ invoke(
1055
+ fresh_clipboard,
1056
+ method_name,
1057
+ args: args,
1058
+ timeout: timeout,
1059
+ on_result: on_result
1060
+ )
1061
+ else
1062
+ on_result&.call(result, error)
1063
+ end
1064
+ }
1065
+ )
1066
+ rescue StandardError => e
1067
+ on_result&.call(nil, e.message)
1068
+ end
1069
+
1070
+ def ensure_url_launcher_service
1071
+ url_launcher = services.find { |service| service.is_a?(Control) && %w[urllauncher url_launcher].include?(service.type) }
1072
+ return url_launcher if url_launcher
1073
+
1074
+ url_launcher = build_widget(:url_launcher)
1075
+ add_service(url_launcher)
1076
+ url_launcher
1077
+ end
1078
+
1079
+ def ensure_connectivity_service
1080
+ connectivity = services.find { |service| service.is_a?(Control) && service.type == "connectivity" }
1081
+ return [connectivity, false] if connectivity
1082
+
1083
+ connectivity = build_widget(:connectivity)
1084
+ add_service(connectivity)
1085
+ [connectivity, true]
1086
+ end
1087
+
1088
+ def invoke_connectivity_method(method_name, timeout:, on_result:)
1089
+ connectivity, created = ensure_connectivity_service
1090
+ send_view_patch if created
1091
+ sleep(0.05) if created
1092
+ invoke(
1093
+ connectivity,
1094
+ method_name,
1095
+ timeout: timeout,
1096
+ on_result: lambda { |result, error|
1097
+ message = error.to_s
1098
+ if message.include?("inexistent control")
1099
+ remove_service(connectivity)
1100
+ fresh_connectivity, = ensure_connectivity_service
1101
+ sleep(0.08)
1102
+ invoke(
1103
+ fresh_connectivity,
1104
+ method_name,
1105
+ timeout: timeout,
1106
+ on_result: on_result
1107
+ )
1108
+ else
1109
+ on_result&.call(result, error)
1110
+ end
1111
+ }
1112
+ )
1113
+ rescue StandardError => e
1114
+ on_result&.call(nil, e.message)
1115
+ end
1116
+
1117
+ def ensure_battery_service
1118
+ battery = services.find { |service| service.is_a?(Control) && service.type == "battery" }
1119
+ return [battery, false] if battery
1120
+
1121
+ battery = build_widget(:battery)
1122
+ add_service(battery)
1123
+ [battery, true]
1124
+ end
1125
+
1126
+ def ensure_share_service
1127
+ share = services.find { |service| service.is_a?(Control) && service.type == "share" }
1128
+ return share if share
1129
+
1130
+ share = build_widget(:share)
1131
+ add_service(share)
1132
+ share
1133
+ end
1134
+
1135
+ def invoke_battery_method(method_name, timeout:, on_result:)
1136
+ battery, created = ensure_battery_service
1137
+ send_view_patch if created
1138
+ sleep(0.05) if created
1139
+ invoke(
1140
+ battery,
1141
+ method_name,
1142
+ timeout: timeout,
1143
+ on_result: lambda { |result, error|
1144
+ message = error.to_s
1145
+ if message.include?("inexistent control")
1146
+ remove_service(battery)
1147
+ fresh_battery, = ensure_battery_service
1148
+ sleep(0.08)
1149
+ invoke(
1150
+ fresh_battery,
1151
+ method_name,
1152
+ timeout: timeout,
1153
+ on_result: on_result
1154
+ )
1155
+ else
1156
+ on_result&.call(result, error)
1157
+ end
1158
+ }
1159
+ )
1160
+ rescue StandardError => e
1161
+ on_result&.call(nil, e.message)
1162
+ end
1163
+
1164
+ def ensure_storage_paths_service
1165
+ storage_paths = services.find do |service|
1166
+ service.is_a?(Control) && %w[storage_paths storagepaths].include?(service.type.to_s)
1167
+ end
1168
+ return [storage_paths, false] if storage_paths
1169
+
1170
+ storage_paths = build_widget(:storage_paths)
1171
+ add_service(storage_paths)
1172
+ [storage_paths, true]
1173
+ end
1174
+
1175
+ def invoke_storage_paths(method_name, timeout:, on_result:)
1176
+ storage_paths, created = ensure_storage_paths_service
1177
+ send_view_patch if created
1178
+ sleep(0.05) if created
1179
+ invoke(
1180
+ storage_paths,
1181
+ method_name,
1182
+ timeout: timeout,
1183
+ on_result: lambda { |result, error|
1184
+ message = error.to_s
1185
+ if message.include?("inexistent control")
1186
+ remove_service(storage_paths)
1187
+ fresh_storage_paths, = ensure_storage_paths_service
1188
+ sleep(0.08)
1189
+ invoke(
1190
+ fresh_storage_paths,
1191
+ method_name,
1192
+ timeout: timeout,
1193
+ on_result: on_result
1194
+ )
1195
+ else
1196
+ on_result&.call(result, error)
1197
+ end
1198
+ }
1199
+ )
1200
+ rescue StandardError => e
1201
+ on_result&.call(nil, e.message)
1202
+ end
1203
+
1204
+ def invoke_file_picker(method_name, args, timeout:, on_result:)
1205
+ picker = build_widget(:file_picker)
1206
+ add_service(picker)
1207
+ invoke(
1208
+ picker,
1209
+ method_name,
1210
+ args: args,
1211
+ timeout: timeout,
1212
+ on_result: lambda { |result, error|
1213
+ remove_service(picker)
1214
+ on_result&.call(result, error)
1215
+ }
1216
+ )
1217
+ rescue StandardError => e
1218
+ remove_service(picker) if picker
1219
+ on_result&.call(nil, e.message)
693
1220
  end
694
1221
  end
695
1222
  end