├── dart_test.yaml
├── example
├── linux
│ ├── .gitignore
│ ├── main.cc
│ ├── flutter
│ │ ├── generated_plugin_registrant.cc
│ │ ├── generated_plugin_registrant.h
│ │ └── generated_plugins.cmake
│ └── my_application.h
├── README.md
├── ios
│ ├── Runner
│ │ ├── Runner-Bridging-Header.h
│ │ ├── Assets.xcassets
│ │ │ ├── LaunchImage.imageset
│ │ │ │ ├── LaunchImage.png
│ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ ├── LaunchImage@3x.png
│ │ │ │ ├── README.md
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ ├── AppDelegate.swift
│ │ └── Base.lproj
│ │ │ └── Main.storyboard
│ ├── Flutter
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── AppFrameworkInfo.plist
│ ├── Runner.xcodeproj
│ │ └── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── RunnerTests
│ │ └── RunnerTests.swift
│ ├── .gitignore
│ ├── Podfile.lock
│ └── Podfile
├── web
│ ├── favicon.png
│ ├── icons
│ │ ├── Icon-192.png
│ │ ├── Icon-512.png
│ │ ├── Icon-maskable-192.png
│ │ └── Icon-maskable-512.png
│ └── manifest.json
├── android
│ ├── gradle.properties
│ ├── app
│ │ └── src
│ │ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── drawable
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-v21
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── values
│ │ │ │ │ └── styles.xml
│ │ │ │ └── values-night
│ │ │ │ │ └── styles.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── liveview_flutter
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ ├── .gitignore
│ ├── build.gradle
│ └── settings.gradle
├── macos
│ ├── Runner
│ │ ├── Configs
│ │ │ ├── Debug.xcconfig
│ │ │ ├── Release.xcconfig
│ │ │ ├── Warnings.xcconfig
│ │ │ └── AppInfo.xcconfig
│ │ ├── Assets.xcassets
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── app_icon_16.png
│ │ │ │ ├── app_icon_32.png
│ │ │ │ ├── app_icon_64.png
│ │ │ │ ├── app_icon_1024.png
│ │ │ │ ├── app_icon_128.png
│ │ │ │ ├── app_icon_256.png
│ │ │ │ ├── app_icon_512.png
│ │ │ │ └── Contents.json
│ │ ├── AppDelegate.swift
│ │ ├── Release.entitlements
│ │ ├── DebugProfile.entitlements
│ │ ├── MainFlutterWindow.swift
│ │ └── Info.plist
│ ├── .gitignore
│ ├── Flutter
│ │ ├── Flutter-Debug.xcconfig
│ │ ├── Flutter-Release.xcconfig
│ │ └── GeneratedPluginRegistrant.swift
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── Runner.xcodeproj
│ │ └── project.xcworkspace
│ │ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── RunnerTests
│ │ └── RunnerTests.swift
│ └── Podfile
├── windows
│ ├── runner
│ │ ├── resources
│ │ │ └── app_icon.ico
│ │ ├── resource.h
│ │ ├── utils.h
│ │ ├── runner.exe.manifest
│ │ ├── flutter_window.h
│ │ └── main.cpp
│ ├── flutter
│ │ ├── generated_plugin_registrant.cc
│ │ ├── generated_plugin_registrant.h
│ │ └── generated_plugins.cmake
│ └── .gitignore
├── devtools_options.yaml
├── .gitignore
├── lib
│ └── main.dart
└── analysis_options.yaml
├── lib
├── live_view
│ ├── ui
│ │ ├── theme
│ │ │ └── theme_loader.dart
│ │ ├── root_view
│ │ │ ├── internal_view.dart
│ │ │ ├── root_material_app.dart
│ │ │ ├── root_view.dart
│ │ │ ├── root_app_bar.dart
│ │ │ └── root_bottom_navigation_bar.dart
│ │ ├── components
│ │ │ ├── live_bottom_sheet.dart
│ │ │ ├── live_stack.dart
│ │ │ ├── live_view_body.dart
│ │ │ ├── live_expanded.dart
│ │ │ ├── live_link.dart
│ │ │ ├── live_title_attribute.dart
│ │ │ ├── live_avatar_attribute.dart
│ │ │ ├── live_hint_attribute.dart
│ │ │ ├── live_icon_attribute.dart
│ │ │ ├── live_label_attribute.dart
│ │ │ ├── live_leading_attribute.dart
│ │ │ ├── live_subtitle_attribute.dart
│ │ │ ├── live_trailing_attribute.dart
│ │ │ ├── live_content_attribute.dart
│ │ │ ├── live_underline_attribute.dart
│ │ │ ├── live_disabled_hint_attribute.dart
│ │ │ ├── live_icon_selected_attribute.dart
│ │ │ ├── live_persistent_footer_button.dart
│ │ │ ├── live_sized_box.dart
│ │ │ ├── live_filled_button.dart
│ │ │ ├── live_center.dart
│ │ │ ├── live_positioned.dart
│ │ │ ├── live_cached_networked_image.dart
│ │ │ ├── live_safe_area.dart
│ │ │ ├── live_drawer.dart
│ │ │ ├── live_text.dart
│ │ │ ├── live_end_drawer.dart
│ │ │ ├── live_container.dart
│ │ │ ├── live_drawer_header.dart
│ │ │ ├── live_scaffold.dart
│ │ │ ├── live_bottom_app_bar.dart
│ │ │ ├── live_card.dart
│ │ │ ├── live_icon.dart
│ │ │ ├── live_checkbox.dart
│ │ │ ├── live_text_button.dart
│ │ │ ├── live_elevated_button.dart
│ │ │ ├── live_row.dart
│ │ │ ├── live_column.dart
│ │ │ └── live_single_child_scroll_view.dart
│ │ ├── live_view_ui_registry.dart
│ │ ├── page_transition.dart
│ │ ├── errors
│ │ │ ├── error_404.dart
│ │ │ ├── flutter_error_view.dart
│ │ │ ├── no_server_error_view.dart
│ │ │ └── missing_page_component.dart
│ │ ├── loading
│ │ │ └── reload_widget.dart
│ │ └── node_state.dart
│ ├── reactive
│ │ ├── live_go_back_notifier.dart
│ │ ├── live_connection_notifier.dart
│ │ └── state_notifier.dart
│ ├── mapping
│ │ ├── boolean.dart
│ │ ├── duration.dart
│ │ ├── axis_direction.dart
│ │ ├── text_direction.dart
│ │ ├── mouse_cursor.dart
│ │ ├── notched_shape.dart
│ │ ├── text_baseline.dart
│ │ ├── drag_start_behavior.dart
│ │ ├── options_view_open_direction.dart
│ │ ├── material_tap_target_size.dart
│ │ ├── number.dart
│ │ ├── shape_border.dart
│ │ ├── clip_behavior.dart
│ │ ├── tooltip_trigger_mode.dart
│ │ ├── overflow_bar_alignment.dart
│ │ ├── scroll_view_keyboard_dismiss_behavior.dart
│ │ ├── navigation_rail_label_type.dart
│ │ ├── visual_density.dart
│ │ ├── border_radius.dart
│ │ ├── text_align.dart
│ │ ├── list_title_alignment.dart
│ │ ├── box_height_style.dart
│ │ ├── button_style.dart
│ │ ├── material_state.dart
│ │ ├── bottom_navigation_bar_type.dart
│ │ ├── input_decoration.dart
│ │ ├── alignment_directional.dart
│ │ ├── keyboard_type.dart
│ │ ├── edge_insets.dart
│ │ ├── decoration.dart
│ │ ├── border.dart
│ │ ├── axis_alignment.dart
│ │ └── css.dart
│ ├── plugin.dart
│ ├── state
│ │ └── element_key.dart
│ ├── routes
│ │ ├── no_transition_page.dart
│ │ └── live_custom_page.dart
│ └── webdocs.dart
├── liveview_flutter.dart
├── exec
│ ├── exec_go_back.dart
│ ├── exec_save_current_theme.dart
│ ├── exec_live_patch.dart
│ ├── exec_show_bottom_sheet.dart
│ ├── exec_switch_theme.dart
│ ├── exec_live_event.dart
│ ├── exec_phx_href.dart
│ ├── exec.dart
│ ├── live_view_exec_registry.dart
│ └── exec_confirmable.dart
└── platform_name.dart
├── CHANGELOG.md
├── test
├── components
│ ├── card_test.png
│ ├── modal_test.png
│ ├── action_chip_test.png
│ ├── icon_button_test.png
│ ├── list_title_test.png
│ ├── text_button_test.png
│ ├── bottom_sheet_test.png
│ ├── modal_closed_test.png
│ ├── modal_opened_test.png
│ ├── show_scaffold_test.png
│ ├── bottom_app_bar_test.png
│ ├── material_banner_test.png
│ ├── foating_action_button_test.png
│ ├── show_scaffold_removed_test.png
│ ├── persistent_footer_button_test.png
│ ├── foating_action_button_extended_test.png
│ ├── text_button_test.dart
│ ├── card_test.dart
│ ├── icon_button_test.dart
│ ├── list_tile_test.dart
│ ├── persistent_footer_button_test.dart
│ ├── material_banner_test.dart
│ ├── bottom_app_bar_test.dart
│ ├── bottom_sheet_test.dart
│ ├── floating_action_button_test.dart
│ └── action_chip_test.dart
├── theme
│ └── switch_theme_test.png
├── forms
│ ├── elevated_button_test.png
│ ├── live_segmented_button.png
│ ├── value_attribute_test.dart
│ └── text_field_test.dart
├── render
│ ├── implicit_column_test.png
│ ├── button_text_test.dart
│ ├── data_phx_main_test.dart
│ ├── implicit_column_test.dart
│ ├── render_with_variables_test.dart
│ ├── single_child_test.dart
│ ├── render_component_slots_test.dart
│ ├── whole_component_update_test.dart
│ ├── multiple_components_test.dart
│ ├── margin_test.dart
│ ├── padding_test.dart
│ └── dynamic_diff_test.dart
├── navigation
│ ├── navigation_rail_test.png
│ ├── transitions_test_loading.png
│ └── transitions_test_second_page.png
├── screenshots
│ ├── bottom_navigation_bar.png
│ └── bottom_navigation_bar_test.dart
├── errors
│ └── missing_view_body_test.dart
├── plugins
│ ├── basic_plugin_test.dart
│ └── basic_plugin.dart
├── events
│ └── phx_onload_test.dart
├── mapping
│ ├── colors_test.dart
│ ├── edge_insets_test.dart
│ ├── css_test.dart
│ ├── edge_colors_test.dart
│ └── text_style_test.dart
├── network
│ ├── reconnect_test.dart
│ └── cookies_test.dart
└── when
│ └── when_test.dart
├── documentation
├── execute-actions.md
├── lifecycle.md
└── widgets.md
├── .metadata
├── .gitignore
└── analysis_options.yaml
/dart_test.yaml:
--------------------------------------------------------------------------------
1 | tags:
2 | golden:
3 |
--------------------------------------------------------------------------------
/example/linux/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral
2 |
--------------------------------------------------------------------------------
/lib/live_view/ui/theme/theme_loader.dart:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.0.1
2 |
3 | * TODO: Describe initial release.
4 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # example
2 |
3 | A basic demo project for the live view client
4 |
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/example/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/web/favicon.png
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/lib/liveview_flutter.dart:
--------------------------------------------------------------------------------
1 | library liveview_flutter;
2 |
3 | export 'package:liveview_flutter/live_view/live_view.dart';
4 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/test/components/card_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/card_test.png
--------------------------------------------------------------------------------
/test/components/modal_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/modal_test.png
--------------------------------------------------------------------------------
/example/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/test/theme/switch_theme_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/theme/switch_theme_test.png
--------------------------------------------------------------------------------
/test/components/action_chip_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/action_chip_test.png
--------------------------------------------------------------------------------
/test/components/icon_button_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/icon_button_test.png
--------------------------------------------------------------------------------
/test/components/list_title_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/list_title_test.png
--------------------------------------------------------------------------------
/test/components/text_button_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/text_button_test.png
--------------------------------------------------------------------------------
/test/forms/elevated_button_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/forms/elevated_button_test.png
--------------------------------------------------------------------------------
/test/forms/live_segmented_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/forms/live_segmented_button.png
--------------------------------------------------------------------------------
/test/render/implicit_column_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/render/implicit_column_test.png
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/test/components/bottom_sheet_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/bottom_sheet_test.png
--------------------------------------------------------------------------------
/test/components/modal_closed_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/modal_closed_test.png
--------------------------------------------------------------------------------
/test/components/modal_opened_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/modal_opened_test.png
--------------------------------------------------------------------------------
/test/components/show_scaffold_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/show_scaffold_test.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/test/components/bottom_app_bar_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/bottom_app_bar_test.png
--------------------------------------------------------------------------------
/test/components/material_banner_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/material_banner_test.png
--------------------------------------------------------------------------------
/test/navigation/navigation_rail_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/navigation/navigation_rail_test.png
--------------------------------------------------------------------------------
/test/screenshots/bottom_navigation_bar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/screenshots/bottom_navigation_bar.png
--------------------------------------------------------------------------------
/example/windows/runner/resources/app_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/windows/runner/resources/app_icon.ico
--------------------------------------------------------------------------------
/test/components/foating_action_button_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/foating_action_button_test.png
--------------------------------------------------------------------------------
/test/components/show_scaffold_removed_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/show_scaffold_removed_test.png
--------------------------------------------------------------------------------
/test/navigation/transitions_test_loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/navigation/transitions_test_loading.png
--------------------------------------------------------------------------------
/test/navigation/transitions_test_second_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/navigation/transitions_test_second_page.png
--------------------------------------------------------------------------------
/example/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/test/components/persistent_footer_button_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/persistent_footer_button_test.png
--------------------------------------------------------------------------------
/example/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/test/components/foating_action_button_extended_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/test/components/foating_action_button_extended_test.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/lib/live_view/reactive/live_go_back_notifier.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class LiveGoBackNotifier extends ChangeNotifier {
4 | void notify() => notifyListeners();
5 | }
6 |
--------------------------------------------------------------------------------
/lib/live_view/reactive/live_connection_notifier.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class LiveConnectionNotifier extends ChangeNotifier {
4 | void wipeState() => notifyListeners();
5 | }
6 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
--------------------------------------------------------------------------------
/example/devtools_options.yaml:
--------------------------------------------------------------------------------
1 | description: This file stores settings for Dart & Flutter DevTools.
2 | documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
3 | extensions:
4 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/linux/main.cc:
--------------------------------------------------------------------------------
1 | #include "my_application.h"
2 |
3 | int main(int argc, char** argv) {
4 | g_autoptr(MyApplication) app = my_application_new();
5 | return g_application_run(G_APPLICATION(app), argc, argv);
6 | }
7 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/com/example/liveview_flutter/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.liveview_flutter
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex-min/live_view_native_flutter_client/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/macos/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/documentation/execute-actions.md:
--------------------------------------------------------------------------------
1 | # How to execute actions on the client
2 |
3 | Properties on the server-side which execute actions like phx-click, live-patch or switchTheme are defined in the client as "Execs".
4 |
5 |
6 | [....Add more here...]
--------------------------------------------------------------------------------
/lib/live_view/mapping/boolean.dart:
--------------------------------------------------------------------------------
1 | bool? getBoolean(String? prop) {
2 | switch (prop) {
3 | case 'true':
4 | return true;
5 | case 'false':
6 | return false;
7 | default:
8 | return null;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/example/linux/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #include "generated_plugin_registrant.h"
8 |
9 |
10 | void fl_register_plugins(FlPluginRegistry* registry) {
11 | }
12 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
6 |
--------------------------------------------------------------------------------
/example/windows/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #include "generated_plugin_registrant.h"
8 |
9 |
10 | void RegisterPlugins(flutter::PluginRegistry* registry) {
11 | }
12 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/duration.dart:
--------------------------------------------------------------------------------
1 | Duration? getDuration(String? prop) {
2 | if (prop == null) {
3 | return null;
4 | }
5 | var parse = int.tryParse(prop);
6 | if (parse == null) {
7 | return null;
8 | }
9 | return Duration(milliseconds: parse);
10 | }
11 |
--------------------------------------------------------------------------------
/example/macos/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | @NSApplicationMain
5 | class AppDelegate: FlutterAppDelegate {
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 | return true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/example/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/axis_direction.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/rendering.dart';
2 |
3 | Axis? getAxis(String? prop) {
4 | switch (prop) {
5 | case 'vertical':
6 | return Axis.vertical;
7 | case 'horizontal':
8 | return Axis.horizontal;
9 | default:
10 | return null;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/lib/live_view/ui/root_view/internal_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class InternalView extends StatelessWidget {
4 | final Widget child;
5 | const InternalView({super.key, required this.child});
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | return child;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/text_direction.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | TextDirection? getTextDirection(String? property) {
4 | switch (property) {
5 | case 'ltr':
6 | return TextDirection.ltr;
7 | case 'rtl':
8 | return TextDirection.rtl;
9 | default:
10 | return null;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/mouse_cursor.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/rendering.dart';
2 |
3 | MouseCursor? getMouseCursor(String? attribute) {
4 | switch (attribute) {
5 | case 'defer':
6 | return MouseCursor.defer;
7 | case 'uncontrolled':
8 | return MouseCursor.uncontrolled;
9 | default:
10 | return null;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/notched_shape.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | NotchedShape? getNotchedShape(String? prop) {
4 | // TODO: handle AutomaticNotchedReactable
5 | switch (prop) {
6 | case 'CircularNotchedRectangle':
7 | return const CircularNotchedRectangle();
8 | default:
9 | return null;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/text_baseline.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | TextBaseline? getTextBaseline(String? prop) {
4 | switch (prop) {
5 | case 'alphabetic':
6 | return TextBaseline.alphabetic;
7 | case 'ideographic':
8 | return TextBaseline.ideographic;
9 | default:
10 | return null;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9"
8 | channel: "stable"
9 |
10 | project_type: package
11 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/drag_start_behavior.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/gestures.dart';
2 |
3 | DragStartBehavior? getDragStartBehavior(String? prop) {
4 | switch (prop) {
5 | case 'down':
6 | return DragStartBehavior.down;
7 | case 'start':
8 | return DragStartBehavior.start;
9 | default:
10 | return null;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/lib/live_view/plugin.dart:
--------------------------------------------------------------------------------
1 | import 'package:liveview_flutter/exec/live_view_exec_registry.dart';
2 | import 'package:liveview_flutter/live_view/ui/live_view_ui_registry.dart';
3 |
4 | abstract class Plugin {
5 | String get name;
6 |
7 | void registerWidgets(LiveViewUiRegistry registry);
8 |
9 | void registerExecs(LiveViewExecRegistry registry);
10 | }
11 |
--------------------------------------------------------------------------------
/lib/exec/exec_go_back.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 | import 'package:liveview_flutter/exec/exec.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
4 |
5 | class ExecGoBack extends Exec {
6 | @override
7 | void handler(BuildContext context, StateWidget widget) {
8 | widget.liveView.goBack();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/example/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/example/macos/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import FlutterMacOS
2 | import Cocoa
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/example/windows/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral/
2 |
3 | # Visual Studio user-specific files.
4 | *.suo
5 | *.user
6 | *.userosscache
7 | *.sln.docstates
8 |
9 | # Visual Studio build-related files.
10 | x64/
11 | x86/
12 |
13 | # Visual Studio cache files
14 | # files ending in .cache can be ignored
15 | *.[Cc]ache
16 | # but keep track of directories ending in .cache
17 | !*.[Cc]ache/
18 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/options_view_open_direction.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | OptionsViewOpenDirection? getOptionsViewOpenDirection(String? prop) {
4 | switch (prop) {
5 | case 'down':
6 | return OptionsViewOpenDirection.down;
7 | case 'up':
8 | return OptionsViewOpenDirection.up;
9 | default:
10 | return null;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/material_tap_target_size.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | MaterialTapTargetSize? getMaterialTapTargetSize(String? prop) {
4 | switch (prop) {
5 | case 'padded':
6 | return MaterialTapTargetSize.padded;
7 | case 'shrinkWrap':
8 | return MaterialTapTargetSize.shrinkWrap;
9 | default:
10 | return null;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/lib/exec/exec_save_current_theme.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 | import 'package:liveview_flutter/exec/exec.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
4 |
5 | class ExecSaveCurrentTheme extends Exec {
6 | @override
7 | void handler(BuildContext context, StateWidget widget) {
8 | widget.liveView.saveCurrentTheme();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/number.dart:
--------------------------------------------------------------------------------
1 | double? getDouble(String? property) {
2 | if (property == null) {
3 | return null;
4 | }
5 | if (property == 'infinity') {
6 | return double.infinity;
7 | }
8 | return double.tryParse(property);
9 | }
10 |
11 | int? getInt(String? property) {
12 | if (property == null) {
13 | return null;
14 | }
15 | return int.tryParse(property);
16 | }
17 |
--------------------------------------------------------------------------------
/example/linux/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #ifndef GENERATED_PLUGIN_REGISTRANT_
8 | #define GENERATED_PLUGIN_REGISTRANT_
9 |
10 | #include
11 |
12 | // Registers Flutter plugins.
13 | void fl_register_plugins(FlPluginRegistry* registry);
14 |
15 | #endif // GENERATED_PLUGIN_REGISTRANT_
16 |
--------------------------------------------------------------------------------
/example/windows/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #ifndef GENERATED_PLUGIN_REGISTRANT_
8 | #define GENERATED_PLUGIN_REGISTRANT_
9 |
10 | #include
11 |
12 | // Registers Flutter plugins.
13 | void RegisterPlugins(flutter::PluginRegistry* registry);
14 |
15 | #endif // GENERATED_PLUGIN_REGISTRANT_
16 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/shape_border.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/rendering.dart';
2 |
3 | ShapeBorder? getShapeBorder(String? prop) {
4 | // TODO: handle all the kinds of custom shape
5 | switch (prop) {
6 | case 'CircleBorder':
7 | return const CircleBorder();
8 | case 'BeveledRectangleBorder':
9 | return const BeveledRectangleBorder();
10 | default:
11 | return null;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/clip_behavior.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | Clip? getClip(String? prop) {
4 | switch (prop) {
5 | case 'antiAlias':
6 | return Clip.antiAlias;
7 | case 'antiAliasWithSaveLayer':
8 | return Clip.antiAliasWithSaveLayer;
9 | case 'hardEdge':
10 | return Clip.hardEdge;
11 | case 'none':
12 | return Clip.none;
13 | default:
14 | return null;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/tooltip_trigger_mode.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | TooltipTriggerMode? getTooltipTriggerMode(String? prop) {
4 | switch (prop) {
5 | case 'longPress':
6 | return TooltipTriggerMode.longPress;
7 | case 'manual':
8 | return TooltipTriggerMode.manual;
9 | case 'tap':
10 | return TooltipTriggerMode.tap;
11 | default:
12 | return null;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/macos/Runner/DebugProfile.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.network.server
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/overflow_bar_alignment.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | OverflowBarAlignment? getOverflowBarAlignment(String? prop) {
4 | switch (prop) {
5 | case 'center':
6 | return OverflowBarAlignment.center;
7 | case 'end':
8 | return OverflowBarAlignment.end;
9 | case 'start':
10 | return OverflowBarAlignment.start;
11 | default:
12 | return null;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/scroll_view_keyboard_dismiss_behavior.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | ScrollViewKeyboardDismissBehavior? getScrollViewKeyboardDismissBehavior(
4 | String? prop) {
5 | switch (prop) {
6 | case 'manual':
7 | return ScrollViewKeyboardDismissBehavior.manual;
8 | case 'onDrag':
9 | return ScrollViewKeyboardDismissBehavior.onDrag;
10 | default:
11 | return null;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/exec/exec_live_patch.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 | import 'package:liveview_flutter/exec/exec.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
4 |
5 | class ExecLivePatch extends Exec {
6 | String url;
7 |
8 | ExecLivePatch({required this.url});
9 |
10 | @override
11 | void handler(BuildContext context, StateWidget widget) {
12 | widget.liveView.livePatch(url);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/lib/live_view/state/element_key.dart:
--------------------------------------------------------------------------------
1 | class ElementKey {
2 | ElementKey(this.key);
3 |
4 | final String key;
5 |
6 | @override
7 | bool operator ==(Object other) {
8 | if (other is! ElementKey) return false;
9 | if (key != other.key) return false;
10 | return true;
11 | }
12 |
13 | @override
14 | int get hashCode => key.hashCode;
15 |
16 | @override
17 | String toString() {
18 | return 'ElementKey{key: $key}';
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/navigation_rail_label_type.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | NavigationRailLabelType? getNavigationRailLabelType(String? attribute) {
4 | switch (attribute) {
5 | case 'all':
6 | return NavigationRailLabelType.all;
7 | case 'selected':
8 | return NavigationRailLabelType.selected;
9 | case 'none':
10 | return NavigationRailLabelType.none;
11 | default:
12 | return null;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController()
7 | let windowFrame = self.frame
8 | self.contentViewController = flutterViewController
9 | self.setFrame(windowFrame, display: true)
10 |
11 | RegisterGeneratedPlugins(registry: flutterViewController)
12 |
13 | super.awakeFromNib()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lib/exec/exec_show_bottom_sheet.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 | import 'package:liveview_flutter/exec/exec.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
4 | import 'package:liveview_flutter/live_view/ui/root_view/root_scaffold.dart';
5 |
6 | class ExecShowBottomSheet extends Exec {
7 | @override
8 | void handler(BuildContext context, StateWidget widget) {
9 | ShowBottomSheetNotification().dispatch(context);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/example/linux/my_application.h:
--------------------------------------------------------------------------------
1 | #ifndef FLUTTER_MY_APPLICATION_H_
2 | #define FLUTTER_MY_APPLICATION_H_
3 |
4 | #include
5 |
6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
7 | GtkApplication)
8 |
9 | /**
10 | * my_application_new:
11 | *
12 | * Creates a new Flutter-based application.
13 | *
14 | * Returns: a new #MyApplication.
15 | */
16 | MyApplication* my_application_new();
17 |
18 | #endif // FLUTTER_MY_APPLICATION_H_
19 |
--------------------------------------------------------------------------------
/lib/exec/exec_switch_theme.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 | import 'package:liveview_flutter/exec/exec.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
4 |
5 | class ExecSwitchTheme extends Exec {
6 | String theme;
7 | String mode;
8 |
9 | ExecSwitchTheme({required this.theme, required this.mode});
10 |
11 | @override
12 | void handler(BuildContext context, StateWidget widget) {
13 | widget.liveView.switchTheme(theme, mode);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/visual_density.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | VisualDensity? getVisualDensity(String? prop) {
4 | switch (prop) {
5 | case 'adaptivePlatformDensity':
6 | return VisualDensity.adaptivePlatformDensity;
7 | case 'comfortable':
8 | return VisualDensity.comfortable;
9 | case 'standard':
10 | return VisualDensity.standard;
11 | case 'compact':
12 | return VisualDensity.compact;
13 | default:
14 | return null;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/example/windows/runner/resource.h:
--------------------------------------------------------------------------------
1 | //{{NO_DEPENDENCIES}}
2 | // Microsoft Visual C++ generated include file.
3 | // Used by Runner.rc
4 | //
5 | #define IDI_APP_ICON 101
6 |
7 | // Next default values for new objects
8 | //
9 | #ifdef APSTUDIO_INVOKED
10 | #ifndef APSTUDIO_READONLY_SYMBOLS
11 | #define _APS_NEXT_RESOURCE_VALUE 102
12 | #define _APS_NEXT_COMMAND_VALUE 40001
13 | #define _APS_NEXT_CONTROL_VALUE 1001
14 | #define _APS_NEXT_SYMED_VALUE 101
15 | #endif
16 | #endif
17 |
--------------------------------------------------------------------------------
/test/components/text_button_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 |
3 | import '../test_helpers.dart';
4 |
5 | main() async {
6 | testWidgets('looks okay', (tester) => tester.checkScreenshot("""
7 |
8 |
9 |
10 | hello
11 | hello
12 |
13 |
14 |
15 | """, 'text_button_test.png'));
16 | }
17 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/components/card_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 |
3 | import '../test_helpers.dart';
4 |
5 | main() async {
6 | testWidgets('icon button test', (tester) => tester.checkScreenshot("""
7 |
8 |
9 |
10 | demo
11 | demo
12 | demo
13 |
14 |
15 |
16 | """, 'card_test.png'));
17 | }
18 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/border_radius.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/mapping/edge_insets.dart';
3 |
4 | BorderRadius? getBorderRadius(String? borderRadio) {
5 | var edges = getEdgeInsets(borderRadio);
6 |
7 | if (edges == null) {
8 | return null;
9 | }
10 |
11 | return BorderRadius.only(
12 | topLeft: Radius.circular(edges.top),
13 | topRight: Radius.circular(edges.right),
14 | bottomRight: Radius.circular(edges.bottom),
15 | bottomLeft: Radius.circular(edges.left),
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/text_align.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | TextAlign? getTextAlign(String? prop) {
4 | switch (prop) {
5 | case 'center':
6 | return TextAlign.center;
7 | case 'end':
8 | return TextAlign.end;
9 | case 'start':
10 | return TextAlign.start;
11 | case 'right':
12 | return TextAlign.right;
13 | case 'justify':
14 | return TextAlign.justify;
15 | default:
16 | if (prop != null) {
17 | debugPrint("Unknown text align property $prop");
18 | }
19 | return null;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/list_title_alignment.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | ListTileTitleAlignment? getListTitleAligment(String? prop) {
4 | switch (prop) {
5 | case 'bottom':
6 | return ListTileTitleAlignment.bottom;
7 | case 'center':
8 | return ListTileTitleAlignment.center;
9 | case 'threeLine':
10 | return ListTileTitleAlignment.threeLine;
11 | case 'titleHeight':
12 | return ListTileTitleAlignment.titleHeight;
13 | case 'top':
14 | return ListTileTitleAlignment.top;
15 | default:
16 | return null;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/test/render/button_text_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 |
4 | import '../test_helpers.dart';
5 |
6 | main() async {
7 | testWidgets('Button with inner text are rendered as a Text element',
8 | (tester) async {
9 | var view = LiveView()
10 | ..handleRenderedMessage({
11 | 's': ['My button'],
12 | });
13 |
14 | await tester.runLiveView(view);
15 | await tester.pumpAndSettle();
16 |
17 | expect(find.firstText(), 'My button');
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/example/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 | import path_provider_foundation
9 | import shared_preferences_foundation
10 | import sqflite_darwin
11 |
12 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
13 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
14 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
15 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
16 | }
17 |
--------------------------------------------------------------------------------
/test/render/data_phx_main_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 |
4 | import '../test_helpers.dart';
5 |
6 | main() async {
7 | testWidgets('The main data-phx-main div does not break the render',
8 | (tester) async {
9 | var view = LiveView()
10 | ..handleRenderedMessage({
11 | 's': ['works
'],
12 | });
13 |
14 | await tester.runLiveView(view);
15 | await tester.pumpAndSettle();
16 | expect(find.firstText(), 'works');
17 | });
18 | }
19 |
--------------------------------------------------------------------------------
/lib/exec/exec_live_event.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 | import 'package:liveview_flutter/exec/exec_confirmable.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
4 |
5 | class ExecLiveEvent extends ExecConfirmable {
6 | final String type;
7 | final String name;
8 | final dynamic value;
9 |
10 | ExecLiveEvent({
11 | required this.type,
12 | required this.name,
13 | required this.value,
14 | super.dataConfirm,
15 | });
16 |
17 | @override
18 | void handler(BuildContext context, StateWidget widget) {
19 | widget.liveView.sendEvent(this);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_bottom_sheet.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveBottomSheet extends LiveStateWidget {
5 | const LiveBottomSheet({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveBottomSheetState();
9 | }
10 |
11 | class _LiveBottomSheetState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) => singleChild();
17 | }
18 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_stack.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveStack extends LiveStateWidget {
5 | const LiveStack({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveStackState();
9 | }
10 |
11 | class _LiveStackState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) {
17 | return Stack(children: multipleChildren());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_view_body.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveViewBody extends LiveStateWidget {
5 | const LiveViewBody({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveMainViewState();
9 | }
10 |
11 | class _LiveMainViewState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) {
17 | return singleChild();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/documentation/lifecycle.md:
--------------------------------------------------------------------------------
1 | # How the state lifecycle works
2 |
3 | Components are defined in ```lib/live_view/ui/components/``` and all inherit the class ```LiveStateWidget```.
4 |
5 | This class is responsible for handling all the state changes and refresh widgets if necessary.
6 |
7 |
8 | ## Changing pages & Wiping states
9 |
10 | When changing pages, the existing state is wiped. Any changes which was done a result of a user action is reset.
11 |
12 | This is done to avoid "phantom states", where the UI would carry the previous state into the next page.
13 |
14 | This client is only supporting a single page at a time at the moment so this is necessary.
--------------------------------------------------------------------------------
/test/components/icon_button_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 |
3 | import '../test_helpers.dart';
4 |
5 | main() async {
6 | testWidgets('icon button test', (tester) => tester.checkScreenshot("""
7 |
8 |
9 |
10 |
11 |
12 |
13 | demo
14 |
15 |
16 |
17 | """, 'icon_button_test.png'));
18 | }
19 |
--------------------------------------------------------------------------------
/test/render/implicit_column_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 |
4 | import '../test_helpers.dart';
5 |
6 | bool? checkValue() =>
7 | (find.byType(Checkbox).evaluate().first.widget as Checkbox).value;
8 |
9 | main() async {
10 | testWidgets('implicit columns behaves like html',
11 | (tester) => tester.checkScreenshot("""
12 |
13 | multiple
14 | lines
15 | in a container supporting a single child
16 |
17 | """, 'implicit_column_test.png'));
18 | }
19 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_expanded.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveExpanded extends LiveStateWidget {
5 | const LiveExpanded({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveExpandedState();
9 | }
10 |
11 | class _LiveExpandedState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) {
17 | return Expanded(child: singleChild());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_link.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveLink extends LiveStateWidget {
5 | const LiveLink({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveCenterState();
9 | }
10 |
11 | class _LiveCenterState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) {
17 | return MouseRegion(cursor: SystemMouseCursors.click, child: singleChild());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Warnings.xcconfig:
--------------------------------------------------------------------------------
1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
2 | GCC_WARN_UNDECLARED_SELECTOR = YES
3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
6 | CLANG_WARN_PRAGMA_PACK = YES
7 | CLANG_WARN_STRICT_PROTOTYPES = YES
8 | CLANG_WARN_COMMA = YES
9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES
10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
12 | GCC_WARN_SHADOW = YES
13 | CLANG_WARN_UNREACHABLE_CODE = YES
14 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/box_height_style.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | BoxHeightStyle? getBoxHeightStyle(String? prop) {
4 | switch (prop) {
5 | case 'includeLineSpacingBottom':
6 | return BoxHeightStyle.includeLineSpacingBottom;
7 | case 'includeLineSpacingMiddle':
8 | return BoxHeightStyle.includeLineSpacingMiddle;
9 | case 'includeLineSpacingTop':
10 | return BoxHeightStyle.includeLineSpacingTop;
11 | case 'max':
12 | return BoxHeightStyle.max;
13 | case 'strut':
14 | return BoxHeightStyle.strut;
15 | case 'tight':
16 | return BoxHeightStyle.tight;
17 | default:
18 | return null;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_title_attribute.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveTitleAttribute extends LiveStateWidget {
5 | const LiveTitleAttribute({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveTitleState();
9 | }
10 |
11 | class _LiveTitleState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) {
17 | return singleChild();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_avatar_attribute.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveAvatarAttribute extends LiveStateWidget {
5 | const LiveAvatarAttribute({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveAvatarAttributeState();
9 | }
10 |
11 | class _LiveAvatarAttributeState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) => singleChild();
17 | }
18 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_hint_attribute.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveHintAttribute extends LiveStateWidget {
5 | const LiveHintAttribute({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveHintAttributeState();
9 | }
10 |
11 | class _LiveHintAttributeState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) {
17 | return singleChild();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_icon_attribute.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveIconAttribute extends LiveStateWidget {
5 | const LiveIconAttribute({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveIconAttributeState();
9 | }
10 |
11 | class _LiveIconAttributeState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) {
17 | return singleChild();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/components/list_tile_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 |
3 | import '../test_helpers.dart';
4 |
5 | main() async {
6 | testWidgets('icon button test', (tester) => tester.checkScreenshot("""
7 |
8 |
9 |
10 |
11 | my list tile
12 | subtitle here
13 |
14 | hello
15 |
16 |
17 |
18 | """, 'list_title_test.png'));
19 | }
20 |
--------------------------------------------------------------------------------
/test/errors/missing_view_body_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 |
4 | import '../test_helpers.dart';
5 |
6 | main() async {
7 | testWidgets('Button with inner text are rendered as a Text element',
8 | (tester) async {
9 | var view = LiveView()
10 | ..handleRenderedMessage({
11 | 's': ['helloworld'],
12 | });
13 |
14 | await tester.runLiveView(view);
15 | await tester.pumpAndSettle();
16 |
17 | expect(
18 | find.firstText(), 'Unable to find any component on url /');
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/button_style.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/mapping/css.dart';
3 | import 'package:liveview_flutter/live_view/mapping/text_style_map.dart';
4 |
5 | ButtonStyle? getButtonStyle(BuildContext context, String? style) {
6 | if (style == null) {
7 | return null;
8 | }
9 | MaterialStateProperty? textStyle;
10 |
11 | for (var (styleKey, styleValue) in parseCss(style)) {
12 | switch (styleKey) {
13 | case 'textStyle':
14 | textStyle = getMaterialTextStyle(styleValue, context);
15 | break;
16 | }
17 | }
18 | return ButtonStyle(textStyle: textStyle);
19 | }
20 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_label_attribute.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveLabelAttribute extends LiveStateWidget {
5 | const LiveLabelAttribute({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveLabelAttributeState();
9 | }
10 |
11 | class _LiveLabelAttributeState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) {
17 | return singleChild();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_leading_attribute.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveLeadingAttribute extends LiveStateWidget {
5 | const LiveLeadingAttribute({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveLeadingState();
9 | }
10 |
11 | class _LiveLeadingState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) {
17 | return singleChild();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_subtitle_attribute.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveSubtitleAttribute extends LiveStateWidget {
5 | const LiveSubtitleAttribute({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveTitleState();
9 | }
10 |
11 | class _LiveTitleState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) {
17 | return singleChild();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_trailing_attribute.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveTrailingAttribute extends LiveStateWidget {
5 | const LiveTrailingAttribute({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveTitleState();
9 | }
10 |
11 | class _LiveTitleState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) {
17 | return singleChild();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_content_attribute.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveContentAttribute extends LiveStateWidget {
5 | const LiveContentAttribute({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveContentAttributeState();
9 | }
10 |
11 | class _LiveContentAttributeState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) {
17 | return singleChild();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/AppInfo.xcconfig:
--------------------------------------------------------------------------------
1 | // Application-level settings for the Runner target.
2 | //
3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
4 | // future. If not, the values below would default to using the project name when this becomes a
5 | // 'flutter create' template.
6 |
7 | // The application's name. By default this is also the title of the Flutter window.
8 | PRODUCT_NAME = liveview_flutter
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.liveviewFlutter
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2023 com.example. All rights reserved.
15 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_underline_attribute.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveUnderlineAttribute extends LiveStateWidget {
5 | const LiveUnderlineAttribute({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveUnderlineAttributeState();
9 | }
10 |
11 | class _LiveUnderlineAttributeState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {}
14 |
15 | @override
16 | Widget render(BuildContext context) {
17 | return singleChild();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/render/render_with_variables_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 |
4 | import '../test_helpers.dart';
5 |
6 | main() async {
7 | testWidgets('handles variable inside text', (tester) async {
8 | var view = LiveView()
9 | ..handleRenderedMessage({
10 | 's': [
11 | 'the first counter is (',
12 | ') and the second one is (',
13 | ')'
14 | ],
15 | '0': 10,
16 | '1': 12
17 | });
18 |
19 | await tester.runLiveView(view);
20 | expect(find.firstText(),
21 | 'the first counter is (10) and the second one is (12)');
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/lib/platform_name.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io' show Platform;
2 |
3 | import 'package:flutter/foundation.dart';
4 |
5 | String getPlatformName() {
6 | var platformName = '';
7 | if (kIsWeb) {
8 | platformName = "Web";
9 | } else {
10 | if (Platform.isAndroid) {
11 | platformName = "Android";
12 | } else if (Platform.isIOS) {
13 | platformName = "IOS";
14 | } else if (Platform.isFuchsia) {
15 | platformName = "Fuchsia";
16 | } else if (Platform.isLinux) {
17 | platformName = "Linux";
18 | } else if (Platform.isMacOS) {
19 | platformName = "MacOS";
20 | } else if (Platform.isWindows) {
21 | platformName = "Windows";
22 | }
23 | }
24 | return platformName;
25 | }
26 |
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/material_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | MaterialState? getMaterialState(String? value) {
4 | switch (value) {
5 | case 'disabled':
6 | return MaterialState.disabled;
7 | case 'hovered':
8 | return MaterialState.hovered;
9 | case 'focused':
10 | return MaterialState.focused;
11 | case 'pressed':
12 | return MaterialState.pressed;
13 | case 'dragged':
14 | return MaterialState.dragged;
15 | case 'selected':
16 | return MaterialState.selected;
17 | case 'scrolledUnder':
18 | return MaterialState.scrolledUnder;
19 | case 'error':
20 | return MaterialState.error;
21 | default:
22 | return null;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/live_view/routes/no_transition_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class NoTransitionPage extends MaterialPage {
4 | const NoTransitionPage({
5 | required super.child,
6 | super.maintainState = true,
7 | super.fullscreenDialog = false,
8 | super.allowSnapshotting = true,
9 | super.key,
10 | super.name,
11 | super.arguments,
12 | super.restorationId,
13 | });
14 |
15 | @override
16 | Route createRoute(BuildContext context) {
17 | return PageRouteBuilder(
18 | settings: this,
19 | pageBuilder: (BuildContext context, Animation animation,
20 | Animation secondaryAnimation) {
21 | return child;
22 | });
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/test/render/single_child_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:liveview_flutter/live_view/live_view.dart';
4 |
5 | import '../test_helpers.dart';
6 |
7 | main() async {
8 | testWidgets('supports implicit Columns for single child widgets',
9 | (tester) async {
10 | var view = LiveView()
11 | ..handleRenderedMessage({
12 | 's': [
13 | """
14 | hello
15 | world
16 | """
17 | ],
18 | });
19 |
20 | await tester.runLiveView(view);
21 |
22 | find.firstOf();
23 | expect(find.allTexts(), ['hello', 'world']);
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/lib/exec/exec_phx_href.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/exec/exec.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
4 |
5 | class ExecPhxHref extends Exec {
6 | String url;
7 |
8 | ExecPhxHref({required this.url});
9 |
10 | @override
11 | void handler(BuildContext context, StateWidget widget) {
12 | widget.liveView.execHrefClick(url);
13 | }
14 | }
15 |
16 | class ExecPhxHrefModal extends Exec {
17 | String url;
18 |
19 | ExecPhxHrefModal({required this.url});
20 |
21 | @override
22 | void handler(BuildContext context, StateWidget widget) {
23 | Navigator.of(context).pop();
24 | widget.liveView.execHrefClick(url);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.7.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.3.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | tasks.register("clean", Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/test/render/render_component_slots_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 |
4 | import '../test_helpers.dart';
5 |
6 | void main() async {
7 | testWidgets('handles live components', (tester) async {
8 | var view = LiveView()
9 | ..handleRenderedMessage({
10 | "0": {
11 | "0": {
12 | "s": ["Hello"]
13 | },
14 | "s": ["", ""],
15 | "r": 1
16 | },
17 | "s": ["", ""],
18 | "r": 1
19 | });
20 |
21 | await tester.runLiveView(view);
22 | await tester.pumpAndSettle();
23 |
24 | expect(find.allTexts(), ['Hello']);
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/example/windows/runner/utils.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_UTILS_H_
2 | #define RUNNER_UTILS_H_
3 |
4 | #include
5 | #include
6 |
7 | // Creates a console for the process, and redirects stdout and stderr to
8 | // it for both the runner and the Flutter library.
9 | void CreateAndAttachConsole();
10 |
11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
12 | // encoded in UTF-8. Returns an empty std::string on failure.
13 | std::string Utf8FromUtf16(const wchar_t* utf16_string);
14 |
15 | // Gets the command line arguments passed in as a std::vector,
16 | // encoded in UTF-8. Returns an empty std::vector on failure.
17 | std::vector GetCommandLineArguments();
18 |
19 | #endif // RUNNER_UTILS_H_
20 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_disabled_hint_attribute.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveDisabledHintAttribute
5 | extends LiveStateWidget {
6 | const LiveDisabledHintAttribute({super.key, required super.state});
7 |
8 | @override
9 | State createState() =>
10 | _LiveDisabledHintAttributeState();
11 | }
12 |
13 | class _LiveDisabledHintAttributeState
14 | extends StateWidget {
15 | @override
16 | void onStateChange(Map diff) {}
17 |
18 | @override
19 | Widget render(BuildContext context) {
20 | return singleChild();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_icon_selected_attribute.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveIconSelectedAttribute
5 | extends LiveStateWidget {
6 | const LiveIconSelectedAttribute({super.key, required super.state});
7 |
8 | @override
9 | State createState() =>
10 | _LiveIconSelectedAttributeState();
11 | }
12 |
13 | class _LiveIconSelectedAttributeState
14 | extends StateWidget {
15 | @override
16 | void onStateChange(Map diff) {}
17 |
18 | @override
19 | Widget render(BuildContext context) {
20 | return singleChild();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_persistent_footer_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LivePersistentFooterButton
5 | extends LiveStateWidget {
6 | const LivePersistentFooterButton({super.key, required super.state});
7 |
8 | @override
9 | State createState() =>
10 | _LivePersistentFooterButtonState();
11 | }
12 |
13 | class _LivePersistentFooterButtonState
14 | extends StateWidget {
15 | @override
16 | void onStateChange(Map diff) {}
17 |
18 | @override
19 | Widget render(BuildContext context) {
20 | return singleChild();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }
9 | settings.ext.flutterSdkPath = flutterSdkPath()
10 |
11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
12 |
13 | plugins {
14 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
15 | }
16 | }
17 |
18 | include ":app"
19 |
20 | apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle"
21 |
--------------------------------------------------------------------------------
/lib/exec/exec.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 | import 'package:liveview_flutter/live_view/ui/utils.dart';
4 | import 'package:liveview_flutter/when/when.dart';
5 |
6 | /// Represents an action that can be executed in the view.
7 | ///
8 | /// It can be an event or a command, like changing the current theme or switching pages.
9 | abstract class Exec {
10 | When conditions = When();
11 |
12 | void conditionalHandler(BuildContext context, StateWidget widget) {
13 | if (conditions.execute(context) == false) return;
14 | handler(context, widget);
15 | }
16 |
17 | void handler(BuildContext context, StateWidget widget) {
18 | reportError("Unimplemented action handler: $this");
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/live_view/routes/live_custom_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class LiveCustomPage extends MaterialPage {
4 | const LiveCustomPage({
5 | required super.child,
6 | super.maintainState = true,
7 | super.fullscreenDialog = false,
8 | super.allowSnapshotting = true,
9 | super.key,
10 | super.name,
11 | super.arguments,
12 | super.restorationId,
13 | });
14 |
15 | @override
16 | Route createRoute(BuildContext context) {
17 | return PageRouteBuilder(
18 | settings: this,
19 | pageBuilder: (BuildContext context, Animation animation,
20 | Animation secondaryAnimation) {
21 | return FadeTransition(
22 | opacity: animation,
23 | child: child,
24 | );
25 | });
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/bottom_navigation_bar_type.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | BottomNavigationBarType? getBottomNavigationBarType(String? prop) {
4 | switch (prop) {
5 | case 'fixed':
6 | return BottomNavigationBarType.fixed;
7 | case 'shifting':
8 | return BottomNavigationBarType.shifting;
9 | default:
10 | return null;
11 | }
12 | }
13 |
14 | BottomNavigationBarLandscapeLayout? getBottomNavigationBarLandscapeLayout(
15 | String? prop) {
16 | switch (prop) {
17 | case 'centered':
18 | return BottomNavigationBarLandscapeLayout.centered;
19 | case 'linear':
20 | return BottomNavigationBarLandscapeLayout.linear;
21 | case 'spread':
22 | return BottomNavigationBarLandscapeLayout.spread;
23 | default:
24 | return null;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/components/persistent_footer_button_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 |
4 | import '../test_helpers.dart';
5 |
6 | bool? checkValue() =>
7 | (find.byType(Checkbox).evaluate().first.widget as Checkbox).value;
8 |
9 | main() async {
10 | testWidgets('looks okay', (tester) => tester.checkScreenshot("""
11 |
12 | hello
13 | hello
14 |
15 |
16 | hello
17 |
18 |
19 |
20 | """, 'persistent_footer_button_test.png'));
21 | }
22 |
--------------------------------------------------------------------------------
/test/plugins/basic_plugin_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 |
4 | import '../test_helpers.dart';
5 | import 'basic_plugin.dart';
6 |
7 | void main() {
8 | testWidgets('basic plugin', (tester) async {
9 | var (view, _) =
10 | await connect(LiveView()..installPlugins([BasicPlugin()]), rendered: {
11 | 's': [
12 | """
13 |
14 | """
15 | ],
16 | });
17 |
18 | await tester.runLiveView(view);
19 | await tester.pumpAndSettle();
20 |
21 | expect(find.allTexts(), ['MyComponent']);
22 |
23 | await tester.tap(find.text('MyComponent'), warnIfMissed: false);
24 | await tester.pumpAndSettle();
25 |
26 | expect(myPluginActions, ['1']);
27 | });
28 | }
29 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_sized_box.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveSizedBox extends LiveStateWidget {
5 | const LiveSizedBox({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveSizedBoxState();
9 | }
10 |
11 | class _LiveSizedBoxState extends StateWidget {
12 | final attributes = ['height', 'width'];
13 |
14 | @override
15 | void onStateChange(Map diff) =>
16 | reloadAttributes(node, attributes);
17 |
18 | @override
19 | Widget render(BuildContext context) {
20 | return SizedBox(
21 | height: doubleAttribute('height'),
22 | width: doubleAttribute('width'),
23 | child: singleChild(),
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_filled_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveFilledButton extends LiveStateWidget {
5 | const LiveFilledButton({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveFilledButtonState();
9 | }
10 |
11 | class _LiveFilledButtonState extends StateWidget {
12 | @override
13 | handleClickState() => HandleClickState.manual;
14 |
15 | @override
16 | void onStateChange(Map diff) {}
17 |
18 | @override
19 | Widget render(BuildContext context) {
20 | return FilledButton(
21 | onPressed: () {
22 | executeTapEventsManually();
23 | },
24 | child: singleChild());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/example/linux/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | )
7 |
8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
9 | )
10 |
11 | set(PLUGIN_BUNDLED_LIBRARIES)
12 |
13 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
18 | endforeach(plugin)
19 |
20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
23 | endforeach(ffi_plugin)
24 |
--------------------------------------------------------------------------------
/lib/live_view/webdocs.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 | import "package:universal_html/html.dart" as web_html;
4 |
5 | void bindWebDocs(LiveView view) {
6 | if (kIsWeb) {
7 | view.clientType == ClientType.webDocs;
8 | var renderFromUrl =
9 | Uri.parse('http://localhost${web_html.window.location.search}')
10 | .queryParameters['r'];
11 | if (renderFromUrl != null) {
12 | view.handleRenderedMessage({
13 | 's': [renderFromUrl]
14 | }, viewType: ViewType.deadView);
15 | }
16 | web_html.window.onMessage.listen((event) {
17 | var data = event.data;
18 | if (data is String) {
19 | view.handleRenderedMessage({
20 | 's': [data]
21 | }, viewType: ViewType.deadView);
22 | }
23 | });
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/example/windows/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | )
7 |
8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
9 | )
10 |
11 | set(PLUGIN_BUNDLED_LIBRARIES)
12 |
13 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
18 | endforeach(plugin)
19 |
20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
23 | endforeach(ffi_plugin)
24 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/input_decoration.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/mapping/boolean.dart';
3 | import 'package:liveview_flutter/live_view/mapping/colors.dart';
4 | import 'package:liveview_flutter/live_view/mapping/css.dart';
5 |
6 | InputDecoration getInputDecoration(BuildContext context, String? css,
7 | {Widget? icon}) {
8 | Color? fillColor;
9 | bool? filled;
10 | bool? isDense;
11 | for (var (prop, value) in parseCss(css ?? '')) {
12 | switch (prop) {
13 | case 'fillColor':
14 | fillColor = getColor(context, value);
15 | case 'filled':
16 | filled = getBoolean(value);
17 | case 'isDense':
18 | isDense = getBoolean(value);
19 | }
20 | }
21 | return InputDecoration(
22 | fillColor: fillColor, icon: icon, filled: filled, isDense: isDense);
23 | }
24 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_center.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveCenter extends LiveStateWidget {
5 | const LiveCenter({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveCenterState();
9 | }
10 |
11 | class _LiveCenterState extends StateWidget {
12 | final attributes = ['widthFactor', 'heightFactor'];
13 |
14 | @override
15 | void onStateChange(Map diff) =>
16 | reloadAttributes(node, attributes);
17 |
18 | @override
19 | Widget render(BuildContext context) {
20 | return Center(
21 | widthFactor: doubleAttribute('widthFactor'),
22 | heightFactor: doubleAttribute('heightFactor'),
23 | child: singleChild());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 | /pubspec.lock
27 | **/doc/api/
28 | .dart_tool/
29 | .flutter-plugins
30 | .flutter-plugins-dependencies
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Symbolication related
36 | app.*.symbols
37 |
38 | # Obfuscation related
39 | app.*.map.json
40 |
41 | test/**/failures/**
42 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Symbolication related
35 | app.*.symbols
36 |
37 | # Obfuscation related
38 | app.*.map.json
39 |
40 | # Android Studio will place build artifacts here
41 | /android/app/debug
42 | /android/app/profile
43 | /android/app/release
44 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/alignment_directional.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | AlignmentDirectional? getAlignmentDirectional(String? prop) {
4 | switch (prop) {
5 | case 'bottomCenter':
6 | return AlignmentDirectional.bottomCenter;
7 | case 'bottomEnd':
8 | return AlignmentDirectional.bottomEnd;
9 | case 'bottomStart':
10 | return AlignmentDirectional.bottomStart;
11 | case 'center':
12 | return AlignmentDirectional.center;
13 | case 'centerEnd':
14 | return AlignmentDirectional.centerEnd;
15 | case 'centerStart':
16 | return AlignmentDirectional.centerStart;
17 | case 'topCenter':
18 | return AlignmentDirectional.topCenter;
19 | case 'topEnd':
20 | return AlignmentDirectional.topEnd;
21 | case 'topStart':
22 | return AlignmentDirectional.topStart;
23 | default:
24 | return null;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/test/events/phx_onload_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 |
4 | import '../test_helpers.dart';
5 |
6 | main() async {
7 | testWidgets('phx-onload', (tester) async {
8 | var (view, server) = await connect(LiveView(), rendered: {
9 | 's': ['The counter is ', ''],
10 | '0': 2
11 | });
12 |
13 | await tester.runLiveView(view);
14 | await tester.pumpAndSettle();
15 |
16 | expect(server.lastChannelActions,
17 | [liveEvents.join, liveEvents.event('backend_event')]);
18 |
19 | view.handleDiffMessage({'0': 5});
20 | await tester.pumpAndSettle();
21 |
22 | expect(server.lastChannelActions,
23 | [liveEvents.join, liveEvents.event('backend_event')],
24 | reason: 'changing the view should not retrigger the event');
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/keyboard_type.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | TextInputType? getTextInputType(String? type) {
4 | switch (type) {
5 | case 'datetime':
6 | return TextInputType.datetime;
7 | case 'emailAddress':
8 | return TextInputType.emailAddress;
9 | case 'multiline':
10 | return TextInputType.multiline;
11 | case 'name':
12 | return TextInputType.name;
13 | case 'none':
14 | return TextInputType.none;
15 | case 'number':
16 | return TextInputType.number;
17 | case 'phone':
18 | return TextInputType.phone;
19 | case 'streetAddress':
20 | return TextInputType.streetAddress;
21 | case 'text':
22 | return TextInputType.text;
23 | case 'url':
24 | return TextInputType.url;
25 | case 'visiblePassword':
26 | return TextInputType.visiblePassword;
27 | default:
28 | return null;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/test/mapping/colors_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:liveview_flutter/live_view/mapping/colors.dart';
4 |
5 | void main() {
6 | testWidgets('parse colors', (tester) async {
7 | await tester.pumpWidget(
8 | Builder(
9 | builder: (BuildContext context) {
10 | expect(
11 | getColor(context, "#FAD"),
12 | const Color(0xFFFFAADD),
13 | );
14 | expect(
15 | getColor(context, "#000000"),
16 | Colors.black,
17 | );
18 | expect(
19 | getColor(context, "red"),
20 | Colors.red,
21 | );
22 | expect(
23 | getColor(context, "@theme.colorScheme.primary"),
24 | Theme.of(context).colorScheme.primary,
25 | );
26 |
27 | return const SizedBox.shrink();
28 | },
29 | ),
30 | );
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/example/windows/runner/runner.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PerMonitorV2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/documentation/widgets.md:
--------------------------------------------------------------------------------
1 | # Widgets
2 |
3 | Widgets are defined in ```lib/live_view/ui/components/``` and all inherit the class ```LiveStateWidget```.
4 |
5 | # Widget child
6 |
7 | In Flutter, components can accept either a single child or multiple children but not both.
8 | How the client reconciles this is to add a ```Column``` widget if needed to behave more like HTML.
9 |
10 | Raw text elements in the xml payload are transformed into a basic Flutter ```Text``` widget.
11 |
12 | Those two buttons are equivalent:
13 |
14 | ```xml
15 | Click me
16 | Click me
17 | ```
18 |
19 | And those two buttons are exactly rendered the same way as well:
20 |
21 | ```xml
22 |
23 |
24 | Click
25 | me
26 |
27 |
28 |
29 |
30 | Click
31 | me
32 |
33 | ```
--------------------------------------------------------------------------------
/lib/live_view/mapping/edge_insets.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/utils.dart';
3 |
4 | EdgeInsets? getEdgeInsets(String? edges) {
5 | if (edges == null) {
6 | return null;
7 | }
8 | edges = edges.trim();
9 | if (!edges.matches(r'^[\d\s]+$')) {
10 | return null;
11 | }
12 | if (edges == '0') {
13 | return EdgeInsets.zero;
14 | }
15 |
16 | var values = edges
17 | .replaceAll(RegExp(r'\s+'), ' ')
18 | .replaceAll(RegExp(r'[^\d\s]'), '')
19 | .split(' ')
20 | .map((e) => double.tryParse(e))
21 | .toList();
22 |
23 | if (values.any((e) => e == null)) {
24 | return null;
25 | }
26 |
27 | var top = values[0] ?? 0.0;
28 | var right = values.elementAtOrNull(1) ?? top;
29 | var bottom = values.elementAtOrNull(2) ?? top;
30 | var left = values.elementAtOrNull(3) ?? right;
31 |
32 | return EdgeInsets.only(top: top, left: left, right: right, bottom: bottom);
33 | }
34 |
--------------------------------------------------------------------------------
/test/components/material_banner_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 |
3 | import '../test_helpers.dart';
4 |
5 | main() async {
6 | testWidgets('looks okay', (tester) => tester.checkScreenshot("""
7 |
8 |
9 |
10 | action 1
11 | action 2
12 |
13 |
14 | hello
15 | icon
16 | action 1
17 | action 2
18 |
19 | hello
20 |
21 |
22 | """, 'material_banner_test.png'));
23 | }
24 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/decoration.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/mapping/border.dart';
3 | import 'package:liveview_flutter/live_view/mapping/border_radius.dart';
4 | import 'package:liveview_flutter/live_view/mapping/colors.dart';
5 | import 'package:liveview_flutter/live_view/mapping/css.dart';
6 |
7 | Decoration? getDecoration(BuildContext context, String? css) {
8 | if (css == null) {
9 | return null;
10 | }
11 |
12 | Color? color;
13 | BorderRadius? borderRadius;
14 | Border? border;
15 |
16 | for (var (prop, value) in parseCss(css)) {
17 | switch (prop) {
18 | case 'background':
19 | color = getColor(context, value);
20 | case 'borderRadius':
21 | borderRadius = getBorderRadius(value);
22 | case 'border':
23 | border = getBorder(context, value);
24 | }
25 | }
26 |
27 | return BoxDecoration(
28 | color: color,
29 | borderRadius: borderRadius,
30 | border: border,
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/test/forms/value_attribute_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:liveview_flutter/live_view/live_view.dart';
4 |
5 | import '../test_helpers.dart';
6 |
7 | main() async {
8 | testWidgets('changing value does not reset the input', (tester) async {
9 | var view = LiveView()
10 | ..handleRenderedMessage({
11 | 's': [''],
12 | '0': 'name="myfield"',
13 | '1': 'initialValue="content"',
14 | });
15 |
16 | await tester.runLiveView(view);
17 | await tester.pumpAndSettle();
18 |
19 | expect(find.firstOf().initialValue, 'content');
20 | expect((find.firstOf()).controller?.text, 'content');
21 |
22 | view.handleDiffMessage({
23 | '0': 'name="new name"',
24 | '1': 'initialValue="new content"',
25 | });
26 | await tester.pumpAndSettle();
27 |
28 | expect((find.firstOf()).controller?.text, 'content');
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_positioned.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LivePositioned extends LiveStateWidget {
5 | const LivePositioned({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LivePositionedState();
9 | }
10 |
11 | class _LivePositionedState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) {
14 | reloadAttributes(
15 | node, ['left', 'top', 'right', 'bottom', 'width', 'height']);
16 | }
17 |
18 | @override
19 | Widget render(BuildContext context) {
20 | return Positioned(
21 | left: doubleAttribute('left'),
22 | top: doubleAttribute('top'),
23 | right: doubleAttribute('right'),
24 | bottom: doubleAttribute('bottom'),
25 | width: doubleAttribute('width'),
26 | height: doubleAttribute('height'),
27 | child: singleChild(),
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/test/network/reconnect_test.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter_test/flutter_test.dart';
4 | import 'package:http/testing.dart';
5 | import 'package:liveview_flutter/live_view/live_view.dart';
6 | import 'package:shared_preferences/shared_preferences.dart';
7 | import 'package:fake_async/fake_async.dart';
8 |
9 | import '../test_helpers.dart';
10 |
11 | void main() {
12 | var view = LiveView();
13 | TestWidgetsFlutterBinding.ensureInitialized();
14 | SharedPreferences.setMockInitialValues({});
15 | var retries = 0;
16 |
17 | final MockClient failedNetwork = MockClient((request) async {
18 | retries += 1;
19 | throw const SocketException('Unable to connect');
20 | });
21 |
22 | view.httpClient = failedNetwork;
23 | view.liveSocket = FakeLiveSocket();
24 |
25 | test('reconnecting when having a network failure', () async {
26 | fakeAsync((async) {
27 | view.connect('http://localhost:9999');
28 | async.elapse(const Duration(seconds: 20));
29 | });
30 | expect(retries, 5);
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/lib/live_view/ui/live_view_ui_registry.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 | import 'package:liveview_flutter/live_view/ui/node_state.dart';
3 | import 'package:liveview_flutter/live_view/ui/utils.dart';
4 |
5 | typedef WidgetBuilder = List Function(NodeState);
6 |
7 | class LiveViewUiRegistry {
8 | LiveViewUiRegistry._internal();
9 |
10 | static final LiveViewUiRegistry _instance = LiveViewUiRegistry._internal();
11 |
12 | final Map _widgets = {};
13 |
14 | static LiveViewUiRegistry get instance => _instance;
15 |
16 | void add(List componentNames, WidgetBuilder buildWidget) {
17 | for (var componentName in componentNames) {
18 | _widgets[componentName] = buildWidget;
19 | }
20 | }
21 |
22 | List buildWidget(String componentName, NodeState state) {
23 | if (_widgets.containsKey(componentName)) {
24 | return _widgets[componentName]!.call(state);
25 | }
26 |
27 | reportError("unknown widget $componentName");
28 | return [const SizedBox.shrink()];
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/live_view/ui/root_view/root_material_app.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 | import 'package:liveview_flutter/live_view/reactive/theme_settings.dart';
4 | import 'package:liveview_flutter/live_view/ui/root_view/root_scaffold.dart';
5 | import 'package:provider/provider.dart';
6 |
7 | class LiveViewRootMaterialApp extends StatefulWidget {
8 | final LiveView view;
9 | const LiveViewRootMaterialApp({super.key, required this.view});
10 |
11 | @override
12 | State createState() =>
13 | _LiveViewRootMaterialAppState();
14 | }
15 |
16 | class _LiveViewRootMaterialAppState extends State {
17 | @override
18 | Widget build(BuildContext context) {
19 | var theme = Provider.of(context);
20 | return MaterialApp(
21 | title: 'Flutter Demo',
22 | themeMode: theme.themeMode,
23 | theme: theme.lightTheme,
24 | darkTheme: theme.darkTheme,
25 | home: RootScaffold(view: widget.view));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/render/whole_component_update_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 |
4 | import '../test_helpers.dart';
5 |
6 | main() async {
7 | // this checks that there's no reloading problem
8 | // with previous states which arent cleared in state_widget.dart#initState
9 | testWidgets('whole component update works', (tester) async {
10 | var view = LiveView()
11 | ..handleRenderedMessage({
12 | "0": {
13 | "0": 'padding="10"',
14 | "1": {
15 | "s": ["Hello"]
16 | },
17 | "s": ["", ""],
18 | "r": 1
19 | },
20 | "s": ["", ""],
21 | "r": 1
22 | });
23 |
24 | await tester.runLiveView(view);
25 | await tester.pumpAndSettle();
26 |
27 | view.handleDiffMessage({
28 | '0': {'0': 'padding="10"', '1': 'world'}
29 | });
30 | await tester.pumpAndSettle();
31 | expect(find.allTexts(), ['world']);
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/example/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "liveview_flutter",
3 | "short_name": "liveview_flutter",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A new Flutter project.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/example/windows/runner/flutter_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_FLUTTER_WINDOW_H_
2 | #define RUNNER_FLUTTER_WINDOW_H_
3 |
4 | #include
5 | #include
6 |
7 | #include
8 |
9 | #include "win32_window.h"
10 |
11 | // A window that does nothing but host a Flutter view.
12 | class FlutterWindow : public Win32Window {
13 | public:
14 | // Creates a new FlutterWindow hosting a Flutter view running |project|.
15 | explicit FlutterWindow(const flutter::DartProject& project);
16 | virtual ~FlutterWindow();
17 |
18 | protected:
19 | // Win32Window:
20 | bool OnCreate() override;
21 | void OnDestroy() override;
22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
23 | LPARAM const lparam) noexcept override;
24 |
25 | private:
26 | // The project to run.
27 | flutter::DartProject project_;
28 |
29 | // The Flutter instance hosted by this window.
30 | std::unique_ptr flutter_controller_;
31 | };
32 |
33 | #endif // RUNNER_FLUTTER_WINDOW_H_
34 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/border.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/mapping/edge_colors.dart';
3 | import 'package:liveview_flutter/live_view/mapping/edge_insets.dart';
4 |
5 | Border? getBorder(BuildContext context, String? border) {
6 | var exp = RegExp(
7 | r'^((?:\d+\s?)+)((?:(?:[@a-z\.]+\s?)|(?:#[a-f0-9]+\s?))*)$',
8 | caseSensitive: false,
9 | );
10 | if (border == null || !exp.hasMatch(border)) {
11 | return null;
12 | }
13 |
14 | var match = exp.firstMatch(border)!;
15 | var widths = match[0];
16 | var colors = match[1];
17 |
18 | var edgesWidths = getEdgeInsets(widths);
19 |
20 | if (edgesWidths == null) {
21 | return null;
22 | }
23 |
24 | var edgesColors = getEdgeColors(context, colors);
25 |
26 | return Border(
27 | top: BorderSide(width: edgesWidths.top, color: edgesColors.top),
28 | right: BorderSide(width: edgesWidths.right, color: edgesColors.right),
29 | bottom: BorderSide(width: edgesWidths.bottom, color: edgesColors.bottom),
30 | left: BorderSide(width: edgesWidths.left, color: edgesColors.left),
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/test/mapping/edge_insets_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/painting.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:liveview_flutter/live_view/mapping/edge_insets.dart';
4 |
5 | void main() {
6 | test('common cases', () {
7 | expect(getEdgeInsets(""), null);
8 | expect(getEdgeInsets("10"), const EdgeInsets.all(10));
9 | expect(
10 | getEdgeInsets("10 0"),
11 | const EdgeInsets.symmetric(
12 | vertical: 10,
13 | horizontal: 0,
14 | ),
15 | );
16 | expect(
17 | getEdgeInsets("10 0 5"),
18 | const EdgeInsets.only(
19 | top: 10,
20 | right: 0,
21 | bottom: 5,
22 | left: 0,
23 | ),
24 | );
25 | expect(
26 | getEdgeInsets("10 2 5"),
27 | const EdgeInsets.only(
28 | top: 10,
29 | right: 2,
30 | bottom: 5,
31 | left: 2,
32 | ),
33 | );
34 | expect(
35 | getEdgeInsets("10 1 5 8"),
36 | const EdgeInsets.only(
37 | top: 10,
38 | right: 1,
39 | bottom: 5,
40 | left: 8,
41 | ),
42 | );
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/lib/live_view/reactive/state_notifier.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/dynamic_component.dart';
3 |
4 | Map nestedDiff(
5 | Map diff, List nestedState) {
6 | var fullDiff = diff;
7 | var currentDiff = fullDiff;
8 | for (var state in nestedState) {
9 | if (currentDiff.containsKey(state)) {
10 | if (currentDiff[state] == '') {
11 | currentDiff = {};
12 | } else {
13 | currentDiff = Map.from(currentDiff[state]);
14 | }
15 | }
16 | }
17 | return currentDiff;
18 | }
19 |
20 | class StateNotifier extends ChangeNotifier {
21 | late Map _diff;
22 | StateNotifier() {
23 | _diff = {};
24 | }
25 |
26 | void setDiff(Map diff) {
27 | _diff = expandVariables(diff);
28 | notifyListeners();
29 | }
30 |
31 | void emptyData() {
32 | _diff = {};
33 | }
34 |
35 | Map getDiff() => _diff;
36 |
37 | Map getNestedDiff(List nestedState) =>
38 | nestedDiff(_diff, nestedState);
39 | }
40 |
--------------------------------------------------------------------------------
/lib/live_view/ui/page_transition.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class CustomPageTransition extends MaterialPage {
5 | const CustomPageTransition({
6 | required super.child,
7 | super.maintainState = true,
8 | super.fullscreenDialog = false,
9 | super.allowSnapshotting = true,
10 | super.key,
11 | super.name,
12 | super.arguments,
13 | super.restorationId,
14 | });
15 | }
16 |
17 | class PageTransition extends MaterialPageRoute {
18 | PageTransition({required WidgetBuilder builder, RouteSettings? settings})
19 | : super(builder: builder, settings: settings);
20 |
21 | @override
22 | get transitionDuration => const Duration(milliseconds: 250);
23 |
24 | @override
25 | Widget buildTransitions(BuildContext context, Animation animation,
26 | Animation secondaryAnimation, Widget child) {
27 | return CupertinoPageTransition(
28 | primaryRouteAnimation: animation,
29 | secondaryRouteAnimation: secondaryAnimation,
30 | linearTransition: true,
31 | child: child,
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/foundation.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:liveview_flutter/liveview_flutter.dart';
6 |
7 | void main() {
8 | WidgetsFlutterBinding.ensureInitialized();
9 | runApp(const MyApp());
10 | }
11 |
12 | class MyApp extends StatefulWidget {
13 | const MyApp({super.key});
14 |
15 | @override
16 | State createState() => _MyAppState();
17 | }
18 |
19 | class _MyAppState extends State {
20 | final LiveView view = LiveView();
21 |
22 | @override
23 | initState() {
24 | Future.microtask(boot);
25 | super.initState();
26 | }
27 |
28 | void boot() async {
29 | if (kIsWeb) {
30 | view.connectToDocs();
31 | return;
32 | }
33 |
34 | await view.connect(
35 | Platform.isAndroid
36 | ?
37 | // android emulator
38 | 'http://10.0.2.2:4000'
39 | // computer
40 | : 'http://localhost:4000/',
41 | );
42 | }
43 |
44 | // This widget is the root of your application.
45 | @override
46 | Widget build(BuildContext context) {
47 | return view.rootView;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/test/screenshots/bottom_navigation_bar_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:golden_toolkit/golden_toolkit.dart';
4 | import 'package:liveview_flutter/live_view/live_view.dart';
5 |
6 | import '../test_helpers.dart';
7 |
8 | main() async {
9 | testGoldens('appbar', (tester) async {
10 | loadAppFonts();
11 | var (view, _) = await connect(LiveView(), rendered: {
12 | 's': [
13 | """
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | """
23 | ]
24 | });
25 |
26 | await tester.runLiveView(view);
27 |
28 | await tester.pumpAndSettle();
29 |
30 | await expectLater(find.byType(MaterialApp),
31 | matchesGoldenFile('bottom_navigation_bar.png'));
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/example/macos/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | $(PRODUCT_COPYRIGHT)
27 | NSMainNibFile
28 | MainMenu
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_cached_networked_image.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
4 |
5 | class LiveCachedNetworkImage extends LiveStateWidget {
6 | const LiveCachedNetworkImage({super.key, required super.state});
7 |
8 | @override
9 | State createState() => _LiveCachedNetworkImageState();
10 | }
11 |
12 | class _LiveCachedNetworkImageState extends StateWidget {
13 | @override
14 | void onStateChange(Map diff) {
15 | reloadAttributes(node, ['imageUrl', 'width', 'height']);
16 | }
17 |
18 | @override
19 | Widget render(BuildContext context) {
20 | var url = getAttribute('imageUrl')!;
21 | if (!url.startsWith('http')) {
22 | url = '${liveView.endpointScheme}://${liveView.host}/$url';
23 | }
24 |
25 | return CachedNetworkImage(
26 | placeholder: (context, url) => const CircularProgressIndicator(),
27 | imageUrl: url,
28 | width: doubleAttribute('width'),
29 | height: doubleAttribute('height'));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test/render/multiple_components_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 |
4 | import '../test_helpers.dart';
5 |
6 | main() async {
7 | testWidgets('multiple dynamic components on the same rendering pass',
8 | (tester) async {
9 | var view = LiveView()
10 | ..handleRenderedMessage({
11 | 's': ['', '', ''],
12 | '0': {
13 | 's': ['hello'],
14 | },
15 | '1': {
16 | 's': ['world'],
17 | }
18 | });
19 |
20 | await tester.runLiveView(view);
21 | await tester.pumpAndSettle();
22 |
23 | expect(find.allTexts(), ['hello', 'world']);
24 | });
25 |
26 | testWidgets('multiple components on the same rendering pass', (tester) async {
27 | var view = LiveView()
28 | ..handleRenderedMessage({
29 | 's': ['', ''],
30 | '0': {
31 | 's': ['helloworld'],
32 | }
33 | });
34 |
35 | await tester.runLiveView(view);
36 | await tester.pumpAndSettle();
37 |
38 | expect(find.allTexts(), ['hello', 'world']);
39 | });
40 | }
41 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_safe_area.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveSafeArea extends LiveStateWidget {
5 | const LiveSafeArea({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveSafeAreaState();
9 | }
10 |
11 | class _LiveSafeAreaState extends StateWidget {
12 | final attributes = [
13 | 'left',
14 | 'top',
15 | 'right',
16 | 'bottom',
17 | 'minimum',
18 | 'maintainBottomViewPadding',
19 | ];
20 |
21 | @override
22 | void onStateChange(Map diff) =>
23 | reloadAttributes(node, attributes);
24 |
25 | @override
26 | Widget render(BuildContext context) {
27 | return SafeArea(
28 | left: booleanAttribute('left') ?? true,
29 | top: booleanAttribute('top') ?? true,
30 | right: booleanAttribute('right') ?? true,
31 | bottom: booleanAttribute('bottom') ?? true,
32 | minimum: marginOrPaddingAttribute('minimum') ?? EdgeInsets.zero,
33 | maintainBottomViewPadding:
34 | booleanAttribute('maintainBottomViewPadding') ?? false,
35 | child: singleChild(),
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_drawer.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveDrawer extends LiveStateWidget {
5 | const LiveDrawer({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveDrawerState();
9 | }
10 |
11 | class _LiveDrawerState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) => reloadAttributes(node, [
14 | 'backgroundColor',
15 | 'elevation',
16 | 'shadowColor',
17 | 'surfaceTintColor',
18 | 'width',
19 | 'semanticLabel',
20 | 'clipBehavior'
21 | ]);
22 |
23 | @override
24 | Widget render(BuildContext context) {
25 | return Drawer(
26 | backgroundColor: colorAttribute(context, 'backgroundColor'),
27 | elevation: doubleAttribute('elevation'),
28 | shadowColor: colorAttribute(context, 'shadowColor'),
29 | surfaceTintColor: colorAttribute(context, 'surfaceTintColor'),
30 | width: doubleAttribute('width'),
31 | semanticLabel: getAttribute('semanticLabel'),
32 | clipBehavior: clipAttribute('clipBehavior'),
33 | child: singleChild());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/test/components/bottom_app_bar_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 |
3 | import '../test_helpers.dart';
4 |
5 | main() async {
6 | testWidgets('looks okay', (tester) => tester.checkScreenshot("""
7 |
8 |
9 | hello
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | """, 'bottom_app_bar_test.png'));
26 | }
27 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_text.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/mapping/text_align.dart';
3 | import 'package:liveview_flutter/live_view/mapping/text_replacement.dart';
4 | import 'package:liveview_flutter/live_view/mapping/text_style_map.dart';
5 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
6 | import 'package:xml/xml.dart';
7 |
8 | class LiveText extends LiveStateWidget {
9 | const LiveText({required super.state, Key? key}) : super(key: key);
10 |
11 | @override
12 | State createState() => _LiveViewTextState();
13 | }
14 |
15 | class _LiveViewTextState extends StateWidget {
16 | @override
17 | void onStateChange(Map diff) {
18 | reloadAttributes(node, ['style', 'textAlign']);
19 | listenInnerTextKeys();
20 | }
21 |
22 | @override
23 | Widget render(BuildContext context) {
24 | var text = widget.state.node.innerText == ''
25 | ? widget.state.node.value ?? ''
26 | : widget.state.node.innerText;
27 | return Text(
28 | replaceVariables(text, currentVariables).trim(),
29 | style: getTextStyle(getAttribute('style'), context),
30 | textAlign: getTextAlign(getAttribute('textAlign')),
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/example/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - path_provider_foundation (0.0.1):
4 | - Flutter
5 | - FlutterMacOS
6 | - shared_preferences_foundation (0.0.1):
7 | - Flutter
8 | - FlutterMacOS
9 | - sqflite (0.0.3):
10 | - Flutter
11 | - FlutterMacOS
12 |
13 | DEPENDENCIES:
14 | - Flutter (from `Flutter`)
15 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
16 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
17 | - sqflite (from `.symlinks/plugins/sqflite/darwin`)
18 |
19 | EXTERNAL SOURCES:
20 | Flutter:
21 | :path: Flutter
22 | path_provider_foundation:
23 | :path: ".symlinks/plugins/path_provider_foundation/darwin"
24 | shared_preferences_foundation:
25 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
26 | sqflite:
27 | :path: ".symlinks/plugins/sqflite/darwin"
28 |
29 | SPEC CHECKSUMS:
30 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
31 | path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
32 | shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
33 | sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
34 |
35 | PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189
36 |
37 | COCOAPODS: 1.15.0
38 |
--------------------------------------------------------------------------------
/test/network/cookies_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/live_elevated_button.dart';
4 | import 'package:shared_preferences/shared_preferences.dart';
5 |
6 | import '../test_helpers.dart';
7 |
8 | void main() async {
9 | testWidgets('cookies are stored and sent properly', (tester) async {
10 | var (view, server) = await connect(LiveView(), rendered: {
11 | 's': [
12 | """
16 | """
17 | ],
18 | }, sharedPreferences: {
19 | 'cookie': 'storedCookie'
20 | });
21 |
22 | await tester.runLiveView(view);
23 | await tester.pumpAndSettle();
24 | await tester.tap(find.byType(LiveElevatedButton));
25 | await tester.pumpAndSettle();
26 |
27 | expect(server.httpRequestsMade[0].headers['cookie'], 'storedCookie');
28 | expect(server.httpRequestsMade.last.headers['cookie'], 'live_view=session');
29 | var prefs = await SharedPreferences.getInstance();
30 | expect(prefs.getString('cookie'), 'live_view=session');
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_end_drawer.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveEndDrawer extends LiveStateWidget {
5 | const LiveEndDrawer({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveEndDrawerState();
9 | }
10 |
11 | class _LiveEndDrawerState extends StateWidget {
12 | @override
13 | void onStateChange(Map diff) => reloadAttributes(node, [
14 | 'backgroundColor',
15 | 'elevation',
16 | 'shadowColor',
17 | 'surfaceTintColor',
18 | 'width',
19 | 'semanticLabel',
20 | 'clipBehavior'
21 | ]);
22 |
23 | @override
24 | Widget render(BuildContext context) {
25 | return Drawer(
26 | backgroundColor: colorAttribute(context, 'backgroundColor'),
27 | elevation: doubleAttribute('elevation'),
28 | shadowColor: colorAttribute(context, 'shadowColor'),
29 | surfaceTintColor: colorAttribute(context, 'surfaceTintColor'),
30 | width: doubleAttribute('width'),
31 | semanticLabel: getAttribute('semanticLabel'),
32 | clipBehavior: clipAttribute('clipBehavior'),
33 | child: singleChild());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/live_view/ui/root_view/root_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 | import 'package:liveview_flutter/live_view/ui/root_view/root_material_app.dart';
4 | import 'package:provider/provider.dart';
5 |
6 | class LiveRootView extends StatefulWidget {
7 | final LiveView view;
8 | const LiveRootView({super.key, required this.view});
9 |
10 | @override
11 | State createState() => _LiveRootViewState();
12 | }
13 |
14 | class _LiveRootViewState extends State {
15 | LiveView get liveView => widget.view;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | if (liveView.catchExceptions) {
20 | ErrorWidget.builder = (err) {
21 | return liveView.fallbackPages.buildFlutterError(
22 | liveView,
23 | FlutterErrorDetails(
24 | exception: err,
25 | stack: StackTrace.current,
26 | ),
27 | );
28 | };
29 | }
30 | return MultiProvider(providers: [
31 | ChangeNotifierProvider.value(value: liveView.changeNotifier),
32 | ChangeNotifierProvider.value(value: liveView.connectionNotifier),
33 | ChangeNotifierProvider.value(value: liveView.themeSettings)
34 | ], child: LiveViewRootMaterialApp(view: widget.view));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_container.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/mapping/alignment_directional.dart';
3 | import 'package:liveview_flutter/live_view/mapping/decoration.dart';
4 | import 'package:liveview_flutter/live_view/mapping/edge_insets.dart';
5 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
6 |
7 | class LiveContainer extends LiveStateWidget {
8 | const LiveContainer({super.key, required super.state});
9 |
10 | @override
11 | State createState() => _LiveContainerState();
12 | }
13 |
14 | class _LiveContainerState extends StateWidget {
15 | @override
16 | void onStateChange(Map diff) => reloadAttributes(node,
17 | ['margin', 'padding', 'decoration', 'height', 'width', 'alignment']);
18 |
19 | @override
20 | Widget render(BuildContext context) {
21 | return Container(
22 | alignment: getAlignmentDirectional('alignment'),
23 | height: doubleAttribute('height'),
24 | width: doubleAttribute('width'),
25 | margin: getEdgeInsets(getAttribute('margin')),
26 | padding: getEdgeInsets(getAttribute('padding')),
27 | decoration: getDecoration(context, getAttribute('decoration')),
28 | child: singleChild(),
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_drawer_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveDrawerHeader extends LiveStateWidget {
5 | const LiveDrawerHeader({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveDrawerHeaderState();
9 | }
10 |
11 | class _LiveDrawerHeaderState extends StateWidget {
12 | final attributes = [
13 | 'decoration',
14 | 'margin',
15 | 'padding',
16 | 'duration',
17 | 'curve',
18 | ];
19 |
20 | @override
21 | void onStateChange(Map diff) =>
22 | reloadAttributes(node, attributes);
23 |
24 | @override
25 | Widget render(BuildContext context) {
26 | return DrawerHeader(
27 | decoration: decorationAttribute(context, 'decoration'),
28 | margin:
29 | edgeInsetsAttribute('margin') ?? const EdgeInsets.only(bottom: 8.0),
30 | padding: edgeInsetsAttribute('padding') ??
31 | const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 8.0),
32 | duration:
33 | Duration(milliseconds: (doubleAttribute('duration') ?? 250).toInt()),
34 | curve: curveAttribute('curve') ?? Curves.fastOutSlowIn,
35 | child: singleChild(),
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/test/components/bottom_sheet_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:golden_toolkit/golden_toolkit.dart';
4 | import 'package:liveview_flutter/live_view/live_view.dart';
5 |
6 | import '../test_helpers.dart';
7 |
8 | main() async {
9 | testWidgets('looks okay', (tester) async {
10 | await loadAppFonts();
11 | var (view, _) = await connect(LiveView(), rendered: {
12 | 's': [
13 | """
14 |
15 | bottom sheet
16 |
17 |
18 | open
19 |
20 |
21 |
22 | """
23 | ],
24 | });
25 |
26 | await tester.runLiveView(view);
27 | await tester.pumpAndSettle();
28 |
29 | expect(find.text('bottom sheet'), findsNothing);
30 |
31 | await tester.tap(find.text('open'), warnIfMissed: false);
32 | await tester.pumpAndSettle();
33 |
34 | expect(find.text('bottom sheet'), findsOneWidget);
35 |
36 | await expectLater(
37 | find.byType(MaterialApp), matchesGoldenFile('bottom_sheet_test.png'));
38 | });
39 | }
40 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_scaffold.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/state/state_child.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/live_appbar.dart';
4 | import 'package:liveview_flutter/live_view/ui/components/live_drawer.dart';
5 | import 'package:liveview_flutter/live_view/ui/components/live_bottom_navigation_bar.dart';
6 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
7 |
8 | class LiveScaffold extends LiveStateWidget {
9 | const LiveScaffold({super.key, required super.state});
10 |
11 | @override
12 | State createState() => _LiveScaffoldState();
13 | }
14 |
15 | class _LiveScaffoldState extends StateWidget {
16 | @override
17 | void onStateChange(Map diff) {}
18 |
19 | @override
20 | Widget render(BuildContext context) {
21 | var children = multipleChildren();
22 | var appBar = StateChild.extractWidgetChild(children);
23 | var drawer = StateChild.extractWidgetChild(children);
24 | var bottomNavigationBar =
25 | StateChild.extractWidgetChild(children);
26 |
27 | return Scaffold(
28 | appBar: appBar,
29 | body: body(children),
30 | drawer: drawer,
31 | bottomNavigationBar: bottomNavigationBar,
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_bottom_app_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveBottomAppBar extends LiveStateWidget {
5 | const LiveBottomAppBar({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveBottomAppBarState();
9 | }
10 |
11 | class _LiveBottomAppBarState extends StateWidget {
12 | final attributes = [
13 | 'color',
14 | 'elevation',
15 | 'clipBehavior',
16 | 'padding',
17 | 'color',
18 | 'elevation',
19 | 'shape',
20 | 'height'
21 | ];
22 |
23 | @override
24 | void onStateChange(Map diff) =>
25 | reloadAttributes(node, attributes);
26 |
27 | @override
28 | Widget render(BuildContext context) {
29 | return BottomAppBar(
30 | height: doubleAttribute('height'),
31 | clipBehavior: clipAttribute('clipBehavior') ?? Clip.none,
32 | padding: marginOrPaddingAttribute('padding'),
33 | shape: notchedShapeAttribute('shape'),
34 | color: colorAttribute(context, 'color'),
35 | elevation: doubleAttribute('elevation'),
36 | surfaceTintColor: colorAttribute(context, 'surfaceTintColor'),
37 | shadowColor: colorAttribute(context, 'shadowColor'),
38 | child: singleChild());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/example/windows/runner/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "flutter_window.h"
6 | #include "utils.h"
7 |
8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
9 | _In_ wchar_t *command_line, _In_ int show_command) {
10 | // Attach to console when present (e.g., 'flutter run') or create a
11 | // new console when running with a debugger.
12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
13 | CreateAndAttachConsole();
14 | }
15 |
16 | // Initialize COM, so that it is available for use in the library and/or
17 | // plugins.
18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
19 |
20 | flutter::DartProject project(L"data");
21 |
22 | std::vector command_line_arguments =
23 | GetCommandLineArguments();
24 |
25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
26 |
27 | FlutterWindow window(project);
28 | Win32Window::Point origin(10, 10);
29 | Win32Window::Size size(1280, 720);
30 | if (!window.Create(L"liveview_flutter", origin, size)) {
31 | return EXIT_FAILURE;
32 | }
33 | window.SetQuitOnClose(true);
34 |
35 | ::MSG msg;
36 | while (::GetMessage(&msg, nullptr, 0, 0)) {
37 | ::TranslateMessage(&msg);
38 | ::DispatchMessage(&msg);
39 | }
40 |
41 | ::CoUninitialize();
42 | return EXIT_SUCCESS;
43 | }
44 |
--------------------------------------------------------------------------------
/test/render/margin_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:liveview_flutter/live_view/live_view.dart';
4 |
5 | import '../test_helpers.dart';
6 |
7 | main() async {
8 | testWidgets('global margin', (tester) async {
9 | var view = LiveView()
10 | ..handleRenderedMessage({
11 | 's': [''],
12 | });
13 |
14 | await tester.runLiveView(view);
15 |
16 | expect(find.firstOf().margin, const EdgeInsets.all(10.0));
17 | });
18 |
19 | testWidgets('symetric margin', (tester) async {
20 | var view = LiveView()
21 | ..handleRenderedMessage({
22 | 's': [''],
23 | });
24 |
25 | await tester.runLiveView(view);
26 |
27 | expect(
28 | find.firstOf().margin,
29 | const EdgeInsets.symmetric(
30 | vertical: 10.0,
31 | horizontal: 20.0,
32 | ));
33 | });
34 |
35 | testWidgets('full margin', (tester) async {
36 | var view = LiveView()
37 | ..handleRenderedMessage({
38 | 's': [''],
39 | });
40 |
41 | await tester.runLiveView(view);
42 |
43 | expect(
44 | find.firstOf().margin,
45 | const EdgeInsets.only(
46 | top: 10.0,
47 | right: 20.0,
48 | bottom: 30.0,
49 | left: 40.0,
50 | ));
51 | });
52 | }
53 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
3 |
4 | class LiveCard extends LiveStateWidget {
5 | const LiveCard({super.key, required super.state});
6 |
7 | @override
8 | State createState() => _LiveCardState();
9 | }
10 |
11 | class _LiveCardState extends StateWidget {
12 | final attributes = [
13 | 'color',
14 | 'shadowColor',
15 | 'surfaceTintColor',
16 | 'elevation',
17 | 'shape',
18 | 'borderOnForeground',
19 | 'margin',
20 | 'clipBehavior',
21 | 'semanticContainer'
22 | ];
23 |
24 | @override
25 | void onStateChange(Map diff) =>
26 | reloadAttributes(node, attributes);
27 |
28 | @override
29 | Widget render(BuildContext context) {
30 | return Card(
31 | color: colorAttribute(context, 'color'),
32 | shadowColor: colorAttribute(context, 'shadowColor'),
33 | surfaceTintColor: colorAttribute(context, 'surfaceTintColor'),
34 | elevation: doubleAttribute('elevation'),
35 | shape: shapeBorderAttribute('shape'),
36 | borderOnForeground: booleanAttribute('borderOnForeground') ?? true,
37 | margin: marginOrPaddingAttribute('margin'),
38 | clipBehavior: clipAttribute('clipBehavior'),
39 | semanticContainer: booleanAttribute('semanticContainer') ?? true,
40 | child: singleChild());
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/test/render/padding_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:liveview_flutter/live_view/live_view.dart';
4 |
5 | import '../test_helpers.dart';
6 |
7 | main() async {
8 | testWidgets('global padding', (tester) async {
9 | var view = LiveView()
10 | ..handleRenderedMessage({
11 | 's': [''],
12 | });
13 |
14 | await tester.runLiveView(view);
15 |
16 | expect(find.firstOf().padding, const EdgeInsets.all(10.0));
17 | });
18 |
19 | testWidgets('symetric padding', (tester) async {
20 | var view = LiveView()
21 | ..handleRenderedMessage({
22 | 's': [''],
23 | });
24 |
25 | await tester.runLiveView(view);
26 |
27 | expect(
28 | find.firstOf().padding,
29 | const EdgeInsets.symmetric(
30 | vertical: 10.0,
31 | horizontal: 20.0,
32 | ));
33 | });
34 |
35 | testWidgets('full padding', (tester) async {
36 | var view = LiveView()
37 | ..handleRenderedMessage({
38 | 's': [''],
39 | });
40 |
41 | await tester.runLiveView(view);
42 |
43 | expect(
44 | find.firstOf().padding,
45 | const EdgeInsets.only(
46 | top: 10.0,
47 | right: 20.0,
48 | bottom: 30.0,
49 | left: 40.0,
50 | ));
51 | });
52 | }
53 |
--------------------------------------------------------------------------------
/lib/live_view/ui/errors/error_404.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class Error404 extends StatefulWidget {
4 | final String url;
5 | const Error404({super.key, required this.url});
6 |
7 | @override
8 | State createState() => _Error404State();
9 | }
10 |
11 | class _Error404State extends State {
12 | @override
13 | Widget build(BuildContext context) {
14 | List doc = [
15 | Container(
16 | color: Colors.grey[200],
17 | padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
18 | child: const Column(
19 | crossAxisAlignment: CrossAxisAlignment.start,
20 | children: [
21 | Text(
22 | '404 Page not found',
23 | style: TextStyle(
24 | color: Colors.red,
25 | fontWeight: FontWeight.bold,
26 | fontSize: 20),
27 | ),
28 | ]))
29 | ];
30 | doc.addAll([
31 | Container(
32 | padding: const EdgeInsets.all(20),
33 | child: Text(
34 | "Flutter was unable to GET '${widget.url}', please add an HTML view at this endpoint so that the live view can get the metadada.",
35 | style: const TextStyle(color: Colors.black, fontSize: 15))),
36 | ]);
37 | return Scaffold(
38 | backgroundColor: Colors.white,
39 | body: ListView(children: doc),
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at https://dart.dev/lints.
17 | #
18 | # Instead of disabling a lint rule for the entire project in the
19 | # section below, it can also be suppressed for a single line of code
20 | # or a specific dart file by using the `// ignore: name_of_lint` and
21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
22 | # producing the lint.
23 | rules:
24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26 |
27 | # Additional information about this file can be found at
28 | # https://dart.dev/guides/language/analysis-options
29 |
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at https://dart.dev/lints.
17 | #
18 | # Instead of disabling a lint rule for the entire project in the
19 | # section below, it can also be suppressed for a single line of code
20 | # or a specific dart file by using the `// ignore: name_of_lint` and
21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
22 | # producing the lint.
23 | rules:
24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26 |
27 | # Additional information about this file can be found at
28 | # https://dart.dev/guides/language/analysis-options
29 |
--------------------------------------------------------------------------------
/lib/exec/live_view_exec_registry.dart:
--------------------------------------------------------------------------------
1 | import 'package:liveview_flutter/exec/exec.dart';
2 |
3 | typedef ExecBuilder = Exec Function(
4 | Map? value, Map? attributes);
5 |
6 | enum LiveViewExecTrigger { onTap }
7 |
8 | class LiveViewExecRegistry {
9 | LiveViewExecRegistry._internal();
10 |
11 | static final LiveViewExecRegistry _instance =
12 | LiveViewExecRegistry._internal();
13 |
14 | final Map _execs = {};
15 | final Map> _execsByTriggers = {};
16 |
17 | static LiveViewExecRegistry get instance => _instance;
18 |
19 | List execsByTrigger(LiveViewExecTrigger trigger) =>
20 | _execsByTriggers[trigger] ?? [];
21 |
22 | void add(List execNames, ExecBuilder execBuilder,
23 | {List triggers = const []}) {
24 | for (var execName in execNames) {
25 | _execs[execName] = execBuilder;
26 | }
27 | for (var trigger in triggers) {
28 | _execsByTriggers[trigger] ??= [];
29 | for (var execName in execNames) {
30 | if (!_execsByTriggers[trigger]!.contains(execName)) {
31 | _execsByTriggers[trigger]!.add(execName);
32 | }
33 | }
34 | }
35 | }
36 |
37 | Exec? exec(
38 | String name, {
39 | Map? value,
40 | Map? attributes,
41 | }) {
42 | if (_execs.containsKey(name)) {
43 | return _execs[name]!.call(value, attributes);
44 | }
45 |
46 | return null;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/test/mapping/css_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:liveview_flutter/live_view/mapping/css.dart';
3 |
4 | void main() {
5 | test('basic cases', () {
6 | expect(parseCss(''), []);
7 |
8 | expect(parseCss('hello: world'), [('hello', 'world')]);
9 | expect(parseCss(' hello : world '), [('hello', 'world')]);
10 |
11 | expect(parseCss('hello: world; a: b'), [('hello', 'world'), ('a', 'b')]);
12 | expect(parseCss(' hello : world ; a : b '),
13 | [('hello', 'world'), ('a', 'b')]);
14 |
15 | expect(parseCss('a: b; c:'), [('a', 'b')]);
16 | expect(parseCss('background: @theme.appBarTheme.backgroundColor'),
17 | [('background', '@theme.appBarTheme.backgroundColor')]);
18 | });
19 |
20 | test('nested css', () {
21 | expect(parseCss('a: b; hello: { myprop: 1; something: 2 }; d: e'),
22 | [('a', 'b'), ('hello', 'myprop: 1; something: 2'), ('d', 'e')]);
23 | expect(parseCss('hello: { myprop: 1; world: { a: 1 }; b: 2 };'),
24 | [('hello', 'myprop: 1; world: { a: 1 }; b: 2')]);
25 |
26 | expect(parseCss('a: {b: 2}'), [('a', 'b: 2')]);
27 | expect(parseCss('a: {b: 2'), [('a', 'b: 2')]);
28 | });
29 |
30 | test('multiline css', () {
31 | expect(parseCss("""
32 | pressed: {
33 | fontWeight: bold
34 | }
35 | disabled: {
36 | fontWeight: w100
37 | }
38 | """),
39 | [('pressed', 'fontWeight: bold'), ('disabled', 'fontWeight: w100')]);
40 | });
41 | }
42 |
--------------------------------------------------------------------------------
/lib/live_view/ui/errors/flutter_error_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class FlutterErrorView extends StatefulWidget {
4 | final FlutterErrorDetails error;
5 | const FlutterErrorView({super.key, required this.error});
6 |
7 | @override
8 | State createState() => _FlutterErrorViewState();
9 | }
10 |
11 | class _FlutterErrorViewState extends State {
12 | @override
13 | Widget build(BuildContext context) {
14 | debugPrint(widget.error.toString());
15 | List doc = [
16 | Container(
17 | color: Colors.grey[200],
18 | padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
19 | child:
20 | Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
21 | Text(
22 | "Flutter exception: ${widget.error.summary.toString()}",
23 | style: const TextStyle(
24 | color: Colors.red, fontWeight: FontWeight.bold, fontSize: 20),
25 | ),
26 | Text('Stacktrace is shown below',
27 | style: TextStyle(fontSize: 15, color: Colors.grey[500]))
28 | ]))
29 | ];
30 | doc.addAll([
31 | Container(
32 | padding: const EdgeInsets.all(20),
33 | child: Text(widget.error.stack.toString(),
34 | style: const TextStyle(color: Colors.black, fontSize: 15)))
35 | ]);
36 | return Scaffold(
37 | backgroundColor: Colors.white,
38 | body: ListView(children: doc),
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/example/macos/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.14'
2 |
3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
5 |
6 | project 'Runner', {
7 | 'Debug' => :debug,
8 | 'Profile' => :release,
9 | 'Release' => :release,
10 | }
11 |
12 | def flutter_root
13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
14 | unless File.exist?(generated_xcode_build_settings_path)
15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
16 | end
17 |
18 | File.foreach(generated_xcode_build_settings_path) do |line|
19 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
20 | return matches[1].strip if matches
21 | end
22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
23 | end
24 |
25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
26 |
27 | flutter_macos_podfile_setup
28 |
29 | target 'Runner' do
30 | use_frameworks!
31 | use_modular_headers!
32 |
33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
34 | target 'RunnerTests' do
35 | inherit! :search_paths
36 | end
37 | end
38 |
39 | post_install do |installer|
40 | installer.pods_project.targets.each do |target|
41 | flutter_additional_macos_build_settings(target)
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/test/when/when_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:liveview_flutter/when/when.dart';
4 |
5 | import '../test_helpers.dart';
6 |
7 | Future checkCondition(
8 | WidgetTester tester, String conditions, dynamic result) async {
9 | await tester.pumpWidget(
10 | MaterialApp(
11 | home: Builder(
12 | builder: (context) => Text(
13 | When(conditions: conditions).execute(context).toString()))),
14 | );
15 | expect(find.firstText(), result.toString());
16 | }
17 |
18 | void main() {
19 | testWidgets('when conditions', (tester) async {
20 | await checkCondition(tester, '500 > 600', false);
21 | await checkCondition(tester, '600 > 500', true);
22 | await checkCondition(tester, '700.0 > 500', true);
23 | await checkCondition(tester, '500 == 600', false);
24 | await checkCondition(tester, '500 == 500', true);
25 | await checkCondition(tester, '500 != 500', false);
26 | await checkCondition(tester, '500 != 600', true);
27 | });
28 |
29 | testWidgets('and conditions', (tester) async {
30 | await checkCondition(tester, '600 > 500 and 300 > 400', false);
31 | await checkCondition(tester, '700 > 500 and 500 > 400', true);
32 | });
33 |
34 | testWidgets('or conditions', (tester) async {
35 | await checkCondition(tester, '600 > 500 or 300 > 400', true);
36 | await checkCondition(tester, '700 > 500 or 500 > 400', true);
37 | await checkCondition(tester, '200 > 500 or 200 > 400', false);
38 | });
39 | }
40 |
--------------------------------------------------------------------------------
/lib/live_view/ui/errors/no_server_error_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class NoServerError extends StatefulWidget {
4 | final FlutterErrorDetails error;
5 | const NoServerError({super.key, required this.error});
6 |
7 | @override
8 | State createState() => _NoServerErrorState();
9 | }
10 |
11 | class _NoServerErrorState extends State {
12 | @override
13 | Widget build(BuildContext context) {
14 | debugPrint(widget.error.toString());
15 | List doc = [
16 | Container(
17 | color: Colors.grey[200],
18 | padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
19 | child:
20 | Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
21 | const Text(
22 | "Unable to connect to the Live View Server",
23 | style: TextStyle(
24 | color: Colors.red, fontWeight: FontWeight.bold, fontSize: 20),
25 | ),
26 | Text(widget.error.summary.toString()),
27 | Text('Stacktrace is shown below',
28 | style: TextStyle(fontSize: 15, color: Colors.grey[500]))
29 | ]))
30 | ];
31 | doc.addAll([
32 | Container(
33 | padding: const EdgeInsets.all(20),
34 | child: Text(widget.error.stack.toString(),
35 | style: const TextStyle(color: Colors.black, fontSize: 15)))
36 | ]);
37 | return Scaffold(
38 | backgroundColor: Colors.white,
39 | body: ListView(children: doc),
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '11.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | target 'RunnerTests' do
36 | inherit! :search_paths
37 | end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/test/mapping/edge_colors_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:liveview_flutter/live_view/mapping/edge_colors.dart';
4 |
5 | void main() {
6 | testWidgets('material text style', (tester) async {
7 | await tester.pumpWidget(
8 | Builder(
9 | builder: (BuildContext context) {
10 | expect(
11 | getEdgeColors(context, ""),
12 | const EdgeColors.all(Colors.black),
13 | );
14 | expect(
15 | getEdgeColors(context, "red"),
16 | const EdgeColors.all(Colors.red),
17 | );
18 | expect(
19 | getEdgeColors(context, "red blue"),
20 | const EdgeColors.symmetric(
21 | vertical: Colors.red,
22 | horizontal: Colors.blue,
23 | ),
24 | );
25 | expect(
26 | getEdgeColors(context, "red blue green"),
27 | const EdgeColors.only(
28 | top: Colors.red,
29 | right: Colors.blue,
30 | bottom: Colors.green,
31 | left: Colors.blue,
32 | ),
33 | );
34 | expect(
35 | getEdgeColors(context, "red blue green pink"),
36 | const EdgeColors.only(
37 | top: Colors.red,
38 | right: Colors.blue,
39 | bottom: Colors.green,
40 | left: Colors.pink,
41 | ),
42 | );
43 |
44 | return const SizedBox.shrink();
45 | },
46 | ),
47 | );
48 | });
49 | }
50 |
--------------------------------------------------------------------------------
/lib/live_view/ui/root_view/root_app_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/live_appbar.dart';
4 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
5 |
6 | class RootAppBar extends StatefulWidget implements PreferredSizeWidget {
7 | final LiveView view;
8 | const RootAppBar({super.key, required this.view})
9 | : preferredSize = const Size.fromHeight(kToolbarHeight);
10 |
11 | @override
12 | final Size preferredSize;
13 |
14 | @override
15 | State createState() => _RootAppBarState();
16 | }
17 |
18 | class _RootAppBarState extends State {
19 | LiveAppBar? bar;
20 |
21 | @override
22 | void initState() {
23 | widget.view.router.addListener(routeChange);
24 | super.initState();
25 | }
26 |
27 | void routeChange() {
28 | if (mounted) {
29 | setState(() {
30 | bar = extractChild(widget.view.router.pages.last.widgets);
31 | });
32 | }
33 | }
34 |
35 | T? extractChild(List children) {
36 | for (var child in children) {
37 | if (child is T) {
38 | return child;
39 | }
40 | }
41 | return null;
42 | }
43 |
44 | @override
45 | Widget build(BuildContext context) {
46 | bar ??= extractChild(widget.view.router.pages.last.widgets);
47 | return bar != null
48 | ? Container(key: const Key('main_app_bar'), child: bar)
49 | : const SizedBox.shrink();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_icon.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/mapping/colors.dart';
3 | import 'package:liveview_flutter/live_view/mapping/number.dart';
4 | import 'package:liveview_flutter/live_view/mapping/icons.dart';
5 | import 'package:liveview_flutter/live_view/mapping/text_direction.dart';
6 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
7 |
8 | class LiveIcon extends LiveStateWidget {
9 | const LiveIcon({super.key, required super.state});
10 |
11 | @override
12 | State createState() => _LiveIconState();
13 | }
14 |
15 | class _LiveIconState extends StateWidget {
16 | @override
17 | void onStateChange(Map diff) {
18 | reloadAttributes(node, [
19 | 'name',
20 | 'size',
21 | 'fill',
22 | 'weight',
23 | 'grade',
24 | 'opticalSize',
25 | 'color',
26 | 'semanticLabel',
27 | 'textDirection'
28 | ]);
29 | }
30 |
31 | @override
32 | Widget render(BuildContext context) {
33 | return Icon(getIcon(getAttribute('name')),
34 | size: getDouble(getAttribute('size')),
35 | fill: getDouble(getAttribute('fill')),
36 | weight: getDouble(getAttribute('weight')),
37 | grade: getDouble(getAttribute('grade')),
38 | opticalSize: getDouble(getAttribute('opticalSize')),
39 | color: getColor(context, getAttribute('color')),
40 | semanticLabel: getAttribute('semanticLabel'),
41 | textDirection: getTextDirection(getAttribute('textDirection')));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/live_view/mapping/axis_alignment.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | MainAxisAlignment? getMainAxisAlignment(String? prop) {
4 | switch (prop) {
5 | case 'center':
6 | return MainAxisAlignment.center;
7 | case 'start':
8 | return MainAxisAlignment.start;
9 | case 'end':
10 | return MainAxisAlignment.end;
11 | case 'spaceAround':
12 | return MainAxisAlignment.spaceAround;
13 | case 'spaceBetween':
14 | return MainAxisAlignment.spaceBetween;
15 | case 'spaceEvenly':
16 | return MainAxisAlignment.spaceEvenly;
17 | default:
18 | return null;
19 | }
20 | }
21 |
22 | MainAxisSize? getMainAxisSize(String? prop) {
23 | switch (prop) {
24 | case 'max':
25 | return MainAxisSize.max;
26 | case 'min':
27 | return MainAxisSize.min;
28 | default:
29 | return null;
30 | }
31 | }
32 |
33 | CrossAxisAlignment? getCrossAxisAlignment(String? prop) {
34 | switch (prop) {
35 | case 'center':
36 | return CrossAxisAlignment.center;
37 | case 'start':
38 | return CrossAxisAlignment.start;
39 | case 'end':
40 | return CrossAxisAlignment.end;
41 | case 'baseline':
42 | return CrossAxisAlignment.baseline;
43 | case 'stretch':
44 | return CrossAxisAlignment.stretch;
45 | default:
46 | return null;
47 | }
48 | }
49 |
50 | VerticalDirection? getVerticalDirection(String? prop) {
51 | switch (prop) {
52 | case 'down':
53 | return VerticalDirection.down;
54 | case 'up':
55 | return VerticalDirection.up;
56 | default:
57 | return null;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/test/forms/text_field_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:liveview_flutter/live_view/live_view.dart';
4 |
5 | import '../test_helpers.dart';
6 |
7 | String? fieldValue() =>
8 | (find.byType(TextField).evaluate().first.widget as TextField)
9 | .controller
10 | ?.text;
11 |
12 | main() async {
13 | testWidgets('initial value cannot change from the server', (tester) async {
14 | var (view, _) = await connect(LiveView(), rendered: {
15 | 's': [''],
16 | '0': 'initialValue="initialValue"'
17 | });
18 | await tester.runLiveView(view);
19 | await tester.pumpAndSettle();
20 |
21 | expect(fieldValue(), 'initialValue');
22 |
23 | view.handleDiffMessage({'0': 'initialValue="new value"'});
24 | await tester.pumpAndSettle();
25 |
26 | expect(fieldValue(), 'initialValue');
27 | });
28 |
29 | testWidgets('handles form change', (tester) async {
30 | var (view, server) = await connect(LiveView());
31 | await tester.runLiveView(view);
32 |
33 | view.handleRenderedMessage({
34 | 's': [
35 | """
36 |
39 | """
40 | ],
41 | });
42 | await tester.pumpAndSettle();
43 |
44 | await tester.enterText(find.byType(TextField), 'typing');
45 |
46 | expect(
47 | server.lastChannelAction,
48 | liveEvents.phxFormValidate(
49 | 'my_validate_event', 'myfield=typing&_target=myfield'));
50 | });
51 | }
52 |
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "app_icon_16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "app_icon_32.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "app_icon_32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "app_icon_64.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "app_icon_128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "app_icon_256.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "app_icon_256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "app_icon_512.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "app_icon_512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "app_icon_1024.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/lib/live_view/ui/loading/reload_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ReloadWidget extends StatefulWidget {
4 | const ReloadWidget({super.key});
5 |
6 | @override
7 | State createState() => _ReloadWidgetState();
8 | }
9 |
10 | class _ReloadWidgetState extends State
11 | with TickerProviderStateMixin {
12 | late AnimationController controller;
13 |
14 | @override
15 | void initState() {
16 | controller = AnimationController(
17 | duration: const Duration(milliseconds: 1000),
18 | vsync: this,
19 | )..drive(CurveTween(curve: Curves.easeIn));
20 | Tween(begin: 0, end: 1).animate(controller);
21 | controller.forward();
22 | super.initState();
23 | }
24 |
25 | @override
26 | void dispose() {
27 | controller.dispose();
28 | super.dispose();
29 | }
30 |
31 | @override
32 | Widget build(BuildContext context) {
33 | return AnimatedBuilder(
34 | animation: controller,
35 | builder: (_, __) {
36 | return FractionallySizedBox(
37 | widthFactor: controller.value,
38 | child: Container(
39 | height: 5,
40 | decoration: const BoxDecoration(
41 | gradient: LinearGradient(
42 | begin: Alignment.topLeft,
43 | end: Alignment(0.8, 1),
44 | colors: [
45 | Colors.green,
46 | Colors.red,
47 | Colors.blue,
48 | Colors.purple
49 | ]))));
50 | });
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/test/mapping/text_style_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:liveview_flutter/live_view/mapping/text_style_map.dart';
4 |
5 | MaterialStateProperty? materialTextStyle() =>
6 | (find.byType(FilledButton).evaluate().first.widget as FilledButton)
7 | .style
8 | ?.textStyle;
9 |
10 | Future setStyle(WidgetTester tester, String style) async {
11 | await tester.pumpWidget(MaterialApp(home: Builder(builder: (context) {
12 | return FilledButton(
13 | onPressed: () {},
14 | style: ButtonStyle(textStyle: getMaterialTextStyle(style, context)),
15 | child: const Text('hello'),
16 | );
17 | })));
18 | await tester.pumpAndSettle();
19 | }
20 |
21 | main() {
22 | testWidgets('material text style', (tester) async {
23 | await setStyle(tester, 'hello');
24 | expect(materialTextStyle()!.resolve({}), const TextStyle());
25 |
26 | await setStyle(tester, 'fontWeight: bold');
27 | expect(materialTextStyle()!.resolve({}),
28 | const TextStyle(fontWeight: FontWeight.bold));
29 |
30 | await setStyle(tester, """'
31 | pressed: {
32 | fontWeight: bold
33 | color: #F44336
34 | }
35 | disabled: {
36 | fontWeight: w100
37 | }
38 | """);
39 | var style = materialTextStyle()!;
40 | expect(style.resolve({MaterialState.pressed}),
41 | const TextStyle(fontWeight: FontWeight.bold, color: Color(0xfff44336)));
42 | expect(style.resolve({MaterialState.disabled}),
43 | const TextStyle(fontWeight: FontWeight.w100));
44 | });
45 | }
46 |
--------------------------------------------------------------------------------
/test/plugins/basic_plugin.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/exec/exec.dart';
3 | import 'package:liveview_flutter/exec/live_view_exec_registry.dart';
4 | import 'package:liveview_flutter/live_view/plugin.dart';
5 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
6 | import 'package:liveview_flutter/live_view/ui/live_view_ui_registry.dart';
7 |
8 | class MyWidget extends LiveStateWidget {
9 | const MyWidget({super.key, required super.state});
10 |
11 | @override
12 | State createState() => _MyWidget();
13 | }
14 |
15 | class _MyWidget extends StateWidget {
16 | @override
17 | void onStateChange(Map diff) {}
18 |
19 | @override
20 | Widget render(BuildContext context) => const Text('MyComponent');
21 | }
22 |
23 | List myPluginActions = [];
24 |
25 | class MyPluginExec extends Exec {
26 | Map? attributes;
27 | MyPluginExec({required this.attributes});
28 |
29 | @override
30 | void handler(BuildContext context, StateWidget widget) {
31 | myPluginActions.add(attributes!['phx-my-plugin']);
32 | }
33 | }
34 |
35 | class BasicPlugin extends Plugin {
36 | @override
37 | String get name => "my_plugin";
38 |
39 | @override
40 | registerWidgets(LiveViewUiRegistry registry) {
41 | registry.add(['MyComponent'], (state) => [MyWidget(state: state)]);
42 | }
43 |
44 | @override
45 | registerExecs(LiveViewExecRegistry registry) {
46 | registry.add(['phx-my-plugin'],
47 | (value, attributes) => MyPluginExec(attributes: attributes),
48 | triggers: [LiveViewExecTrigger.onTap]);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_checkbox.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/live_form.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
4 | import 'package:uuid/uuid.dart';
5 |
6 | class LiveCheckbox extends LiveStateWidget {
7 | const LiveCheckbox({super.key, required super.state});
8 |
9 | @override
10 | State createState() => _LiveCheckboxState();
11 | }
12 |
13 | class _LiveCheckboxState extends StateWidget {
14 | var unamedInput = const Uuid().v4();
15 | bool _isChecked = false;
16 | bool _initialBoot = true;
17 |
18 | @override
19 | HandleClickState handleClickState() => HandleClickState.manual;
20 |
21 | @override
22 | void onWipeState() {
23 | _initialBoot = true;
24 | super.onWipeState();
25 | }
26 |
27 | @override
28 | void onStateChange(Map diff) {
29 | reloadAttributes(node, ['checked', 'name']);
30 | if (_initialBoot) {
31 | _isChecked = booleanAttribute('checked') ?? false;
32 | _initialBoot = false;
33 | }
34 | }
35 |
36 | @override
37 | Widget render(BuildContext context) {
38 | return Checkbox(
39 | value: _isChecked,
40 | onChanged: (val) {
41 | setState(() {
42 | _isChecked = val ?? false;
43 | });
44 | FormFieldEvent(
45 | name: getAttribute('name') ?? "unamed-text-field-$unamedInput",
46 | data: _isChecked ? 'on' : null,
47 | type: FormFieldEventType.change,
48 | ).dispatch(context);
49 | executeTapEventsManually();
50 | },
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/exec/exec_confirmable.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/exec/exec.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
4 |
5 | class DataConfirm {
6 | final String title;
7 | final String message;
8 | final String confirm;
9 | final String cancel;
10 |
11 | DataConfirm({
12 | required this.message,
13 | String? title,
14 | String? confirm,
15 | String? cancel,
16 | }) : title = title ?? 'Confirm?',
17 | confirm = confirm ?? 'Ok',
18 | cancel = cancel ?? 'Cancel';
19 | }
20 |
21 | abstract class ExecConfirmable extends Exec {
22 | /// This is responsible for show an alert before executing this action
23 | final DataConfirm? dataConfirm;
24 |
25 | ExecConfirmable({this.dataConfirm});
26 |
27 | @override
28 | void conditionalHandler(BuildContext context, StateWidget widget) {
29 | if (conditions.execute(context) == false) return;
30 |
31 | if (dataConfirm == null) return handler(context, widget);
32 |
33 | showDialog(
34 | context: context,
35 | builder: (context) => AlertDialog(
36 | title: Text(dataConfirm!.title),
37 | content: Text(dataConfirm!.message),
38 | actions: [
39 | TextButton(
40 | child: Text(dataConfirm!.cancel),
41 | onPressed: () => Navigator.of(context).pop(),
42 | ),
43 | TextButton(
44 | child: Text(dataConfirm!.confirm),
45 | onPressed: () => Navigator.of(context).pop(true),
46 | ),
47 | ],
48 | ),
49 | ).then((result) {
50 | if (result == true) return handler(context, widget);
51 | });
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_text_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/live_form.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
4 | import 'package:uuid/uuid.dart';
5 |
6 | class LiveTextButton extends LiveStateWidget {
7 | const LiveTextButton({super.key, required super.state});
8 |
9 | @override
10 | State createState() => _LiveTextButtonState();
11 | }
12 |
13 | class _LiveTextButtonState extends StateWidget {
14 | var unamedInput = const Uuid().v4();
15 |
16 | @override
17 | handleClickState() => HandleClickState.manual;
18 |
19 | @override
20 | void onStateChange(Map diff) {
21 | reloadAttributes(node, [
22 | 'type',
23 | 'name',
24 | 'style',
25 | 'autofocus',
26 | 'clipBehavior',
27 | ]);
28 | }
29 |
30 | @override
31 | Widget render(BuildContext context) {
32 | return TextButton(
33 | style: buttonStyleAttribute(context, 'style'),
34 | autofocus: booleanAttribute('autofocus') ?? false,
35 | clipBehavior: clipAttribute('clipBehavior') ?? Clip.none,
36 | onPressed: () {
37 | if (getAttribute('type') == 'submit') {
38 | FormFieldEvent(
39 | name: getAttribute('name') ??
40 | 'unamed-text-button-$unamedInput',
41 | data: null,
42 | type: FormFieldEventType.submit)
43 | .dispatch(context);
44 | }
45 | executeTapEventsManually();
46 | },
47 | child: AbsorbPointer(child: singleChild()));
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_elevated_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/ui/components/live_form.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
4 | import 'package:uuid/uuid.dart';
5 |
6 | class LiveElevatedButton extends LiveStateWidget {
7 | const LiveElevatedButton({super.key, required super.state});
8 |
9 | @override
10 | State createState() => _LiveElevatedButtonState();
11 | }
12 |
13 | class _LiveElevatedButtonState extends StateWidget {
14 | var unamedInput = const Uuid().v4();
15 |
16 | @override
17 | void onStateChange(Map diff) {
18 | reloadAttributes(node, [
19 | 'type',
20 | 'name',
21 | 'style',
22 | 'autofocus',
23 | 'clipBehavior',
24 | ]);
25 | }
26 |
27 | @override
28 | handleClickState() => HandleClickState.manual;
29 |
30 | @override
31 | Widget render(BuildContext context) {
32 | return ElevatedButton(
33 | style: buttonStyleAttribute(context, 'style'),
34 | autofocus: booleanAttribute('autofocus') ?? false,
35 | clipBehavior: clipAttribute('clipBehavior') ?? Clip.none,
36 | onPressed: () {
37 | if (getAttribute('type') == 'submit') {
38 | FormFieldEvent(
39 | name: getAttribute('name') ??
40 | 'unamed-elevated-button-$unamedInput',
41 | data: null,
42 | type: FormFieldEventType.submit)
43 | .dispatch(context);
44 | }
45 | executeTapEventsManually();
46 | },
47 | child: AbsorbPointer(child: singleChild()));
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/test/components/floating_action_button_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 | import 'package:liveview_flutter/live_view/live_view.dart';
4 |
5 | import '../test_helpers.dart';
6 |
7 | bool? checkValue() =>
8 | (find.byType(Checkbox).evaluate().first.widget as Checkbox).value;
9 |
10 | main() async {
11 | testWidgets('normal looks okay', (tester) => tester.checkScreenshot("""
12 |
13 |
14 | hello world
15 |
16 | """, 'foating_action_button_test.png'));
17 |
18 | testWidgets('extended looks okay', (tester) => tester.checkScreenshot("""
19 |
20 | hello
21 | hello world
22 |
23 | """, 'foating_action_button_extended_test.png'));
24 |
25 | testWidgets('phx click works', (tester) async {
26 | var (view, server) = await connect(LiveView(), rendered: {
27 | 's': [
28 | """
29 |
30 | hello
31 | my view
32 |
33 | """
34 | ],
35 | });
36 |
37 | await tester.runLiveView(view);
38 | await tester.pumpAndSettle();
39 |
40 | await tester.tap(find.text('hello'));
41 | await tester.pumpAndSettle();
42 |
43 | expect(server.lastChannelAction,
44 | liveEvents.phxClick({}, eventName: 'server_event'));
45 | });
46 | }
47 |
--------------------------------------------------------------------------------
/lib/live_view/ui/root_view/root_bottom_navigation_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/live_view.dart';
3 | import 'package:liveview_flutter/live_view/ui/components/live_bottom_app_bar.dart';
4 | import 'package:liveview_flutter/live_view/ui/components/live_bottom_navigation_bar.dart';
5 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
6 |
7 | class RootBottomNavigationBar extends StatefulWidget {
8 | final LiveView view;
9 | const RootBottomNavigationBar({super.key, required this.view});
10 |
11 | @override
12 | State createState() =>
13 | _RootBottomNavigationBarState();
14 | }
15 |
16 | class _RootBottomNavigationBarState extends State {
17 | Widget? bar;
18 |
19 | @override
20 | void initState() {
21 | widget.view.router.addListener(routeChange);
22 |
23 | super.initState();
24 | }
25 |
26 | @override
27 | void dispose() {
28 | super.dispose();
29 | }
30 |
31 | void routeChange() {
32 | setState(() {});
33 | }
34 |
35 | T? extractChild(List children) {
36 | for (var child in children) {
37 | if (child is T) {
38 | return child;
39 | }
40 | }
41 | return null;
42 | }
43 |
44 | @override
45 | Widget build(BuildContext context) {
46 | if (widget.view.router.pages.last.containsGlobalNavigationWidgets) {
47 | bar = extractChild(
48 | widget.view.router.pages.last.widgets);
49 | bar ??=
50 | extractChild(widget.view.router.pages.last.widgets);
51 | } else {
52 | bar = null;
53 | }
54 | return bar ?? const SizedBox.shrink();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_row.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/mapping/axis_alignment.dart';
3 | import 'package:liveview_flutter/live_view/mapping/text_baseline.dart';
4 | import 'package:liveview_flutter/live_view/mapping/text_direction.dart';
5 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
6 |
7 | class LiveRow extends LiveStateWidget {
8 | const LiveRow({super.key, required super.state});
9 |
10 | @override
11 | State createState() => _LiveColState();
12 | }
13 |
14 | class _LiveColState extends StateWidget {
15 | @override
16 | void onStateChange(Map diff) => reloadAttributes(node, [
17 | 'mainAxisAlignment',
18 | 'crossAxisAlignment',
19 | 'textDirection',
20 | 'mainAxisSize',
21 | 'verticalDirection',
22 | 'textBaseline'
23 | ]);
24 |
25 | @override
26 | Widget render(BuildContext context) {
27 | return Row(
28 | mainAxisAlignment:
29 | getMainAxisAlignment(getAttribute('mainAxisAlignment')) ??
30 | MainAxisAlignment.start,
31 | crossAxisAlignment:
32 | getCrossAxisAlignment(getAttribute('crossAxisAlignment')) ??
33 | CrossAxisAlignment.center,
34 | mainAxisSize:
35 | getMainAxisSize(getAttribute('mainAxisSize')) ?? MainAxisSize.max,
36 | textDirection: getTextDirection(getAttribute('textDirection')),
37 | verticalDirection:
38 | getVerticalDirection(getAttribute('verticalDirection')) ??
39 | VerticalDirection.down,
40 | textBaseline: getTextBaseline(getAttribute('textBaseline')),
41 | children: multipleChildren());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/live_view/ui/components/live_column.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:liveview_flutter/live_view/mapping/axis_alignment.dart';
3 | import 'package:liveview_flutter/live_view/mapping/text_baseline.dart';
4 | import 'package:liveview_flutter/live_view/mapping/text_direction.dart';
5 | import 'package:liveview_flutter/live_view/ui/components/state_widget.dart';
6 |
7 | class LiveColumn extends LiveStateWidget {
8 | const LiveColumn({super.key, required super.state});
9 |
10 | @override
11 | State createState() => _LiveColState();
12 | }
13 |
14 | class _LiveColState extends StateWidget {
15 | @override
16 | void onStateChange(Map