Class: Ruflet::Page

Inherits:
Object
  • Object
show all
Defined in:
lib/ruflet_ui/ruflet/page.rb

Defined Under Namespace

Classes: FlashlightService, ScreenBrightnessService, SharedPreferencesService, WakelockService

Constant Summary collapse

PAGE_PROP_KEYS =
%w[dark_theme fonts route rtl show_semantics_debugger theme theme_mode theme_animation_style title vertical_alignment horizontal_alignment scroll].freeze
DIALOG_PROP_KEYS =
%w[dialog snack_bar bottom_sheet].freeze
PAGE_ADD_RESERVED_KEYS =
%i[appbar bottom_appbar floating_action_button navigation_bar dialog snack_bar bottom_sheet].freeze
WIDGET_HELPER_METHODS =
(
  Ruflet::UI::MaterialControlMethods.instance_methods(false) +
  Ruflet::UI::CupertinoControlMethods.instance_methods(false) +
  %i[control widget]
).map(&:to_s).uniq.freeze
RELOAD_PRESERVED_PAGE_PROPS =

Page props that must survive a hot reload: the route (kept on purpose), the client-reported window, and the internal overlay/services/dialogs containers (kept mounted so the client keeps their bindings).

%w[route window _overlay _dialogs _services].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(session_id:, client_details:, sender:) ⇒ Page

Returns a new instance of Page.



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/ruflet_ui/ruflet/page.rb', line 163

def initialize(session_id:, client_details:, sender:)
  @session_id = session_id
  @client_details = client_details
  @sender = sender
  @control_index = {}
  @wire_index = {}
  @next_wire_id = 100
  @view_id = 20
  @root_controls = []
  @views = []
  @dialogs = []
  @overlay_container_mounted = false
  @dialogs_container_mounted = false
  @services_container_mounted = false
  @visual_service_controls = {}
  @page_event_handlers = {}
  @view_props = {}
  @page_props = { "route" => (client_details["route"] || "/") }
  @window = build_client_window(client_details["window"])
  @page_props["window"] = @window
  @overlay_container = Ruflet::Control.new(
    type: "overlay",
    id: "_overlay",
    controls: []
  )
  @services_container = Ruflet::Control.new(
    type: "service_registry",
    id: "_services",
    "_services": [],
    "_internals": { "uid" => Ruflet::Control.generate_id }
  )
  @dialogs_container = Ruflet::Control.new(
    type: "dialogs",
    id: "_dialogs",
    controls: []
  )
  @invoke_waiters = {}
  @invoke_callbacks = {}
  @invoke_waiters_mutex = Mutex.new
  @shared_preferences_proxy = SharedPreferencesService.new(self)
  @wakelock_proxy = WakelockService.new(self)
  @flashlight_proxy = FlashlightService.new(self)
  @screen_brightness_proxy = ScreenBrightnessService.new(self)
  refresh_overlay_container!
  refresh_services_container!
  refresh_dialogs_container!
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object



1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
# File 'lib/ruflet_ui/ruflet/page.rb', line 1373

def method_missing(name, *args, &block)
  method_name = name.to_s
  prop_name = method_name.delete_suffix("=")

  if method_name.end_with?("=")
    if widget_helper_method?(prop_name)
      raise NoMethodError, "Use `#{prop_name}(...)` as a free widget helper, then attach with `page.add(...)`."
    end
    assign_split_prop(prop_name, normalize_value(prop_name, args.first))
    return args.first
  end

  if args.empty? && !block
    return @page_props[method_name] if @page_props.key?(method_name)
    return @view_props[method_name] if @view_props.key?(method_name)
    # Client-reported page properties (width, height, platform,
    # platform_brightness, media) arrive in the register payload.
    return @client_details[method_name] if @client_details.respond_to?(:key?) && @client_details.key?(method_name)
    return instance_variable_get("@#{method_name}") if DIALOG_PROP_KEYS.include?(method_name)
  end

  if widget_helper_method?(name)
    raise NoMethodError, "Use `#{name}(...)` as a free widget helper, then attach with `page.add(...)`."
  end

  super
end

Instance Attribute Details

#client_detailsObject (readonly)

Returns the value of attribute client_details.



161
162
163
# File 'lib/ruflet_ui/ruflet/page.rb', line 161

def client_details
  @client_details
end

#session_idObject (readonly)

Returns the value of attribute session_id.



161
162
163
# File 'lib/ruflet_ui/ruflet/page.rb', line 161

def session_id
  @session_id
end

#viewsObject

Returns the value of attribute views.



161
162
163
# File 'lib/ruflet_ui/ruflet/page.rb', line 161

def views
  @views
end

#windowObject (readonly)

Returns the value of attribute window.



161
162
163
# File 'lib/ruflet_ui/ruflet/page.rb', line 161

def window
  @window
end

Instance Method Details

#accelerometer(**props) ⇒ Object



1055
1056
1057
# File 'lib/ruflet_ui/ruflet/page.rb', line 1055

def accelerometer(**props)
  service(:accelerometer, **props)
end

#add(control) ⇒ Object

Raises:

  • (ArgumentError)


260
261
262
263
264
265
266
267
268
269
# File 'lib/ruflet_ui/ruflet/page.rb', line 260

def add(control)
  if control.is_a?(Hash) && (control.keys.map(&:to_sym) & PAGE_ADD_RESERVED_KEYS).any?
    raise ArgumentError, "Page#add accepts only controls; assign page.appbar, page.floating_action_button, dialogs, or other page properties before calling add"
  end

  raise ArgumentError, "Page#add accepts exactly one control" unless control.is_a?(Control)

  replace_root_controls(@root_controls + [control])
  self
end

#add_service(*value) ⇒ Object



450
451
452
453
454
455
# File 'lib/ruflet_ui/ruflet/page.rb', line 450

def add_service(*value)
  @services_container.props["_services"] = services + value.flatten.compact
  refresh_services_container!
  push_services_update!
  self
end

#appbarObject



564
565
566
# File 'lib/ruflet_ui/ruflet/page.rb', line 564

def appbar
  @view_props["appbar"]
end

#appbar=(value) ⇒ Object



568
569
570
# File 'lib/ruflet_ui/ruflet/page.rb', line 568

def appbar=(value)
  @view_props["appbar"] = value
end

#apply_client_update(control_or_id, props) ⇒ Object



1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
# File 'lib/ruflet_ui/ruflet/page.rb', line 1338

def apply_client_update(control_or_id, props)
  control = resolve_control(control_or_id)
  return self unless control

  patch = normalize_props(props || {})
  patch.each { |k, v| control.props[k] = v }

  remove_dialog_tracking(control) if patch.key?("open") && patch["open"] == false

  self
end

#audio(**props) ⇒ Object



442
443
444
# File 'lib/ruflet_ui/ruflet/page.rb', line 442

def audio(**props)
  service(:audio, **props)
end

#audio_recorder(**props) ⇒ Object



446
447
448
# File 'lib/ruflet_ui/ruflet/page.rb', line 446

def audio_recorder(**props)
  service(:audio_recorder, **props)
end

#auto_scrollObject



600
601
602
# File 'lib/ruflet_ui/ruflet/page.rb', line 600

def auto_scroll
  @view_props["auto_scroll"]
end

#auto_scroll=(value) ⇒ Object



604
605
606
# File 'lib/ruflet_ui/ruflet/page.rb', line 604

def auto_scroll=(value)
  @view_props["auto_scroll"] = value
end

#barometer(**props) ⇒ Object



1071
1072
1073
# File 'lib/ruflet_ui/ruflet/page.rb', line 1071

