From cc9965c6bf8c32398fbca4d25a05cd8fb468d34e Mon Sep 17 00:00:00 2001 From: Pebkac03 Date: Thu, 8 Aug 2024 00:08:11 +0200 Subject: [PATCH] full ui rework, configurable path, archive old code --- .vscode/settings.json | 3 +- lib/app/app.dart | 12 +- lib/app/app.router.dart | 94 ++++---- lib/main.dart | 4 +- lib/models/espanso_match_model.freezed.dart | 36 +++- lib/models/espanso_replace_field.freezed.dart | 17 +- lib/models/espanso_trigger_field.freezed.dart | 17 +- lib/services/espanso_matches_service.dart | 22 +- .../espanso/espanso_view.dart | 2 +- .../espanso/espanso_viewmodel.dart | 0 .../matches_table2/matches_table2.dart | 4 +- .../matches_table2/matches_table2_model.dart | 0 .../menu_row/matches_table2_menu_row.dart | 1 + .../matches_table2_menu_row_model.dart | 0 .../fields/advanced_field/advanced_field.dart | 105 +++++++++ .../advanced_field/advanced_field_model.dart | 44 ++++ .../checkbox_field/checkbox_field.dart | 0 .../checkbox_field/checkbox_field_model.dart | 0 .../label_field/label_field/label_field.dart | 0 .../label_field/label_field_model.dart | 0 .../fields/replace_field/replace_field.dart | 0 .../replace_field/replace_field_model.dart | 0 .../fields/trigger_field/trigger_field.dart | 0 .../trigger_field/trigger_field_model.dart | 0 .../row/overlays/replace_field_overlay.dart | 0 .../overlays/replace_field_overlay_model.dart | 0 lib/ui/archive/matches_table2/row/row.dart | 202 ++++++++++++++++++ .../matches_table2/row/row_model.dart | 9 + .../views/espanso_new/espanso_new_view.dart | 121 +++++++++++ .../espanso_new/espanso_new_viewmodel.dart | 29 +++ .../matches_details/matches_details.dart | 23 ++ .../matches_details_model.dart | 3 + lib/ui/widgets/matches_table2/row/row.dart | 114 ---------- .../matches_table3/matches_table3.dart | 115 ++++++++++ .../matches_table3/matches_table3_model.dart | 22 ++ lib/ui/widgets/table_window/table_window.dart | 45 ++++ .../table_window/table_window_model.dart | 3 + pubspec.lock | 113 +++++----- pubspec.yaml | 2 +- test/helpers/test_helpers.mocks.dart | 9 + .../espanso_new_viewmodel_test.dart | 11 + .../advanced_field_model_test.dart | 11 + .../matches_details_model_test.dart | 11 + .../matches_table3_model_test.dart | 11 + .../table_window_model_test.dart | 11 + 45 files changed, 991 insertions(+), 235 deletions(-) rename lib/ui/{views => archive}/espanso/espanso_view.dart (97%) rename lib/ui/{views => archive}/espanso/espanso_viewmodel.dart (100%) rename lib/ui/{widgets => archive}/matches_table2/matches_table2.dart (94%) rename lib/ui/{widgets => archive}/matches_table2/matches_table2_model.dart (100%) rename lib/ui/{widgets => archive}/matches_table2/menu_row/matches_table2_menu_row.dart (98%) rename lib/ui/{widgets => archive}/matches_table2/menu_row/matches_table2_menu_row_model.dart (100%) create mode 100644 lib/ui/archive/matches_table2/row/fields/advanced_field/advanced_field.dart create mode 100644 lib/ui/archive/matches_table2/row/fields/advanced_field/advanced_field_model.dart rename lib/ui/{widgets => archive}/matches_table2/row/fields/checkbox_field/checkbox_field/checkbox_field.dart (100%) rename lib/ui/{widgets => archive}/matches_table2/row/fields/checkbox_field/checkbox_field/checkbox_field_model.dart (100%) rename lib/ui/{widgets => archive}/matches_table2/row/fields/label_field/label_field/label_field.dart (100%) rename lib/ui/{widgets => archive}/matches_table2/row/fields/label_field/label_field/label_field_model.dart (100%) rename lib/ui/{widgets => archive}/matches_table2/row/fields/replace_field/replace_field.dart (100%) rename lib/ui/{widgets => archive}/matches_table2/row/fields/replace_field/replace_field_model.dart (100%) rename lib/ui/{widgets => archive}/matches_table2/row/fields/trigger_field/trigger_field.dart (100%) rename lib/ui/{widgets => archive}/matches_table2/row/fields/trigger_field/trigger_field_model.dart (100%) rename lib/ui/{widgets => archive}/matches_table2/row/overlays/replace_field_overlay.dart (100%) rename lib/ui/{widgets => archive}/matches_table2/row/overlays/replace_field_overlay_model.dart (100%) create mode 100644 lib/ui/archive/matches_table2/row/row.dart rename lib/ui/{widgets => archive}/matches_table2/row/row_model.dart (94%) create mode 100644 lib/ui/views/espanso_new/espanso_new_view.dart create mode 100644 lib/ui/views/espanso_new/espanso_new_viewmodel.dart create mode 100644 lib/ui/widgets/matches_details/matches_details.dart create mode 100644 lib/ui/widgets/matches_details/matches_details_model.dart delete mode 100644 lib/ui/widgets/matches_table2/row/row.dart create mode 100644 lib/ui/widgets/table_window/matches_table3/matches_table3.dart create mode 100644 lib/ui/widgets/table_window/matches_table3/matches_table3_model.dart create mode 100644 lib/ui/widgets/table_window/table_window.dart create mode 100644 lib/ui/widgets/table_window/table_window_model.dart create mode 100644 test/viewmodels/espanso_new_viewmodel_test.dart create mode 100644 test/widget_models/advanced_field_model_test.dart create mode 100644 test/widget_models/matches_details_model_test.dart create mode 100644 test/widget_models/matches_table3_model_test.dart create mode 100644 test/widget_models/table_window_model_test.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 25d97f4..5ce4545 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ "explorer.fileNesting.enabled": true, "explorer.fileNesting.patterns": { "*.dart": "${capture}.mobile.dart, ${capture}.tablet.dart, ${capture}.desktop.dart, ${capture}.form.dart, ${capture}.g.dart, ${capture}.freezed.dart, ${capture}.logger.dart, ${capture}.locator.dart, ${capture}.router.dart, ${capture}.dialogs.dart, ${capture}.bottomsheets.dart" - } + }, + "cmake.configureOnOpen": false } diff --git a/lib/app/app.dart b/lib/app/app.dart index f5b1bfe..2241df2 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -1,14 +1,15 @@ +import 'package:espanso_gui_v2/services/espanso_matches_service.dart'; +import 'package:espanso_gui_v2/services/file_service.dart'; +import 'package:espanso_gui_v2/services/theme_service.dart'; +import 'package:espanso_gui_v2/services/theme_service_service.dart'; +import 'package:espanso_gui_v2/ui/archive/espanso/espanso_view.dart'; import 'package:espanso_gui_v2/ui/bottom_sheets/notice/notice_sheet.dart'; import 'package:espanso_gui_v2/ui/dialogs/info_alert/info_alert_dialog.dart'; +import 'package:espanso_gui_v2/ui/views/espanso_new/espanso_new_view.dart'; import 'package:espanso_gui_v2/ui/views/home/home_view.dart'; import 'package:espanso_gui_v2/ui/views/startup/startup_view.dart'; import 'package:stacked/stacked_annotations.dart'; import 'package:stacked_services/stacked_services.dart'; -import 'package:espanso_gui_v2/ui/views/espanso/espanso_view.dart'; -import 'package:espanso_gui_v2/services/espanso_matches_service.dart'; -import 'package:espanso_gui_v2/services/file_service.dart'; -import 'package:espanso_gui_v2/services/theme_service_service.dart'; -import 'package:espanso_gui_v2/services/theme_service.dart'; // @stacked-import @StackedApp( @@ -16,6 +17,7 @@ import 'package:espanso_gui_v2/services/theme_service.dart'; MaterialRoute(page: HomeView), MaterialRoute(page: StartupView), MaterialRoute(page: EspansoView), + MaterialRoute(page: EspansoNewView), // @stacked-route ], dependencies: [ diff --git a/lib/app/app.router.dart b/lib/app/app.router.dart index cee7471..163692b 100644 --- a/lib/app/app.router.dart +++ b/lib/app/app.router.dart @@ -5,13 +5,15 @@ // ************************************************************************** // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:espanso_gui_v2/ui/views/espanso/espanso_view.dart' as _i4; +import 'package:espanso_gui_v2/ui/archive/espanso/espanso_view.dart' as _i4; +import 'package:espanso_gui_v2/ui/views/espanso_new/espanso_new_view.dart' + as _i5; import 'package:espanso_gui_v2/ui/views/home/home_view.dart' as _i2; import 'package:espanso_gui_v2/ui/views/startup/startup_view.dart' as _i3; -import 'package:flutter/material.dart' as _i5; +import 'package:flutter/material.dart' as _i6; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart' as _i1; -import 'package:stacked_services/stacked_services.dart' as _i6; +import 'package:stacked_services/stacked_services.dart' as _i7; class Routes { static const homeView = '/home-view'; @@ -20,10 +22,13 @@ class Routes { static const espansoView = '/espanso-view'; + static const espansoNewView = '/espanso-new-view'; + static const all = { homeView, startupView, espansoView, + espansoNewView, }; } @@ -41,27 +46,34 @@ class StackedRouter extends _i1.RouterBase { Routes.espansoView, page: _i4.EspansoView, ), + _i1.RouteDef( + Routes.espansoNewView, + page: _i5.EspansoNewView, + ), ]; final _pagesMap = { _i2.HomeView: (data) { - return _i5.MaterialPageRoute( + return _i6.MaterialPageRoute( builder: (context) => const _i2.HomeView(), settings: data, ); }, _i3.StartupView: (data) { - return _i5.MaterialPageRoute( + return _i6.MaterialPageRoute( builder: (context) => const _i3.StartupView(), settings: data, ); }, _i4.EspansoView: (data) { - final args = data.getArgs( - orElse: () => const EspansoViewArguments(), + return _i6.MaterialPageRoute( + builder: (context) => const _i4.EspansoView(), + settings: data, ); - return _i5.MaterialPageRoute( - builder: (context) => _i4.EspansoView(key: args.key), + }, + _i5.EspansoNewView: (data) { + return _i6.MaterialPageRoute( + builder: (context) => const _i5.EspansoNewView(), settings: data, ); }, @@ -74,29 +86,7 @@ class StackedRouter extends _i1.RouterBase { Map get pagesMap => _pagesMap; } -class EspansoViewArguments { - const EspansoViewArguments({this.key}); - - final _i5.Key? key; - - @override - String toString() { - return '{"key": "$key"}'; - } - - @override - bool operator ==(covariant EspansoViewArguments other) { - if (identical(this, other)) return true; - return other.key == key; - } - - @override - int get hashCode { - return key.hashCode; - } -} - -extension NavigatorStateExtension on _i6.NavigationService { +extension NavigatorStateExtension on _i7.NavigationService { Future navigateToHomeView([ int? routerId, bool preventDuplicates = true, @@ -125,16 +115,28 @@ extension NavigatorStateExtension on _i6.NavigationService { transition: transition); } - Future navigateToEspansoView({ - _i5.Key? key, + Future navigateToEspansoView([ int? routerId, bool preventDuplicates = true, Map? parameters, Widget Function(BuildContext, Animation, Animation, Widget)? transition, - }) async { + ]) async { return navigateTo(Routes.espansoView, - arguments: EspansoViewArguments(key: key), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToEspansoNewView([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return navigateTo(Routes.espansoNewView, id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, @@ -169,16 +171,28 @@ extension NavigatorStateExtension on _i6.NavigationService { transition: transition); } - Future replaceWithEspansoView({ - _i5.Key? key, + Future replaceWithEspansoView([ int? routerId, bool preventDuplicates = true, Map? parameters, Widget Function(BuildContext, Animation, Animation, Widget)? transition, - }) async { + ]) async { return replaceWith(Routes.espansoView, - arguments: EspansoViewArguments(key: key), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithEspansoNewView([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return replaceWith(Routes.espansoNewView, id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, diff --git a/lib/main.dart b/lib/main.dart index 3c8c4c7..0d55027 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,8 @@ -import 'package:flutter/material.dart'; import 'package:espanso_gui_v2/app/app.bottomsheets.dart'; import 'package:espanso_gui_v2/app/app.dialogs.dart'; import 'package:espanso_gui_v2/app/app.locator.dart'; import 'package:espanso_gui_v2/app/app.router.dart'; +import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:stacked_services/stacked_services.dart'; @@ -25,7 +25,7 @@ class MainApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - initialRoute: Routes.espansoView, + initialRoute: Routes.espansoNewView, onGenerateRoute: StackedRouter().onGenerateRoute, navigatorKey: StackedService.navigatorKey, navigatorObservers: [ diff --git a/lib/models/espanso_match_model.freezed.dart b/lib/models/espanso_match_model.freezed.dart index 2d31c1c..f81e135 100644 --- a/lib/models/espanso_match_model.freezed.dart +++ b/lib/models/espanso_match_model.freezed.dart @@ -62,7 +62,9 @@ mixin _$EspansoMatch { /// list of variables set variables(List value) => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of EspansoMatch + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $EspansoMatchCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -96,6 +98,8 @@ class _$EspansoMatchCopyWithImpl<$Res, $Val extends EspansoMatch> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of EspansoMatch + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -139,6 +143,8 @@ class _$EspansoMatchCopyWithImpl<$Res, $Val extends EspansoMatch> ) as $Val); } + /// Create a copy of EspansoMatch + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $EspansoTriggerFieldCopyWith<$Res> get trigger { @@ -147,6 +153,8 @@ class _$EspansoMatchCopyWithImpl<$Res, $Val extends EspansoMatch> }); } + /// Create a copy of EspansoMatch + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $EspansoReplaceFieldCopyWith<$Res> get replace { @@ -187,6 +195,8 @@ class __$$EspansoMatchImplCopyWithImpl<$Res> _$EspansoMatchImpl _value, $Res Function(_$EspansoMatchImpl) _then) : super(_value, _then); + /// Create a copy of EspansoMatch + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -279,7 +289,9 @@ class _$EspansoMatchImpl extends _EspansoMatch { return 'EspansoMatch(trigger: $trigger, replace: $replace, label: $label, propagateCase: $propagateCase, titleCase: $titleCase, onlyOnWord: $onlyOnWord, variables: $variables)'; } - @JsonKey(ignore: true) + /// Create a copy of EspansoMatch + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$EspansoMatchImplCopyWith<_$EspansoMatchImpl> get copyWith => @@ -297,61 +309,63 @@ abstract class _EspansoMatch extends EspansoMatch { required List variables}) = _$EspansoMatchImpl; _EspansoMatch._() : super._(); - @override - /// string to trigger expansion + @override EspansoTriggerField get trigger; /// string to trigger expansion set trigger(EspansoTriggerField value); - @override /// what the trigger expands to + @override EspansoReplaceField get replace; /// what the trigger expands to set replace(EspansoReplaceField value); - @override /// The label to display as the match's identifier in UI elements. /// /// If empty, defaults to first trigger. + @override String get label; /// The label to display as the match's identifier in UI elements. /// /// If empty, defaults to first trigger. set label(String value); - @override /// ex. alh - although, Alh - Although, ALH - ALTHOUGH + @override bool get propagateCase; /// ex. alh - although, Alh - Although, ALH - ALTHOUGH set propagateCase(bool value); - @override /// if propagateCase should capitalize each word. `false` by default + @override bool get titleCase; /// if propagateCase should capitalize each word. `false` by default set titleCase(bool value); - @override /// only trigger on matching word + @override bool get onlyOnWord; /// only trigger on matching word set onlyOnWord(bool value); - @override /// list of variables + @override List get variables; /// list of variables set variables(List value); + + /// Create a copy of EspansoMatch + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$EspansoMatchImplCopyWith<_$EspansoMatchImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/espanso_replace_field.freezed.dart b/lib/models/espanso_replace_field.freezed.dart index c6d3372..3c59f12 100644 --- a/lib/models/espanso_replace_field.freezed.dart +++ b/lib/models/espanso_replace_field.freezed.dart @@ -19,7 +19,9 @@ mixin _$EspansoReplaceField { String get text => throw _privateConstructorUsedError; EspansoReplaceType get type => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of EspansoReplaceField + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $EspansoReplaceFieldCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -43,6 +45,8 @@ class _$EspansoReplaceFieldCopyWithImpl<$Res, $Val extends EspansoReplaceField> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of EspansoReplaceField + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -81,6 +85,8 @@ class __$$EspansoReplaceFieldImplCopyWithImpl<$Res> $Res Function(_$EspansoReplaceFieldImpl) _then) : super(_value, _then); + /// Create a copy of EspansoReplaceField + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -123,7 +129,9 @@ class _$EspansoReplaceFieldImpl extends _EspansoReplaceField { @override int get hashCode => Object.hash(runtimeType, text, type); - @JsonKey(ignore: true) + /// Create a copy of EspansoReplaceField + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$EspansoReplaceFieldImplCopyWith<_$EspansoReplaceFieldImpl> get copyWith => @@ -141,8 +149,11 @@ abstract class _EspansoReplaceField extends EspansoReplaceField { String get text; @override EspansoReplaceType get type; + + /// Create a copy of EspansoReplaceField + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$EspansoReplaceFieldImplCopyWith<_$EspansoReplaceFieldImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/espanso_trigger_field.freezed.dart b/lib/models/espanso_trigger_field.freezed.dart index 320b5d8..311c967 100644 --- a/lib/models/espanso_trigger_field.freezed.dart +++ b/lib/models/espanso_trigger_field.freezed.dart @@ -21,7 +21,9 @@ mixin _$EspansoTriggerField { List? get triggers => throw _privateConstructorUsedError; set triggers(List? value) => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of EspansoTriggerField + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $EspansoTriggerFieldCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -45,6 +47,8 @@ class _$EspansoTriggerFieldCopyWithImpl<$Res, $Val extends EspansoTriggerField> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of EspansoTriggerField + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -83,6 +87,8 @@ class __$$EspansoTriggerFieldImplCopyWithImpl<$Res> $Res Function(_$EspansoTriggerFieldImpl) _then) : super(_value, _then); + /// Create a copy of EspansoTriggerField + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -112,7 +118,9 @@ class _$EspansoTriggerFieldImpl extends _EspansoTriggerField { @override List? triggers; - @JsonKey(ignore: true) + /// Create a copy of EspansoTriggerField + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$EspansoTriggerFieldImplCopyWith<_$EspansoTriggerFieldImpl> get copyWith => @@ -131,8 +139,11 @@ abstract class _EspansoTriggerField extends EspansoTriggerField { @override List? get triggers; set triggers(List? value); + + /// Create a copy of EspansoTriggerField + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$EspansoTriggerFieldImplCopyWith<_$EspansoTriggerFieldImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/services/espanso_matches_service.dart b/lib/services/espanso_matches_service.dart index 35be176..5776fa3 100644 --- a/lib/services/espanso_matches_service.dart +++ b/lib/services/espanso_matches_service.dart @@ -1,3 +1,4 @@ +//import 'dart:io'; import 'dart:io'; import 'package:espanso_gui_v2/app/app.locator.dart'; @@ -9,8 +10,25 @@ import 'package:yaml_writer/yaml_writer.dart'; class EspansoMatchesService { final _fileService = locator(); - final _path = (Platform.environment['UserProfile'] ?? '') + - r'\AppData\Roaming\espanso\match'; + final String _platform = Platform.operatingSystem; + String get _defaultPath { + switch (_platform) { + case 'windows': + return (Platform.environment['UserProfile'] ?? '') + + r'\AppData\Roaming\espanso\match'; + case 'linux': + return ''; + case 'macos': + return ''; + default: + return ''; + } + } + + String? _choosenPath; + void setPath(path) => _choosenPath = path; + + String get _path => _choosenPath ?? _defaultPath; final _yamlWriter = YamlWriter(); Map> loadAllMatches() { diff --git a/lib/ui/views/espanso/espanso_view.dart b/lib/ui/archive/espanso/espanso_view.dart similarity index 97% rename from lib/ui/views/espanso/espanso_view.dart rename to lib/ui/archive/espanso/espanso_view.dart index 780876a..4e0ca67 100644 --- a/lib/ui/views/espanso/espanso_view.dart +++ b/lib/ui/archive/espanso/espanso_view.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:espanso_gui_v2/ui/widgets/matches_table2/matches_table2.dart'; +import 'package:espanso_gui_v2/ui/archive/matches_table2/matches_table2.dart'; import 'package:flutter/material.dart'; import 'package:overflow_tab_bar/overflow_tab_bar.dart'; import 'package:stacked/stacked.dart'; diff --git a/lib/ui/views/espanso/espanso_viewmodel.dart b/lib/ui/archive/espanso/espanso_viewmodel.dart similarity index 100% rename from lib/ui/views/espanso/espanso_viewmodel.dart rename to lib/ui/archive/espanso/espanso_viewmodel.dart diff --git a/lib/ui/widgets/matches_table2/matches_table2.dart b/lib/ui/archive/matches_table2/matches_table2.dart similarity index 94% rename from lib/ui/widgets/matches_table2/matches_table2.dart rename to lib/ui/archive/matches_table2/matches_table2.dart index c58c672..3906bf3 100644 --- a/lib/ui/widgets/matches_table2/matches_table2.dart +++ b/lib/ui/archive/matches_table2/matches_table2.dart @@ -1,7 +1,7 @@ import 'dart:io'; -import 'package:espanso_gui_v2/ui/widgets/matches_table2/menu_row/matches_table2_menu_row.dart'; -import 'package:espanso_gui_v2/ui/widgets/matches_table2/row/row.dart'; +import 'package:espanso_gui_v2/ui/archive/matches_table2/menu_row/matches_table2_menu_row.dart'; +import 'package:espanso_gui_v2/ui/archive/matches_table2/row/row.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; diff --git a/lib/ui/widgets/matches_table2/matches_table2_model.dart b/lib/ui/archive/matches_table2/matches_table2_model.dart similarity index 100% rename from lib/ui/widgets/matches_table2/matches_table2_model.dart rename to lib/ui/archive/matches_table2/matches_table2_model.dart diff --git a/lib/ui/widgets/matches_table2/menu_row/matches_table2_menu_row.dart b/lib/ui/archive/matches_table2/menu_row/matches_table2_menu_row.dart similarity index 98% rename from lib/ui/widgets/matches_table2/menu_row/matches_table2_menu_row.dart rename to lib/ui/archive/matches_table2/menu_row/matches_table2_menu_row.dart index c36f218..ea0c95c 100644 --- a/lib/ui/widgets/matches_table2/menu_row/matches_table2_menu_row.dart +++ b/lib/ui/archive/matches_table2/menu_row/matches_table2_menu_row.dart @@ -50,6 +50,7 @@ class MatchesTable2MenuRow extends StackedView { titleCell('Require Word'), titleCell('Match trigger capitalization'), titleCell('Capitalize each word'), + titleCell('Advanced'), ], ); } diff --git a/lib/ui/widgets/matches_table2/menu_row/matches_table2_menu_row_model.dart b/lib/ui/archive/matches_table2/menu_row/matches_table2_menu_row_model.dart similarity index 100% rename from lib/ui/widgets/matches_table2/menu_row/matches_table2_menu_row_model.dart rename to lib/ui/archive/matches_table2/menu_row/matches_table2_menu_row_model.dart diff --git a/lib/ui/archive/matches_table2/row/fields/advanced_field/advanced_field.dart b/lib/ui/archive/matches_table2/row/fields/advanced_field/advanced_field.dart new file mode 100644 index 0000000..26bebfa --- /dev/null +++ b/lib/ui/archive/matches_table2/row/fields/advanced_field/advanced_field.dart @@ -0,0 +1,105 @@ +import 'package:espanso_gui_v2/intents.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:stacked/stacked.dart'; + +import 'advanced_field_model.dart'; + +class AdvancedField extends StackedView { + final bool entry; + final double rowHeight; + final double cellPadding; + final double offWidth; + final double focusWidthFactor; + final Color offColor; + final Color onColor; + final TextStyle? textStyle; + final void Function(bool value) onSubmitted; + final void Function(bool value) onDropdown; + + static const double iconWidth = 50; + + const AdvancedField({ + super.key, + required this.entry, + required this.rowHeight, + required this.cellPadding, + required this.offWidth, + this.focusWidthFactor = 2, + required this.offColor, + required this.onColor, + this.textStyle, + required this.onSubmitted, + required this.onDropdown, + }); + + @override + Widget builder( + BuildContext context, + AdvancedFieldModel viewModel, + Widget? child, + ) { + final double onWidth = offWidth * focusWidthFactor; + + final Map shortcuts = { + const SingleActivator(LogicalKeyboardKey.enter): ChangeCheckboxIntent(), + const SingleActivator(LogicalKeyboardKey.space): ChangeCheckboxIntent(), + }; + + final Map> actions = { + ChangeCheckboxIntent: + CallbackAction(onInvoke: (_) => viewModel.toggleCheckbox()), + }; + + return Expanded( + child: FocusableActionDetector( + descendantsAreTraversable: false, + onShowFocusHighlight: (e) => viewModel.showFocus = e, + shortcuts: shortcuts, + actions: actions, + child: Container( + height: rowHeight, + alignment: Alignment.center, + decoration: BoxDecoration( + border: Border.all( + width: viewModel.showFocus ? onWidth : offWidth, + color: viewModel.showFocus ? onColor : offColor)), + child: Row( + children: [ + Expanded( + child: Checkbox( + value: viewModel.checked, + onChanged: (value) { + viewModel.setCheckbox(value); + }, + ), + ), + Expanded( + child: IconButton( + onPressed: viewModel.checked + ? () { + viewModel.showAdvancedMenu = + !viewModel.showAdvancedMenu; + } + : null, + icon: Icon(viewModel.showAdvancedMenu + ? Icons.arrow_drop_up + : Icons.arrow_drop_down), + ), + ) + ], + ), + ), + ), + ); + } + + @override + AdvancedFieldModel viewModelBuilder( + BuildContext context, + ) => + AdvancedFieldModel( + onSubmitted: onSubmitted, + onDropdown: onDropdown, + initialValue: entry); +} diff --git a/lib/ui/archive/matches_table2/row/fields/advanced_field/advanced_field_model.dart b/lib/ui/archive/matches_table2/row/fields/advanced_field/advanced_field_model.dart new file mode 100644 index 0000000..b785814 --- /dev/null +++ b/lib/ui/archive/matches_table2/row/fields/advanced_field/advanced_field_model.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +class AdvancedFieldModel extends BaseViewModel { + final bool initialValue; + final void Function(bool e) onDropdown; + final void Function(bool value) onSubmitted; + bool? _checked; + + AdvancedFieldModel({ + required this.initialValue, + required this.onSubmitted, + required this.onDropdown, + }); + + bool _showAdvancedMenu = false; + bool get showAdvancedMenu => _showAdvancedMenu; + set showAdvancedMenu(e) { + _showAdvancedMenu = e; + onDropdown(e); + } + + bool _showFocus = false; + bool get showFocus => _showFocus; + + get checked => _checked ??= initialValue; + void setCheckbox(e) { + _checked = e; + onSubmitted(e); + rebuildUi(); + } + + void toggleCheckbox() { + _checked = !checked; + onSubmitted(checked); + rebuildUi(); + } + + set showFocus(bool e) { + _showFocus = e; + !e ? debugPrint('EspansoLabelField: text submitted') : {}; + rebuildUi(); + } +} diff --git a/lib/ui/widgets/matches_table2/row/fields/checkbox_field/checkbox_field/checkbox_field.dart b/lib/ui/archive/matches_table2/row/fields/checkbox_field/checkbox_field/checkbox_field.dart similarity index 100% rename from lib/ui/widgets/matches_table2/row/fields/checkbox_field/checkbox_field/checkbox_field.dart rename to lib/ui/archive/matches_table2/row/fields/checkbox_field/checkbox_field/checkbox_field.dart diff --git a/lib/ui/widgets/matches_table2/row/fields/checkbox_field/checkbox_field/checkbox_field_model.dart b/lib/ui/archive/matches_table2/row/fields/checkbox_field/checkbox_field/checkbox_field_model.dart similarity index 100% rename from lib/ui/widgets/matches_table2/row/fields/checkbox_field/checkbox_field/checkbox_field_model.dart rename to lib/ui/archive/matches_table2/row/fields/checkbox_field/checkbox_field/checkbox_field_model.dart diff --git a/lib/ui/widgets/matches_table2/row/fields/label_field/label_field/label_field.dart b/lib/ui/archive/matches_table2/row/fields/label_field/label_field/label_field.dart similarity index 100% rename from lib/ui/widgets/matches_table2/row/fields/label_field/label_field/label_field.dart rename to lib/ui/archive/matches_table2/row/fields/label_field/label_field/label_field.dart diff --git a/lib/ui/widgets/matches_table2/row/fields/label_field/label_field/label_field_model.dart b/lib/ui/archive/matches_table2/row/fields/label_field/label_field/label_field_model.dart similarity index 100% rename from lib/ui/widgets/matches_table2/row/fields/label_field/label_field/label_field_model.dart rename to lib/ui/archive/matches_table2/row/fields/label_field/label_field/label_field_model.dart diff --git a/lib/ui/widgets/matches_table2/row/fields/replace_field/replace_field.dart b/lib/ui/archive/matches_table2/row/fields/replace_field/replace_field.dart similarity index 100% rename from lib/ui/widgets/matches_table2/row/fields/replace_field/replace_field.dart rename to lib/ui/archive/matches_table2/row/fields/replace_field/replace_field.dart diff --git a/lib/ui/widgets/matches_table2/row/fields/replace_field/replace_field_model.dart b/lib/ui/archive/matches_table2/row/fields/replace_field/replace_field_model.dart similarity index 100% rename from lib/ui/widgets/matches_table2/row/fields/replace_field/replace_field_model.dart rename to lib/ui/archive/matches_table2/row/fields/replace_field/replace_field_model.dart diff --git a/lib/ui/widgets/matches_table2/row/fields/trigger_field/trigger_field.dart b/lib/ui/archive/matches_table2/row/fields/trigger_field/trigger_field.dart similarity index 100% rename from lib/ui/widgets/matches_table2/row/fields/trigger_field/trigger_field.dart rename to lib/ui/archive/matches_table2/row/fields/trigger_field/trigger_field.dart diff --git a/lib/ui/widgets/matches_table2/row/fields/trigger_field/trigger_field_model.dart b/lib/ui/archive/matches_table2/row/fields/trigger_field/trigger_field_model.dart similarity index 100% rename from lib/ui/widgets/matches_table2/row/fields/trigger_field/trigger_field_model.dart rename to lib/ui/archive/matches_table2/row/fields/trigger_field/trigger_field_model.dart diff --git a/lib/ui/widgets/matches_table2/row/overlays/replace_field_overlay.dart b/lib/ui/archive/matches_table2/row/overlays/replace_field_overlay.dart similarity index 100% rename from lib/ui/widgets/matches_table2/row/overlays/replace_field_overlay.dart rename to lib/ui/archive/matches_table2/row/overlays/replace_field_overlay.dart diff --git a/lib/ui/widgets/matches_table2/row/overlays/replace_field_overlay_model.dart b/lib/ui/archive/matches_table2/row/overlays/replace_field_overlay_model.dart similarity index 100% rename from lib/ui/widgets/matches_table2/row/overlays/replace_field_overlay_model.dart rename to lib/ui/archive/matches_table2/row/overlays/replace_field_overlay_model.dart diff --git a/lib/ui/archive/matches_table2/row/row.dart b/lib/ui/archive/matches_table2/row/row.dart new file mode 100644 index 0000000..aba20fc --- /dev/null +++ b/lib/ui/archive/matches_table2/row/row.dart @@ -0,0 +1,202 @@ +import 'package:espanso_gui_v2/models/espanso_match_model.dart'; +import 'package:espanso_gui_v2/models/espanso_replace_field.dart'; +import 'package:espanso_gui_v2/ui/archive/matches_table2/row/fields/advanced_field/advanced_field.dart'; +import 'package:espanso_gui_v2/ui/archive/matches_table2/row/fields/checkbox_field/checkbox_field/checkbox_field.dart'; +import 'package:espanso_gui_v2/ui/archive/matches_table2/row/fields/label_field/label_field/label_field.dart'; +import 'package:espanso_gui_v2/ui/archive/matches_table2/row/fields/replace_field/replace_field.dart'; +import 'package:espanso_gui_v2/ui/archive/matches_table2/row/fields/trigger_field/trigger_field.dart'; +import 'package:espanso_gui_v2/ui/archive/matches_table2/row/overlays/replace_field_overlay.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'row_model.dart'; + +class MatchesTable2Row extends StackedView { + final EspansoMatch match; + final double? onWidth; + final double offWidth; + final double rowHeight; + final double cellPadding; + final Color onColor; + final Color offColor; + final TextStyle? textStyle; + + static const List variablesTestData = [ + TableRow( + children: [ + Text('test 1'), + Text('test 2'), + Text('test 3'), + ], + ), + TableRow( + children: [ + Text('test 1'), + Text('test 2'), + Text('test 3'), + ], + ), + TableRow( + children: [ + Text('test 1'), + Text('test 2'), + Text('test 3'), + ], + ), + ]; + + const MatchesTable2Row( + {super.key, + required this.match, + this.onWidth, + required this.offWidth, + required this.rowHeight, + this.cellPadding = 8, + required this.onColor, + required this.offColor, + this.textStyle}); + + @override + Widget builder( + BuildContext context, + MatchesTable2RowModel viewModel, + Widget? child, + ) { + debugPrint('ui rebuilt'); + final BorderSide borderSide = + BorderSide(color: Theme.of(context).colorScheme.outline); + + return Column( + children: [ + Row( + children: [ + LabelField( + entry: match.label, + offWidth: offWidth, + focusWidthFactor: 2, + offColor: offColor, + onColor: onColor, + rowHeight: rowHeight, + cellPadding: cellPadding, + textStyle: textStyle, + onSubmitted: (text) => match.label = text, + ), + TriggerField( + entry: match.trigger, + offWidth: offWidth, + onWidth: onWidth, + offColor: offColor, + onColor: onColor, + rowHeight: rowHeight, + cellPadding: cellPadding, + textStyle: textStyle, + onSubmitted: (entry) => match.trigger = entry, + overlay: (controller) => null, + ), + ReplaceField( + value: match.replace, + offWidth: offWidth, + focusWidthFactor: 1, + offColor: offColor, + onColor: onColor, + rowHeight: rowHeight, + cellPadding: cellPadding, + textStyle: textStyle, + onSubmitted: (EspansoReplaceField entry) => match.replace = entry, + overlay: (controller) => ReplaceFieldOverlay( + controller: controller, + ), + ), + CheckboxField( + entry: match.onlyOnWord, + rowHeight: rowHeight, + cellPadding: cellPadding, + offWidth: offWidth, + offColor: offColor, + onColor: onColor, + onSubmitted: (e) => match.onlyOnWord = e), + CheckboxField( + entry: match.onlyOnWord, + rowHeight: rowHeight, + cellPadding: cellPadding, + offWidth: offWidth, + offColor: offColor, + onColor: onColor, + onSubmitted: (e) => match.propagateCase = e), + CheckboxField( + entry: match.onlyOnWord, + rowHeight: rowHeight, + cellPadding: cellPadding, + offWidth: offWidth, + offColor: offColor, + onColor: onColor, + onSubmitted: (e) => match.titleCase = e), + AdvancedField( + entry: viewModel.advancedMode, + rowHeight: rowHeight, + cellPadding: cellPadding, + offWidth: offWidth, + offColor: offColor, + onColor: onColor, + onSubmitted: (e) => viewModel.advancedMode = e, + onDropdown: (e) { + viewModel.visibility = e; + }, + ), + ], + ), + Visibility( + visible: viewModel.visibility, + child: Container( + height: 200, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + border: Border.symmetric( + vertical: borderSide, + ), + ), + child: Row( + children: [ + const Expanded( + child: Column( + children: [ + Text('Triggers'), + ], + ), + ), + VerticalDivider(color: Theme.of(context).colorScheme.outline), + Expanded( + child: Column( + children: [ + const Text('Variables'), + Table( + border: TableBorder( + verticalInside: borderSide, + horizontalInside: borderSide), + children: variablesTestData) + ], + ), + ), + VerticalDivider( + color: Theme.of(context).colorScheme.outline, + thickness: 1, + ), + const Expanded( + child: Column( + children: [Text('Forms')], + ), + ), + ], + ), + ), + ) + ], + ); + } + + @override + MatchesTable2RowModel viewModelBuilder( + BuildContext context, + ) => + MatchesTable2RowModel(match: match); +} diff --git a/lib/ui/widgets/matches_table2/row/row_model.dart b/lib/ui/archive/matches_table2/row/row_model.dart similarity index 94% rename from lib/ui/widgets/matches_table2/row/row_model.dart rename to lib/ui/archive/matches_table2/row/row_model.dart index 2dee47b..e3a4a62 100644 --- a/lib/ui/widgets/matches_table2/row/row_model.dart +++ b/lib/ui/archive/matches_table2/row/row_model.dart @@ -6,6 +6,15 @@ class MatchesTable2RowModel extends BaseViewModel { final EspansoMatch match; MatchesTable2RowModel({required this.match}); + bool _visibility = false; + bool get visibility => _visibility; + set visibility(e) { + _visibility = e; + rebuildUi(); + } + + bool advancedMode = false; + bool get labelFocused => labelFocusNode.hasFocus; bool get triggerFocused => triggerFocusNode.hasFocus; bool get replaceFocused => replaceFocusNode.hasFocus; diff --git a/lib/ui/views/espanso_new/espanso_new_view.dart b/lib/ui/views/espanso_new/espanso_new_view.dart new file mode 100644 index 0000000..5748323 --- /dev/null +++ b/lib/ui/views/espanso_new/espanso_new_view.dart @@ -0,0 +1,121 @@ +import 'dart:math'; + +import 'package:espanso_gui_v2/models/espanso_match_model.dart'; +import 'package:espanso_gui_v2/ui/widgets/table_window/matches_table3/matches_table3.dart'; +import 'package:espanso_gui_v2/ui/widgets/table_window/table_window.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'espanso_new_viewmodel.dart'; + +class EspansoNewView extends StackedView { + static const EdgeInsets _margin = EdgeInsets.all(32); + static const EdgeInsets _padding = EdgeInsets.all(32); + static const BorderRadius _borderRadius = + BorderRadius.all(Radius.circular(32)); + static const Map _flex = {'left': 34, 'right': 21}; + + static Color _windowColor(context) => + Theme.of(context).colorScheme.surfaceContainer; + static Color _backgroundColor(context) => + Theme.of(context).colorScheme.surface; + + static const double borderRadius = 16; + const EspansoNewView({super.key}); + + @override + Widget builder( + BuildContext context, + EspansoNewViewModel viewModel, + Widget? child, + ) { + final double screenSize = MediaQuery.sizeOf(context).width; + const double minWidth = 1000; + final double windowWidth = max(screenSize, minWidth); + final ValueNotifier currentMatch = ValueNotifier(null); + + final List widgets = viewModel.categories + .map((e) => + MatchesTable3(file: e, onChanged: (e) => currentMatch.value = e)) + .toList(); + + Container windowContainer({required child}) { + return Container( + decoration: BoxDecoration( + color: _windowColor(context), + boxShadow: kElevationToShadow[4], + borderRadius: _borderRadius), + margin: _margin, + padding: _padding, + child: child, + ); + } + + return Scaffold( + backgroundColor: _backgroundColor(context), + appBar: AppBar( + title: TextField( + onSubmitted: (e) => viewModel.setPath(e), + decoration: const InputDecoration(hintText: 'matches path'), + )), + body: Scrollbar( + controller: viewModel.scrollController, + thumbVisibility: true, + trackVisibility: true, + child: SingleChildScrollView( + controller: viewModel.scrollController, + scrollDirection: Axis.horizontal, + child: SizedBox( + width: windowWidth, + child: Row( + children: [ + // Left table window + Expanded( + flex: _flex['left']!, + child: windowContainer( + child: TableWindow( + widgets: widgets, titles: viewModel.titles)), + ), + + // Right window + Expanded( + flex: _flex['right']!, + child: windowContainer( + child: ValueListenableBuilder( + valueListenable: currentMatch, + builder: (context, value, widget) { + return value == null + ? Container() + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + fit: FlexFit.loose, + child: Text( + value.trigger.trigger ?? '', + textAlign: TextAlign.left, + style: Theme.of(context) + .textTheme + .displayMedium, + ), + ), + const Flexible(child: Divider()) + ]); + }, + ), + ), + ), + ], + ), + ), + ), + ), + ); + } + + @override + EspansoNewViewModel viewModelBuilder( + BuildContext context, + ) => + EspansoNewViewModel(); +} diff --git a/lib/ui/views/espanso_new/espanso_new_viewmodel.dart b/lib/ui/views/espanso_new/espanso_new_viewmodel.dart new file mode 100644 index 0000000..7a06d1e --- /dev/null +++ b/lib/ui/views/espanso_new/espanso_new_viewmodel.dart @@ -0,0 +1,29 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import '../../../app/app.locator.dart'; +import '../../../services/espanso_matches_service.dart'; + +class EspansoNewViewModel extends BaseViewModel { + final EspansoMatchesService _matchesService = + locator(); + final ScrollController scrollController = ScrollController(); + Iterable? _categories; + Iterable get categories => _categories ??= _matchesService.listFiles(); + void setPath(String path) { + _matchesService.setPath(path); + _categories = _matchesService.listFiles(); + rebuildUi(); + } + + Iterable get titles => + categories.map((e) => e.path.split(r'\').last.replaceFirst('.yml', '')); + + @override + void dispose() { + super.dispose(); + scrollController.dispose(); + } +} diff --git a/lib/ui/widgets/matches_details/matches_details.dart b/lib/ui/widgets/matches_details/matches_details.dart new file mode 100644 index 0000000..a079f63 --- /dev/null +++ b/lib/ui/widgets/matches_details/matches_details.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'matches_details_model.dart'; + +class MatchesDetails extends StackedView { + const MatchesDetails({super.key}); + + @override + Widget builder( + BuildContext context, + MatchesDetailsModel viewModel, + Widget? child, + ) { + return const SizedBox.shrink(); + } + + @override + MatchesDetailsModel viewModelBuilder( + BuildContext context, + ) => + MatchesDetailsModel(); +} diff --git a/lib/ui/widgets/matches_details/matches_details_model.dart b/lib/ui/widgets/matches_details/matches_details_model.dart new file mode 100644 index 0000000..66655e4 --- /dev/null +++ b/lib/ui/widgets/matches_details/matches_details_model.dart @@ -0,0 +1,3 @@ +import 'package:stacked/stacked.dart'; + +class MatchesDetailsModel extends BaseViewModel {} diff --git a/lib/ui/widgets/matches_table2/row/row.dart b/lib/ui/widgets/matches_table2/row/row.dart deleted file mode 100644 index a3e770f..0000000 --- a/lib/ui/widgets/matches_table2/row/row.dart +++ /dev/null @@ -1,114 +0,0 @@ -import 'package:espanso_gui_v2/models/espanso_match_model.dart'; -import 'package:espanso_gui_v2/models/espanso_replace_field.dart'; -import 'package:espanso_gui_v2/ui/widgets/matches_table2/row/fields/checkbox_field/checkbox_field/checkbox_field.dart'; -import 'package:espanso_gui_v2/ui/widgets/matches_table2/row/fields/label_field/label_field/label_field.dart'; -import 'package:espanso_gui_v2/ui/widgets/matches_table2/row/fields/replace_field/replace_field.dart'; -import 'package:espanso_gui_v2/ui/widgets/matches_table2/row/fields/trigger_field/trigger_field.dart'; -import 'package:espanso_gui_v2/ui/widgets/matches_table2/row/overlays/replace_field_overlay.dart'; -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; - -import 'row_model.dart'; - -class MatchesTable2Row extends StackedView { - final EspansoMatch match; - final double? onWidth; - final double offWidth; - final double rowHeight; - final double cellPadding; - final Color onColor; - final Color offColor; - final TextStyle? textStyle; - - const MatchesTable2Row( - {super.key, - required this.match, - this.onWidth, - required this.offWidth, - required this.rowHeight, - this.cellPadding = 8, - required this.onColor, - required this.offColor, - this.textStyle}); - - @override - Widget builder( - BuildContext context, - MatchesTable2RowModel viewModel, - Widget? child, - ) { - debugPrint('ui rebuilt'); - - return Row( - children: [ - LabelField( - entry: match.label, - offWidth: offWidth, - focusWidthFactor: 2, - offColor: offColor, - onColor: onColor, - rowHeight: rowHeight, - cellPadding: cellPadding, - textStyle: textStyle, - onSubmitted: (text) => match.label = text, - ), - TriggerField( - entry: match.trigger, - offWidth: offWidth, - onWidth: onWidth, - offColor: offColor, - onColor: onColor, - rowHeight: rowHeight, - cellPadding: cellPadding, - textStyle: textStyle, - onSubmitted: (entry) => match.trigger = entry, - overlay: (controller) => null, - ), - ReplaceField( - value: match.replace, - offWidth: offWidth, - focusWidthFactor: 1, - offColor: offColor, - onColor: onColor, - rowHeight: rowHeight, - cellPadding: cellPadding, - textStyle: textStyle, - onSubmitted: (EspansoReplaceField entry) => match.replace = entry, - overlay: (controller) => ReplaceFieldOverlay( - controller: controller, - ), - ), - CheckboxField( - entry: match.onlyOnWord, - rowHeight: rowHeight, - cellPadding: cellPadding, - offWidth: offWidth, - offColor: offColor, - onColor: onColor, - onSubmitted: (e) => match.onlyOnWord = e), - CheckboxField( - entry: match.onlyOnWord, - rowHeight: rowHeight, - cellPadding: cellPadding, - offWidth: offWidth, - offColor: offColor, - onColor: onColor, - onSubmitted: (e) => match.propagateCase = e), - CheckboxField( - entry: match.onlyOnWord, - rowHeight: rowHeight, - cellPadding: cellPadding, - offWidth: offWidth, - offColor: offColor, - onColor: onColor, - onSubmitted: (e) => match.titleCase = e), - ], - ); - } - - @override - MatchesTable2RowModel viewModelBuilder( - BuildContext context, - ) => - MatchesTable2RowModel(match: match); -} diff --git a/lib/ui/widgets/table_window/matches_table3/matches_table3.dart b/lib/ui/widgets/table_window/matches_table3/matches_table3.dart new file mode 100644 index 0000000..298169d --- /dev/null +++ b/lib/ui/widgets/table_window/matches_table3/matches_table3.dart @@ -0,0 +1,115 @@ +import 'dart:io'; + +import 'package:espanso_gui_v2/models/espanso_match_model.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'matches_table3_model.dart'; + +class MatchesTable3 extends StackedView { + static const double _triggerMaxWidth = 50; + static const double _replaceMaxWidth = 100; + Function(EspansoMatch e) onChanged; + + final File file; + MatchesTable3({super.key, required this.file, required this.onChanged}); + + @override + Widget builder( + BuildContext context, + MatchesTable3Model viewModel, + Widget? child, + ) { + final TextStyle textStyle = Theme.of(context).textTheme.bodyMedium ?? + const TextStyle(fontSize: 14, color: Colors.black); + + Widget textCell( + {required text, + required Function(String value) onChanged, + double maxWidth = double.infinity}) { + final TextEditingController controller = + TextEditingController(text: text); + return TextField( + controller: controller, + onSubmitted: (value) { + text = value; + onChanged(value); + }, + style: textStyle, + decoration: InputDecoration( + border: InputBorder.none, + constraints: BoxConstraints(maxWidth: maxWidth)), + ); + } + + Widget checkboxCell( + {required bool value, required Function(bool value) onChanged}) { + return StatefulBuilder( + builder: (context, setState) { + return Center( + child: Checkbox( + value: value, + onChanged: (f) { + setState(() => f != null ? value = f : {}); + onChanged(f ?? false); + }, + ), + ); + }, + ); + } + + final List rows = viewModel.matches.map( + (e) { + return DataRow( + cells: [ + DataCell(textCell( + onChanged: (value) => + e.trigger = e.trigger.copyWith(trigger: value), + text: e.trigger.trigger)), + DataCell(textCell( + text: e.replace.text, + onChanged: (value) => e.replace = e.replace.copyWith(text: value), + )), + DataCell(checkboxCell( + value: e.onlyOnWord, + onChanged: (value) => e.onlyOnWord = value)), + DataCell(checkboxCell( + value: e.propagateCase, + onChanged: (value) => e.propagateCase = value, + )), + DataCell(checkboxCell( + value: e.titleCase, + onChanged: (value) => e.titleCase = value, + )), + ], + ); + }, + ).toList(); + + const List columns = [ + DataColumn(label: Text('trigger')), + DataColumn(label: Text('replace')), + DataColumn( + label: Text('require word'), + headingRowAlignment: MainAxisAlignment.center), + DataColumn( + label: Text('match case'), + headingRowAlignment: MainAxisAlignment.center), + DataColumn( + label: Text('title case'), + headingRowAlignment: MainAxisAlignment.center), + ]; + return DataTable( + columns: columns, + rows: rows, + columnSpacing: 24, + ); + } + + @override + MatchesTable3Model viewModelBuilder( + BuildContext context, + ) => + MatchesTable3Model(file: file); +} diff --git a/lib/ui/widgets/table_window/matches_table3/matches_table3_model.dart b/lib/ui/widgets/table_window/matches_table3/matches_table3_model.dart new file mode 100644 index 0000000..cf02cba --- /dev/null +++ b/lib/ui/widgets/table_window/matches_table3/matches_table3_model.dart @@ -0,0 +1,22 @@ +import 'dart:io'; + +import 'package:espanso_gui_v2/app/app.locator.dart'; +import 'package:stacked/stacked.dart'; + +import '../../../../models/espanso_match_model.dart'; +import '../../../../services/espanso_matches_service.dart'; + +class MatchesTable3Model extends BaseViewModel { + final EspansoMatchesService _matchesService = + locator(); + final File file; + + MatchesTable3Model({required this.file}); + + List? _matches; + List get matches => + _matches ??= _matchesService.loadMatches(file); + void save() { + _matchesService.saveMatches(matches, file); + } +} diff --git a/lib/ui/widgets/table_window/table_window.dart b/lib/ui/widgets/table_window/table_window.dart new file mode 100644 index 0000000..19a95b6 --- /dev/null +++ b/lib/ui/widgets/table_window/table_window.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:overflow_tab_bar/overflow_tab_bar.dart'; +import 'package:stacked/stacked.dart'; + +import 'table_window_model.dart'; + +class TableWindow extends StackedView { + final List widgets; + final Iterable titles; + const TableWindow({super.key, required this.widgets, required this.titles}); + + @override + Widget builder( + BuildContext context, + TableWindowModel viewModel, + Widget? child, + ) { + return DefaultTabController( + length: widgets.length, + child: Padding( + padding: const EdgeInsets.all(0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 60, + padding: const EdgeInsets.only(bottom: 15), + child: OverflowTabBar( + tabs: titles.toList(), + textStyle: Theme.of(context).textTheme.titleMedium, + ), + ), + Expanded(child: TabBarView(children: widgets)), + ], + ), + ), + ); + } + + @override + TableWindowModel viewModelBuilder( + BuildContext context, + ) => + TableWindowModel(); +} diff --git a/lib/ui/widgets/table_window/table_window_model.dart b/lib/ui/widgets/table_window/table_window_model.dart new file mode 100644 index 0000000..495b853 --- /dev/null +++ b/lib/ui/widgets/table_window/table_window_model.dart @@ -0,0 +1,3 @@ +import 'package:stacked/stacked.dart'; + +class TableWindowModel extends BaseViewModel {} diff --git a/pubspec.lock b/pubspec.lock index 62f4b56..d27eb2f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: "5aaf60d96c4cd00fe7f21594b5ad6a1b699c80a27420f8a837f4d68473ef09e3" url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "68.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.1.5" analyzer: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: "21f1d3720fd1c70316399d5e2bccaebb415c434592d778cce8acb967b8578808" url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "6.5.0" args: dependency: transitive description: @@ -69,10 +74,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" build_resolvers: dependency: transitive description: @@ -85,18 +90,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" url: "https://pub.dev" source: hosted - version: "2.4.9" + version: "2.4.11" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.3.1" built_collection: dependency: transitive description: @@ -231,18 +236,18 @@ packages: dependency: "direct dev" description: name: freezed - sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 + sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.7" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.4" frontend_server_client: dependency: transitive description: @@ -287,18 +292,18 @@ packages: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" http: dependency: transitive description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -403,6 +408,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + macros: + dependency: transitive + description: + name: macros + sha256: a8403c89b36483b4cbf9f1fcd24562f483cb34a5c9bf101cf2b0d8a083cf1239 + url: "https://pub.dev" + source: hosted + version: "0.1.0-main.5" matcher: dependency: transitive description: @@ -478,18 +491,18 @@ packages: dependency: transitive description: name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.9" path_provider_foundation: dependency: transitive description: @@ -518,18 +531,18 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" platform: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -574,10 +587,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" recase: dependency: transitive description: @@ -590,10 +603,10 @@ packages: dependency: "direct main" description: name: rich_text_controller - sha256: "2697b0313c03429ac4d924e14f16e07d16e31a234d31c46bcce9f8f8e713f05f" + sha256: "34750b3a688748d4b8224f84129c7ac3322a9301d272fdca8244dd6cbbfc4cae" url: "https://pub.dev" source: hosted - version: "1.4.2" + version: "2.0.1" rxdart: dependency: transitive description: @@ -614,10 +627,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -659,18 +672,18 @@ packages: dependency: "direct main" description: name: stacked - sha256: "32641025f7bedf3acddd068008932c5f4d89bd089f1b091f61c9fe466c66229e" + sha256: ed19ecdc2dcc682b9be9c7e34646e603c0f770437a914b15c7d2d13391c92a09 url: "https://pub.dev" source: hosted - version: "3.4.2" + version: "3.4.3" stacked_generator: dependency: "direct dev" description: name: stacked_generator - sha256: ed9fcada06d97def2fe2d9d1df620da17a7c01a6b319fe115e035c2ac1a9f2c8 + sha256: eaa6447e3fd4d4010b746629b5518364d7fa7f6453ffb6416ad449fd352d1181 url: "https://pub.dev" source: hosted - version: "1.6.0" + version: "1.6.1" stacked_services: dependency: "direct main" description: @@ -683,10 +696,10 @@ packages: dependency: transitive description: name: stacked_shared - sha256: e6bc2921eb59b7c741c551fbb4060f22a543ea9c2d9351315fb58aa055b535f3 + sha256: "26e11dcfe23df81d565d0180eb5bcf4742efed066ba3328623b458f21a82b346" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" stream_channel: dependency: transitive description: @@ -779,26 +792,26 @@ packages: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "0.5.1" - web_socket_channel: + version: "1.0.0" + web_socket: dependency: transitive description: - name: web_socket_channel - sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" url: "https://pub.dev" source: hosted - version: "2.4.5" - win32: + version: "0.1.6" + web_socket_channel: dependency: transitive description: - name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + name: web_socket_channel + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "3.0.1" xdg_directories: dependency: transitive description: @@ -824,5 +837,5 @@ packages: source: hosted version: "2.0.0" sdks: - dart: ">=3.4.0-282.3.beta <4.0.0" - flutter: ">=3.19.2" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 36eaaa7..0d18e59 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: google_fonts: ^6.2.1 json_annotation: ^4.9.0 pluto_grid: ^8.0.0 - rich_text_controller: ^1.4.2 + rich_text_controller: ^2.0.0 stacked: ^3.4.0 stacked_services: ^1.1.0 yaml: ^3.1.2 diff --git a/test/helpers/test_helpers.mocks.dart b/test/helpers/test_helpers.mocks.dart index 77a0825..380d54a 100644 --- a/test/helpers/test_helpers.mocks.dart +++ b/test/helpers/test_helpers.mocks.dart @@ -697,6 +697,15 @@ class MockDialogService extends _i1.Mock implements _i3.DialogService { class MockEspansoMatchesService extends _i1.Mock implements _i7.EspansoMatchesService { @override + void setPath(dynamic path) => super.noSuchMethod( + Invocation.method( + #setPath, + [path], + ), + returnValueForMissingStub: null, + ); + + @override Map> loadAllMatches() => (super.noSuchMethod( Invocation.method( #loadAllMatches, diff --git a/test/viewmodels/espanso_new_viewmodel_test.dart b/test/viewmodels/espanso_new_viewmodel_test.dart new file mode 100644 index 0000000..5dfa8d9 --- /dev/null +++ b/test/viewmodels/espanso_new_viewmodel_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:espanso_gui_v2/app/app.locator.dart'; + +import '../helpers/test_helpers.dart'; + +void main() { + group('EspansoNewViewModel Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/test/widget_models/advanced_field_model_test.dart b/test/widget_models/advanced_field_model_test.dart new file mode 100644 index 0000000..9967f96 --- /dev/null +++ b/test/widget_models/advanced_field_model_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:espanso_gui_v2/app/app.locator.dart'; + +import '../helpers/test_helpers.dart'; + +void main() { + group('AdvancedFieldModel Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/test/widget_models/matches_details_model_test.dart b/test/widget_models/matches_details_model_test.dart new file mode 100644 index 0000000..e3bd412 --- /dev/null +++ b/test/widget_models/matches_details_model_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:espanso_gui_v2/app/app.locator.dart'; + +import '../helpers/test_helpers.dart'; + +void main() { + group('MatchesDetailsModel Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/test/widget_models/matches_table3_model_test.dart b/test/widget_models/matches_table3_model_test.dart new file mode 100644 index 0000000..543dbb4 --- /dev/null +++ b/test/widget_models/matches_table3_model_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:espanso_gui_v2/app/app.locator.dart'; + +import '../helpers/test_helpers.dart'; + +void main() { + group('MatchesTable3Model Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/test/widget_models/table_window_model_test.dart b/test/widget_models/table_window_model_test.dart new file mode 100644 index 0000000..8c5805d --- /dev/null +++ b/test/widget_models/table_window_model_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:espanso_gui_v2/app/app.locator.dart'; + +import '../helpers/test_helpers.dart'; + +void main() { + group('TableWindowModel Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +}