diff --git a/flutter/android/app/src/main/AndroidManifest.xml b/flutter/android/app/src/main/AndroidManifest.xml index 30bcc0df..5bed7d5c 100644 --- a/flutter/android/app/src/main/AndroidManifest.xml +++ b/flutter/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ path; @@ -206,9 +211,62 @@ LUIFlutterExtensionRegistry logseqChatExtensionRegistry({ ], builder: (context) => _OverflowMenu(context: context), ), + ) + ..register( + LUIFlutterExtension( + identifier: 'composer-asset', + fingerprint: _composerAssetFingerprint, + properties: [ + _requiredStringProperty('title'), + _requiredStringProperty('local-path'), + ], + builder: (context) => _ComposerAssetPreview(context: context), + ), ); } +final class _ComposerAssetPreview extends StatelessWidget { + const _ComposerAssetPreview({required this.context}); + + final LUIFlutterExtensionContext context; + + @override + Widget build(BuildContext buildContext) { + final title = context.property('title') as String? ?? ''; + final localPath = context.property('local-path') as String? ?? ''; + final Widget content = + localPath.isNotEmpty && isAndroidImageAsset('', localPath) + ? Image.file( + File(localPath), + fit: BoxFit.cover, + errorBuilder: (_, _, _) => _fallback(buildContext, title), + ) + : _fallback(buildContext, title); + return Semantics( + label: title, + image: true, + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: SizedBox.square(dimension: 128, child: content), + ), + ); + } + + Widget _fallback(BuildContext buildContext, String title) => ColoredBox( + color: Theme.of(buildContext).colorScheme.surfaceContainerHigh, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 4, + children: [ + const Icon(Icons.attach_file_rounded), + Text(title, maxLines: 2, overflow: TextOverflow.ellipsis), + ], + ), + ), + ); +} + LUIExtensionEventField _requiredStringField(String name) => LUIExtensionEventField( name: name, @@ -380,6 +438,7 @@ final class _SearchPresentationState extends State<_SearchPresentation> controller: _controller, focusNode: _focusNode, autoFocus: query.isEmpty, + textInputAction: TextInputAction.search, hintText: 'Search pages and blocks', leading: IconButton( tooltip: depth > 0 ? 'Back' : 'Close search', diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 702ba0d0..ed873afb 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:lui_flutter_backend/lui_flutter_backend.dart'; import 'android_platform_effects.dart'; @@ -32,6 +33,20 @@ void _traceStartup(String message) { void main() { _traceStartup('main entered'); WidgetsFlutterBinding.ensureInitialized(); + unawaited( + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge).then((_) { + SystemChrome.setSystemUIOverlayStyle( + const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + systemNavigationBarColor: Colors.transparent, + systemNavigationBarDividerColor: Colors.transparent, + systemNavigationBarContrastEnforced: false, + statusBarBrightness: Brightness.dark, + statusBarIconBrightness: Brightness.dark, + ), + ); + }), + ); _traceStartup('Flutter binding initialized'); runApp(const LogseqChatFlutterApp()); _traceStartup('runApp returned'); @@ -47,6 +62,7 @@ class LogseqChatFlutterApp extends StatefulWidget { class _LogseqChatFlutterAppState extends State with WidgetsBindingObserver { final _navigatorKey = GlobalKey(); + final _messengerKey = GlobalKey(); late final LUIFlutterBackend _backend; late final FlutterPatchApplier _patchApplier; LogseqChatNativeBridge? _bridge; @@ -602,6 +618,14 @@ class _LogseqChatFlutterAppState extends State void _reportEffectError(Object error, StackTrace stack) { _logRuntime(level: 'error', source: 'ui', message: error.toString()); + _messengerKey.currentState + ?..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + behavior: SnackBarBehavior.floating, + content: Text('Something went wrong: $error'), + ), + ); FlutterError.reportError( FlutterErrorDetails( exception: error, @@ -654,6 +678,7 @@ class _LogseqChatFlutterAppState extends State Widget build(BuildContext context) { return MaterialApp( navigatorKey: _navigatorKey, + scaffoldMessengerKey: _messengerKey, debugShowCheckedModeBanner: false, title: 'Logseq Chat', theme: LogseqChatTheme.light(), @@ -666,9 +691,36 @@ class _LogseqChatFlutterAppState extends State Widget _content() { if (_startupError != null) { return SafeArea( - child: Padding( - padding: const EdgeInsets.all(24), - child: Text('Could not start Logseq Chat: $_startupError'), + child: Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 16, + children: [ + Icon( + Icons.error_outline_rounded, + size: 48, + color: Theme.of(context).colorScheme.error, + ), + Text( + 'Could not start Logseq Chat', + style: Theme.of(context).textTheme.titleMedium, + ), + Text( + '$_startupError', + style: Theme.of(context).textTheme.bodySmall, + ), + FilledButton( + onPressed: () { + setState(() => _startupError = null); + unawaited(_start()); + }, + child: const Text('Retry'), + ), + ], + ), + ), ), ); } diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 39bb6bff..f68c449e 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -159,8 +159,8 @@ packages: dependency: "direct main" description: path: "platform/flutter" - ref: b65da35 - resolved-ref: b65da35dc226b744174b8693151f3262fe5d94ec + ref: db506b502fce3350839a194314a9542729e9f819 + resolved-ref: db506b502fce3350839a194314a9542729e9f819 url: "ssh://git@github.com/logseq/lui.git" source: git version: "0.1.0" diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index a62278eb..7e28ac2b 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: lui_flutter_backend: git: url: ssh://git@github.com/logseq/lui.git - ref: b65da35 + ref: db506b502fce3350839a194314a9542729e9f819 path: platform/flutter webview_flutter: ^4.14.1 diff --git a/shared/src/logseq_chat/model.ml b/shared/src/logseq_chat/model.ml index b0b03747..4c32d1e0 100644 --- a/shared/src/logseq_chat/model.ml +++ b/shared/src/logseq_chat/model.ml @@ -1923,7 +1923,11 @@ let rec update (current : chat_model) action = app_navigation_previews = remove_node_projection updated.app_navigation_previews uuid; } - | BackAppNavigation count -> back_app_navigation current count + | BackAppNavigation count -> + if current.outliner_selected_block_ids <> [] then + let id = current.next_effect_id in + enqueue_effect current (OutlinerToolbarEffect (id, "unselect")) + else back_app_navigation current count | RequestSearchNode uuid -> let path = current.search_navigation_path in let requested = request_route path (NodeRoute uuid) in diff --git a/shared/src/logseq_chat/view.ml b/shared/src/logseq_chat/view.ml index 14e28979..f515b05e 100644 --- a/shared/src/logseq_chat/view.ml +++ b/shared/src/logseq_chat/view.ml @@ -4,7 +4,8 @@ let chat_view = View_screens.chat_view let composer_asset_schema () = Lui_extension.component "composer-asset" - [ Lui_protocol.profile Lui_protocol.IOS Lui_protocol.SwiftUIHost ] + [ Lui_protocol.profile Lui_protocol.IOS Lui_protocol.SwiftUIHost; + Lui_protocol.profile Lui_protocol.AndroidOS Lui_protocol.FlutterHost ] false [] [ Lui_extension.property "title" Lui_extension.StringScalar true None; diff --git a/shared/src/logseq_chat/view_base.ml b/shared/src/logseq_chat/view_base.ml index 08ad25bc..5d993375 100644 --- a/shared/src/logseq_chat/view_base.ml +++ b/shared/src/logseq_chat/view_base.ml @@ -260,6 +260,9 @@ let model_graph_menu_open_ (current : Model.chat_model) = let model_task_status_picker_open_ (current : Model.chat_model) = current.task_status_picker_open +let model_attachment_picker_open_ (current : Model.chat_model) = + current.attachment_picker_open + let model_new_graph_name (current : Model.chat_model) = current.new_graph_name let model_new_graph_encrypted_ (current : Model.chat_model) = diff --git a/shared/src/logseq_chat/view_base.mli b/shared/src/logseq_chat/view_base.mli index 741026ad..0dc1de8d 100644 --- a/shared/src/logseq_chat/view_base.mli +++ b/shared/src/logseq_chat/view_base.mli @@ -62,6 +62,7 @@ val model_community_links : Model.chat_model -> Model.settings_community_link list val model_graph_menu_open_ : Model.chat_model -> bool val model_task_status_picker_open_ : Model.chat_model -> bool +val model_attachment_picker_open_ : Model.chat_model -> bool val model_new_graph_name : Model.chat_model -> string val model_new_graph_encrypted_ : Model.chat_model -> bool val model_graph_password : Model.chat_model -> string diff --git a/shared/src/logseq_chat/view_composer.ml b/shared/src/logseq_chat/view_composer.ml index 05125e22..bd176836 100644 --- a/shared/src/logseq_chat/view_composer.ml +++ b/shared/src/logseq_chat/view_composer.ml @@ -30,6 +30,36 @@ let attachment_menu send : t = []; ] +let attachment_picker_menu send : t = + dropdown_menu ~anchor:`above ~anchor_alignment:`start ~min_width:200 + ~on_dismiss:(press send Model.CloseAttachmentPicker) + [ + menu_item ~icon:(`app "toolbar-attachment") + ~accessibility_identifier:"button.attachment.files" + ~text:"File" + ~on_press:(fun _ -> + ignore (send (Model.ChooseAttachment "files"))) + []; + menu_item ~icon:(`app "toolbar-camera") + ~accessibility_identifier:"button.attachment.camera" + ~text:"Camera" + ~on_press:(fun _ -> + ignore (send (Model.ChooseAttachment "camera"))) + []; + menu_item ~icon:(`app "composer-photo") + ~accessibility_identifier:"button.attachment.photos" + ~text:"Photo" + ~on_press:(fun _ -> + ignore (send (Model.ChooseAttachment "photos"))) + []; + menu_item ~icon:(`app "toolbar-audio") + ~accessibility_identifier:"button.attachment.audio" + ~text:"Audio recording" + ~on_press:(fun _ -> + ignore (send (Model.ChooseAttachment "audio"))) + []; + ] + let composer_attachment_button (context : Lui_ui.ui_context) send : t = if Lui_ui.platform context = AndroidOS then button ~icon:(`app "add") ~variant:`ghost ~label:"Add attachment" @@ -230,7 +260,18 @@ let composer_view (context : Lui_ui.ui_context) model_source send : t = row ~gap:8 ~height:44 ~cross:`center ~accessibility_identifier:"row.composer.controls" [ - composer_attachment_button context send; + if Lui_ui.host context = FlutterHost then + stack + [ + composer_attachment_button context send; + if_ + ~test: + (Signal.map + View_base.model_attachment_picker_open_ + model_source) + (attachment_picker_menu send); + ] + else composer_attachment_button context send; stack [ composer_task_status_button context send; diff --git a/shared/src/logseq_chat/view_graphs.ml b/shared/src/logseq_chat/view_graphs.ml index 31a4367d..70338d15 100644 --- a/shared/src/logseq_chat/view_graphs.ml +++ b/shared/src/logseq_chat/view_graphs.ml @@ -378,11 +378,61 @@ let graph_picker_error_banner model_source : t = ]; ] -let graph_password_sheet model_source send : t = - sheet ~text:"Unlock encrypted graphs" ~style_class:"navigation-form" - ~accessibility_identifier:"sheet.graph-unlock" - ~on_dismiss:(press send Model.CancelGraphUnlock) - [ +let graph_password_sheet (context : Lui_ui.ui_context) model_source send + : t = + if Lui_ui.host context = FlutterHost then + sheet ~text:"Unlock encrypted graphs" ~height:360 + ~accessibility_identifier:"sheet.graph-unlock" + ~on_dismiss:(press send Model.CancelGraphUnlock) + [ + column ~gap:20 ~cross:`stretch ~grow:1.0 + ~accessibility_identifier:"form.graph-unlock" + [ + secure_field + ~text_signal: + (Signal.map View_base.model_graph_password model_source) + ~placeholder:"E2EE password" ~label:"E2EE password" + ~accessibility_identifier:"field.graph-password" + ~on_input: + (on_input send (fun text -> + Model.ChangeGraphPassword text)) + []; + text ~style_class:"footnote" ~foreground:"muted-foreground" + ~value:"Enter your E2EE password to unlock this graph." []; + if_ + ~test: + (Signal.map View_base.graph_unlock_error_present_ + model_source) + (text + ~value_signal: + (Signal.map View_base.graph_unlock_error_message + model_source) + ~style_class:"footnote" ~foreground:"destructive" + ~accessibility_identifier:"text.graph-unlock-error" []); + spacer ~grow:1.0 []; + toolbar ~orientation:`horizontal ~label:"Graph unlock actions" + ~accessibility_identifier:"toolbar.graph-unlock" + [ + spacer ~grow:1.0 []; + button ~variant:`ghost + ~accessibility_identifier:"button.graph-unlock.cancel" + ~on_press:(press send Model.CancelGraphUnlock) + ~text:"Cancel" []; + button ~variant:`primary + ~accessibility_identifier:"button.graph-unlock" + ~disabled_signal: + (Signal.map View_base.graph_unlock_disabled_ + model_source) + ~on_press:(press send Model.SubmitGraphPassword) + ~text:"Unlock" []; + ]; + ]; + ] + else + sheet ~text:"Unlock encrypted graphs" ~style_class:"navigation-form" + ~accessibility_identifier:"sheet.graph-unlock" + ~on_dismiss:(press send Model.CancelGraphUnlock) + [ column ~style_class:"form" ~accessibility_identifier:"form.graph-unlock" [ diff --git a/shared/src/logseq_chat/view_graphs.mli b/shared/src/logseq_chat/view_graphs.mli index 2ce03d7b..cfbf1885 100644 --- a/shared/src/logseq_chat/view_graphs.mli +++ b/shared/src/logseq_chat/view_graphs.mli @@ -21,6 +21,7 @@ val graph_picker_overflow_menu : val graph_picker_error_banner : Model.chat_model Signal.signal -> Lui_elements.t val graph_password_sheet : + Lui_ui.ui_context -> Model.chat_model Signal.signal -> (Model.chat_action -> bool) -> Lui_elements.t val graphs_screen : diff --git a/shared/src/logseq_chat/view_screens.ml b/shared/src/logseq_chat/view_screens.ml index 744ef106..2c0ec3e7 100644 --- a/shared/src/logseq_chat/view_screens.ml +++ b/shared/src/logseq_chat/view_screens.ml @@ -791,14 +791,14 @@ let application_main_content (context : Lui_ui.ui_context) model_source if_ ~test:(Signal.map View_base.model_graph_password_open_ model_source) - (View_graphs.graph_password_sheet model_source send); + (View_graphs.graph_password_sheet context model_source send); if_ ~test:(Signal.map View_base.page_deletion_pending_ model_source) - (View_settings.page_delete_dialog send); + (View_settings.page_delete_dialog context send); if_ ~test:(Signal.map View_base.model_sync_details_open_ model_source) - (View_settings.sync_status_sheet model_source send); + (View_settings.sync_status_sheet context model_source send); ] else [ @@ -829,14 +829,14 @@ let application_main_content (context : Lui_ui.ui_context) model_source if_ ~test:(Signal.map View_base.model_graph_password_open_ model_source) - (View_graphs.graph_password_sheet model_source send); + (View_graphs.graph_password_sheet context model_source send); if_ ~test:(Signal.map View_base.page_deletion_pending_ model_source) - (View_settings.page_delete_dialog send); + (View_settings.page_delete_dialog context send); if_ ~test:(Signal.map View_base.model_sync_details_open_ model_source) - (View_settings.sync_status_sheet model_source send); + (View_settings.sync_status_sheet context model_source send); ] in let stack_grow = Lui_ui.platform context = AndroidOS in diff --git a/shared/src/logseq_chat/view_settings.ml b/shared/src/logseq_chat/view_settings.ml index 8da46873..036764d0 100644 --- a/shared/src/logseq_chat/view_settings.ml +++ b/shared/src/logseq_chat/view_settings.ml @@ -56,7 +56,25 @@ let settings_language_control (context : Lui_ui.ui_context) model_source ~key:View_base.settings_language_choice_identifier ~cmp:compare ~mount:(fun choice_source -> - settings_language_choice_menu_item choice_source send); + let choice = Signal.sample choice_source in + menu_item + ~text_signal: + (Signal.map + View_base.settings_language_choice_title + choice_source) + ~selected_signal: + (Signal.map + (fun (model : Model.chat_model) -> + Model.settings_language_choice_selected_ + model choice) + model_source) + ~accessibility_identifier: + (View_base.settings_language_choice_identifier + choice) + ~on_press:(fun _ -> + ignore + (send (Model.ChooseSettingsLanguage choice.id))) + []); ]); ] else @@ -74,6 +92,18 @@ let settings_language_control (context : Lui_ui.ui_context) model_source let settings_appearance_control (context : Lui_ui.ui_context) model_source send : t = if Lui_ui.host context = FlutterHost then + let appearance_item id title : t = + menu_item ~text:title + ~selected_signal: + (Signal.map + (fun (model : Model.chat_model) -> model.appearance = id) + model_source) + ~accessibility_identifier: + ("picker.settings.appearance.option." ^ id) + ~on_press:(fun _ -> + ignore (send (Model.ChangeAppearance id))) + [] + in stack ~accessibility_identifier:"layout.settings.appearance-control" [ @@ -92,18 +122,9 @@ let settings_appearance_control (context : Lui_ui.ui_context) model_source ~min_width:160 ~on_dismiss:(press send Model.CloseSettingsAppearanceMenu) [ - menu_item ~text:"System" - ~on_press:(fun _ -> - ignore (send (Model.ChangeAppearance "system"))) - []; - menu_item ~text:"Light" - ~on_press:(fun _ -> - ignore (send (Model.ChangeAppearance "light"))) - []; - menu_item ~text:"Dark" - ~on_press:(fun _ -> - ignore (send (Model.ChangeAppearance "dark"))) - []; + appearance_item "system" "System"; + appearance_item "light" "Light"; + appearance_item "dark" "Dark"; ]); ] else @@ -923,24 +944,54 @@ let settings_sheet (context : Lui_ui.ui_context) model_source send : t = ] else settings_main_sheet context model_source send -let page_delete_dialog send : t = - dialog ~text:"Delete page?" ~style_class:"alert" - ~accessibility_identifier:"dialog.page-delete" - ~on_dismiss:(press send Model.CancelDeleteActivePage) - [ - column - [ - text ~value:"The page will be moved to Recycle." []; - button ~accessibility_identifier:"button.page-delete.cancel" - ~on_press:(press send Model.CancelDeleteActivePage) - ~text:"Cancel" []; - button ~accessibility_identifier:"button.page-delete.confirm" - ~on_press:(press send Model.ConfirmDeleteActivePage) - ~text:"Delete" []; - ]; - ] +let page_delete_dialog (context : Lui_ui.ui_context) send : t = + if Lui_ui.host context = FlutterHost then + dialog ~text:"Delete page?" ~height:280 + ~accessibility_identifier:"dialog.page-delete" + ~on_dismiss:(press send Model.CancelDeleteActivePage) + [ + column ~gap:16 ~cross:`stretch + [ + text ~value:"The page will be moved to Recycle." ~grow:1.0 []; + spacer ~grow:1.0 []; + row ~main:`end_ + [ + toolbar ~orientation:`horizontal ~toolbar_gap:12 + ~label:"Page deletion actions" + ~accessibility_identifier:"toolbar.page-delete" + [ + button ~variant:`ghost + ~accessibility_identifier: + "button.page-delete.cancel" + ~on_press:(press send Model.CancelDeleteActivePage) + ~text:"Cancel" []; + button ~variant:`destructive + ~accessibility_identifier: + "button.page-delete.confirm" + ~on_press:(press send Model.ConfirmDeleteActivePage) + ~text:"Delete" []; + ]; + ]; + ]; + ] + else + dialog ~text:"Delete page?" ~style_class:"alert" + ~accessibility_identifier:"dialog.page-delete" + ~on_dismiss:(press send Model.CancelDeleteActivePage) + [ + column + [ + text ~value:"The page will be moved to Recycle." []; + button ~accessibility_identifier:"button.page-delete.cancel" + ~on_press:(press send Model.CancelDeleteActivePage) + ~text:"Cancel" []; + button ~accessibility_identifier:"button.page-delete.confirm" + ~on_press:(press send Model.ConfirmDeleteActivePage) + ~text:"Delete" []; + ]; + ] -let sync_status_sheet model_source send : t = +let sync_status_sheet (context : Lui_ui.ui_context) model_source send : t = let sync_row identifier label value_signal : t = list_item ~accessibility_identifier:identifier [ @@ -952,15 +1003,83 @@ let sync_status_sheet model_source send : t = ]; ] in - sheet ~text:"Sync status" ~style_class:"navigation-form" - ~accessibility_identifier:"sheet.sync-status" - ~on_dismiss:(press send Model.CloseSyncDetails) - [ - column ~style_class:"form" - ~accessibility_identifier:"form.sync-status" - [ - sync_row "row.sync.status" "Status" - (Signal.map View_base.sync_label model_source); + if Lui_ui.host context = FlutterHost then + sheet ~text:"Sync status" ~height:420 + ~accessibility_identifier:"sheet.sync-status" + ~on_dismiss:(press send Model.CloseSyncDetails) + [ + column ~gap:8 ~cross:`stretch ~grow:1.0 + ~accessibility_identifier:"form.sync-status" + [ + sync_row "row.sync.status" "Status" + (Signal.map View_base.sync_label model_source); + sync_row "row.sync.graph" "Graph" + (Signal.map View_base.graph_label model_source); + sync_row "row.sync.connection" "Connection" + (Signal.map View_base.sync_connection_label model_source); + list_item ~accessibility_identifier:"row.sync.pending" + [ + row ~grow:1.0 ~cross:`center ~main:`space_between + [ + text ~value:"Local changes" []; + text + ~value_signal: + (Signal.map View_base.sync_pending_label + model_source) + ~foreground:"secondary" ~text_alignment:`end_ + ~accessibility_identifier:"sync.pending" []; + ]; + ]; + list_item ~accessibility_identifier:"row.sync.cursor" + [ + row ~grow:1.0 ~cross:`center ~main:`space_between + [ + text ~value:"Server cursor" []; + text + ~value_signal: + (Signal.map View_base.sync_cursor_label + model_source) + ~foreground:"secondary" ~text_alignment:`end_ + ~accessibility_identifier:"sync.cursor" []; + ]; + ]; + if_ + ~test:(Signal.map View_base.sync_error_present_ model_source) + (text ~style_class:"footnote" ~foreground:"muted-foreground" + ~value:"Last error" []); + if_ + ~test:(Signal.map View_base.sync_error_present_ model_source) + (text + ~value_signal: + (Signal.map View_base.sync_error_message model_source) + ~foreground:"destructive" + ~accessibility_identifier:"sync.error" []); + spacer ~grow:1.0 []; + button ~variant:`primary ~grow:1.0 + ~accessibility_identifier:"button.sync-now" + ~on_press:(press send Model.SyncNow) + ~text:"Sync now" []; + toolbar ~orientation:`horizontal ~label:"Sync status actions" + ~accessibility_identifier:"toolbar.sync.actions" + [ + spacer ~grow:1.0 []; + button ~variant:`ghost + ~accessibility_identifier:"button.sync.done" + ~on_press:(press send Model.CloseSyncDetails) + ~text:"Done" []; + ]; + ]; + ] + else + sheet ~text:"Sync status" ~style_class:"navigation-form" + ~accessibility_identifier:"sheet.sync-status" + ~on_dismiss:(press send Model.CloseSyncDetails) + [ + column ~style_class:"form" + ~accessibility_identifier:"form.sync-status" + [ + sync_row "row.sync.status" "Status" + (Signal.map View_base.sync_label model_source); sync_row "row.sync.graph" "Graph" (Signal.map View_base.graph_label model_source); sync_row "row.sync.connection" "Connection" diff --git a/shared/src/logseq_chat/view_settings.mli b/shared/src/logseq_chat/view_settings.mli index 21cbef85..ded04c22 100644 --- a/shared/src/logseq_chat/view_settings.mli +++ b/shared/src/logseq_chat/view_settings.mli @@ -76,7 +76,9 @@ val settings_sheet : Lui_ui.ui_context -> Model.chat_model Signal.signal -> (Model.chat_action -> bool) -> Lui_elements.t -val page_delete_dialog : (Model.chat_action -> bool) -> Lui_elements.t +val page_delete_dialog : + Lui_ui.ui_context -> (Model.chat_action -> bool) -> Lui_elements.t val sync_status_sheet : + Lui_ui.ui_context -> Model.chat_model Signal.signal -> (Model.chat_action -> bool) -> Lui_elements.t diff --git a/shared/test/logseq_chat/app_test.ml b/shared/test/logseq_chat/app_test.ml index 67a2976b..509df28e 100644 --- a/shared/test/logseq_chat/app_test.ml +++ b/shared/test/logseq_chat/app_test.ml @@ -8317,7 +8317,7 @@ let composer_asset_failure_retains_draft_and_success_removes_it () = let composer_asset_schema_matches_native_thumbnail_renderer () = check_eq ~msg:"native attachment thumbnails share the LG schema" (Lui_extension.fingerprint (View.composer_asset_schema ())) - "lui-extension-v1|14:composer-asset|profiles:ios/swiftui|standard-children:0|children:|properties:10:local-path:string:required:none,5:title:string:required:none|events:" + "lui-extension-v1|14:composer-asset|profiles:android/flutter,ios/swiftui|standard-children:0|children:|properties:10:local-path:string:required:none,5:title:string:required:none|events:" let block_node_breadcrumbs_preserve_navigation_context () = List.iter