def barometer(**props)
  service(:barometer, **props)
end

#battery(**props) ⇒ Object



1099
1100
1101
# File 'lib/ruflet_ui/ruflet/page.rb', line 1099

def battery(**props)
  service(:battery, **props)
end

#battery_save_mode?(timeout: nil, on_result: nil) ⇒ Boolean

Returns:

  • (Boolean)


1051
1052
1053
# File 'lib/ruflet_ui/ruflet/page.rb', line 1051

def battery_save_mode?(timeout: nil, on_result: nil)
  is_in_battery_save_mode(timeout: timeout, on_result: on_result)
end

#bgcolorObject



252
253
254
# File 'lib/ruflet_ui/ruflet/page.rb', line 252

def bgcolor
  @view_props["bgcolor"]
end

#bgcolor=(value) ⇒ Object



256
257
258
# File 'lib/ruflet_ui/ruflet/page.rb', line 256

def bgcolor=(value)
  @view_props["bgcolor"] = normalize_value("bgcolor", value)
end

#bottom_appbarObject



572
573
574
# File 'lib/ruflet_ui/ruflet/page.rb', line 572

def bottom_appbar
  @view_props["bottom_appbar"]
end

#bottom_appbar=(value) ⇒ Object



576
577
578
# File 'lib/ruflet_ui/ruflet/page.rb', line 576

def bottom_appbar=(value)
  @view_props["bottom_appbar"] = value
end

#bottom_sheetObject



700
701
702
# File 'lib/ruflet_ui/ruflet/page.rb', line 700

def bottom_sheet
  @bottom_sheet
end

#bottom_sheet=(value) ⇒ Object



694
695
696
697
698
# File 'lib/ruflet_ui/ruflet/page.rb', line 694

def bottom_sheet=(value)
  @bottom_sheet = value
  refresh_dialogs_container!
  push_dialogs_update! if @dialogs_container_mounted
end

#bottomappbar=(value) ⇒ Object



580
581
582
# File 'lib/ruflet_ui/ruflet/page.rb', line 580

def bottomappbar=(value)
  self.bottom_appbar = value
end

#bottomsheet=(value) ⇒ Object



704
705
706
# File 'lib/ruflet_ui/ruflet/page.rb', line 704

def bottomsheet=(value)
  self.bottom_sheet = value
end

#browser_context_menuObject



608
609
610
# File 'lib/ruflet_ui/ruflet/page.rb', line 608

def browser_context_menu
  @view_props["browser_context_menu"]
end

#browser_context_menu=(value) ⇒ Object



612
613
614
# File 'lib/ruflet_ui/ruflet/page.rb', line 612

def browser_context_menu=(value)
  @view_props["browser_context_menu"] = value
end

#camera(**props) ⇒ Object



1127
1128
1129
# File 'lib/ruflet_ui/ruflet/page.rb', line 1127

def camera(**props)
  service(:camera, **props)
end

#can_launch_url(url, timeout: 10) ⇒ Object



871
872
873
874
# File 'lib/ruflet_ui/ruflet/page.rb', line 871

def can_launch_url(url, timeout: 10)
  url_launcher = ensure_url_launcher_service
  invoke(url_launcher, "can_launch_url", args: { "url" => url }, timeout: timeout)
end

#cleanObject



298
299
300
301
# File 'lib/ruflet_ui/ruflet/page.rb', line 298

def clean
  replace_root_controls([])
  self
end

#clipboard(**props) ⇒ Object



1107
1108
1109
# File 'lib/ruflet_ui/ruflet/page.rb', line 1107

def clipboard(**props)
  service(:clipboard, **props)
end

#close(dialog_control = nil) ⇒ Object



760
761
762
# File 'lib/ruflet_ui/ruflet/page.rb', line 760

def close(dialog_control = nil)
  close_dialog(dialog_control)
end

#close_banner(banner_control = nil) ⇒ Object



750
751
752
# File 'lib/ruflet_ui/ruflet/page.rb', line 750

def close_banner(banner_control = nil)
  close_dialog(banner_control)
end

#close_bottom_sheet(bottom_sheet_control = nil) ⇒ Object



738
739
740
# File 'lib/ruflet_ui/ruflet/page.rb', line 738

def close_bottom_sheet(bottom_sheet_control = nil)
  close_dialog(bottom_sheet_control || @bottom_sheet)
end

#close_bottomsheet(bottom_sheet_control = nil) ⇒ Object



742
743
744
# File 'lib/ruflet_ui/ruflet/page.rb', line 742

def close_bottomsheet(bottom_sheet_control = nil)
  close_bottom_sheet(bottom_sheet_control)
end

#close_dialog(dialog_control = nil) ⇒ Object



764
765
766
767
768
769
770
771
772
773
774
775
776
# File 'lib/ruflet_ui/ruflet/page.rb', line 764

def close_dialog(dialog_control = nil)
  target = dialog_control || latest_open_dialog
  return nil unless target

  clear_dialog_slots(target)
  # The client pops a dialog's route only when the mounted control sees
  # its `open` prop flip to false (alert_dialog.dart tracks open/_open).
  # Replacing the dialogs container recreates the control client-side and
  # the transition is lost, so patch the open prop on the control itself.
  target.props["open"] = false
  update(target, open: false)
  target
end

#close_drawer(timeout: 10, on_result: nil) ⇒ Object



784
785
786
# File 'lib/ruflet_ui/ruflet/page.rb', line 784

def close_drawer(timeout: 10, on_result: nil)
  invoke(:page, "close_drawer", timeout: timeout, on_result: on_result)
end

#close_end_drawer(timeout: 10, on_result: nil) ⇒ Object



794
795
796
# File 'lib/ruflet_ui/ruflet/page.rb', line 794

def close_end_drawer(timeout: 10, on_result: nil)
  invoke(:page, "close_end_drawer", timeout: timeout, on_result: on_result)
end

#close_in_app_web_view(timeout: 10, on_result: nil) ⇒ Object



876
877
878
879
# File 'lib/ruflet_ui/ruflet/page.rb', line 876

def close_in_app_web_view(timeout: 10, on_result: nil)
  url_launcher = ensure_url_launcher_service
  invoke(url_launcher, "close_in_app_web_view", timeout: timeout, on_result: on_result)
end

#connectivity(**props) ⇒ Object



1103
1104
1105
# File 'lib/ruflet_ui/ruflet/page.rb', line 1103

def connectivity(**props)
  service(:connectivity, **props)
end

#controlsObject



271
272
273
# File 'lib/ruflet_ui/ruflet/page.rb', line 271

def controls
  @root_controls
end

#controls=(value) ⇒ Object



275
276
277
278
# File 'lib/ruflet_ui/ruflet/page.rb', line 275

def controls=(value)
  replace_root_controls(Array(value).flatten.compact)
  self
end

#decorationObject



616
617
618
# File 'lib/ruflet_ui/ruflet/page.rb', line 616

def decoration
  @view_props["decoration"]
end

#decoration=(value) ⇒ Object



620
621
622
# File 'lib/ruflet_ui/ruflet/page.rb', line 620

def decoration=(value)
  @view_props["decoration"] = value
end

#dialogObject



672
# File 'lib/ruflet_ui/ruflet/page.rb', line 672

def dialog = @dialog

#dialog=(value) ⇒ Object



674
675
676
677
678
# File 'lib/ruflet_ui/ruflet/page.rb', line 674

def dialog=(value)
  @dialog = value
  refresh_dialogs_container!
  push_dialogs_update! if @dialogs_container_mounted
end

#dispatch_event(target:, name:, data:) ⇒ Object



1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
# File 'lib/ruflet_ui/ruflet/page.rb', line 1350

def dispatch_event(target:, name:, data:)
  if page_control_target?(target)
    if name.to_s == "route_change"
      route_from_event = extract_route(data)
      return if route_from_event && route_from_event == @page_props["route"]
      @page_props["route"] = route_from_event if route_from_event
    end
    dispatch_page_event(name: name, data: data)
    return
  end

  control = @wire_index[target.to_i] || @control_index[target.to_s]
  return unless control

  event = Event.new(name: name, target: target, raw_data: data, page: self, control: control)
  apply_event_value_to_control(control, event) if %w[change select select_change].include?(name.to_s)
  control.emit(name, event)

  if name.to_s == "dismiss" && remove_dialog_tracking(control)
    push_dialogs_update!
  end
end

#drawerObject



656
657
658
# File 'lib/ruflet_ui/ruflet/page.rb', line 656

def drawer
  @view_props["drawer"]
end

#drawer=(value) ⇒ Object



660
661
662
# File 'lib/ruflet_ui/ruflet/page.rb', line 660

def drawer=(value)
  @view_props["drawer"] = value
end

#end_drawerObject



664
665
666
# File 'lib/ruflet_ui/ruflet/page.rb', line 664

def end_drawer
  @view_props["end_drawer"]
end

#end_drawer=(value) ⇒ Object



668
669
670
# File 'lib/ruflet_ui/ruflet/page.rb', line 668

def end_drawer=(value)
  @view_props["end_drawer"] = value
end

#file_picker(**props) ⇒ Object



1111
1112
1113
# File 'lib/ruflet_ui/ruflet/page.rb', line 1111

def file_picker(**props)
  service(:file_picker, **props)
end

#finalize_reload!Object

Called after the reloaded app block ran, before the reload view patch. Page-level props merge on the client (the Page control is not recreated), so chrome the reloaded block dropped must be sent as an explicit nil or it lingers. View props need no such treatment: the View is rebuilt wholesale from @view_props. Also flushes the overlay container, which stays mounted and is skipped by a plain view patch.



364
365
366
367
368
369
370
371
372
# File 'lib/ruflet_ui/ruflet/page.rb', line 364

def finalize_reload!
  keys = @reload_prior_page_prop_keys
  if keys
    (keys - @page_props.keys).each { |key| @page_props[key] = nil }
    @reload_prior_page_prop_keys = nil
  end
  push_overlay_update!
  self
end

#flashlight(**props) ⇒ Object



430
431
432
433
434
# File 'lib/ruflet_ui/ruflet/page.rb', line 430

def flashlight(**props)
  return service(:flashlight, **props) unless props.empty?

  @flashlight_proxy
end

#floating_action_buttonObject



584
585
586
# File 'lib/ruflet_ui/ruflet/page.rb', line 584

def floating_action_button
  @view_props["floating_action_button"]
end

#floating_action_button=(value) ⇒ Object



588
589
590
# File 'lib/ruflet_ui/ruflet/page.rb', line 588

def floating_action_button=(value)
  @view_props["floating_action_button"] = value
end

#floating_action_button_locationObject



624
625
626
# File 'lib/ruflet_ui/ruflet/page.rb', line 624

def floating_action_button_location
  @view_props["floating_action_button_location"]
end

#floating_action_button_location=(value) ⇒ Object



628
629
630
# File 'lib/ruflet_ui/ruflet/page.rb', line 628

def floating_action_button_location=(value)
  @view_props["floating_action_button_location"] = value
end

#foreground_decorationObject



632
633
634
# File 'lib/ruflet_ui/ruflet/page.rb', line 632

def foreground_decoration
  @view_props["foreground_decoration"]
end

#foreground_decoration=(value) ⇒ Object



636
637
638
# File 'lib/ruflet_ui/ruflet/page.rb', line 636

def foreground_decoration=(value)
  @view_props["foreground_decoration"] = value
end

#geolocator(**props) ⇒ Object



1075
1076
1077
# File 'lib/ruflet_ui/ruflet/page.rb', line 1075

def geolocator(**props)
  service(:geolocator, **props)
end

#get_application_cache_directory(timeout: nil, on_result: nil) ⇒ Object



1135
1136
1137
# File 'lib/ruflet_ui/ruflet/page.rb', line 1135

def get_application_cache_directory(timeout: nil, on_result: nil)
  invoke_storage_paths("get_application_cache_directory", timeout: timeout, on_result: on_result)
end

#get_application_documents_directory(timeout: nil, on_result: nil) ⇒ Object



1139
1140
1141
# File 'lib/ruflet_ui/ruflet/page.rb', line 1139

def get_application_documents_directory(timeout: nil, on_result: nil)
  invoke_storage_paths("get_application_documents_directory", timeout: timeout, on_result: on_result)
end

#get_application_support_directory(timeout: nil, on_result: nil) ⇒ Object



1143
1144
1145
# File 'lib/ruflet_ui/ruflet/page.rb', line 1143

def get_application_support_directory(timeout: nil, on_result: nil)
  invoke_storage_paths("get_application_support_directory", timeout: timeout, on_result: on_result)
end

#get_battery_level(timeout: nil, on_result: nil) ⇒ Object



1039
1040
1041
# File 'lib/ruflet_ui/ruflet/page.rb', line 1039

def get_battery_level(timeout: nil, on_result: nil)
  invoke_battery_method("get_battery_level", timeout: timeout, on_result: on_result)
end

#get_battery_state(timeout: nil, on_result: nil) ⇒ Object



1043
1044
1045
# File 'lib/ruflet_ui/ruflet/page.rb', line 1043

def get_battery_state(timeout: nil, on_result: nil)
  invoke_battery_method("get_battery_state", timeout: timeout, on_result: on_result)
end

#get_clipboard(timeout: nil, on_result: nil) ⇒ Object



1005
1006
1007
# File 'lib/ruflet_ui/ruflet/page.rb', line 1005

def get_clipboard(timeout: nil, on_result: nil)
  invoke_clipboard_method("get", timeout: timeout, on_result: on_result)
end

#get_clipboard_files(timeout: nil, on_result: nil) ⇒ Object



1018
1019
1020
# File 'lib/ruflet_ui/ruflet/page.rb', line 1018

def get_clipboard_files(timeout: nil, on_result: nil)
  invoke_clipboard_method("get_files", timeout: timeout, on_result: on_result)
end

#get_clipboard_image(timeout: nil, on_result: nil) ⇒ Object



1031
1032
1033
# File 'lib/ruflet_ui/ruflet/page.rb', line 1031

def get_clipboard_image(timeout: nil, on_result: nil)
  invoke_clipboard_method("get_image", timeout: timeout, on_result: on_result)
end

#get_connectivity(timeout: nil, on_result: nil) ⇒ Object



1035
1036
1037
# File 'lib/ruflet_ui/ruflet/page.rb', line 1035

def get_connectivity(timeout: nil, on_result: nil)
  invoke_connectivity_method("get_connectivity", timeout: timeout, on_result: on_result)
end

#get_console_log_filename(timeout: nil, on_result: nil) ⇒ Object



1171
1172
1173
# File 'lib/ruflet_ui/ruflet/page.rb', line 1171

def get_console_log_filename(timeout: nil, on_result: nil)
  invoke_storage_paths("get_console_log_filename", timeout: timeout, on_result: on_result)
end

#get_control(id) ⇒ Object



397
398
399
400
# File 'lib/ruflet_ui/ruflet/page.rb', line 397

def get_control(id)
  refresh_control_indexes!
  resolve_control(id)
end

#get_directory_path(dialog_title: nil, initial_directory: nil, timeout: nil, on_result: nil) ⇒ Object



951
952
953
954
955
956
957
958
959
960
961
# File 'lib/ruflet_ui/ruflet/page.rb', line 951

def get_directory_path(dialog_title: nil, initial_directory: nil, timeout: nil, on_result: nil)
  invoke_file_picker(
    "get_directory_path",
    compact_service_args(
      "dialog_title" => dialog_title,
      "initial_directory" => initial_directory
    ),
    timeout: timeout,
    on_result: on_result
  )
end

#get_downloads_directory(timeout: nil, on_result: nil) ⇒ Object



1147
1148
1149
# File 'lib/ruflet_ui/ruflet/page.rb', line 1147

def get_downloads_directory(timeout: nil, on_result: nil)
  invoke_storage_paths("get_downloads_directory", timeout: timeout, on_result: on_result)
end

#get_external_cache_directories(timeout: nil, on_result: nil) ⇒ Object



1151
1152
1153
# File 'lib/ruflet_ui/ruflet/page.rb', line 1151

def get_external_cache_directories(timeout: nil, on_result: nil)
  invoke_storage_paths("get_external_cache_directories", timeout: timeout, on_result: on_result)
end

#get_external_storage_directories(timeout: nil, on_result: nil) ⇒ Object



1155
1156
1157
# File 'lib/ruflet_ui/ruflet/page.rb', line 1155

def get_external_storage_directories(timeout: nil, on_result: nil)
  invoke_storage_paths("get_external_storage_directories", timeout: timeout, on_result: on_result)
end

#get_external_storage_directory(timeout: nil, on_result: nil) ⇒ Object



1163
1164
1165
# File 'lib/ruflet_ui/ruflet/page.rb', line 1163

def get_external_storage_directory(timeout: nil, on_result: nil)
  invoke_storage_paths("get_external_storage_directory", timeout: timeout, on_result: on_result)
end

#get_library_directory(timeout: nil, on_result: nil) ⇒ Object



1159
1160
1161
# File 'lib/ruflet_ui/ruflet/page.rb', line 1159

def get_library_directory(timeout: nil, on_result: nil)
  invoke_storage_paths("get_library_directory", timeout: timeout, on_result: on_result)
end

#get_temporary_directory(timeout: nil, on_result: nil) ⇒ Object



1167
1168
1169
# File 'lib/ruflet_ui/ruflet/page.rb', line 1167

def get_temporary_directory(timeout: nil, on_result: nil)
  invoke_storage_paths("get_temporary_directory", timeout: timeout, on_result: on_result)
end

#go(route, **query_params) ⇒ Object



520
521
522
523
524
525
# File 'lib/ruflet_ui/ruflet/page.rb', line 520

def go(route, **query_params)
  @page_props["route"] = build_route(route, query_params)
  dispatch_page_event(name: "route_change", data: @page_props["route"])
  send_view_patch
  self
end

#gyroscope(**props) ⇒ Object



1059
1060
1061
# File 'lib/ruflet_ui/ruflet/page.rb', line 1059

def gyroscope(**props)
  service(:gyroscope, **props)
end

#handle_invoke_method_result(payload) ⇒ Object



1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
# File 'lib/ruflet_ui/ruflet/page.rb', line 1260

def handle_invoke_method_result(payload)
  call_id = payload["call_id"].to_s
  waiter = @invoke_waiters_mutex.synchronize { @invoke_waiters[call_id] }
  if waiter
    waiter << payload
    return true
  end

  callback = @invoke_waiters_mutex.synchronize { @invoke_callbacks.delete(call_id) }
  return false unless callback

  callback.call(payload["result"], payload["error"])
  true
rescue StandardError => e
  Kernel.warn("invoke callback error: #{e.class}: #{e.message}")
  false
end

#haptic_feedback(**props) ⇒ Object



1131
1132
1133
# File 'lib/ruflet_ui/ruflet/page.rb', line 1131

def haptic_feedback(**props)
  service(:haptic_feedback, **props)
end

#heavy_impact(timeout: 10, on_result: nil) ⇒ Object



976
977
978
# File 'lib/ruflet_ui/ruflet/page.rb', line 976

def heavy_impact(timeout: 10, on_result: nil)
  invoke_haptic_feedback("heavy_impact", timeout: timeout, on_result: on_result)
end

#horizontal_alignmentObject



242
243
244
# File 'lib/ruflet_ui/ruflet/page.rb', line 242

def horizontal_alignment
  @page_props["horizontal_alignment"] || @view_props["horizontal_alignment"]
end

#horizontal_alignment=(value) ⇒ Object



246
247
248
249
250
# File 'lib/ruflet_ui/ruflet/page.rb', line 246

def horizontal_alignment=(value)
  v = normalize_value("horizontal_alignment", value)
  @page_props["horizontal_alignment"] = v
  @view_props["horizontal_alignment"] = v
end

#insert(at, *controls) ⇒ Object



280
281
282
283
284
# File 'lib/ruflet_ui/ruflet/page.rb', line 280

def insert(at, *controls)
  @root_controls.insert(at.to_i, *controls.flatten.compact)
  send_view_patch
  self
end

#invoke(control_or_id, method_name, args: nil, timeout: 10, on_result: nil) ⇒ Object



814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
# File 'lib/ruflet_ui/ruflet/page.rb', line 814

def invoke(control_or_id, method_name, args: nil, timeout: 10, on_result: nil)
  control_id =
    if page_control_target?(control_or_id)
      1
    else
      control = resolve_control(control_or_id)
      return nil unless control
      control.wire_id
    end

  call_id = "call_#{Ruflet::Control.generate_id}"
  if on_result.respond_to?(:call)
    @invoke_waiters_mutex.synchronize { @invoke_callbacks[call_id] = on_result }
    unless timeout.nil?
      Thread.new(call_id, timeout.to_f) do |pending_call_id, invoke_timeout|
        sleep([invoke_timeout, 0.0].max + 0.1)
        callback = @invoke_waiters_mutex.synchronize { @invoke_callbacks.delete(pending_call_id) }
        callback&.call(nil, "execution expired")
      rescue StandardError => e
        Kernel.warn("invoke timeout callback error: #{e.class}: #{e.message}")
      end
    end
  end
  payload = {
    "control_id" => control_id,
    "call_id" => call_id,
    "name" => method_name.to_s,
    "args" => args
  }
  payload["timeout"] = timeout unless timeout.nil?
  send_message(Protocol::ACTIONS[:invoke_control_method], payload)

  call_id
end

#invoke_sync(control_or_id, method_name, args: nil, timeout: 10) ⇒ Object

Synchronous invoke for controls/services that must return a value before continuing (e.g. picker selection, camera discovery/init).



851
852
853
# File 'lib/ruflet_ui/ruflet/page.rb', line 851

def invoke_sync(control_or_id, method_name, args: nil, timeout: 10)
  invoke_and_wait(control_or_id, method_name, args: args, timeout: timeout)
end

#is_in_battery_save_mode(timeout: nil, on_result: nil) ⇒ Object



1047
1048
1049
# File 'lib/ruflet_ui/ruflet/page.rb', line 1047

def is_in_battery_save_mode(timeout: nil, on_result: nil)
  invoke_battery_method("is_in_battery_save_mode", timeout: timeout, on_result: on_result)
end

#launch_url(url, mode: nil, web_view_configuration: nil, browser_configuration: nil, web_only_window_name: nil, timeout: 10, on_result: nil) ⇒ Object



855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
# File 'lib/ruflet_ui/ruflet/page.rb', line 855

def launch_url(url, mode: nil, web_view_configuration: nil, browser_configuration: nil, web_only_window_name: nil, timeout: 10, on_result: nil)
  url_launcher = ensure_url_launcher_service
  args = { "url" => url }
  args["mode"] = mode unless mode.nil?
  args["web_view_configuration"] = web_view_configuration unless web_view_configuration.nil?
  args["browser_configuration"] = browser_configuration unless browser_configuration.nil?
  args["web_only_window_name"] = web_only_window_name unless web_only_window_name.nil?
  invoke(
    url_launcher,
    "launch_url",
    args: args,
    timeout: timeout,
    on_result: on_result
  )
end

#light_impact(timeout: 10, on_result: nil) ⇒ Object



984
985
986
# File 'lib/ruflet_ui/ruflet/page.rb', line 984

def light_impact(timeout: 10, on_result: nil)
  invoke_haptic_feedback("light_impact", timeout: timeout, on_result: on_result)
end

#magnetometer(**props) ⇒ Object



1067
1068
1069
# File 'lib/ruflet_ui/ruflet/page.rb', line 1067

def magnetometer(**props)
  service(:magnetometer, **props)
end

#medium_impact(timeout: 10, on_result: nil) ⇒ Object



980
981
982
# File 'lib/ruflet_ui/ruflet/page.rb', line 980

def medium_impact(timeout: 10, on_result: nil)
  invoke_haptic_feedback("medium_impact", timeout: timeout, on_result: on_result)
end

#mount(&block) ⇒ Object



558
559
560
561
562
# File 'lib/ruflet_ui/ruflet/page.rb', line 558

def mount(&block)
  builder = WidgetBuilder.new
  builder.instance_eval(&block)
  builder.children.each { |control| add(control) }
end


527
528
529
# File 'lib/ruflet_ui/ruflet/page.rb', line 527

def navigate(route, **query_params)
  go(route, **query_params)
end


592
593
594
# File 'lib/ruflet_ui/ruflet/page.rb', line 592

def navigation_bar
  @view_props["navigation_bar"]
end


596
597
598
# File 'lib/ruflet_ui/ruflet/page.rb', line 596

def navigation_bar=(value)
  @view_props["navigation_bar"] = value
end

#on(event_name, &block) ⇒ Object



551
552
553
554
555
556
# File 'lib/ruflet_ui/ruflet/page.rb', line 551

def on(event_name, &block)
  name = event_name.to_s
  name = name[3..-1] if name.start_with?("on_")
  @page_event_handlers[name] = block
  self
end

#on_route_change=(handler) ⇒ Object



539
540
541
# File 'lib/ruflet_ui/ruflet/page.rb', line 539

def on_route_change=(handler)
  @page_event_handlers["route_change"] = handler
end

#on_view_pop=(handler) ⇒ Object



543
544
545
# File 'lib/ruflet_ui/ruflet/page.rb', line 543

def on_view_pop=(handler)
  @page_event_handlers["view_pop"] = handler
end

#on_views_pop_until=(handler) ⇒ Object



547
548
549
# File 'lib/ruflet_ui/ruflet/page.rb', line 547

def on_views_pop_until=(handler)
  @page_event_handlers["views_pop_until"] = handler
end

#open(dialog_control) ⇒ Object

Flet-compatible generic overlay API. Prefer close_bottom_sheet for sheets so the page's current bottom-sheet slot is handled explicitly.



756
757
758
# File 'lib/ruflet_ui/ruflet/page.rb', line 756

def open(dialog_control)
  show_dialog(dialog_control)
end

#open_window(url, title: nil, width: nil, height: nil, timeout: 10, on_result: nil) ⇒ Object



881
882
883
884
885
886
887
888
# File 'lib/ruflet_ui/ruflet/page.rb', line 881

def open_window(url, title: nil, width: nil, height: nil, timeout: 10, on_result: nil)
  url_launcher = ensure_url_launcher_service
  args = { "url" => url }
  args["title"] = title unless title.nil?
  args["width"] = width unless width.nil?
  args["height"] = height unless height.nil?
  invoke(url_launcher, "open_window", args: args, timeout: timeout, on_result: on_result)
end

#overlayObject



387
388
389
# File 'lib/ruflet_ui/ruflet/page.rb', line 387

def overlay
  @overlay_container.children
end

#overlay=(value) ⇒ Object



391
392
393
394
395
# File 'lib/ruflet_ui/ruflet/page.rb', line 391

def overlay=(value)
  @overlay_container.children.replace(Array(value).flatten.compact)
  push_overlay_update!
  self
end

#paddingObject



640
641
642
# File 'lib/ruflet_ui/ruflet/page.rb', line 640

def padding
  @view_props["padding"]
end

#padding=(value) ⇒ Object



644
645
646
# File 'lib/ruflet_ui/ruflet/page.rb', line 644

def padding=(value)
  @view_props["padding"] = value
end

#patch_page(control_id, **props) ⇒ Object



1334
1335
1336
# File 'lib/ruflet_ui/ruflet/page.rb', line 1334

def patch_page(control_id, **props)
  update(control_id, **props)
end

#permission_handler(**props) ⇒ Object



1079
1080
1081
# File 'lib/ruflet_ui/ruflet/page.rb', line 1079

def permission_handler(**props)
  service(:permission_handler, **props)
end

#pick_files(dialog_title: nil, initial_directory: nil, file_type: "any", allowed_extensions: nil, allow_multiple: false, with_data: false, timeout: nil, on_result: nil) ⇒ Object

File picker helpers: create an ephemeral service, invoke method, and dispose it.



901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
# File 'lib/ruflet_ui/ruflet/page.rb', line 901

def pick_files(
  dialog_title: nil,
  initial_directory: nil,
  file_type: "any",
  allowed_extensions: nil,
  allow_multiple: false,
  with_data: false,
  timeout: nil,
  on_result: nil
)
  invoke_file_picker(
    "pick_files",
    compact_service_args(
      "dialog_title" => dialog_title,
      "initial_directory" => initial_directory,
      "file_type" => file_type,
      "allowed_extensions" => allowed_extensions,
      "allow_multiple" => allow_multiple,
      "with_data" => with_data
    ),
    timeout: timeout,
    on_result: on_result
  )
end

#pop_dialogObject



1278
1279
1280
# File 'lib/ruflet_ui/ruflet/page.rb', line 1278

def pop_dialog
  close_dialog
end

#push_route(route, **query_params) ⇒ Object



531
532
533
# File 'lib/ruflet_ui/ruflet/page.rb', line 531

def push_route(route, **query_params)
  go(route, **query_params)
end

#queryObject



535
536
537
# File 'lib/ruflet_ui/ruflet/page.rb', line 535

def query
  parse_query(route)
end

#register_page_patchObject

Complete page state as a property map for the register_client response (Flet's page_patch). The client applies this through Control.update, which merges by control id and keeps existing instances alive — the only patch path that survives a re-register on a client with prior state (reconnect after a backend restart). The op-based view patch replaces control instances and detaches containers on such clients.



309
310
311
312
313
314
315
316
317
# File 'lib/ruflet_ui/ruflet/page.rb', line 309

def register_page_patch
  refresh_control_indexes!
  patch = { "views" => build_view_patches }
  @page_props.each { |key, value| patch[key] = serialize_patch_value(value) }
  @overlay_container_mounted = true if @overlay_container.wire_id
  @dialogs_container_mounted = true if @dialogs_container.wire_id
  @services_container_mounted = true if @services_container.wire_id
  patch
end

#remove(*controls) ⇒ Object



286
287
288
289
290
# File 'lib/ruflet_ui/ruflet/page.rb', line 286

def remove(*controls)
  controls.flatten.each { |control| @root_controls.delete(control) }
  send_view_patch
  self
end

#remove_at(index) ⇒ Object



292
293
294
295
296
# File 'lib/ruflet_ui/ruflet/page.rb', line 292

def remove_at(index)
  @root_controls.delete_at(index.to_i)
  send_view_patch
  self
end

#remove_service(*value) ⇒ Object



457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# File 'lib/ruflet_ui/ruflet/page.rb', line 457

def remove_service(*value)
  targets = value.flatten.compact
  return self if targets.empty?

  @services_container.props["_services"] = services.reject do |service|
    targets.any? do |target|
      case target
      when Control
        service.equal?(target) || (!target.id.nil? && service.id.to_s == target.id.to_s)
      else
        needle = target.to_s
        service.id.to_s == needle || service.type.to_s.downcase == needle.downcase
      end
    end
  end

  refresh_services_container!
  push_services_update!
  self
end

#replay_route_after_reload!Object

Called after the reloaded app block ran. The route survives a reload (page props are kept), but the client only sends its route_change event at connect time — apps that render routes exclusively inside on_route_change would be stuck on stale content after a reload. Replay the event for them, unless the block already routed itself (page.go).



379
380
381
382
383
384
385
# File 'lib/ruflet_ui/ruflet/page.rb', line 379

def replay_route_after_reload!
  return self if @route_change_seen_since_reset
  return self unless @page_event_handlers["route_change"].respond_to?(:call)

  dispatch_page_event(name: "route_change", data: @page_props["route"])
  self
end

#reset_for_reload!Object

Quietly clears session content and page chrome so a reloaded app block can re-render on this same Page without recreating it. The page object must survive a hot reload: a new Page re-sends the internal overlay/service/dialogs containers, which replaces their client-side instances and detaches them (see build_page_patch_ops) — after that, dialog and service patches are silently ignored by the client.

Chrome (appbar, drawer, FAB, bgcolor, title, ...) lives in @view_props and @page_props. If the reloaded block no longer sets a piece of chrome, the previous run's value would linger, so both are cleared here. The view is fully rebuilt on the next patch, which drops any @view_props not re-set; page-level props merge on the client, so finalize_reload! sends explicit nils for the ones that disappeared. Sends nothing itself.



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/ruflet_ui/ruflet/page.rb', line 337

def reset_for_reload!
  @root_controls = []
  @views = []
  @dialogs = []
  @dialog = nil
  @snack_bar = nil
  @bottom_sheet = nil
  @route_change_seen_since_reset = false

  @reload_prior_page_prop_keys = @page_props.keys - RELOAD_PRESERVED_PAGE_PROPS
  @view_props = {}
  @page_props = @page_props.select { |key, _| RELOAD_PRESERVED_PAGE_PROPS.include?(key) }
  @page_props["route"] ||= (@client_details["route"] || "/")
  @page_props["window"] ||= @window
  @overlay_container.children.clear
  refresh_overlay_container!
  refresh_services_container!
  refresh_dialogs_container!
  self
end

#respond_to_missing?(name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
# File 'lib/ruflet_ui/ruflet/page.rb', line 1401

def respond_to_missing?(name, include_private = false)
  method_name = name.to_s
  prop_name = method_name.delete_suffix("=")
  widget_helper_method?(name) ||
    widget_helper_method?(prop_name) ||
    method_name.end_with?("=") ||
    @page_props.key?(method_name) ||
    @view_props.key?(method_name) ||
    DIALOG_PROP_KEYS.include?(method_name) ||
    super
end

#routeObject



224
225
226
# File 'lib/ruflet_ui/ruflet/page.rb', line 224

def route
  @page_props["route"]
end

#route=(value) ⇒ Object



228
229
230
# File 'lib/ruflet_ui/ruflet/page.rb', line 228

def route=(value)
  @page_props["route"] = value
end

#save_file(dialog_title: nil, file_name: nil, initial_directory: nil, file_type: "any", allowed_extensions: nil, src_bytes: nil, timeout: nil, on_result: nil) ⇒ Object



926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
# File 'lib/ruflet_ui/ruflet/page.rb', line 926

def save_file(
  dialog_title: nil,
  file_name: nil,
  initial_directory: nil,
  file_type: "any",
  allowed_extensions: nil,
  src_bytes: nil,
  timeout: nil,
  on_result: nil
)
  invoke_file_picker(
    "save_file",
    compact_service_args(
      "dialog_title" => dialog_title,
      "file_name" => file_name,
      "initial_directory" => initial_directory,
      "file_type" => file_type,
      "allowed_extensions" => allowed_extensions,
      "src_bytes" => src_bytes
    ),
    timeout: timeout,
    on_result: on_result
  )
end

#schedule_updateObject



1330
1331
1332
# File 'lib/ruflet_ui/ruflet/page.rb', line 1330

def schedule_update
  update
end

#screen_brightness(**props) ⇒ Object



436
437
438
439
440
# File 'lib/ruflet_ui/ruflet/page.rb', line 436

def screen_brightness(**props)
  return service(:screen_brightness, **props) unless props.empty?

  @screen_brightness_proxy
end

#screenshot(**props) ⇒ Object



1095
1096
1097
# File 'lib/ruflet_ui/ruflet/page.rb', line 1095

def screenshot(**props)
  Ruflet::UI::ControlFactory.build(:screenshot, **props)
end

#scroll_to(offset: nil, delta: nil, scroll_key: nil, duration: nil, curve: nil, timeout: 10, on_result: nil) ⇒ Object



798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
# File 'lib/ruflet_ui/ruflet/page.rb', line 798

def scroll_to(offset: nil, delta: nil, scroll_key: nil, duration: nil, curve: nil, timeout: 10, on_result: nil)
  invoke(
    :page,
    "scroll_to",
    args: {
      "offset" => offset,
      "delta" => delta,
      "scroll_key" => scroll_key,
      "duration" => duration,
      "curve" => curve
    },
    timeout: timeout,
    on_result: on_result
  )
end

#secure_storage(**props) ⇒ Object



1083
1084
1085
# File 'lib/ruflet_ui/ruflet/page.rb', line 1083

def secure_storage(**props)
  service(:secure_storage, **props)
end

#selection_click(timeout: 10, on_result: nil) ⇒ Object



988
989
990
# File 'lib/ruflet_ui/ruflet/page.rb', line 988

def selection_click(timeout: 10, on_result: nil)
  invoke_haptic_feedback("selection_click", timeout: timeout, on_result: on_result)
end

#semantics_service(**props) ⇒ Object



1091
1092
1093
# File 'lib/ruflet_ui/ruflet/page.rb', line 1091

def semantics_service(**props)
  service(:semantics_service, **props)
end

#service(type, **props) ⇒ Object



478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
# File 'lib/ruflet_ui/ruflet/page.rb', line 478

def service(type, **props)
  mapped_props = normalize_props(props || {})
  id = mapped_props.delete("id")
  normalized_type = type.to_s.downcase
  compact_type = normalized_type.delete("_")

  if visual_service_type?(normalized_type)
    key = id ? "id:#{id}" : normalized_type
    existing = @visual_service_controls[key]
    return existing if existing

    svc = Ruflet::UI::ControlFactory.build(type.to_s, id: id&.to_s, **mapped_props)
    @visual_service_controls[key] = svc
    return svc
  end

  unless service_control_type?(normalized_type)
    raise ArgumentError, "#{type} is a visual control, not a page service"
  end

  existing =
    if id
      services.find { |s| s.is_a?(Control) && s.id.to_s == id.to_s }
    else
      services.find do |s|
        s.is_a?(Control) && s.type.to_s.downcase.delete("_") == compact_type
      end
    end
  if existing
    unless mapped_props.empty?
      existing.merge_props(mapped_props)
      refresh_services_container!
      push_services_update!
    end
    return existing
  end

  svc = Ruflet::UI::ControlFactory.build(type.to_s, id: id&.to_s, **mapped_props)
  add_service(svc) unless services.include?(svc)
  svc
end

#servicesObject



407
408
409
# File 'lib/ruflet_ui/ruflet/page.rb', line 407

def services
  @services_container.props["_services"] ||= []
end

#services=(value) ⇒ Object



411
412
413
414
415
416
# File 'lib/ruflet_ui/ruflet/page.rb', line 411

def services=(value)
  @services_container.props["_services"] = Array(value).compact
  refresh_services_container!
  push_services_update!
  self
end

#set_clipboard(value, timeout: nil, on_result: nil) ⇒ Object



996
997
998
999
1000
1001
1002
1003
# File 'lib/ruflet_ui/ruflet/page.rb', line 996

def set_clipboard(value, timeout: nil, on_result: nil)
  invoke_clipboard_method(
    "set",
    args: { "data" => value.to_s },
    timeout: timeout,
    on_result: on_result
  )
end

#set_clipboard_files(files, timeout: nil, on_result: nil) ⇒ Object



1009
1010
1011
1012
1013
1014
1015
1016
# File 'lib/ruflet_ui/ruflet/page.rb', line 1009

def set_clipboard_files(files, timeout: nil, on_result: nil)
  invoke_clipboard_method(
    "set_files",
    args: { "files" => Array(files).map(&:to_s) },
    timeout: timeout,
    on_result: on_result
  )
end

#set_clipboard_image(value, timeout: nil, on_result: nil) ⇒ Object



1022
1023
1024
1025
1026
1027
1028
1029
# File 'lib/ruflet_ui/ruflet/page.rb', line 1022

def set_clipboard_image(value, timeout: nil, on_result: nil)
  invoke_clipboard_method(
    "set_image",
    args: { "data" => value },
    timeout: timeout,
    on_result: on_result
  )
end

#set_view_props(props) ⇒ Object



211
212
213
214
# File 'lib/ruflet_ui/ruflet/page.rb', line 211

def set_view_props(props)
  split_props(normalize_props(props || {}))
  self
end

#shake_detector(**props) ⇒ Object



1087
1088
1089
# File 'lib/ruflet_ui/ruflet/page.rb', line 1087

def shake_detector(**props)
  service(:shake_detector, **props)
end

#share(**props) ⇒ Object



1123
1124
1125
# File 'lib/ruflet_ui/ruflet/page.rb', line 1123

def share(**props)
  service(:share, **props)
end

#share_files(files = nil, text: nil, title: nil, subject: nil, preview_thumbnail: nil, share_position_origin: nil, download_fallback_enabled: true, mail_to_fallback_enabled: true, excluded_cupertino_activities: nil, timeout: nil, on_result: nil) ⇒ Object



1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
# File 'lib/ruflet_ui/ruflet/page.rb', line 1227

def share_files(
  files = nil,
  text: nil,
  title: nil,
  subject: nil,
  preview_thumbnail: nil,
  share_position_origin: nil,
  download_fallback_enabled: true,
  mail_to_fallback_enabled: true,
  excluded_cupertino_activities: nil,
  timeout: nil,
  on_result: nil
)
  share = ensure_share_service
  invoke(
    share,
    "share_files",
    args: compact_service_args(
      "files" => normalize_share_files(files),
      "text" => text,
      "title" => title,
      "subject" => subject,
      "preview_thumbnail" => normalize_share_file(preview_thumbnail),
      "share_position_origin" => share_position_origin,
      "download_fallback_enabled" => download_fallback_enabled,
      "mail_to_fallback_enabled" => mail_to_fallback_enabled,
      "excluded_cupertino_activities" => excluded_cupertino_activities
    ),
    timeout: timeout,
    on_result: on_result
  )
end

#share_text(text = nil, title: nil, subject: nil, preview_thumbnail: nil, share_position_origin: nil, download_fallback_enabled: true, mail_to_fallback_enabled: true, excluded_cupertino_activities: nil, timeout: nil, on_result: nil) ⇒ Object



1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
# File 'lib/ruflet_ui/ruflet/page.rb', line 1175

def share_text(
  text = nil,
  title: nil,
  subject: nil,
  preview_thumbnail: nil,
  share_position_origin: nil,
  download_fallback_enabled: true,
  mail_to_fallback_enabled: true,
  excluded_cupertino_activities: nil,
  timeout: nil,
  on_result: nil
)
  share = ensure_share_service
  invoke(
    share,
    "share_text",
    args: compact_service_args(
      "text" => text,
      "title" => title,
      "subject" => subject,
      "preview_thumbnail" => preview_thumbnail,
      "share_position_origin" => share_position_origin,
      "download_fallback_enabled" => download_fallback_enabled,
      "mail_to_fallback_enabled" => mail_to_fallback_enabled,
      "excluded_cupertino_activities" => excluded_cupertino_activities
    ),
    timeout: timeout,
    on_result: on_result
  )
end

#share_uri(uri = nil, share_position_origin: nil, excluded_cupertino_activities: nil, timeout: nil, on_result: nil) ⇒ Object



1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
# File 'lib/ruflet_ui/ruflet/page.rb', line 1206

def share_uri(
  uri = nil,
  share_position_origin: nil,
  excluded_cupertino_activities: nil,
  timeout: nil,
  on_result: nil
)
  share = ensure_share_service
  invoke(
    share,
    "share_uri",
    args: compact_service_args(
      "uri" => uri,
      "share_position_origin" => share_position_origin,
      "excluded_cupertino_activities" => excluded_cupertino_activities
    ),
    timeout: timeout,
    on_result: on_result
  )
end

#shared_preferences(**props) ⇒ Object



418
419
420
421
422
# File 'lib/ruflet_ui/ruflet/page.rb', line 418

def shared_preferences(**props)
  return service(:shared_preferences, **props) unless props.empty?

  @shared_preferences_proxy
end

#show_banner(banner_control) ⇒ Object



746
747
748
# File 'lib/ruflet_ui/ruflet/page.rb', line 746

def show_banner(banner_control)
  show_dialog(banner_control)
end

#show_bottom_sheet(bottom_sheet_control) ⇒ Object



729
730
731
732
# File 'lib/ruflet_ui/ruflet/page.rb', line 729

def show_bottom_sheet(bottom_sheet_control)
  @bottom_sheet = bottom_sheet_control
  show_dialog(bottom_sheet_control)
end

#show_bottomsheet(bottom_sheet_control) ⇒ Object



734
735
736
# File 'lib/ruflet_ui/ruflet/page.rb', line 734

def show_bottomsheet(bottom_sheet_control)
  show_bottom_sheet(bottom_sheet_control)
end

#show_dialog(dialog_control) ⇒ Object



708
709
710
711
712
713
714
715
716
717
718
719
# File 'lib/ruflet_ui/ruflet/page.rb', line 708

def show_dialog(dialog_control)
  return self unless dialog_control

  return self if dialog_open?(dialog_control)

  dialog_control.props["open"] = true
  @dialogs << dialog_control unless @dialogs.include?(dialog_control)
  refresh_dialogs_container!
  send_view_patch unless @dialogs_container.wire_id
  push_dialogs_update!
  self
end

#show_drawer(timeout: 10, on_result: nil) ⇒ Object

Raises:

  • (ArgumentError)


778
779
780
781
782
# File 'lib/ruflet_ui/ruflet/page.rb', line 778

def show_drawer(timeout: 10, on_result: nil)
  raise ArgumentError, "No drawer defined" unless drawer

  invoke(:page, "show_drawer", timeout: timeout, on_result: on_result)
end

#show_end_drawer(timeout: 10, on_result: nil) ⇒ Object

Raises:

  • (ArgumentError)


788
789
790
791
792
# File 'lib/ruflet_ui/ruflet/page.rb', line 788

def show_end_drawer(timeout: 10, on_result: nil)
  raise ArgumentError, "No end_drawer defined" unless end_drawer

  invoke(:page, "show_end_drawer", timeout: timeout, on_result: on_result)
end

#show_snack_bar(snack_bar_control) ⇒ Object



721
722
723
# File 'lib/ruflet_ui/ruflet/page.rb', line 721

def show_snack_bar(snack_bar_control)
  show_dialog(snack_bar_control)
end

#show_snackbar(snack_bar_control) ⇒ Object



725
726
727
# File 'lib/ruflet_ui/ruflet/page.rb', line 725

def show_snackbar(snack_bar_control)
  show_dialog(snack_bar_control)
end

#snack_barObject



686
687
688
# File 'lib/ruflet_ui/ruflet/page.rb', line 686

def snack_bar
  @snack_bar
end

#snack_bar=(value) ⇒ Object



680
681
682
683
684
# File 'lib/ruflet_ui/ruflet/page.rb', line 680

def snack_bar=(value)
  @snack_bar = value
  refresh_dialogs_container!
  push_dialogs_update! if @dialogs_container_mounted
end

#snackbar=(value) ⇒ Object



690
691
692
# File 'lib/ruflet_ui/ruflet/page.rb', line 690

def snackbar=(value)
  self.snack_bar = value
end

#spacingObject



648
649
650
# File 'lib/ruflet_ui/ruflet/page.rb', line 648

def spacing
  @view_props["spacing"]
end

#spacing=(value) ⇒ Object



652
653
654
# File 'lib/ruflet_ui/ruflet/page.rb', line 652

def spacing=(value)
  @view_props["spacing"] = value
end

#storage_paths(**props) ⇒ Object



1119
1120
1121
# File 'lib/ruflet_ui/ruflet/page.rb', line 1119

def storage_paths(**props)
  service(:storage_paths, **props)
end

#supports_close_for_launch_mode(mode, timeout: 10, on_result: nil) ⇒ Object



895
896
897
898
# File 'lib/ruflet_ui/ruflet/page.rb', line 895

def supports_close_for_launch_mode(mode, timeout: 10, on_result: nil)
  url_launcher = ensure_url_launcher_service
  invoke(url_launcher, "supports_close_for_launch_mode", args: { "mode" => mode }, timeout: timeout, on_result: on_result)
end

#supports_launch_mode(mode, timeout: 10, on_result: nil) ⇒ Object



890
891
892
893
# File 'lib/ruflet_ui/ruflet/page.rb', line 890

def supports_launch_mode(mode, timeout: 10, on_result: nil)
  url_launcher = ensure_url_launcher_service
  invoke(url_launcher, "supports_launch_mode", args: { "mode" => mode }, timeout: timeout, on_result: on_result)
end

#titleObject



216
217
218
# File 'lib/ruflet_ui/ruflet/page.rb', line 216

def title
  @page_props["title"]
end

#title=(value) ⇒ Object



220
221
222
# File 'lib/ruflet_ui/ruflet/page.rb', line 220

def title=(value)
  @page_props["title"] = value
end

#update(control_or_id = nil, **props) ⇒ Object



1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
# File 'lib/ruflet_ui/ruflet/page.rb', line 1282

def update(control_or_id = nil, **props)
  if control_or_id.nil? && props.empty?
    send_view_patch
    return self
  end

  if page_control_target?(control_or_id)
    split_props(normalize_props(props))
    send_view_patch
    return self
  end

  control = resolve_control(control_or_id)
  return self unless control
  wire_id = control.wire_id
  if wire_id.nil?
    # Events can race with navigation/disposal; never emit patch_control with nil id.
    refresh_control_indexes!
    wire_id = control.wire_id
  end
  return self if wire_id.nil?

  patch = normalize_props(props)
  if text_maps_to_content?(control, patch)
    patch["content"] = patch.delete("text")
  end

  patch.each { |key, value| control.props[key] = value }

  # Keep runtime control tree aligned with incremental patches.
  if patch.key?("controls")
    control.children.clear
    Array(patch["controls"]).each { |child| control.children << child if child.is_a?(Control) }
  end

  visited = Set.new
  patch.each_value { |value| register_embedded_value(value, visited) }

  patch_ops = patch.map { |k, v| [0, 0, k, serialize_patch_value(v)] }

  send_message(Protocol::ACTIONS[:patch_control], {
    "id" => wire_id,
    "patch" => [[0], *patch_ops]
  })

  self
end

#upload(files, timeout: nil, on_result: nil) ⇒ Object



963
964
965
966
967
968
969
970
# File 'lib/ruflet_ui/ruflet/page.rb', line 963

def upload(files, timeout: nil, on_result: nil)
  invoke_file_picker(
    "upload",
    { "files" => Array(files).map { |file| normalize_service_value(file) } },
    timeout: timeout,
    on_result: on_result
  )
end

#upload_files(files, timeout: nil, on_result: nil) ⇒ Object



972
973
974
# File 'lib/ruflet_ui/ruflet/page.rb', line 972

def upload_files(files, timeout: nil, on_result: nil)
  upload(files, timeout: timeout, on_result: on_result)
end

#url_launcher(**props) ⇒ Object



1115
1116
1117
# File 'lib/ruflet_ui/ruflet/page.rb', line 1115

def url_launcher(**props)
  service(:url_launcher, **props)
end

#user_accelerometer(**props) ⇒ Object



1063
1064
1065
# File 'lib/ruflet_ui/ruflet/page.rb', line 1063

def user_accelerometer(**props)
  service(:user_accelerometer, **props)
end

#vertical_alignmentObject



232
233
234
# File 'lib/ruflet_ui/ruflet/page.rb', line 232

def vertical_alignment
  @page_props["vertical_alignment"] || @view_props["vertical_alignment"]
end

#vertical_alignment=(value) ⇒ Object



236
237
238
239
240
# File 'lib/ruflet_ui/ruflet/page.rb', line 236

def vertical_alignment=(value)
  v = normalize_value("vertical_alignment", value)
  @page_props["vertical_alignment"] = v
  @view_props["vertical_alignment"] = v
end

#vibrate(timeout: 10, on_result: nil) ⇒ Object



992
993
994
# File 'lib/ruflet_ui/ruflet/page.rb', line 992

def vibrate(timeout: 10, on_result: nil)
  invoke_haptic_feedback("vibrate", timeout: timeout, on_result: on_result)
end

#wakelock(**props) ⇒ Object



424
425
426
427
428
# File 'lib/ruflet_ui/ruflet/page.rb', line 424

def wakelock(**props)
  return service(:wakelock, **props) unless props.empty?

  @wakelock_proxy
end

#webObject Also known as: web?

Older and embedded clients may omit this capability from registration. Ruflet applications should still be able to branch on page.web just as they do with a full desktop client.



21
22
23
# File 'lib/ruflet_ui/ruflet/page.rb', line 21

def web
  !!(@client_details && @client_details["web"])
end