From 1b7ac31085d4ad6117c4a28e28e150a0bfe114ea Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Fri, 2 Aug 2024 23:18:39 -0500 Subject: [PATCH 01/93] dialog cleanup --- assets/i18n/en.json | 1 + .../views/edit_account_page.dart | 12 +- .../views/new_account_page.dart | 6 +- lib/layout/home/home_screen.dart | 24 +- lib/theme/views/styled_alert.dart | 276 ++++++++++++++++++ lib/theme/views/views.dart | 1 + lib/theme/views/widget_helpers.dart | 14 - lib/veilid_processor/views/developer.dart | 30 +- .../views/signal_strength_meter.dart | 8 +- pubspec.lock | 18 +- pubspec.yaml | 2 +- 11 files changed, 320 insertions(+), 72 deletions(-) create mode 100644 lib/theme/views/styled_alert.dart diff --git a/assets/i18n/en.json b/assets/i18n/en.json index b2e6907..cb95eaf 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -94,6 +94,7 @@ "waiting_for_network": "Waiting For Network" }, "toast": { + "confirm": "Confirm", "error": "Error", "info": "Info" }, diff --git a/lib/account_manager/views/edit_account_page.dart b/lib/account_manager/views/edit_account_page.dart index e49b48a..eaa0e37 100644 --- a/lib/account_manager/views/edit_account_page.dart +++ b/lib/account_manager/views/edit_account_page.dart @@ -137,10 +137,10 @@ class _EditAccountPageState extends WindowSetupState { }); } } - } on Exception catch (e) { + } on Exception catch (e, st) { if (mounted) { - await showErrorModal( - context, translate('new_account_page.error'), 'Exception: $e'); + await showErrorStacktraceModal( + context: context, error: e, stackTrace: st); } } } @@ -205,10 +205,10 @@ class _EditAccountPageState extends WindowSetupState { }); } } - } on Exception catch (e) { + } on Exception catch (e, st) { if (mounted) { - await showErrorModal( - context, translate('new_account_page.error'), 'Exception: $e'); + await showErrorStacktraceModal( + context: context, error: e, stackTrace: st); } } } diff --git a/lib/account_manager/views/new_account_page.dart b/lib/account_manager/views/new_account_page.dart index ee2f62c..69c75ae 100644 --- a/lib/account_manager/views/new_account_page.dart +++ b/lib/account_manager/views/new_account_page.dart @@ -102,10 +102,10 @@ class _NewAccountPageState extends WindowSetupState { }); } } - } on Exception catch (e) { + } on Exception catch (e, st) { if (mounted) { - await showErrorModal( - context, translate('new_account_page.error'), 'Exception: $e'); + await showErrorStacktraceModal( + context: context, error: e, stackTrace: st); } } } diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index 893ca97..a90e110 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart'; import 'package:provider/provider.dart'; -import 'package:quickalert/quickalert.dart'; import 'package:transitioned_indexed_stack/transitioned_indexed_stack.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:veilid_support/veilid_support.dart'; @@ -63,25 +62,26 @@ class HomeScreenState extends State Future _doBetaDialog(BuildContext context) async { var displayBetaWarning = true; + final theme = Theme.of(context); + final scale = theme.extension()!; - await QuickAlert.show( + await showWarningWidgetModal( context: context, title: translate('splash.beta_title'), - widget: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ RichText( textAlign: TextAlign.center, text: TextSpan( children: [ TextSpan( text: translate('splash.beta_text'), - style: const TextStyle( - color: Colors.black87, - ), + style: theme.textTheme.bodyMedium! + .copyWith(color: scale.primaryScale.appText), ), TextSpan( text: 'https://veilid.com/chat/beta', - style: const TextStyle( - color: Colors.blue, + style: theme.textTheme.bodyMedium!.copyWith( + color: scale.primaryScale.primary, decoration: TextDecoration.underline, ), recognizer: TapGestureRecognizer() @@ -101,11 +101,13 @@ class HomeScreenState extends State }); }, )), - Text(translate('settings_page.display_beta_warning'), - style: const TextStyle(color: Colors.black)), + Text( + translate('settings_page.display_beta_warning'), + style: theme.textTheme.bodyMedium! + .copyWith(color: scale.primaryScale.appText), + ), ]), ]), - type: QuickAlertType.warning, ); final preferencesInstance = PreferencesRepository.instance; diff --git a/lib/theme/views/styled_alert.dart b/lib/theme/views/styled_alert.dart new file mode 100644 index 0000000..2104fa1 --- /dev/null +++ b/lib/theme/views/styled_alert.dart @@ -0,0 +1,276 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; +import 'package:rflutter_alert/rflutter_alert.dart'; + +import '../theme.dart'; + +AlertStyle _alertStyle(BuildContext context) { + final theme = Theme.of(context); + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + + return AlertStyle( + animationType: AnimationType.grow, + //animationDuration: const Duration(milliseconds: 200), + alertBorder: RoundedRectangleBorder( + side: !scaleConfig.useVisualIndicators + ? BorderSide.none + : BorderSide( + strokeAlign: BorderSide.strokeAlignCenter, + color: scale.primaryScale.border, + width: 2), + borderRadius: BorderRadius.all( + Radius.circular(12 * scaleConfig.borderRadiusScale))), + // isButtonVisible: true, + // isCloseButton: true, + // isOverlayTapDismiss: true, + backgroundColor: scale.primaryScale.subtleBackground, + // overlayColor: Colors.black87, + titleStyle: theme.textTheme.titleMedium! + .copyWith(color: scale.primaryScale.appText), + // titleTextAlign: TextAlign.center, + descStyle: + theme.textTheme.bodyMedium!.copyWith(color: scale.primaryScale.appText), + // descTextAlign: TextAlign.center, + // buttonAreaPadding: const EdgeInsets.all(20.0), + // constraints: null, + // buttonsDirection: ButtonsDirection.row, + // alertElevation: null, + // alertPadding: defaultAlertPadding, + // alertAlignment: Alignment.center, + // isTitleSelectable: false, + // isDescSelectable: false, + // titlePadding: null, + //descPadding: const EdgeInsets.all(0.0), + ); +} + +Color _buttonColor(BuildContext context, bool highlight) { + final theme = Theme.of(context); + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + + if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) { + return scale.secondaryScale.border; + } + + return highlight + ? scale.secondaryScale.elementBackground + : scale.secondaryScale.hoverElementBackground; +} + +TextStyle _buttonTextStyle(BuildContext context) { + final theme = Theme.of(context); + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + + if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) { + return theme.textTheme.bodyMedium! + .copyWith(color: scale.secondaryScale.borderText); + } + + return theme.textTheme.bodyMedium! + .copyWith(color: scale.secondaryScale.appText); +} + +BoxBorder _buttonBorder(BuildContext context) { + final theme = Theme.of(context); + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + + return Border.fromBorderSide(BorderSide( + color: scale.secondaryScale.border, + width: scaleConfig.preferBorders ? 2 : 0)); +} + +BorderRadius _buttonRadius(BuildContext context) { + final theme = Theme.of(context); + final scaleConfig = theme.extension()!; + + return BorderRadius.circular(8 * scaleConfig.borderRadiusScale); +} + +Future showErrorModal( + {required BuildContext context, + required String title, + required String text}) async { + final theme = Theme.of(context); + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + + await Alert( + context: context, + style: _alertStyle(context), + useRootNavigator: false, + type: AlertType.error, + //style: AlertStyle(), + title: title, + desc: text, + buttons: [ + DialogButton( + color: _buttonColor(context, false), + highlightColor: _buttonColor(context, true), + border: _buttonBorder(context), + radius: _buttonRadius(context), + width: 120, + onPressed: () { + Navigator.pop(context); + }, + child: Text( + translate('button.ok'), + style: _buttonTextStyle(context), + ), + ) + ], + + //backgroundColor: Colors.black, + //titleColor: Colors.white, + //textColor: Colors.white, + ).show(); +} + +Future showErrorStacktraceModal( + {required BuildContext context, + required Object error, + StackTrace? stackTrace}) async { + await showErrorModal( + context: context, + title: translate('toast.error'), + text: 'Error: {e}\n StackTrace: {st}', + ); +} + +Future showWarningModal( + {required BuildContext context, + required String title, + required String text}) async { + final theme = Theme.of(context); + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + + await Alert( + context: context, + style: _alertStyle(context), + useRootNavigator: false, + type: AlertType.warning, + //style: AlertStyle(), + title: title, + desc: text, + buttons: [ + DialogButton( + color: _buttonColor(context, false), + highlightColor: _buttonColor(context, true), + border: _buttonBorder(context), + radius: _buttonRadius(context), + width: 120, + onPressed: () { + Navigator.pop(context); + }, + child: Text( + translate('button.ok'), + style: _buttonTextStyle(context), + ), + ) + ], + + //backgroundColor: Colors.black, + //titleColor: Colors.white, + //textColor: Colors.white, + ).show(); +} + +Future showWarningWidgetModal( + {required BuildContext context, + required String title, + required Widget child}) async { + final theme = Theme.of(context); + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + + await Alert( + context: context, + style: _alertStyle(context), + useRootNavigator: false, + type: AlertType.warning, + //style: AlertStyle(), + title: title, + content: child, + buttons: [ + DialogButton( + color: _buttonColor(context, false), + highlightColor: _buttonColor(context, true), + border: _buttonBorder(context), + radius: _buttonRadius(context), + width: 120, + onPressed: () { + Navigator.pop(context); + }, + child: Text( + translate('button.ok'), + style: _buttonTextStyle(context), + ), + ) + ], + + //backgroundColor: Colors.black, + //titleColor: Colors.white, + //textColor: Colors.white, + ).show(); +} + +Future showConfirmModal( + {required BuildContext context, + required String title, + required String text}) async { + final theme = Theme.of(context); + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + + var confirm = false; + + await Alert( + context: context, + style: _alertStyle(context), + useRootNavigator: false, + type: AlertType.none, + title: title, + desc: text, + buttons: [ + DialogButton( + color: _buttonColor(context, false), + highlightColor: _buttonColor(context, true), + border: _buttonBorder(context), + radius: _buttonRadius(context), + width: 120, + onPressed: () { + Navigator.pop(context); + }, + child: Text( + translate('button.no_cancel'), + style: _buttonTextStyle(context), + ), + ), + DialogButton( + color: _buttonColor(context, false), + highlightColor: _buttonColor(context, true), + border: _buttonBorder(context), + radius: _buttonRadius(context), + width: 120, + onPressed: () { + confirm = true; + Navigator.pop(context); + }, + child: Text( + translate('button.yes_proceed'), + style: _buttonTextStyle(context), + ), + ) + ], + + //backgroundColor: Colors.black, + //titleColor: Colors.white, + //textColor: Colors.white, + ).show(); + + return confirm; +} diff --git a/lib/theme/views/views.dart b/lib/theme/views/views.dart index b81f184..e8aa1d8 100644 --- a/lib/theme/views/views.dart +++ b/lib/theme/views/views.dart @@ -8,6 +8,7 @@ export 'pop_control.dart'; export 'recovery_key_widget.dart'; export 'responsive.dart'; export 'scanner_error_widget.dart'; +export 'styled_alert.dart'; export 'styled_dialog.dart'; export 'styled_scaffold.dart'; export 'widget_helpers.dart'; diff --git a/lib/theme/views/widget_helpers.dart b/lib/theme/views/widget_helpers.dart index b079d2b..51cb7b6 100644 --- a/lib/theme/views/widget_helpers.dart +++ b/lib/theme/views/widget_helpers.dart @@ -8,7 +8,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:flutter_sticky_header/flutter_sticky_header.dart'; -import 'package:quickalert/quickalert.dart'; import 'package:sliver_expandable/sliver_expandable.dart'; import '../theme.dart'; @@ -196,19 +195,6 @@ class AsyncBlocBuilder>, S> data: (d) => builder(context, d))); } -Future showErrorModal( - BuildContext context, String title, String text) async { - await QuickAlert.show( - context: context, - type: QuickAlertType.error, - title: title, - text: text, - //backgroundColor: Colors.black, - //titleColor: Colors.white, - //textColor: Colors.white, - ); -} - SliverAppBar styledSliverAppBar( {required BuildContext context, required String title, Color? titleColor}) { final theme = Theme.of(context); diff --git a/lib/veilid_processor/views/developer.dart b/lib/veilid_processor/views/developer.dart index f4fe836..549d228 100644 --- a/lib/veilid_processor/views/developer.dart +++ b/lib/veilid_processor/views/developer.dart @@ -12,7 +12,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:go_router/go_router.dart'; import 'package:loggy/loggy.dart'; -import 'package:quickalert/quickalert.dart'; import 'package:veilid_support/veilid_support.dart'; import 'package:xterm/xterm.dart'; @@ -208,27 +207,14 @@ class _DeveloperPageState extends State { color: scale.primaryScale.primaryText, disabledColor: scale.primaryScale.primaryText.withAlpha(0x3F), onPressed: () async { - await QuickAlert.show( - context: context, - type: QuickAlertType.confirm, - title: translate('developer.are_you_sure_clear'), - titleColor: scale.primaryScale.appText, - textColor: scale.primaryScale.subtleText, - confirmBtnColor: scale.primaryScale.primary, - cancelBtnTextStyle: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 18, - color: scale.primaryScale.appText), - backgroundColor: scale.primaryScale.appBackground, - headerBackgroundColor: scale.primaryScale.primary, - confirmBtnText: translate('button.ok'), - cancelBtnText: translate('button.cancel'), - onConfirmBtnTap: () async { - Navigator.pop(context); - if (context.mounted) { - await clear(context); - } - }); + final confirm = await showConfirmModal( + context: context, + title: translate('toast.confirm'), + text: translate('developer.are_you_sure_clear'), + ); + if (confirm && context.mounted) { + await clear(context); + } }), CoolDropdown( controller: _logLevelController, diff --git a/lib/veilid_processor/views/signal_strength_meter.dart b/lib/veilid_processor/views/signal_strength_meter.dart index 73842f1..74230ed 100644 --- a/lib/veilid_processor/views/signal_strength_meter.dart +++ b/lib/veilid_processor/views/signal_strength_meter.dart @@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; -import 'package:quickalert/quickalert.dart'; import 'package:signal_strength_indicator/signal_strength_indicator.dart'; import 'package:veilid_support/veilid_support.dart'; @@ -75,11 +74,8 @@ class SignalStrengthMeterWidget extends StatelessWidget { loading: () => {iconWidget = const Icon(Icons.warning)}, error: (e, st) => { iconWidget = const Icon(Icons.error).onTap( - () async => QuickAlert.show( - type: QuickAlertType.error, - context: context, - title: 'Error', - text: 'Error: {e}\n StackTrace: {st}'), + () async => showErrorStacktraceModal( + context: context, error: e, stackTrace: st), ) }); diff --git a/pubspec.lock b/pubspec.lock index 2e8947c..d4f1ce7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1193,14 +1193,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" - quickalert: - dependency: "direct main" - description: - name: quickalert - sha256: b5d62b1e20b08cc0ff5f40b6da519bdc7a5de6082f13d90572cf4e72eea56c5e - url: "https://pub.dev" - source: hosted - version: "1.1.0" quiver: dependency: transitive description: @@ -1225,6 +1217,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.10" + rflutter_alert: + dependency: "direct main" + description: + name: rflutter_alert + sha256: "8ff35e3f9712ba24c746499cfa95bf320385edf38901a1a4eab0fe555867f66c" + url: "https://pub.dev" + source: hosted + version: "2.0.7" rxdart: dependency: transitive description: @@ -1702,7 +1702,7 @@ packages: path: "../veilid/veilid-flutter" relative: true source: path - version: "0.3.3" + version: "0.3.4" veilid_support: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 653396f..fe464ea 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,9 +76,9 @@ dependencies: provider: ^6.1.2 qr_code_dart_scan: ^0.8.0 qr_flutter: ^4.1.0 - quickalert: ^1.1.0 radix_colors: ^1.0.4 reorderable_grid: ^1.0.10 + rflutter_alert: ^2.0.7 screenshot: ^3.0.0 scroll_to_index: ^3.0.1 searchable_listview: From cbac96de99703d014e6b5046b8d2cfd40fc8d5c5 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 3 Aug 2024 11:50:33 -0500 Subject: [PATCH 02/93] exception handling work --- .../cubits/account_record_cubit.dart | 2 +- .../waiting_invitations_bloc_map_cubit.dart | 2 +- .../cubits/conversation_cubit.dart | 2 +- .../home/drawer_menu/menu_item_widget.dart | 2 +- lib/theme/views/avatar_widget.dart | 2 +- lib/theme/views/widget_helpers.dart | 2 +- .../dht_record/default_dht_record_cubit.dart | 9 +++++--- .../src/dht_record/dht_record_cubit.dart | 22 +++++++++---------- .../src/dht_record/dht_record_pool.dart | 2 +- 9 files changed, 24 insertions(+), 21 deletions(-) diff --git a/lib/account_manager/cubits/account_record_cubit.dart b/lib/account_manager/cubits/account_record_cubit.dart index 2a3d9e2..ee8ec89 100644 --- a/lib/account_manager/cubits/account_record_cubit.dart +++ b/lib/account_manager/cubits/account_record_cubit.dart @@ -56,7 +56,7 @@ class AccountRecordCubit extends DefaultDHTRecordCubit { Future _updateAccountAsync( AccountSpec accountSpec, Future Function() onSuccess) async { var changed = false; - await record.eventualUpdateProtobuf(proto.Account.fromBuffer, (old) async { + await record?.eventualUpdateProtobuf(proto.Account.fromBuffer, (old) async { changed = false; if (old == null) { return null; diff --git a/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart b/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart index 97e1c76..3787205 100644 --- a/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart +++ b/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart @@ -41,7 +41,7 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit close() async { - await _singleInvitationStatusProcessor.unfollow(); + await _singleInvitationStatusProcessor.close(); await super.close(); } diff --git a/lib/conversation/cubits/conversation_cubit.dart b/lib/conversation/cubits/conversation_cubit.dart index 11ba12f..b42d3a6 100644 --- a/lib/conversation/cubits/conversation_cubit.dart +++ b/lib/conversation/cubits/conversation_cubit.dart @@ -209,7 +209,7 @@ class ConversationCubit extends Cubit> { return; } serialFuture((this, _sfUpdateAccountChange), () async { - await cubit.record.eventualUpdateProtobuf(proto.Conversation.fromBuffer, + await cubit.record?.eventualUpdateProtobuf(proto.Conversation.fromBuffer, (old) async { if (old == null || old.profile == account.profile) { return null; diff --git a/lib/layout/home/drawer_menu/menu_item_widget.dart b/lib/layout/home/drawer_menu/menu_item_widget.dart index e538b15..8529411 100644 --- a/lib/layout/home/drawer_menu/menu_item_widget.dart +++ b/lib/layout/home/drawer_menu/menu_item_widget.dart @@ -80,7 +80,7 @@ class MenuItemWidget extends StatelessWidget { ), onPressed: footerCallback), ], - ), + ).paddingAll(2), ); @override diff --git a/lib/theme/views/avatar_widget.dart b/lib/theme/views/avatar_widget.dart index 43d351b..7a1f610 100644 --- a/lib/theme/views/avatar_widget.dart +++ b/lib/theme/views/avatar_widget.dart @@ -39,7 +39,7 @@ class AvatarWidget extends StatelessWidget { width: _size, decoration: BoxDecoration( shape: BoxShape.circle, - border: _scaleConfig.preferBorders + border: _scaleConfig.useVisualIndicators ? Border.all( color: _borderColor, width: 1 * (_size ~/ 32 + 1), diff --git a/lib/theme/views/widget_helpers.dart b/lib/theme/views/widget_helpers.dart index 51cb7b6..131ec8c 100644 --- a/lib/theme/views/widget_helpers.dart +++ b/lib/theme/views/widget_helpers.dart @@ -116,7 +116,7 @@ Widget buildProgressIndicator() => Builder(builder: (context) { return FittedBox( fit: BoxFit.scaleDown, child: SpinKitFoldingCube( - color: scale.tertiaryScale.primary, + color: scale.tertiaryScale.border, size: 80, )); }); diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/default_dht_record_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_record/default_dht_record_cubit.dart index 5ea6761..e5fb513 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/default_dht_record_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/default_dht_record_cubit.dart @@ -52,8 +52,11 @@ class DefaultDHTRecordCubit extends DHTRecordCubit { Future refreshDefault() async { await initWait(); - - final defaultSubkey = record.subkeyOrDefault(-1); - await refresh([ValueSubkeyRange(low: defaultSubkey, high: defaultSubkey)]); + final rec = record; + if (rec != null) { + final defaultSubkey = rec.subkeyOrDefault(-1); + await refresh( + [ValueSubkeyRange(low: defaultSubkey, high: defaultSubkey)]); + } } } diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart index ac33716..6d42753 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart @@ -26,7 +26,7 @@ abstract class DHTRecordCubit extends Cubit> { // Do record open/create while (!cancel.isCompleted) { try { - _record = await open(); + record = await open(); _wantsCloseRecord = true; break; } on DHTExceptionNotAvailable { @@ -49,7 +49,7 @@ abstract class DHTRecordCubit extends Cubit> { ) async { // Make initial state update try { - final initialState = await initialStateFunction(_record); + final initialState = await initialStateFunction(record!); if (initialState != null) { emit(AsyncValue.data(initialState)); } @@ -57,7 +57,7 @@ abstract class DHTRecordCubit extends Cubit> { emit(AsyncValue.error(e)); } - _subscription = await _record.listen((record, data, subkeys) async { + _subscription = await record!.listen((record, data, subkeys) async { try { final newState = await stateFunction(record, subkeys, data); if (newState != null) { @@ -68,17 +68,17 @@ abstract class DHTRecordCubit extends Cubit> { } }); - await watchFunction(_record); + await watchFunction(record!); } @override Future close() async { await initWait(cancelValue: true); - await _record.cancelWatch(); + await record?.cancelWatch(); await _subscription?.cancel(); _subscription = null; if (_wantsCloseRecord) { - await _record.close(); + await record?.close(); _wantsCloseRecord = false; } await super.close(); @@ -91,10 +91,10 @@ abstract class DHTRecordCubit extends Cubit> { for (final skr in subkeys) { for (var sk = skr.low; sk <= skr.high; sk++) { - final data = await _record.get( - subkey: sk, refreshMode: DHTRecordRefreshMode.update); + final data = await record! + .get(subkey: sk, refreshMode: DHTRecordRefreshMode.update); if (data != null) { - final newState = await _stateFunction(_record, updateSubkeys, data); + final newState = await _stateFunction(record!, updateSubkeys, data); if (newState != null) { // Emit the new state emit(AsyncValue.data(newState)); @@ -108,13 +108,13 @@ abstract class DHTRecordCubit extends Cubit> { } } - DHTRecord get record => _record; + // DHTRecord get record => _record; @protected final WaitSet initWait = WaitSet(); StreamSubscription? _subscription; - late DHTRecord _record; + DHTRecord? record; bool _wantsCloseRecord; final StateFunction _stateFunction; } diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart index 68ea53d..3cfa784 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart @@ -849,7 +849,7 @@ class DHTRecordPool with TableDBBackedJson { openedRecordInfo.shared.needsWatchStateUpdate = false; } } on VeilidAPIException catch (e) { - // Failed to cancel DHT watch, try again next tick + // Failed to update DHT watch, try again next tick log('Exception in watch update: $e'); } } From ab9838f375a1f09141e95bd8e7329f493238da03 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 3 Aug 2024 15:53:11 -0500 Subject: [PATCH 03/93] cancellable waitingpage --- lib/chat/views/chat_component_widget.dart | 27 ++++++++++++++--------- lib/layout/home/home_account_ready.dart | 10 +++++++-- lib/theme/views/widget_helpers.dart | 16 +++++++++++--- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/lib/chat/views/chat_component_widget.dart b/lib/chat/views/chat_component_widget.dart index 5de02a7..3349f7f 100644 --- a/lib/chat/views/chat_component_widget.dart +++ b/lib/chat/views/chat_component_widget.dart @@ -20,9 +20,14 @@ import '../chat.dart'; const onEndReachedThreshold = 0.75; class ChatComponentWidget extends StatelessWidget { - const ChatComponentWidget( - {required super.key, required TypedKey localConversationRecordKey}) - : _localConversationRecordKey = localConversationRecordKey; + const ChatComponentWidget({ + required super.key, + required TypedKey localConversationRecordKey, + required void Function() onCancel, + required void Function() onClose, + }) : _localConversationRecordKey = localConversationRecordKey, + _onCancel = onCancel, + _onClose = onClose; ///////////////////////////////////////////////////////////////////// @@ -48,7 +53,7 @@ class ChatComponentWidget extends StatelessWidget { (x) => x.tryOperateSync(_localConversationRecordKey, closure: (cubit) => cubit)); if (activeConversationCubit == null) { - return waitingPage(); + return waitingPage(onCancel: _onCancel); } // Get the messages cubit @@ -57,7 +62,7 @@ class ChatComponentWidget extends StatelessWidget { (x) => x.tryOperateSync(_localConversationRecordKey, closure: (cubit) => cubit)); if (messagesCubit == null) { - return waitingPage(); + return waitingPage(onCancel: _onCancel); } // Make chat component state @@ -97,7 +102,7 @@ class ChatComponentWidget extends StatelessWidget { final localUser = chatComponentState.localUser; if (localUser == null) { - return waitingPage(); + return const EmptyChatWidget(); } final messageWindow = chatComponentState.messageWindow.asData?.value; @@ -135,10 +140,10 @@ class ChatComponentWidget extends StatelessWidget { )), const Spacer(), IconButton( - icon: Icon(Icons.close, color: scale.primaryScale.borderText), - onPressed: () async { - context.read().setActiveChat(null); - }).paddingLTRB(16, 0, 16, 0) + icon: + Icon(Icons.close, color: scale.primaryScale.borderText), + onPressed: _onClose) + .paddingLTRB(16, 0, 16, 0) ]), ), DecoratedBox( @@ -339,4 +344,6 @@ class ChatComponentWidget extends StatelessWidget { //////////////////////////////////////////////////////////////////////////// final TypedKey _localConversationRecordKey; + final void Function() _onCancel; + final void Function() _onClose; } diff --git a/lib/layout/home/home_account_ready.dart b/lib/layout/home/home_account_ready.dart index 5a6ab2b..7b67c0f 100644 --- a/lib/layout/home/home_account_ready.dart +++ b/lib/layout/home/home_account_ready.dart @@ -122,13 +122,19 @@ class _HomeAccountReadyState extends State { Material(color: Colors.transparent, child: buildUserPanel())); Widget buildRightPane(BuildContext context) { - final activeChatLocalConversationKey = - context.watch().state; + final activeChatCubit = context.watch(); + final activeChatLocalConversationKey = activeChatCubit.state; if (activeChatLocalConversationKey == null) { return const NoConversationWidget(); } return ChatComponentWidget( localConversationRecordKey: activeChatLocalConversationKey, + onCancel: () { + activeChatCubit.setActiveChat(null); + }, + onClose: () { + activeChatCubit.setActiveChat(null); + }, key: ValueKey(activeChatLocalConversationKey)); } diff --git a/lib/theme/views/widget_helpers.dart b/lib/theme/views/widget_helpers.dart index 131ec8c..8404e72 100644 --- a/lib/theme/views/widget_helpers.dart +++ b/lib/theme/views/widget_helpers.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:flutter_sticky_header/flutter_sticky_header.dart'; +import 'package:flutter_translate/flutter_translate.dart'; import 'package:sliver_expandable/sliver_expandable.dart'; import '../theme.dart'; @@ -121,7 +122,8 @@ Widget buildProgressIndicator() => Builder(builder: (context) { )); }); -Widget waitingPage({String? text}) => Builder(builder: (context) { +Widget waitingPage({String? text, void Function()? onCancel}) => + Builder(builder: (context) { final theme = Theme.of(context); final scale = theme.extension()!; return ColoredBox( @@ -130,12 +132,20 @@ Widget waitingPage({String? text}) => Builder(builder: (context) { crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.center, children: [ - buildProgressIndicator(), + buildProgressIndicator().paddingAll(24), if (text != null) Text(text, textAlign: TextAlign.center, style: theme.textTheme.bodySmall! - .copyWith(color: scale.tertiaryScale.appText)) + .copyWith(color: scale.tertiaryScale.appText)), + if (onCancel != null) + ElevatedButton( + onPressed: onCancel, + child: Text(translate('button.cancel'), + textAlign: TextAlign.center, + style: theme.textTheme.bodySmall!.copyWith( + color: scale.tertiaryScale.appText))) + .alignAtCenter(), ])); }); From 83880d79ba8f462e964446984a96e1718cf5e64b Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 3 Aug 2024 19:12:25 -0500 Subject: [PATCH 04/93] fix DHTRecordCubit open() retry bug improve error state reporting for cubits --- lib/chat/cubits/chat_component_cubit.dart | 1 + lib/chat/cubits/single_contact_messages_cubit.dart | 1 + lib/chat/views/empty_chat_widget.dart | 1 - .../active_single_contact_chat_bloc_map_cubit.dart | 1 + .../lib/dht_support/src/dht_log/dht_log_cubit.dart | 1 + .../dht_support/src/dht_record/dht_record_cubit.dart | 11 +++++++---- .../dht_support/src/dht_record/dht_record_pool.dart | 4 +++- .../src/dht_short_array/dht_short_array_cubit.dart | 6 ++++-- .../lib/src/async_table_db_backed_cubit.dart | 10 ++++++---- .../lib/src/table_db_array_protobuf_cubit.dart | 2 ++ packages/veilid_support/pubspec.lock | 6 +++--- packages/veilid_support/pubspec.yaml | 8 ++++---- pubspec.lock | 4 ++-- pubspec.yaml | 6 +++--- 14 files changed, 38 insertions(+), 24 deletions(-) diff --git a/lib/chat/cubits/chat_component_cubit.dart b/lib/chat/cubits/chat_component_cubit.dart index 2f2a2d0..17a481f 100644 --- a/lib/chat/cubits/chat_component_cubit.dart +++ b/lib/chat/cubits/chat_component_cubit.dart @@ -358,6 +358,7 @@ class ChatComponentCubit extends Cubit { final asError = avMessagesState.asError; if (asError != null) { + addError(asError.error, asError.stackTrace); return currentState.copyWith( unknownUsers: const IMap.empty(), messageWindow: AsyncValue.error(asError.error, asError.stackTrace)); diff --git a/lib/chat/cubits/single_contact_messages_cubit.dart b/lib/chat/cubits/single_contact_messages_cubit.dart index 97e474f..cd2d7e5 100644 --- a/lib/chat/cubits/single_contact_messages_cubit.dart +++ b/lib/chat/cubits/single_contact_messages_cubit.dart @@ -180,6 +180,7 @@ class SingleContactMessagesCubit extends Cubit { _reconciliation = MessageReconciliation( output: _reconciledMessagesCubit!, onError: (e, st) { + addError(e, st); emit(AsyncValue.error(e, st)); }); diff --git a/lib/chat/views/empty_chat_widget.dart b/lib/chat/views/empty_chat_widget.dart index c975722..e441a46 100644 --- a/lib/chat/views/empty_chat_widget.dart +++ b/lib/chat/views/empty_chat_widget.dart @@ -7,7 +7,6 @@ class EmptyChatWidget extends StatelessWidget { const EmptyChatWidget({super.key}); @override - // ignore: prefer_expression_function_bodies Widget build( BuildContext context, ) { diff --git a/lib/conversation/cubits/active_single_contact_chat_bloc_map_cubit.dart b/lib/conversation/cubits/active_single_contact_chat_bloc_map_cubit.dart index 02202ed..f3dadcb 100644 --- a/lib/conversation/cubits/active_single_contact_chat_bloc_map_cubit.dart +++ b/lib/conversation/cubits/active_single_contact_chat_bloc_map_cubit.dart @@ -105,6 +105,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit extends Cubit> } } } on Exception catch (e, st) { + addError(e, st); emit(DHTLogBusyState(AsyncValue.error(e, st))); return; } diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart index 6d42753..cab5a77 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart @@ -35,6 +35,7 @@ abstract class DHTRecordCubit extends Cubit> { } } } on Exception catch (e, st) { + addError(e, st); emit(AsyncValue.error(e, st)); return; } @@ -53,8 +54,9 @@ abstract class DHTRecordCubit extends Cubit> { if (initialState != null) { emit(AsyncValue.data(initialState)); } - } on Exception catch (e) { - emit(AsyncValue.error(e)); + } on Exception catch (e, st) { + addError(e, st); + emit(AsyncValue.error(e, st)); } _subscription = await record!.listen((record, data, subkeys) async { @@ -63,8 +65,9 @@ abstract class DHTRecordCubit extends Cubit> { if (newState != null) { emit(AsyncValue.data(newState)); } - } on Exception catch (e) { - emit(AsyncValue.error(e)); + } on Exception catch (e, st) { + addError(e, st); + emit(AsyncValue.error(e, st)); } }); diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart index 3cfa784..f1ce324 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart @@ -402,11 +402,13 @@ class DHTRecordPool with TableDBBackedJson { recordDescriptor = await dhtctx.openDHTRecord(recordKey, writer: writer); break; + } on VeilidAPIExceptionTryAgain { + throw const DHTExceptionNotAvailable(); } on VeilidAPIExceptionKeyNotFound { await asyncSleep(); retry--; if (retry == 0) { - throw DHTExceptionNotAvailable(); + throw const DHTExceptionNotAvailable(); } } } diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart index 30309a3..8bdda7c 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart @@ -46,6 +46,7 @@ class DHTShortArrayCubit extends Cubit> } } } on Exception catch (e, st) { + addError(e, st); emit(DHTShortArrayBusyState(AsyncValue.error(e, st))); return; } @@ -96,8 +97,9 @@ class DHTShortArrayCubit extends Cubit> } emit(AsyncValue.data(newState)); setRefreshed(); - } on Exception catch (e) { - emit(AsyncValue.error(e)); + } on Exception catch (e, st) { + addError(e, st); + emit(AsyncValue.error(e, st)); } } diff --git a/packages/veilid_support/lib/src/async_table_db_backed_cubit.dart b/packages/veilid_support/lib/src/async_table_db_backed_cubit.dart index d637ee1..a41f4a3 100644 --- a/packages/veilid_support/lib/src/async_table_db_backed_cubit.dart +++ b/packages/veilid_support/lib/src/async_table_db_backed_cubit.dart @@ -27,8 +27,9 @@ abstract class AsyncTableDBBackedCubit extends Cubit> await _mutex.protect(() async { emit(AsyncValue.data(await load())); }); - } on Exception catch (e, stackTrace) { - emit(AsyncValue.error(e, stackTrace)); + } on Exception catch (e, st) { + addError(e, st); + emit(AsyncValue.error(e, st)); } } @@ -37,8 +38,9 @@ abstract class AsyncTableDBBackedCubit extends Cubit> await _initWait(); try { emit(AsyncValue.data(await store(newState))); - } on Exception catch (e, stackTrace) { - emit(AsyncValue.error(e, stackTrace)); + } on Exception catch (e, st) { + addError(e, st); + emit(AsyncValue.error(e, st)); } } diff --git a/packages/veilid_support/lib/src/table_db_array_protobuf_cubit.dart b/packages/veilid_support/lib/src/table_db_array_protobuf_cubit.dart index 89408ac..81d7f57 100644 --- a/packages/veilid_support/lib/src/table_db_array_protobuf_cubit.dart +++ b/packages/veilid_support/lib/src/table_db_array_protobuf_cubit.dart @@ -92,6 +92,7 @@ class TableDBArrayProtobufCubit final avElements = await _loadElements(_tail, _count); final err = avElements.asError; if (err != null) { + addError(err.error, err.stackTrace); emit(AsyncValue.error(err.error, err.stackTrace)); return; } @@ -123,6 +124,7 @@ class TableDBArrayProtobufCubit final allItems = (await _array.getRange(start, end)).toIList(); return AsyncValue.data(allItems); } on Exception catch (e, st) { + addError(e, st); return AsyncValue.error(e, st); } } diff --git a/packages/veilid_support/pubspec.lock b/packages/veilid_support/pubspec.lock index 5078198..c3162cf 100644 --- a/packages/veilid_support/pubspec.lock +++ b/packages/veilid_support/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: bloc_advanced_tools - sha256: "2b2dd492a350e7192a933d09f15ea04d5d00e7bd3fe2a906fe629cd461ddbf94" + sha256: "2ad82be752ab5e983ad9097ed9f334e47a4472c04d5c6b61c99a1bb14a039053" url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.1.6" boolean_selector: dependency: transitive description: @@ -718,7 +718,7 @@ packages: path: "../../../veilid/veilid-flutter" relative: true source: path - version: "0.3.3" + version: "0.3.4" vm_service: dependency: transitive description: diff --git a/packages/veilid_support/pubspec.yaml b/packages/veilid_support/pubspec.yaml index cec41f7..280ef3b 100644 --- a/packages/veilid_support/pubspec.yaml +++ b/packages/veilid_support/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: async_tools: ^0.1.5 bloc: ^8.1.4 - bloc_advanced_tools: ^0.1.5 + bloc_advanced_tools: ^0.1.6 charcode: ^1.3.1 collection: ^1.18.0 equatable: ^2.0.5 @@ -26,11 +26,11 @@ dependencies: # veilid: ^0.0.1 path: ../../../veilid/veilid-flutter -# dependency_overrides: +#dependency_overrides: # async_tools: # path: ../../../dart_async_tools -# bloc_advanced_tools: -# path: ../../../bloc_advanced_tools +# bloc_advanced_tools: +# path: ../../../bloc_advanced_tools dev_dependencies: build_runner: ^2.4.10 diff --git a/pubspec.lock b/pubspec.lock index d4f1ce7..33a7671 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -141,10 +141,10 @@ packages: dependency: "direct main" description: name: bloc_advanced_tools - sha256: "2b2dd492a350e7192a933d09f15ea04d5d00e7bd3fe2a906fe629cd461ddbf94" + sha256: "2ad82be752ab5e983ad9097ed9f334e47a4472c04d5c6b61c99a1bb14a039053" url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.1.6" blurry_modal_progress_hud: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index fe464ea..871ba4d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: badges: ^3.1.2 basic_utils: ^5.7.0 bloc: ^8.1.4 - bloc_advanced_tools: ^0.1.5 + bloc_advanced_tools: ^0.1.6 blurry_modal_progress_hud: ^1.1.1 change_case: ^2.1.0 charcode: ^1.3.1 @@ -114,8 +114,8 @@ dependencies: # dependency_overrides: # async_tools: # path: ../dart_async_tools -# bloc_advanced_tools: -# path: ../bloc_advanced_tools +# bloc_advanced_tools: +# path: ../bloc_advanced_tools # searchable_listview: # path: ../Searchable-Listview # flutter_chat_ui: From 47287ba8d4436be08c6bbb7f41bc8aa6beec5843 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 3 Aug 2024 21:55:20 -0500 Subject: [PATCH 05/93] incremental chat state work --- lib/chat/cubits/chat_component_cubit.dart | 3 +- .../cubits/single_contact_messages_cubit.dart | 39 +++++++++++++++++-- .../active_conversations_bloc_map_cubit.dart | 10 ++--- ...ve_single_contact_chat_bloc_map_cubit.dart | 18 +++++++-- .../cubits/conversation_cubit.dart | 18 ++++----- lib/tools/loggy.dart | 6 +-- lib/tools/state_logger.dart | 6 +-- 7 files changed, 70 insertions(+), 30 deletions(-) diff --git a/lib/chat/cubits/chat_component_cubit.dart b/lib/chat/cubits/chat_component_cubit.dart index 17a481f..326a597 100644 --- a/lib/chat/cubits/chat_component_cubit.dart +++ b/lib/chat/cubits/chat_component_cubit.dart @@ -233,7 +233,8 @@ class ChatComponentCubit extends Cubit { return types.User( id: remoteIdentityPublicKey.toString(), - firstName: activeConversationState.remoteConversation.profile.name, + firstName: activeConversationState.remoteConversation?.profile.name ?? + '', metadata: {metadataKeyIdentityPublicKey: remoteIdentityPublicKey}); } diff --git a/lib/chat/cubits/single_contact_messages_cubit.dart b/lib/chat/cubits/single_contact_messages_cubit.dart index cd2d7e5..ab9c5ab 100644 --- a/lib/chat/cubits/single_contact_messages_cubit.dart +++ b/lib/chat/cubits/single_contact_messages_cubit.dart @@ -55,7 +55,7 @@ class SingleContactMessagesCubit extends Cubit { required TypedKey localConversationRecordKey, required TypedKey localMessagesRecordKey, required TypedKey remoteConversationRecordKey, - required TypedKey remoteMessagesRecordKey, + required TypedKey? remoteMessagesRecordKey, }) : _accountInfo = accountInfo, _remoteIdentityPublicKey = remoteIdentityPublicKey, _localConversationRecordKey = localConversationRecordKey, @@ -147,8 +147,14 @@ class SingleContactMessagesCubit extends Cubit { // Open remote messages key Future _initRcvdMessagesCubit() async { + // Don't bother if we don't have a remote messages record key yet + if (_remoteMessagesRecordKey == null) { + return; + } + + // Open new cubit if one is desired _rcvdMessagesCubit = DHTLogCubit( - open: () async => DHTLog.openRead(_remoteMessagesRecordKey, + open: () async => DHTLog.openRead(_remoteMessagesRecordKey!, debugName: 'SingleContactMessagesCubit::_initRcvdMessagesCubit::' 'RcvdMessages', parent: _remoteConversationRecordKey, @@ -159,6 +165,31 @@ class SingleContactMessagesCubit extends Cubit { _updateRcvdMessagesState(_rcvdMessagesCubit!.state); } + Future updateRemoteMessagesRecordKey( + TypedKey? remoteMessagesRecordKey) async { + await _initWait(); + + _sspRemoteConversationRecordKey.updateState(remoteMessagesRecordKey, + (remoteMessagesRecordKey) async { + // Don't bother if nothing is changing + if (_remoteMessagesRecordKey == remoteMessagesRecordKey) { + return; + } + + // Close existing cubit if we have one + final rcvdMessagesCubit = _rcvdMessagesCubit; + _rcvdMessagesCubit = null; + _remoteMessagesRecordKey = null; + await _rcvdSubscription?.cancel(); + _rcvdSubscription = null; + await rcvdMessagesCubit?.close(); + + // Init the new cubit if we should + _remoteMessagesRecordKey = remoteMessagesRecordKey; + await _initRcvdMessagesCubit(); + }); + } + Future _makeLocalMessagesCrypto() async => VeilidCryptoPrivate.fromTypedKey( _accountInfo.userLogin!.identitySecret, 'tabledb'); @@ -452,7 +483,7 @@ class SingleContactMessagesCubit extends Cubit { final TypedKey _localConversationRecordKey; final TypedKey _localMessagesRecordKey; final TypedKey _remoteConversationRecordKey; - final TypedKey _remoteMessagesRecordKey; + TypedKey? _remoteMessagesRecordKey; late final VeilidCrypto _conversationCrypto; late final MessageIntegrity _senderMessageIntegrity; @@ -471,4 +502,6 @@ class SingleContactMessagesCubit extends Cubit { _reconciledSubscription; final StreamController Function()> _commandController; late final Future _commandRunnerFut; + + final _sspRemoteConversationRecordKey = SingleStateProcessor(); } diff --git a/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart b/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart index b983265..4330db6 100644 --- a/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart +++ b/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart @@ -24,7 +24,7 @@ class ActiveConversationState extends Equatable { final TypedKey localConversationRecordKey; final TypedKey remoteConversationRecordKey; final proto.Conversation localConversation; - final proto.Conversation remoteConversation; + final proto.Conversation? remoteConversation; @override List get props => [ @@ -102,7 +102,8 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit, ConversationCubit>(conversationCubit, transform: (avstate) => avstate.when( - data: (data) => (data.localConversation == null || - data.remoteConversation == null) + data: (data) => (data.localConversation == null) ? const AsyncValue.loading() : AsyncValue.data(ActiveConversationState( localConversation: data.localConversation!, - remoteConversation: data.remoteConversation!, + remoteConversation: data.remoteConversation, remoteIdentityPublicKey: remoteIdentityPublicKey, localConversationRecordKey: localConversationRecordKey, remoteConversationRecordKey: diff --git a/lib/conversation/cubits/active_single_contact_chat_bloc_map_cubit.dart b/lib/conversation/cubits/active_single_contact_chat_bloc_map_cubit.dart index f3dadcb..90adafa 100644 --- a/lib/conversation/cubits/active_single_contact_chat_bloc_map_cubit.dart +++ b/lib/conversation/cubits/active_single_contact_chat_bloc_map_cubit.dart @@ -25,7 +25,7 @@ class _SingleContactChatState extends Equatable { final TypedKey localConversationRecordKey; final TypedKey remoteConversationRecordKey; final TypedKey localMessagesRecordKey; - final TypedKey remoteMessagesRecordKey; + final TypedKey? remoteMessagesRecordKey; @override List get props => [ @@ -53,8 +53,16 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit _addConversationMessages(_SingleContactChatState state) async => - add(() => MapEntry( + Future _addConversationMessages(_SingleContactChatState state) async { + // xxx could use atomic update() function + + final cubit = await tryOperateAsync( + state.localConversationRecordKey, closure: (cubit) async { + await cubit.updateRemoteMessagesRecordKey(state.remoteMessagesRecordKey); + return cubit; + }); + if (cubit == null) { + await add(() => MapEntry( state.localConversationRecordKey, SingleContactMessagesCubit( accountInfo: _accountInfo, @@ -64,6 +72,8 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit avInputState) { @@ -78,7 +88,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit> { localConversation: conv, remoteConversation: _incrementalState.remoteConversation); // return loading still if state isn't complete - if ((_localConversationRecordKey != null && - _incrementalState.localConversation == null) || - (_remoteConversationRecordKey != null && - _incrementalState.remoteConversation == null)) { + if (_localConversationRecordKey != null && + _incrementalState.localConversation == null) { return const AsyncValue.loading(); } - // state is complete, all required keys are open + // local state is complete, all remote state is emitted incrementally return AsyncValue.data(_incrementalState); }, loading: AsyncValue.loading, @@ -247,14 +245,12 @@ class ConversationCubit extends Cubit> { _incrementalState = ConversationState( localConversation: _incrementalState.localConversation, remoteConversation: conv); - // return loading still if state isn't complete - if ((_localConversationRecordKey != null && - _incrementalState.localConversation == null) || - (_remoteConversationRecordKey != null && - _incrementalState.remoteConversation == null)) { + // return loading still if the local state isn't complete + if (_localConversationRecordKey != null && + _incrementalState.localConversation == null) { return const AsyncValue.loading(); } - // state is complete, all required keys are open + // local state is complete, all remote state is emitted incrementally return AsyncValue.data(_incrementalState); }, loading: AsyncValue.loading, diff --git a/lib/tools/loggy.dart b/lib/tools/loggy.dart index 69faeb7..1df4c82 100644 --- a/lib/tools/loggy.dart +++ b/lib/tools/loggy.dart @@ -112,9 +112,9 @@ class CallbackPrinter extends LoggyPrinter { @override void onLog(LogRecord record) { final out = record.pretty(); - if (isDesktop) { - debugPrintSynchronously(out); - } + //if (isDesktop) { + debugPrintSynchronously(out); + //} globalDebugTerminal.write('$out\n'.replaceAll('\n', '\r\n')); callback?.call(record); } diff --git a/lib/tools/state_logger.dart b/lib/tools/state_logger.dart index 08e32b3..6baacae 100644 --- a/lib/tools/state_logger.dart +++ b/lib/tools/state_logger.dart @@ -4,12 +4,12 @@ import 'loggy.dart'; const Map _blocChangeLogLevels = { 'ConnectionStateCubit': LogLevel.off, - 'ActiveSingleContactChatBlocMapCubit': LogLevel.off, - 'ActiveConversationsBlocMapCubit': LogLevel.off, + //'ActiveSingleContactChatBlocMapCubit': LogLevel.off, + //'ActiveConversationsBlocMapCubit': LogLevel.off, 'PersistentQueueCubit': LogLevel.off, 'TableDBArrayProtobufCubit': LogLevel.off, 'DHTLogCubit': LogLevel.off, - 'SingleContactMessagesCubit': LogLevel.off, + //'SingleContactMessagesCubit': LogLevel.off, 'ChatComponentCubit': LogLevel.off, }; From 22390f31ff18a1c65f1ee92f2352821f0dc415dc Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 4 Aug 2024 07:27:25 -0500 Subject: [PATCH 06/93] log fix for ios and ability to delete orphaned chats --- assets/i18n/en.json | 1 + lib/chat_list/views/chat_list_widget.dart | 8 ++--- .../chat_single_contact_item_widget.dart | 33 +++++++++++-------- lib/tools/loggy.dart | 10 +++--- lib/tools/state_logger.dart | 6 ++-- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/assets/i18n/en.json b/assets/i18n/en.json index cb95eaf..552da6f 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -233,6 +233,7 @@ "password_does_not_match": "Password does not match" }, "chat_list": { + "deleted_contact": "Deleted Contact", "search": "Search chats", "start_a_conversation": "Start A Conversation", "chats": "Chats", diff --git a/lib/chat_list/views/chat_list_widget.dart b/lib/chat_list/views/chat_list_widget.dart index 8fc57cf..f9a6ad3 100644 --- a/lib/chat_list/views/chat_list_widget.dart +++ b/lib/chat_list/views/chat_list_widget.dart @@ -18,10 +18,10 @@ class ChatListWidget extends StatelessWidget { Widget _itemBuilderDirect( proto.DirectChat direct, IMap contactMap) { final contact = contactMap[direct.localConversationRecordKey]; - if (contact == null) { - return const Text('...'); - } - return ChatSingleContactItemWidget(contact: contact) + return ChatSingleContactItemWidget( + localConversationRecordKey: + direct.localConversationRecordKey.toVeilid(), + contact: contact) .paddingLTRB(0, 4, 0, 0); } diff --git a/lib/chat_list/views/chat_single_contact_item_widget.dart b/lib/chat_list/views/chat_single_contact_item_widget.dart index 1bd5f64..4ac992f 100644 --- a/lib/chat_list/views/chat_single_contact_item_widget.dart +++ b/lib/chat_list/views/chat_single_contact_item_widget.dart @@ -2,6 +2,7 @@ import 'package:async_tools/async_tools.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_translate/flutter_translate.dart'; +import 'package:veilid_support/veilid_support.dart'; import '../../chat/cubits/active_chat_cubit.dart'; import '../../contacts/contacts.dart'; import '../../proto/proto.dart' as proto; @@ -10,13 +11,16 @@ import '../chat_list.dart'; class ChatSingleContactItemWidget extends StatelessWidget { const ChatSingleContactItemWidget({ - required proto.Contact contact, + required TypedKey localConversationRecordKey, + required proto.Contact? contact, bool disabled = false, super.key, - }) : _contact = contact, + }) : _localConversationRecordKey = localConversationRecordKey, + _contact = contact, _disabled = disabled; - final proto.Contact _contact; + final TypedKey _localConversationRecordKey; + final proto.Contact? _contact; final bool _disabled; @override @@ -29,13 +33,16 @@ class ChatSingleContactItemWidget extends StatelessWidget { final scaleConfig = theme.extension()!; final activeChatCubit = context.watch(); - final localConversationRecordKey = - _contact.localConversationRecordKey.toVeilid(); - final selected = activeChatCubit.state == localConversationRecordKey; + final selected = activeChatCubit.state == _localConversationRecordKey; - final name = _contact.nameOrNickname; - final title = _contact.displayName; - final subtitle = _contact.profile.status; + final name = _contact == null ? '?' : _contact.nameOrNickname; + final title = _contact == null + ? translate('chat_list.deleted_contact') + : _contact.displayName; + final subtitle = _contact == null ? '' : _contact.profile.status; + final availability = _contact == null + ? proto.Availability.AVAILABILITY_UNSPECIFIED + : _contact.profile.availability; final avatar = AvatarWidget( name: name, @@ -53,17 +60,17 @@ class ChatSingleContactItemWidget extends StatelessWidget { ); return SliderTile( - key: ObjectKey(_contact), + key: ValueKey(_localConversationRecordKey), disabled: _disabled, selected: selected, tileScale: ScaleKind.secondary, title: title, subtitle: subtitle, leading: avatar, - trailing: AvailabilityWidget(availability: _contact.profile.availability), + trailing: AvailabilityWidget(availability: availability), onTap: () { singleFuture(activeChatCubit, () async { - activeChatCubit.setActiveChat(localConversationRecordKey); + activeChatCubit.setActiveChat(_localConversationRecordKey); }); }, endActions: [ @@ -74,7 +81,7 @@ class ChatSingleContactItemWidget extends StatelessWidget { onPressed: (context) async { final chatListCubit = context.read(); await chatListCubit.deleteChat( - localConversationRecordKey: localConversationRecordKey); + localConversationRecordKey: _localConversationRecordKey); }) ], ); diff --git a/lib/tools/loggy.dart b/lib/tools/loggy.dart index 1df4c82..b22025e 100644 --- a/lib/tools/loggy.dart +++ b/lib/tools/loggy.dart @@ -9,7 +9,6 @@ import 'package:loggy/loggy.dart'; import 'package:veilid_support/veilid_support.dart'; import '../veilid_processor/views/developer.dart'; -import '../theme/views/responsive.dart'; import 'state_logger.dart'; String wrapWithLogColor(LogLevel? level, String text) { @@ -112,13 +111,16 @@ class CallbackPrinter extends LoggyPrinter { @override void onLog(LogRecord record) { final out = record.pretty(); - //if (isDesktop) { - debugPrintSynchronously(out); - //} + if (Platform.isAndroid) { + debugPrint(out); + } else { + debugPrintSynchronously(out); + } globalDebugTerminal.write('$out\n'.replaceAll('\n', '\r\n')); callback?.call(record); } + // ignore: use_setters_to_change_properties void setCallback(void Function(LogRecord)? cb) { callback = cb; } diff --git a/lib/tools/state_logger.dart b/lib/tools/state_logger.dart index 6baacae..08e32b3 100644 --- a/lib/tools/state_logger.dart +++ b/lib/tools/state_logger.dart @@ -4,12 +4,12 @@ import 'loggy.dart'; const Map _blocChangeLogLevels = { 'ConnectionStateCubit': LogLevel.off, - //'ActiveSingleContactChatBlocMapCubit': LogLevel.off, - //'ActiveConversationsBlocMapCubit': LogLevel.off, + 'ActiveSingleContactChatBlocMapCubit': LogLevel.off, + 'ActiveConversationsBlocMapCubit': LogLevel.off, 'PersistentQueueCubit': LogLevel.off, 'TableDBArrayProtobufCubit': LogLevel.off, 'DHTLogCubit': LogLevel.off, - //'SingleContactMessagesCubit': LogLevel.off, + 'SingleContactMessagesCubit': LogLevel.off, 'ChatComponentCubit': LogLevel.off, }; From 8edccb8a0f3b47999533048b1cf5149d32840e16 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 4 Aug 2024 18:49:49 -0500 Subject: [PATCH 07/93] fix deadlock clean up async handling improve styled alerts --- assets/i18n/en.json | 1 + .../cubits/account_record_cubit.dart | 4 +- ...per_account_collection_bloc_map_cubit.dart | 6 +- .../cubits/per_account_collection_cubit.dart | 8 +-- .../views/edit_account_page.dart | 55 +++++++++---------- .../cubits/contact_invitation_list_cubit.dart | 2 +- .../waiting_invitations_bloc_map_cubit.dart | 6 +- .../models/valid_contact_invitation.dart | 2 +- .../active_conversations_bloc_map_cubit.dart | 4 +- ...ve_single_contact_chat_bloc_map_cubit.dart | 29 ++++------ .../cubits/conversation_cubit.dart | 5 +- lib/theme/views/styled_alert.dart | 16 +++--- lib/tick.dart | 6 +- packages/veilid_support/example/pubspec.lock | 10 ++-- packages/veilid_support/example/pubspec.yaml | 2 +- .../src/dht_record/dht_record.dart | 2 +- .../src/dht_record/dht_record_pool.dart | 41 +++++++++++--- packages/veilid_support/pubspec.lock | 8 +-- packages/veilid_support/pubspec.yaml | 10 ++-- pubspec.lock | 8 +-- pubspec.yaml | 8 +-- 21 files changed, 125 insertions(+), 108 deletions(-) diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 552da6f..7e968da 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -4,6 +4,7 @@ }, "menu": { "accounts_menu_tooltip": "Accounts Menu", + "settings_tooltip": "Settings", "contacts_tooltip": "Contacts List", "new_chat_tooltip": "Start New Chat", "add_account_tooltip": "Add Account", diff --git a/lib/account_manager/cubits/account_record_cubit.dart b/lib/account_manager/cubits/account_record_cubit.dart index ee8ec89..a0a24a7 100644 --- a/lib/account_manager/cubits/account_record_cubit.dart +++ b/lib/account_manager/cubits/account_record_cubit.dart @@ -8,7 +8,7 @@ import '../../proto/proto.dart' as proto; import '../account_manager.dart'; typedef AccountRecordState = proto.Account; -typedef _sspUpdateState = ( +typedef _SspUpdateState = ( AccountSpec accountSpec, Future Function() onSuccess ); @@ -96,5 +96,5 @@ class AccountRecordCubit extends DefaultDHTRecordCubit { } } - final _sspUpdate = SingleStateProcessor<_sspUpdateState>(); + final _sspUpdate = SingleStateProcessor<_SspUpdateState>(); } diff --git a/lib/account_manager/cubits/per_account_collection_bloc_map_cubit.dart b/lib/account_manager/cubits/per_account_collection_bloc_map_cubit.dart index f5334c1..ded50e4 100644 --- a/lib/account_manager/cubits/per_account_collection_bloc_map_cubit.dart +++ b/lib/account_manager/cubits/per_account_collection_bloc_map_cubit.dart @@ -24,13 +24,13 @@ class PerAccountCollectionBlocMapCubit extends BlocMapCubit _addPerAccountCollectionCubit( {required TypedKey superIdentityRecordKey}) async => - add(() => MapEntry( + add( superIdentityRecordKey, - PerAccountCollectionCubit( + () async => PerAccountCollectionCubit( locator: _locator, accountInfoCubit: AccountInfoCubit( accountRepository: _accountRepository, - superIdentityRecordKey: superIdentityRecordKey)))); + superIdentityRecordKey: superIdentityRecordKey))); /// StateFollower ///////////////////////// diff --git a/lib/account_manager/cubits/per_account_collection_cubit.dart b/lib/account_manager/cubits/per_account_collection_cubit.dart index 226d213..089443a 100644 --- a/lib/account_manager/cubits/per_account_collection_cubit.dart +++ b/lib/account_manager/cubits/per_account_collection_cubit.dart @@ -78,13 +78,13 @@ class PerAccountCollectionCubit extends Cubit { await _accountRecordSubscription?.cancel(); _accountRecordSubscription = null; - // Update state to 'loading' - nextState = _updateAccountRecordState(nextState, null); - emit(nextState); - // Close AccountRecordCubit await accountRecordCubit?.close(); accountRecordCubit = null; + + // Update state to 'loading' + nextState = _updateAccountRecordState(nextState, null); + emit(nextState); } else { ///////////////// Logged in /////////////////// diff --git a/lib/account_manager/views/edit_account_page.dart b/lib/account_manager/views/edit_account_page.dart index eaa0e37..5674449 100644 --- a/lib/account_manager/views/edit_account_page.dart +++ b/lib/account_manager/views/edit_account_page.dart @@ -120,22 +120,22 @@ class _EditAccountPageState extends WindowSetupState { try { final success = await AccountRepository.instance.deleteLocalAccount( widget.superIdentityRecordKey, widget.accountRecord); - if (success && mounted) { - context - .read() - .info(text: translate('edit_account_page.account_removed')); - GoRouterHelper(context).pop(); - } else if (mounted) { - context - .read() - .error(text: translate('edit_account_page.failed_to_remove')); + if (mounted) { + if (success) { + context + .read() + .info(text: translate('edit_account_page.account_removed')); + GoRouterHelper(context).pop(); + } else { + context + .read() + .error(text: translate('edit_account_page.failed_to_remove')); + } } } finally { - if (mounted) { - setState(() { - _isInAsyncCall = false; - }); - } + setState(() { + _isInAsyncCall = false; + }); } } on Exception catch (e, st) { if (mounted) { @@ -188,22 +188,21 @@ class _EditAccountPageState extends WindowSetupState { try { final success = await AccountRepository.instance.destroyAccount( widget.superIdentityRecordKey, widget.accountRecord); - if (success && mounted) { - context - .read() - .info(text: translate('edit_account_page.account_destroyed')); - GoRouterHelper(context).pop(); - } else if (mounted) { - context - .read() - .error(text: translate('edit_account_page.failed_to_destroy')); + if (mounted) { + if (success) { + context + .read() + .info(text: translate('edit_account_page.account_destroyed')); + GoRouterHelper(context).pop(); + } else { + context.read().error( + text: translate('edit_account_page.failed_to_destroy')); + } } } finally { - if (mounted) { - setState(() { - _isInAsyncCall = false; - }); - } + setState(() { + _isInAsyncCall = false; + }); } } on Exception catch (e, st) { if (mounted) { diff --git a/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart b/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart index f5663af..959445c 100644 --- a/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart +++ b/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart @@ -252,7 +252,7 @@ class ContactInvitationListCubit .openRecordRead(contactRequestInboxKey, debugName: 'ContactInvitationListCubit::validateInvitation::' 'ContactRequestInbox', - parent: pool.getParentRecordKey(contactRequestInboxKey) ?? + parent: await pool.getParentRecordKey(contactRequestInboxKey) ?? _accountInfo.accountRecordKey) .withCancel(cancelRequest)) .maybeDeleteScope(!isSelf, (contactRequestInbox) async { diff --git a/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart b/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart index 3787205..953575d 100644 --- a/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart +++ b/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart @@ -48,15 +48,15 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit _addWaitingInvitation( {required proto.ContactInvitationRecord contactInvitationRecord}) async => - add(() => MapEntry( + add( contactInvitationRecord.contactRequestInbox.recordKey.toVeilid(), - WaitingInvitationCubit( + () async => WaitingInvitationCubit( ContactRequestInboxCubit( accountInfo: _accountInfo, contactInvitationRecord: contactInvitationRecord), accountInfo: _accountInfo, accountRecordCubit: _accountRecordCubit, - contactInvitationRecord: contactInvitationRecord))); + contactInvitationRecord: contactInvitationRecord)); // Process all accepted or rejected invitations Future _invitationStatusListener( diff --git a/lib/contact_invitation/models/valid_contact_invitation.dart b/lib/contact_invitation/models/valid_contact_invitation.dart index fb8b8de..f19e951 100644 --- a/lib/contact_invitation/models/valid_contact_invitation.dart +++ b/lib/contact_invitation/models/valid_contact_invitation.dart @@ -37,7 +37,7 @@ class ValidContactInvitation { return (await pool.openRecordWrite(_contactRequestInboxKey, _writer, debugName: 'ValidContactInvitation::accept::' 'ContactRequestInbox', - parent: pool.getParentRecordKey(_contactRequestInboxKey) ?? + parent: await pool.getParentRecordKey(_contactRequestInboxKey) ?? _accountInfo.accountRecordKey)) // ignore: prefer_expression_function_bodies .maybeDeleteScope(!isSelf, (contactRequestInbox) async { diff --git a/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart b/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart index 4330db6..fbf0e80 100644 --- a/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart +++ b/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart @@ -78,7 +78,7 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit - add(() { + add(localConversationRecordKey, () async { // Conversation cubit the tracks the state between the local // and remote halves of a contact's relationship with this account final conversationCubit = ConversationCubit( @@ -123,7 +123,7 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit _addConversationMessages(_SingleContactChatState state) async { // xxx could use atomic update() function - - final cubit = await tryOperateAsync( - state.localConversationRecordKey, closure: (cubit) async { - await cubit.updateRemoteMessagesRecordKey(state.remoteMessagesRecordKey); - return cubit; - }); - if (cubit == null) { - await add(() => MapEntry( - state.localConversationRecordKey, - SingleContactMessagesCubit( - accountInfo: _accountInfo, - remoteIdentityPublicKey: state.remoteIdentityPublicKey, - localConversationRecordKey: state.localConversationRecordKey, - remoteConversationRecordKey: state.remoteConversationRecordKey, - localMessagesRecordKey: state.localMessagesRecordKey, - remoteMessagesRecordKey: state.remoteMessagesRecordKey, - ))); - } + await update(state.localConversationRecordKey, + onUpdate: (cubit) async => + cubit.updateRemoteMessagesRecordKey(state.remoteMessagesRecordKey), + onCreate: () async => SingleContactMessagesCubit( + accountInfo: _accountInfo, + remoteIdentityPublicKey: state.remoteIdentityPublicKey, + localConversationRecordKey: state.localConversationRecordKey, + remoteConversationRecordKey: state.remoteConversationRecordKey, + localMessagesRecordKey: state.localMessagesRecordKey, + remoteMessagesRecordKey: state.remoteMessagesRecordKey, + )); } _SingleContactChatState? _mapStateValue( diff --git a/lib/conversation/cubits/conversation_cubit.dart b/lib/conversation/cubits/conversation_cubit.dart index b5895d9..e2a1802 100644 --- a/lib/conversation/cubits/conversation_cubit.dart +++ b/lib/conversation/cubits/conversation_cubit.dart @@ -73,8 +73,9 @@ class ConversationCubit extends Cubit> { final record = await pool.openRecordRead(_remoteConversationRecordKey, debugName: 'ConversationCubit::RemoteConversation', - parent: pool.getParentRecordKey(_remoteConversationRecordKey) ?? - accountInfo.accountRecordKey, + parent: + await pool.getParentRecordKey(_remoteConversationRecordKey) ?? + accountInfo.accountRecordKey, crypto: crypto); return record; diff --git a/lib/theme/views/styled_alert.dart b/lib/theme/views/styled_alert.dart index 2104fa1..eb5b492 100644 --- a/lib/theme/views/styled_alert.dart +++ b/lib/theme/views/styled_alert.dart @@ -95,8 +95,8 @@ Future showErrorModal( required String title, required String text}) async { final theme = Theme.of(context); - final scale = theme.extension()!; - final scaleConfig = theme.extension()!; + // final scale = theme.extension()!; + // final scaleConfig = theme.extension()!; await Alert( context: context, @@ -145,8 +145,8 @@ Future showWarningModal( required String title, required String text}) async { final theme = Theme.of(context); - final scale = theme.extension()!; - final scaleConfig = theme.extension()!; + // final scale = theme.extension()!; + // final scaleConfig = theme.extension()!; await Alert( context: context, @@ -184,8 +184,8 @@ Future showWarningWidgetModal( required String title, required Widget child}) async { final theme = Theme.of(context); - final scale = theme.extension()!; - final scaleConfig = theme.extension()!; + // final scale = theme.extension()!; + // final scaleConfig = theme.extension()!; await Alert( context: context, @@ -223,8 +223,8 @@ Future showConfirmModal( required String title, required String text}) async { final theme = Theme.of(context); - final scale = theme.extension()!; - final scaleConfig = theme.extension()!; + // final scale = theme.extension()!; + // final scaleConfig = theme.extension()!; var confirm = false; diff --git a/lib/tick.dart b/lib/tick.dart index 8ec3db7..21c18a3 100644 --- a/lib/tick.dart +++ b/lib/tick.dart @@ -28,11 +28,7 @@ class BackgroundTickerState extends State { @override void dispose() { - final tickTimer = _tickTimer; - if (tickTimer != null) { - tickTimer.cancel(); - } - + _tickTimer?.cancel(); super.dispose(); } diff --git a/packages/veilid_support/example/pubspec.lock b/packages/veilid_support/example/pubspec.lock index aa857d4..236861d 100644 --- a/packages/veilid_support/example/pubspec.lock +++ b/packages/veilid_support/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: "direct dev" description: name: async_tools - sha256: "9166e8fe65fc65eb79202a6d540f4de768553d78141b885f5bd3f8d7d30eef5e" + sha256: "93df8b92d54d92e3323c630277e902b4ad4f05f798b55cfbc451e98c3e2fb7ba" url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.1.6" bloc: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: bloc_advanced_tools - sha256: "2b2dd492a350e7192a933d09f15ea04d5d00e7bd3fe2a906fe629cd461ddbf94" + sha256: f0b2dbe028792c97d1eb30480ed4e8035b5c70ea3bcc95a9c5255142592857f7 url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.1.7" boolean_selector: dependency: transitive description: @@ -650,7 +650,7 @@ packages: path: "../../../../veilid/veilid-flutter" relative: true source: path - version: "0.3.3" + version: "0.3.4" veilid_support: dependency: "direct main" description: diff --git a/packages/veilid_support/example/pubspec.yaml b/packages/veilid_support/example/pubspec.yaml index eb1c45a..17d17f4 100644 --- a/packages/veilid_support/example/pubspec.yaml +++ b/packages/veilid_support/example/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: path: ../ dev_dependencies: - async_tools: ^0.1.5 + async_tools: ^0.1.6 integration_test: sdk: flutter lint_hard: ^4.0.0 diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart index a0994bf..9aa380c 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart @@ -79,7 +79,7 @@ class DHTRecord implements DHTDeleteable { return false; } - await serialFuturePause((this, _sfListen)); + await serialFutureClose((this, _sfListen)); await _watchController?.close(); _watchController = null; await DHTRecordPool.instance._recordClosed(this); diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart index f1ce324..cddea38 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart @@ -65,7 +65,7 @@ class OwnedDHTRecordPointer with _$OwnedDHTRecordPointer { class DHTRecordPool with TableDBBackedJson { DHTRecordPool._(Veilid veilid, VeilidRoutingContext routingContext) : _state = const DHTRecordPoolAllocations(), - _mutex = Mutex(), + _mutex = Mutex(debugLockTimeout: 30), _recordTagLock = AsyncTagLock(), _opened = {}, _markedForDelete = {}, @@ -207,10 +207,8 @@ class DHTRecordPool with TableDBBackedJson { ); /// Get the parent of a DHTRecord key if it exists - TypedKey? getParentRecordKey(TypedKey child) { - final childJson = child.toJson(); - return _state.parentByChild[childJson]; - } + Future getParentRecordKey(TypedKey child) => + _mutex.protect(() async => _getParentRecordKeyInner(child)); /// Check if record is allocated Future isValidRecordKey(TypedKey key) => @@ -505,12 +503,16 @@ class DHTRecordPool with TableDBBackedJson { // Check to see if this key can finally be deleted // If any parents are marked for deletion, try them first Future _checkForLateDeletesInner(TypedKey key) async { + if (!_mutex.isLocked) { + throw StateError('should be locked here'); + } + // Get parent list in bottom up order including our own key final parents = []; TypedKey? nextParent = key; while (nextParent != null) { parents.add(nextParent); - nextParent = getParentRecordKey(nextParent); + nextParent = _getParentRecordKeyInner(nextParent); } // If any parent is ready to delete all its children do it @@ -547,6 +549,10 @@ class DHTRecordPool with TableDBBackedJson { // Actual delete function Future _finalizeDeleteRecordInner(TypedKey recordKey) async { + if (!_mutex.isLocked) { + throw StateError('should be locked here'); + } + log('_finalizeDeleteRecordInner: key=$recordKey'); // Remove this child from parents @@ -557,6 +563,10 @@ class DHTRecordPool with TableDBBackedJson { // Deep delete mechanism inside mutex Future _deleteRecordInner(TypedKey recordKey) async { + if (!_mutex.isLocked) { + throw StateError('should be locked here'); + } + final toDelete = _readyForDeleteInner(recordKey); if (toDelete.isNotEmpty) { // delete now @@ -656,7 +666,20 @@ class DHTRecordPool with TableDBBackedJson { } } + TypedKey? _getParentRecordKeyInner(TypedKey child) { + if (!_mutex.isLocked) { + throw StateError('should be locked here'); + } + + final childJson = child.toJson(); + return _state.parentByChild[childJson]; + } + bool _isValidRecordKeyInner(TypedKey key) { + if (!_mutex.isLocked) { + throw StateError('should be locked here'); + } + if (_state.rootRecords.contains(key)) { return true; } @@ -667,6 +690,10 @@ class DHTRecordPool with TableDBBackedJson { } bool _isDeletedRecordKeyInner(TypedKey key) { + if (!_mutex.isLocked) { + throw StateError('should be locked here'); + } + // Is this key gone? if (!_isValidRecordKeyInner(key)) { return true; @@ -679,7 +706,7 @@ class DHTRecordPool with TableDBBackedJson { if (_markedForDelete.contains(nextParent)) { return true; } - nextParent = getParentRecordKey(nextParent); + nextParent = _getParentRecordKeyInner(nextParent); } return false; diff --git a/packages/veilid_support/pubspec.lock b/packages/veilid_support/pubspec.lock index c3162cf..1fdb0d3 100644 --- a/packages/veilid_support/pubspec.lock +++ b/packages/veilid_support/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: "direct main" description: name: async_tools - sha256: "9166e8fe65fc65eb79202a6d540f4de768553d78141b885f5bd3f8d7d30eef5e" + sha256: "93df8b92d54d92e3323c630277e902b4ad4f05f798b55cfbc451e98c3e2fb7ba" url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.1.6" bloc: dependency: "direct main" description: @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: bloc_advanced_tools - sha256: "2ad82be752ab5e983ad9097ed9f334e47a4472c04d5c6b61c99a1bb14a039053" + sha256: f0b2dbe028792c97d1eb30480ed4e8035b5c70ea3bcc95a9c5255142592857f7 url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "0.1.7" boolean_selector: dependency: transitive description: diff --git a/packages/veilid_support/pubspec.yaml b/packages/veilid_support/pubspec.yaml index 280ef3b..59b821d 100644 --- a/packages/veilid_support/pubspec.yaml +++ b/packages/veilid_support/pubspec.yaml @@ -7,9 +7,9 @@ environment: sdk: '>=3.2.0 <4.0.0' dependencies: - async_tools: ^0.1.5 + async_tools: ^0.1.6 bloc: ^8.1.4 - bloc_advanced_tools: ^0.1.6 + bloc_advanced_tools: ^0.1.7 charcode: ^1.3.1 collection: ^1.18.0 equatable: ^2.0.5 @@ -26,11 +26,11 @@ dependencies: # veilid: ^0.0.1 path: ../../../veilid/veilid-flutter -#dependency_overrides: +# dependency_overrides: # async_tools: # path: ../../../dart_async_tools -# bloc_advanced_tools: -# path: ../../../bloc_advanced_tools +# bloc_advanced_tools: +# path: ../../../bloc_advanced_tools dev_dependencies: build_runner: ^2.4.10 diff --git a/pubspec.lock b/pubspec.lock index 33a7671..bc10095 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -85,10 +85,10 @@ packages: dependency: "direct main" description: name: async_tools - sha256: "9166e8fe65fc65eb79202a6d540f4de768553d78141b885f5bd3f8d7d30eef5e" + sha256: "93df8b92d54d92e3323c630277e902b4ad4f05f798b55cfbc451e98c3e2fb7ba" url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.1.6" awesome_extensions: dependency: "direct main" description: @@ -141,10 +141,10 @@ packages: dependency: "direct main" description: name: bloc_advanced_tools - sha256: "2ad82be752ab5e983ad9097ed9f334e47a4472c04d5c6b61c99a1bb14a039053" + sha256: f0b2dbe028792c97d1eb30480ed4e8035b5c70ea3bcc95a9c5255142592857f7 url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "0.1.7" blurry_modal_progress_hud: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 871ba4d..e9efd02 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,12 +14,12 @@ dependencies: animated_theme_switcher: ^2.0.10 ansicolor: ^2.0.2 archive: ^3.6.1 - async_tools: ^0.1.5 + async_tools: ^0.1.6 awesome_extensions: ^2.0.16 badges: ^3.1.2 basic_utils: ^5.7.0 bloc: ^8.1.4 - bloc_advanced_tools: ^0.1.6 + bloc_advanced_tools: ^0.1.7 blurry_modal_progress_hud: ^1.1.1 change_case: ^2.1.0 charcode: ^1.3.1 @@ -114,8 +114,8 @@ dependencies: # dependency_overrides: # async_tools: # path: ../dart_async_tools -# bloc_advanced_tools: -# path: ../bloc_advanced_tools +# bloc_advanced_tools: +# path: ../bloc_advanced_tools # searchable_listview: # path: ../Searchable-Listview # flutter_chat_ui: From 6665e5dd0f818041b2911439e63f0c2a9679fa8f Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Sun, 4 Aug 2024 19:27:06 -0500 Subject: [PATCH 08/93] Updated changelog for v0.4.2 patch release --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e565bb1..da4c057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## v0.4.2 ## +- Dialogs cleanup +- Incremental chat state work +- Exception handling work +- Cancellable waiting page +- Fix DHTRecordCubit open() retry bug +- Log fix for iOS +- Add ability to delete orphaned chats +- Fix deadlock + ## v0.4.1 ## - Fix creating new accounts - Switch to non-bounce scroll physics because a lot of views want 'stick to bottom' scroll behavior From 120a7105c855848c02375879f7158be4bdea5a9e Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Sun, 4 Aug 2024 19:27:38 -0500 Subject: [PATCH 09/93] =?UTF-8?q?Version=20update:=20v0.4.1=20=E2=86=92=20?= =?UTF-8?q?v0.4.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index f070c6f..ec9cca0 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.4.1+0 +current_version = 0.4.2+0 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)\+(?P\d+) diff --git a/pubspec.yaml b/pubspec.yaml index e9efd02..7ad490d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: veilidchat description: VeilidChat publish_to: 'none' -version: 0.4.1+14 +version: 0.4.2+15 environment: sdk: '>=3.2.0 <4.0.0' From 103975bb5656e98d64aa9380ffd63708e6fd37f2 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 6 Aug 2024 08:51:19 -0700 Subject: [PATCH 10/93] mutex debugging --- assets/i18n/en.json | 4 +-- .../cubits/account_record_cubit.dart | 1 + .../views/edit_account_page.dart | 8 ++--- .../views/edit_profile_form.dart | 32 +++++++++++++------ .../cubits/single_contact_messages_cubit.dart | 2 +- .../views/empty_contact_list_widget.dart | 6 ++-- lib/init.dart | 2 +- lib/router/cubits/router_cubit.dart | 2 +- lib/theme/views/styled_alert.dart | 4 +-- lib/tools/loggy.dart | 2 +- .../lib/dht_support/src/dht_log/dht_log.dart | 4 +-- .../src/dht_log/dht_log_spine.dart | 5 +-- .../src/dht_record/dht_record.dart | 2 +- .../src/dht_record/dht_record_pool.dart | 27 +++++++++------- .../src/dht_short_array/dht_short_array.dart | 4 +-- .../dht_short_array/dht_short_array_head.dart | 2 +- .../lib/src/async_table_db_backed_cubit.dart | 3 +- packages/veilid_support/lib/src/config.dart | 12 +++---- .../lib/src/persistent_queue.dart | 3 +- .../lib/src/table_db_array.dart | 2 +- packages/veilid_support/pubspec.lock | 8 ++--- packages/veilid_support/pubspec.yaml | 4 +-- pubspec.lock | 8 ++--- pubspec.yaml | 6 ++-- 24 files changed, 88 insertions(+), 65 deletions(-) diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 7e968da..22f5840 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -90,8 +90,8 @@ "accept": "Accept", "reject": "Reject", "finish": "Finish", - "yes_proceed": "Yes, proceed", - "no_cancel": "No, cancel", + "yes": "Yes", + "no": "No", "waiting_for_network": "Waiting For Network" }, "toast": { diff --git a/lib/account_manager/cubits/account_record_cubit.dart b/lib/account_manager/cubits/account_record_cubit.dart index a0a24a7..9a73246 100644 --- a/lib/account_manager/cubits/account_record_cubit.dart +++ b/lib/account_manager/cubits/account_record_cubit.dart @@ -56,6 +56,7 @@ class AccountRecordCubit extends DefaultDHTRecordCubit { Future _updateAccountAsync( AccountSpec accountSpec, Future Function() onSuccess) async { var changed = false; + await record?.eventualUpdateProtobuf(proto.Account.fromBuffer, (old) async { changed = false; if (old == null) { diff --git a/lib/account_manager/views/edit_account_page.dart b/lib/account_manager/views/edit_account_page.dart index 5674449..3ba0f65 100644 --- a/lib/account_manager/views/edit_account_page.dart +++ b/lib/account_manager/views/edit_account_page.dart @@ -97,7 +97,7 @@ class _EditAccountPageState extends WindowSetupState { }, child: Row(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.cancel, size: 16).paddingLTRB(0, 0, 4, 0), - Text(translate('button.no_cancel')).paddingLTRB(0, 0, 4, 0) + Text(translate('button.no')).paddingLTRB(0, 0, 4, 0) ])), ElevatedButton( onPressed: () { @@ -105,7 +105,7 @@ class _EditAccountPageState extends WindowSetupState { }, child: Row(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), - Text(translate('button.yes_proceed')).paddingLTRB(0, 0, 4, 0) + Text(translate('button.yes')).paddingLTRB(0, 0, 4, 0) ])) ]).paddingAll(24) ])); @@ -165,7 +165,7 @@ class _EditAccountPageState extends WindowSetupState { }, child: Row(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.cancel, size: 16).paddingLTRB(0, 0, 4, 0), - Text(translate('button.no_cancel')).paddingLTRB(0, 0, 4, 0) + Text(translate('button.no')).paddingLTRB(0, 0, 4, 0) ])), ElevatedButton( onPressed: () { @@ -173,7 +173,7 @@ class _EditAccountPageState extends WindowSetupState { }, child: Row(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), - Text(translate('button.yes_proceed')).paddingLTRB(0, 0, 4, 0) + Text(translate('button.yes')).paddingLTRB(0, 0, 4, 0) ])) ]).paddingAll(24) ])); diff --git a/lib/account_manager/views/edit_profile_form.dart b/lib/account_manager/views/edit_profile_form.dart index 05e6ffe..54e61d1 100644 --- a/lib/account_manager/views/edit_profile_form.dart +++ b/lib/account_manager/views/edit_profile_form.dart @@ -2,6 +2,7 @@ import 'package:async_tools/async_tools.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; @@ -9,6 +10,7 @@ import 'package:form_builder_validators/form_builder_validators.dart'; import '../../contacts/contacts.dart'; import '../../proto/proto.dart' as proto; import '../../theme/theme.dart'; +import '../../veilid_processor/veilid_processor.dart'; import '../models/models.dart'; const _kDoUpdateSubmit = 'doUpdateSubmit'; @@ -291,16 +293,26 @@ class _EditProfileFormState extends State { const Spacer(), ]).paddingSymmetric(vertical: 4), if (widget.onSubmit != null) - ElevatedButton( - onPressed: widget.onSubmit == null ? null : _doSubmit, - child: Row(mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), - Text((widget.onSubmit == null) - ? widget.submitDisabledText - : widget.submitText) - .paddingLTRB(0, 0, 4, 0) - ]), - ) + Builder(builder: (context) { + final networkReady = context + .watch() + .state + .asData + ?.value + .isPublicInternetReady ?? + false; + + return ElevatedButton( + onPressed: networkReady ? _doSubmit : null, + child: Row(mainAxisSize: MainAxisSize.min, children: [ + const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), + Text(networkReady + ? widget.submitText + : widget.submitDisabledText) + .paddingLTRB(0, 0, 4, 0) + ]), + ); + }), ], ), ); diff --git a/lib/chat/cubits/single_contact_messages_cubit.dart b/lib/chat/cubits/single_contact_messages_cubit.dart index ab9c5ab..187a556 100644 --- a/lib/chat/cubits/single_contact_messages_cubit.dart +++ b/lib/chat/cubits/single_contact_messages_cubit.dart @@ -249,7 +249,7 @@ class SingleContactMessagesCubit extends Cubit { void runCommand(String command) { final (cmd, rest) = command.splitOnce(' '); - if (kDebugMode) { + if (kIsDebugMode) { if (cmd == '/repeat' && rest != null) { final (countStr, text) = rest.splitOnce(' '); final count = int.tryParse(countStr); diff --git a/lib/contacts/views/empty_contact_list_widget.dart b/lib/contacts/views/empty_contact_list_widget.dart index ae8a852..d787da3 100644 --- a/lib/contacts/views/empty_contact_list_widget.dart +++ b/lib/contacts/views/empty_contact_list_widget.dart @@ -1,3 +1,4 @@ +import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; @@ -15,7 +16,8 @@ class EmptyContactListWidget extends StatelessWidget { final textTheme = theme.textTheme; final scale = theme.extension()!; - return Column( + return Expanded( + child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.center, @@ -33,6 +35,6 @@ class EmptyContactListWidget extends StatelessWidget { ), ), ], - ); + )); } } diff --git a/lib/init.dart b/lib/init.dart index fc836a5..d2744c7 100644 --- a/lib/init.dart +++ b/lib/init.dart @@ -18,7 +18,7 @@ class VeilidChatGlobalInit { await getDefaultVeilidPlatformConfig(kIsWeb, VeilidChatApp.name)); // Veilid logging - initVeilidLog(kDebugMode); + initVeilidLog(kIsDebugMode); // Startup Veilid await ProcessorRepository.instance.startup(); diff --git a/lib/router/cubits/router_cubit.dart b/lib/router/cubits/router_cubit.dart index d442485..974319a 100644 --- a/lib/router/cubits/router_cubit.dart +++ b/lib/router/cubits/router_cubit.dart @@ -134,7 +134,7 @@ class RouterCubit extends Cubit { return _router = GoRouter( navigatorKey: _rootNavKey, refreshListenable: StreamListenable(stream.startWith(state).distinct()), - debugLogDiagnostics: kDebugMode, + debugLogDiagnostics: kIsDebugMode, initialLocation: '/', routes: routes, redirect: redirect, diff --git a/lib/theme/views/styled_alert.dart b/lib/theme/views/styled_alert.dart index eb5b492..23b9e9e 100644 --- a/lib/theme/views/styled_alert.dart +++ b/lib/theme/views/styled_alert.dart @@ -246,7 +246,7 @@ Future showConfirmModal( Navigator.pop(context); }, child: Text( - translate('button.no_cancel'), + translate('button.no'), style: _buttonTextStyle(context), ), ), @@ -261,7 +261,7 @@ Future showConfirmModal( Navigator.pop(context); }, child: Text( - translate('button.yes_proceed'), + translate('button.yes'), style: _buttonTextStyle(context), ), ) diff --git a/lib/tools/loggy.dart b/lib/tools/loggy.dart index b22025e..0bb259c 100644 --- a/lib/tools/loggy.dart +++ b/lib/tools/loggy.dart @@ -152,7 +152,7 @@ void initLoggy() { if (isTrace) { logLevel = traceLevel; } else { - logLevel = kDebugMode ? LogLevel.debug : LogLevel.info; + logLevel = kIsDebugMode ? LogLevel.debug : LogLevel.info; } Loggy('').level = getLogOptions(logLevel); diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart index 71bcfa2..8fb1999 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart @@ -305,10 +305,10 @@ class DHTLog implements DHTDeleteable { // Openable int _openCount; - final _mutex = Mutex(); + final _mutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); // Watch mutex to ensure we keep the representation valid - final Mutex _listenMutex = Mutex(); + final Mutex _listenMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); // Stream of external changes StreamController? _watchController; } diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart index e9442f0..7d8c519 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart @@ -713,7 +713,7 @@ class _DHTLogSpine { DHTShortArray.maxElements; // Spine head mutex to ensure we keep the representation valid - final Mutex _spineMutex = Mutex(); + final Mutex _spineMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); // Subscription to head record internal changes StreamSubscription? _subscription; // Notify closure for external spine head changes @@ -733,7 +733,8 @@ class _DHTLogSpine { // LRU cache of DHT spine elements accessed recently // Pair of position and associated shortarray segment - final Mutex _spineCacheMutex = Mutex(); + final Mutex _spineCacheMutex = + Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); final List _openCache; final Map _openedSegments; static const int _openCacheSize = 3; diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart index 9aa380c..bddb4e7 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart @@ -562,7 +562,7 @@ class DHTRecord implements DHTDeleteable { final KeyPair? _writer; final VeilidCrypto _crypto; final String debugName; - final _mutex = Mutex(); + final _mutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); int _openCount; StreamController? _watchController; _WatchState? _watchState; diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart index cddea38..3f55687 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart @@ -65,7 +65,7 @@ class OwnedDHTRecordPointer with _$OwnedDHTRecordPointer { class DHTRecordPool with TableDBBackedJson { DHTRecordPool._(Veilid veilid, VeilidRoutingContext routingContext) : _state = const DHTRecordPoolAllocations(), - _mutex = Mutex(debugLockTimeout: 30), + _mutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null), _recordTagLock = AsyncTagLock(), _opened = {}, _markedForDelete = {}, @@ -835,9 +835,11 @@ class DHTRecordPool with TableDBBackedJson { openedRecordInfo.shared.unionWatchState = null; openedRecordInfo.shared.needsWatchStateUpdate = false; + } on VeilidAPIExceptionTimeout { + log('Timeout in watch cancel for key=$openedRecordKey'); } on VeilidAPIException catch (e) { // Failed to cancel DHT watch, try again next tick - log('Exception in watch cancel: $e'); + log('Exception in watch cancel for key=$openedRecordKey: $e'); } return; } @@ -877,12 +879,22 @@ class DHTRecordPool with TableDBBackedJson { openedRecordInfo.records, realExpiration, renewalTime); openedRecordInfo.shared.needsWatchStateUpdate = false; } + } on VeilidAPIExceptionTimeout { + log('Timeout in watch update for key=$openedRecordKey'); } on VeilidAPIException catch (e) { // Failed to update DHT watch, try again next tick - log('Exception in watch update: $e'); + log('Exception in watch update for key=$openedRecordKey: $e'); + } + + // If we still need a state update after this then do a poll instead + if (openedRecordInfo.shared.needsWatchStateUpdate) { + _pollWatch(openedRecordKey, openedRecordInfo, unionWatchState); } } + // In lieu of a completed watch, set off a polling operation + // on the first value of the watched range, which, due to current + // veilid limitations can only be one subkey at a time right now void _pollWatch(TypedKey openedRecordKey, _OpenedRecordInfo openedRecordInfo, _WatchState unionWatchState) { singleFuture((this, _sfPollWatch, openedRecordKey), () async { @@ -942,18 +954,11 @@ class DHTRecordPool with TableDBBackedJson { final unionWatchState = _collectUnionWatchState(openedRecordInfo.records); - final processed = _watchStateProcessors.updateState( + _watchStateProcessors.updateState( openedRecordKey, unionWatchState, (newState) => _watchStateChange(openedRecordKey, unionWatchState)); - - // In lieu of a completed watch, set off a polling operation - // on the first value of the watched range, which, due to current - // veilid limitations can only be one subkey at a time right now - if (!processed && unionWatchState != null) { - _pollWatch(openedRecordKey, openedRecordInfo, unionWatchState); - } } } }); diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart index d0d26a8..c0ec901 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart @@ -289,10 +289,10 @@ class DHTShortArray implements DHTDeleteable { // Openable int _openCount; - final _mutex = Mutex(); + final _mutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); // Watch mutex to ensure we keep the representation valid - final Mutex _listenMutex = Mutex(); + final Mutex _listenMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); // Stream of external changes StreamController? _watchController; } diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart index ff550e8..4a2c79a 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart @@ -518,7 +518,7 @@ class _DHTShortArrayHead { //////////////////////////////////////////////////////////////////////////// // Head/element mutex to ensure we keep the representation valid - final Mutex _headMutex = Mutex(); + final Mutex _headMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); // Subscription to head record internal changes StreamSubscription? _subscription; // Notify closure for external head changes diff --git a/packages/veilid_support/lib/src/async_table_db_backed_cubit.dart b/packages/veilid_support/lib/src/async_table_db_backed_cubit.dart index a41f4a3..313d3e2 100644 --- a/packages/veilid_support/lib/src/async_table_db_backed_cubit.dart +++ b/packages/veilid_support/lib/src/async_table_db_backed_cubit.dart @@ -4,6 +4,7 @@ import 'package:async_tools/async_tools.dart'; import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; +import 'config.dart'; import 'table_db.dart'; abstract class AsyncTableDBBackedCubit extends Cubit> @@ -45,5 +46,5 @@ abstract class AsyncTableDBBackedCubit extends Cubit> } final WaitSet _initWait = WaitSet(); - final Mutex _mutex = Mutex(); + final Mutex _mutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); } diff --git a/packages/veilid_support/lib/src/config.dart b/packages/veilid_support/lib/src/config.dart index a8b5ea0..9f7703c 100644 --- a/packages/veilid_support/lib/src/config.dart +++ b/packages/veilid_support/lib/src/config.dart @@ -5,10 +5,10 @@ import 'package:path_provider/path_provider.dart'; import 'package:veilid/veilid.dart'; // ignore: do_not_use_environment -const bool _kReleaseMode = bool.fromEnvironment('dart.vm.product'); +const bool kIsReleaseMode = bool.fromEnvironment('dart.vm.product'); // ignore: do_not_use_environment -const bool _kProfileMode = bool.fromEnvironment('dart.vm.profile'); -const bool _kDebugMode = !_kReleaseMode && !_kProfileMode; +const bool kIsProfileMode = bool.fromEnvironment('dart.vm.profile'); +const bool kIsDebugMode = !kIsReleaseMode && !kIsProfileMode; Future> getDefaultVeilidPlatformConfig( bool isWeb, String appName) async { @@ -34,7 +34,7 @@ Future> getDefaultVeilidPlatformConfig( logging: VeilidWASMConfigLogging( performance: VeilidWASMConfigLoggingPerformance( enabled: true, - level: _kDebugMode + level: kIsDebugMode ? VeilidConfigLogLevel.debug : VeilidConfigLogLevel.info, logsInTimings: true, @@ -50,8 +50,8 @@ Future> getDefaultVeilidPlatformConfig( logging: VeilidFFIConfigLogging( terminal: VeilidFFIConfigLoggingTerminal( enabled: - _kDebugMode && (Platform.isIOS || Platform.isAndroid), - level: _kDebugMode + kIsDebugMode && (Platform.isIOS || Platform.isAndroid), + level: kIsDebugMode ? VeilidConfigLogLevel.debug : VeilidConfigLogLevel.info, ignoreLogTargets: ignoreLogTargets), diff --git a/packages/veilid_support/lib/src/persistent_queue.dart b/packages/veilid_support/lib/src/persistent_queue.dart index e59a470..750c48e 100644 --- a/packages/veilid_support/lib/src/persistent_queue.dart +++ b/packages/veilid_support/lib/src/persistent_queue.dart @@ -5,6 +5,7 @@ import 'package:async_tools/async_tools.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:protobuf/protobuf.dart'; +import 'config.dart'; import 'table_db.dart'; class PersistentQueue @@ -203,7 +204,7 @@ class PersistentQueue final T Function(Uint8List) _fromBuffer; final bool _deleteOnClose; final WaitSet _initWait = WaitSet(); - final Mutex _queueMutex = Mutex(); + final Mutex _queueMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); IList _queue = IList.empty(); final StreamController> _syncAddController = StreamController(); final StreamController _queueReady = StreamController(); diff --git a/packages/veilid_support/lib/src/table_db_array.dart b/packages/veilid_support/lib/src/table_db_array.dart index 7c3d4d4..c1d54cf 100644 --- a/packages/veilid_support/lib/src/table_db_array.dart +++ b/packages/veilid_support/lib/src/table_db_array.dart @@ -614,7 +614,7 @@ class _TableDBArrayBase { var _initDone = false; final VeilidCrypto _crypto; final WaitSet _initWait = WaitSet(); - final Mutex _mutex = Mutex(); + final Mutex _mutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); // Change tracking int _headDelta = 0; diff --git a/packages/veilid_support/pubspec.lock b/packages/veilid_support/pubspec.lock index 1fdb0d3..20798e2 100644 --- a/packages/veilid_support/pubspec.lock +++ b/packages/veilid_support/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: "direct main" description: name: async_tools - sha256: "93df8b92d54d92e3323c630277e902b4ad4f05f798b55cfbc451e98c3e2fb7ba" + sha256: bbded696bfcb1437d0ca510ac047f261f9c7494fea2c488dd32ba2800e7f49e8 url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "0.1.7" bloc: dependency: "direct main" description: @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: bloc_advanced_tools - sha256: f0b2dbe028792c97d1eb30480ed4e8035b5c70ea3bcc95a9c5255142592857f7 + sha256: d8a680d8a0469456399fb26bae9f7a1d2a1420b5bdf75e204e0fadab9edb0811 url: "https://pub.dev" source: hosted - version: "0.1.7" + version: "0.1.8" boolean_selector: dependency: transitive description: diff --git a/packages/veilid_support/pubspec.yaml b/packages/veilid_support/pubspec.yaml index 59b821d..2634603 100644 --- a/packages/veilid_support/pubspec.yaml +++ b/packages/veilid_support/pubspec.yaml @@ -7,9 +7,9 @@ environment: sdk: '>=3.2.0 <4.0.0' dependencies: - async_tools: ^0.1.6 + async_tools: ^0.1.7 bloc: ^8.1.4 - bloc_advanced_tools: ^0.1.7 + bloc_advanced_tools: ^0.1.8 charcode: ^1.3.1 collection: ^1.18.0 equatable: ^2.0.5 diff --git a/pubspec.lock b/pubspec.lock index bc10095..840746a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -85,10 +85,10 @@ packages: dependency: "direct main" description: name: async_tools - sha256: "93df8b92d54d92e3323c630277e902b4ad4f05f798b55cfbc451e98c3e2fb7ba" + sha256: bbded696bfcb1437d0ca510ac047f261f9c7494fea2c488dd32ba2800e7f49e8 url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "0.1.7" awesome_extensions: dependency: "direct main" description: @@ -141,10 +141,10 @@ packages: dependency: "direct main" description: name: bloc_advanced_tools - sha256: f0b2dbe028792c97d1eb30480ed4e8035b5c70ea3bcc95a9c5255142592857f7 + sha256: d8a680d8a0469456399fb26bae9f7a1d2a1420b5bdf75e204e0fadab9edb0811 url: "https://pub.dev" source: hosted - version: "0.1.7" + version: "0.1.8" blurry_modal_progress_hud: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 7ad490d..526a3e1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,12 +14,12 @@ dependencies: animated_theme_switcher: ^2.0.10 ansicolor: ^2.0.2 archive: ^3.6.1 - async_tools: ^0.1.6 + async_tools: ^0.1.7 awesome_extensions: ^2.0.16 badges: ^3.1.2 basic_utils: ^5.7.0 bloc: ^8.1.4 - bloc_advanced_tools: ^0.1.7 + bloc_advanced_tools: ^0.1.8 blurry_modal_progress_hud: ^1.1.1 change_case: ^2.1.0 charcode: ^1.3.1 @@ -112,7 +112,7 @@ dependencies: zxing2: ^0.2.3 # dependency_overrides: -# async_tools: +# async_tools: # path: ../dart_async_tools # bloc_advanced_tools: # path: ../bloc_advanced_tools From 19a366dcab9dc94bc0bca9a19d9f8665dfa0fec4 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 6 Aug 2024 10:29:03 -0700 Subject: [PATCH 11/93] contacts ui cleanup --- assets/i18n/en.json | 11 ++++--- .../cubits/single_contact_messages_cubit.dart | 1 - lib/contacts/views/contact_item_widget.dart | 13 +++++++-- lib/contacts/views/contacts_browser.dart | 8 ++--- lib/contacts/views/contacts_dialog.dart | 29 +++++++++++++++---- lib/contacts/views/edit_contact_form.dart | 25 ++++++++-------- .../views/empty_contact_list_widget.dart | 12 ++++---- lib/theme/views/styled_alert.dart | 8 ++--- lib/theme/views/widget_helpers.dart | 16 ++++++++++ 9 files changed, 84 insertions(+), 39 deletions(-) diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 22f5840..aa4d16e 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -86,10 +86,12 @@ "button": { "ok": "Ok", "cancel": "Cancel", + "edit": "Edit", "delete": "Delete", "accept": "Accept", "reject": "Reject", "finish": "Finish", + "close": "Close", "yes": "Yes", "no": "No", "waiting_for_network": "Waiting For Network" @@ -120,12 +122,13 @@ "contacts": "Contacts", "edit_contact": "Edit Contact", "invitations": "Invitations", - "no_contact_selected": "No contact selected", - "new_chat": "New Chat" + "no_contact_selected": "Double-click a contact to edit it", + "new_chat": "Open Chat", + "close_contact": "Close Contact" }, "contact_list": { "contacts": "Contacts", - "invite_people": "Invite people to VeilidChat", + "invite_people": "No contacts\n\nPress 'Create Invitation' to invite a contact to VeilidChat", "search": "Search contacts", "invitation": "Invitation", "loading_contacts": "Loading contacts..." @@ -134,7 +137,7 @@ "form_name": "Name", "form_pronouns": "Pronouns", "form_about": "About", - "form_status": "Current Status", + "form_status": "Status", "form_nickname": "Nickname", "form_notes": "Notes", "form_fingerprint": "Fingerprint", diff --git a/lib/chat/cubits/single_contact_messages_cubit.dart b/lib/chat/cubits/single_contact_messages_cubit.dart index 187a556..b4e77db 100644 --- a/lib/chat/cubits/single_contact_messages_cubit.dart +++ b/lib/chat/cubits/single_contact_messages_cubit.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:async_tools/async_tools.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:veilid_support/veilid_support.dart'; diff --git a/lib/contacts/views/contact_item_widget.dart b/lib/contacts/views/contact_item_widget.dart index 46b658a..4cb874d 100644 --- a/lib/contacts/views/contact_item_widget.dart +++ b/lib/contacts/views/contact_item_widget.dart @@ -5,7 +5,6 @@ import '../../proto/proto.dart' as proto; import '../../theme/theme.dart'; const _kOnTap = 'onTap'; -const _kOnDelete = 'onDelete'; class ContactItemWidget extends StatelessWidget { const ContactItemWidget( @@ -70,13 +69,23 @@ class ContactItemWidget extends StatelessWidget { await _onTap(_contact); }), endActions: [ + if (_onDoubleTap != null) + SliderTileAction( + icon: Icons.edit, + label: translate('button.edit'), + actionScale: ScaleKind.secondary, + onPressed: (_context) => + singleFuture((this, _kOnTap), () async { + await _onDoubleTap(_contact); + }), + ), if (_onDelete != null) SliderTileAction( icon: Icons.delete, label: translate('button.delete'), actionScale: ScaleKind.tertiary, onPressed: (_context) => - singleFuture((this, _kOnDelete), () async { + singleFuture((this, _kOnTap), () async { await _onDelete(_contact); }), ), diff --git a/lib/contacts/views/contacts_browser.dart b/lib/contacts/views/contacts_browser.dart index 2eea880..89cea88 100644 --- a/lib/contacts/views/contacts_browser.dart +++ b/lib/contacts/views/contacts_browser.dart @@ -191,13 +191,13 @@ class _ContactsBrowserState extends State //final scaleConfig = theme.extension()!; final cilState = context.watch().state; - final cilBusy = cilState.busy; + //final cilBusy = cilState.busy; final contactInvitationRecordList = cilState.state.asData?.value.map((x) => x.value).toIList() ?? const IListConst([]); final ciState = context.watch().state; - final ciBusy = ciState.busy; + //final ciBusy = ciState.busy; final contactList = ciState.state.asData?.value.map((x) => x.value).toIList(); @@ -243,8 +243,8 @@ class _ContactsBrowserState extends State selected: widget.selectedContactRecordKey == contact.localConversationRecordKey.toVeilid(), disabled: false, - onTap: _onTapContact, - onDoubleTap: _onStartChat, + onDoubleTap: _onTapContact, + onTap: _onStartChat, onDelete: _onDeleteContact) .paddingLTRB(0, 4, 0, 0); case ContactsBrowserElementKind.invitation: diff --git a/lib/contacts/views/contacts_dialog.dart b/lib/contacts/views/contacts_dialog.dart index f994148..8d65338 100644 --- a/lib/contacts/views/contacts_dialog.dart +++ b/lib/contacts/views/contacts_dialog.dart @@ -75,12 +75,29 @@ class _ContactsDialogState extends State { : null, actions: [ if (_selectedContact != null) - IconButton( - icon: const Icon(Icons.chat_bubble), - tooltip: translate('contacts_dialog.new_chat'), - onPressed: () async { - await onChatStarted(_selectedContact!); - }) + Column(mainAxisSize: MainAxisSize.min, children: [ + IconButton( + icon: const Icon(Icons.chat_bubble), + tooltip: translate('contacts_dialog.new_chat'), + onPressed: () async { + await onChatStarted(_selectedContact!); + }), + Text(translate('contacts_dialog.new_chat'), + style: theme.textTheme.labelSmall! + .copyWith(color: scale.primaryScale.borderText)), + ]).paddingLTRB(8, 0, 8, 0), + if (enableSplit && _selectedContact != null) + Column(mainAxisSize: MainAxisSize.min, children: [ + IconButton( + icon: const Icon(Icons.close), + tooltip: translate('contacts_dialog.close_contact'), + onPressed: () async { + await onContactSelected(null); + }), + Text(translate('contacts_dialog.close_contact'), + style: theme.textTheme.labelSmall! + .copyWith(color: scale.primaryScale.borderText)), + ]).paddingLTRB(8, 0, 8, 0), ]), body: LayoutBuilder(builder: (context, constraint) { final maxWidth = constraint.maxWidth; diff --git a/lib/contacts/views/edit_contact_form.dart b/lib/contacts/views/edit_contact_form.dart index 0e8acc1..e515f14 100644 --- a/lib/contacts/views/edit_contact_form.dart +++ b/lib/contacts/views/edit_contact_form.dart @@ -67,6 +67,7 @@ class _EditContactFormState extends State { return FormBuilder( key: widget.formKey, child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ AvatarWidget( name: widget.contact.profile.name, @@ -79,53 +80,53 @@ class _EditContactFormState extends State { ).paddingLTRB(0, 0, 0, 16), SelectableText(widget.contact.profile.name, style: textTheme.headlineMedium) - .decoratorLabel( + .noEditDecoratorLabel( context, translate('contact_form.form_name'), scale: scale.secondaryScale, ) - .paddingSymmetric(vertical: 8), + .paddingSymmetric(vertical: 4), SelectableText(widget.contact.profile.pronouns, style: textTheme.headlineSmall) - .decoratorLabel( + .noEditDecoratorLabel( context, translate('contact_form.form_pronouns'), scale: scale.secondaryScale, ) - .paddingSymmetric(vertical: 8), - Row(children: [ + .paddingSymmetric(vertical: 4), + Row(mainAxisSize: MainAxisSize.min, children: [ _availabilityWidget(context, widget.contact.profile.availability), SelectableText(widget.contact.profile.status, style: textTheme.bodyMedium) .paddingSymmetric(horizontal: 8) ]) - .decoratorLabel( + .noEditDecoratorLabel( context, translate('contact_form.form_status'), scale: scale.secondaryScale, ) - .paddingSymmetric(vertical: 8), + .paddingSymmetric(vertical: 4), SelectableText(widget.contact.profile.about, minLines: 1, maxLines: 8, style: textTheme.bodyMedium) - .decoratorLabel( + .noEditDecoratorLabel( context, translate('contact_form.form_about'), scale: scale.secondaryScale, ) - .paddingSymmetric(vertical: 8), + .paddingSymmetric(vertical: 4), SelectableText( widget.contact.identityPublicKey.value.toVeilid().toString(), style: textTheme.labelMedium! .copyWith(fontFamily: 'Source Code Pro')) - .decoratorLabel( + .noEditDecoratorLabel( context, translate('contact_form.form_fingerprint'), scale: scale.secondaryScale, ) - .paddingSymmetric(vertical: 8), + .paddingSymmetric(vertical: 4), Divider(color: border).paddingLTRB(8, 0, 8, 8), FormBuilderTextField( - autofocus: true, + //autofocus: true, name: EditContactForm.formFieldNickname, initialValue: widget.contact.nickname, decoration: InputDecoration( diff --git a/lib/contacts/views/empty_contact_list_widget.dart b/lib/contacts/views/empty_contact_list_widget.dart index d787da3..2563a1d 100644 --- a/lib/contacts/views/empty_contact_list_widget.dart +++ b/lib/contacts/views/empty_contact_list_widget.dart @@ -1,4 +1,3 @@ -import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; @@ -22,14 +21,15 @@ class EmptyContactListWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - Icons.person_add_sharp, - color: scale.primaryScale.subtleBorder, - size: 48, - ), + // Icon( + // Icons.person_add_sharp, + // color: scale.primaryScale.subtleBorder, + // size: 48, + // ), Text( textAlign: TextAlign.center, translate('contact_list.invite_people'), + //maxLines: 3, style: textTheme.bodyMedium?.copyWith( color: scale.primaryScale.subtleBorder, ), diff --git a/lib/theme/views/styled_alert.dart b/lib/theme/views/styled_alert.dart index 23b9e9e..39fa6f2 100644 --- a/lib/theme/views/styled_alert.dart +++ b/lib/theme/views/styled_alert.dart @@ -94,7 +94,7 @@ Future showErrorModal( {required BuildContext context, required String title, required String text}) async { - final theme = Theme.of(context); + // final theme = Theme.of(context); // final scale = theme.extension()!; // final scaleConfig = theme.extension()!; @@ -144,7 +144,7 @@ Future showWarningModal( {required BuildContext context, required String title, required String text}) async { - final theme = Theme.of(context); + // final theme = Theme.of(context); // final scale = theme.extension()!; // final scaleConfig = theme.extension()!; @@ -183,7 +183,7 @@ Future showWarningWidgetModal( {required BuildContext context, required String title, required Widget child}) async { - final theme = Theme.of(context); + // final theme = Theme.of(context); // final scale = theme.extension()!; // final scaleConfig = theme.extension()!; @@ -222,7 +222,7 @@ Future showConfirmModal( {required BuildContext context, required String title, required String text}) async { - final theme = Theme.of(context); + // final theme = Theme.of(context); // final scale = theme.extension()!; // final scaleConfig = theme.extension()!; diff --git a/lib/theme/views/widget_helpers.dart b/lib/theme/views/widget_helpers.dart index 8404e72..970e065 100644 --- a/lib/theme/views/widget_helpers.dart +++ b/lib/theme/views/widget_helpers.dart @@ -109,6 +109,22 @@ extension LabelExt on Widget { ), child: this); } + + Widget noEditDecoratorLabel(BuildContext context, String label, + {ScaleColor? scale}) { + final theme = Theme.of(context); + final scaleScheme = theme.extension()!; + // final scaleConfig = theme.extension()!; + scale = scale ?? scaleScheme.primaryScale; + + return Wrap(crossAxisAlignment: WrapCrossAlignment.center, children: [ + Text( + '$label:', + style: theme.textTheme.titleLarge!.copyWith(color: scale.border), + ).paddingLTRB(0, 0, 8, 8), + this + ]); + } } Widget buildProgressIndicator() => Builder(builder: (context) { From 6102f32587fe1ad13074336266f94d8cf43222db Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 6 Aug 2024 10:36:18 -0700 Subject: [PATCH 12/93] fix fitting --- lib/contacts/views/contacts_dialog.dart | 51 ++++++++++++++----------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/lib/contacts/views/contacts_dialog.dart b/lib/contacts/views/contacts_dialog.dart index 8d65338..6a963f5 100644 --- a/lib/contacts/views/contacts_dialog.dart +++ b/lib/contacts/views/contacts_dialog.dart @@ -75,29 +75,36 @@ class _ContactsDialogState extends State { : null, actions: [ if (_selectedContact != null) - Column(mainAxisSize: MainAxisSize.min, children: [ - IconButton( - icon: const Icon(Icons.chat_bubble), - tooltip: translate('contacts_dialog.new_chat'), - onPressed: () async { - await onChatStarted(_selectedContact!); - }), - Text(translate('contacts_dialog.new_chat'), - style: theme.textTheme.labelSmall! - .copyWith(color: scale.primaryScale.borderText)), - ]).paddingLTRB(8, 0, 8, 0), + FittedBox( + fit: BoxFit.scaleDown, + child: + Column(mainAxisSize: MainAxisSize.min, children: [ + IconButton( + icon: const Icon(Icons.chat_bubble), + tooltip: translate('contacts_dialog.new_chat'), + onPressed: () async { + await onChatStarted(_selectedContact!); + }), + Text(translate('contacts_dialog.new_chat'), + style: theme.textTheme.labelSmall!.copyWith( + color: scale.primaryScale.borderText)), + ])).paddingLTRB(8, 0, 8, 0), if (enableSplit && _selectedContact != null) - Column(mainAxisSize: MainAxisSize.min, children: [ - IconButton( - icon: const Icon(Icons.close), - tooltip: translate('contacts_dialog.close_contact'), - onPressed: () async { - await onContactSelected(null); - }), - Text(translate('contacts_dialog.close_contact'), - style: theme.textTheme.labelSmall! - .copyWith(color: scale.primaryScale.borderText)), - ]).paddingLTRB(8, 0, 8, 0), + FittedBox( + fit: BoxFit.scaleDown, + child: + Column(mainAxisSize: MainAxisSize.min, children: [ + IconButton( + icon: const Icon(Icons.close), + tooltip: + translate('contacts_dialog.close_contact'), + onPressed: () async { + await onContactSelected(null); + }), + Text(translate('contacts_dialog.close_contact'), + style: theme.textTheme.labelSmall!.copyWith( + color: scale.primaryScale.borderText)), + ])).paddingLTRB(8, 0, 8, 0), ]), body: LayoutBuilder(builder: (context, constraint) { final maxWidth = constraint.maxWidth; From 9fbe97c035272ca52765306e5b824c3d8c799020 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 6 Aug 2024 19:00:10 -0700 Subject: [PATCH 13/93] flutter upgrade and fix some ui --- ios/Runner/AppDelegate.swift | 2 +- lib/contacts/views/contacts_dialog.dart | 66 ++++++++++++++----------- macos/Runner/AppDelegate.swift | 2 +- pubspec.lock | 8 +-- 4 files changed, 42 insertions(+), 36 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4..b636303 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/lib/contacts/views/contacts_dialog.dart b/lib/contacts/views/contacts_dialog.dart index 6a963f5..e6e5391 100644 --- a/lib/contacts/views/contacts_dialog.dart +++ b/lib/contacts/views/contacts_dialog.dart @@ -46,7 +46,11 @@ class _ContactsDialogState extends State { final theme = Theme.of(context); // final textTheme = theme.textTheme; final scale = theme.extension()!; - // final scaleConfig = theme.extension()!; + final scaleConfig = theme.extension()!; + + final appBarIconColor = scaleConfig.useVisualIndicators + ? scale.secondaryScale.border + : scale.secondaryScale.borderText; final enableSplit = !isMobileWidth(context); final enableLeft = enableSplit || _selectedContact == null; @@ -86,8 +90,8 @@ class _ContactsDialogState extends State { await onChatStarted(_selectedContact!); }), Text(translate('contacts_dialog.new_chat'), - style: theme.textTheme.labelSmall!.copyWith( - color: scale.primaryScale.borderText)), + style: theme.textTheme.labelSmall! + .copyWith(color: appBarIconColor)), ])).paddingLTRB(8, 0, 8, 0), if (enableSplit && _selectedContact != null) FittedBox( @@ -102,38 +106,40 @@ class _ContactsDialogState extends State { await onContactSelected(null); }), Text(translate('contacts_dialog.close_contact'), - style: theme.textTheme.labelSmall!.copyWith( - color: scale.primaryScale.borderText)), + style: theme.textTheme.labelSmall! + .copyWith(color: appBarIconColor)), ])).paddingLTRB(8, 0, 8, 0), ]), body: LayoutBuilder(builder: (context, constraint) { final maxWidth = constraint.maxWidth; - return Row(children: [ - Offstage( - offstage: !enableLeft, - child: SizedBox( - width: enableLeft && !enableRight - ? maxWidth - : (maxWidth / 3).clamp(200, 500), - child: DecoratedBox( - decoration: BoxDecoration( - color: scale.primaryScale.subtleBackground), - child: ContactsBrowser( - selectedContactRecordKey: _selectedContact - ?.localConversationRecordKey - .toVeilid(), - onContactSelected: onContactSelected, - onChatStarted: onChatStarted, - ).paddingLTRB(8, 0, 8, 8)))), - if (enableRight) - if (_selectedContact == null) - const NoContactWidget().expanded() - else - ContactDetailsWidget(contact: _selectedContact!) - .paddingAll(8) - .expanded(), - ]); + return ColoredBox( + color: scale.primaryScale.appBackground, + child: Row(children: [ + Offstage( + offstage: !enableLeft, + child: SizedBox( + width: enableLeft && !enableRight + ? maxWidth + : (maxWidth / 3).clamp(200, 500), + child: DecoratedBox( + decoration: BoxDecoration( + color: scale.primaryScale.subtleBackground), + child: ContactsBrowser( + selectedContactRecordKey: _selectedContact + ?.localConversationRecordKey + .toVeilid(), + onContactSelected: onContactSelected, + onChatStarted: onChatStarted, + ).paddingLTRB(8, 0, 8, 8)))), + if (enableRight) + if (_selectedContact == null) + const NoContactWidget().expanded() + else + ContactDetailsWidget(contact: _selectedContact!) + .paddingAll(8) + .expanded(), + ])); }))); } diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index d53ef64..8e02df2 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/pubspec.lock b/pubspec.lock index 840746a..17cdf9b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -893,18 +893,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: "direct main" description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: From c3df5bec8cf1d257c63d773d7bde73f7db5e9633 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 6 Aug 2024 19:54:07 -0700 Subject: [PATCH 14/93] Update CHANGELOG.md for v0.4.3 [ci skip] --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da4c057..bcc4639 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v0.4.3 ## +- Flutter upgrade to 3.24.0 +- Contacts UI cleanup +- Incorporate symmetric NAT fix from veilid-core +- Initial public beta release + ## v0.4.2 ## - Dialogs cleanup - Incremental chat state work From 31074732b60e56421bafec9e23f89317b52f0163 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 6 Aug 2024 19:55:57 -0700 Subject: [PATCH 15/93] =?UTF-8?q?Version=20update:=20v0.4.2=20=E2=86=92=20?= =?UTF-8?q?v0.4.3=20[ci=20skip]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index ec9cca0..275e341 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.4.2+0 +current_version = 0.4.3+0 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)\+(?P\d+) diff --git a/pubspec.yaml b/pubspec.yaml index 526a3e1..a0f3a51 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: veilidchat description: VeilidChat publish_to: 'none' -version: 0.4.2+15 +version: 0.4.3+16 environment: sdk: '>=3.2.0 <4.0.0' From 574fa499a058463ac4abf8c980eaf125710ab7a3 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 7 Aug 2024 12:03:23 -0700 Subject: [PATCH 16/93] update beta dialog --- assets/i18n/en.json | 2 +- lib/layout/home/home_screen.dart | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/assets/i18n/en.json b/assets/i18n/en.json index aa4d16e..d45a746 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -13,7 +13,7 @@ }, "splash": { "beta_title": "VeilidChat is BETA SOFTWARE", - "beta_text": "DO NOT USE THIS FOR ANYTHING IMPORTANT\n\nUntil 1.0 is released:\n\n• You should have no expectations of actual privacy, or guarantees of security.\n• You will likely lose accounts, contacts, and messages and need to recreate them.\n\nPlease read our BETA PARTICIPATION GUIDE located here:\n\n" + "beta_text": "DO NOT USE THIS FOR ANYTHING IMPORTANT\n\nUntil 1.0 is released:\n\n• You should have no expectations of actual privacy, or guarantees of security.\n• You will likely lose accounts, contacts, and messages and need to recreate them.\n\nTo know what to expect, review our known issues located here:\n\n" }, "account": { "form_name": "Name", diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index a90e110..f226717 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -64,6 +64,7 @@ class HomeScreenState extends State var displayBetaWarning = true; final theme = Theme.of(context); final scale = theme.extension()!; + final scaleConfig = theme.extension()!; await showWarningWidgetModal( context: context, @@ -79,14 +80,16 @@ class HomeScreenState extends State .copyWith(color: scale.primaryScale.appText), ), TextSpan( - text: 'https://veilid.com/chat/beta', + text: 'https://veilid.com/chat/knownissues', style: theme.textTheme.bodyMedium!.copyWith( - color: scale.primaryScale.primary, + color: scaleConfig.useVisualIndicators + ? scale.secondaryScale.primaryText + : scale.secondaryScale.primary, decoration: TextDecoration.underline, ), recognizer: TapGestureRecognizer() - ..onTap = - () => launchUrlString('https://veilid.com/chat/beta'), + ..onTap = () => + launchUrlString('https://veilid.com/chat/knownissues'), ), ], ), From 013b24146e5f6ae75d5c10a723c0844cced8cc51 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 7 Aug 2024 12:05:31 -0700 Subject: [PATCH 17/93] Changelog for v0.4.4 --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcc4639..b6f43c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ +## v0.4.4 ## +- Update beta dialog with expectations page +- Temporarily disable relay selection aggressiveness + ## v0.4.3 ## - Flutter upgrade to 3.24.0 - Contacts UI cleanup - Incorporate symmetric NAT fix from veilid-core -- Initial public beta release ## v0.4.2 ## - Dialogs cleanup From dde9d40bf2581b6653664b03d0e4d3f2d9dc71d6 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 7 Aug 2024 12:06:22 -0700 Subject: [PATCH 18/93] =?UTF-8?q?Version=20update:=20v0.4.3=20=E2=86=92=20?= =?UTF-8?q?v0.4.4=20[ci=20skip]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 275e341..9571b62 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.4.3+0 +current_version = 0.4.4+0 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)\+(?P\d+) diff --git a/pubspec.yaml b/pubspec.yaml index a0f3a51..c71d310 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: veilidchat description: VeilidChat publish_to: 'none' -version: 0.4.3+16 +version: 0.4.4+17 environment: sdk: '>=3.2.0 <4.0.0' From 71cbfe0d6b4e76c59877ec2094b08817f14e02c0 Mon Sep 17 00:00:00 2001 From: TC Date: Thu, 8 Aug 2024 16:44:48 +0000 Subject: [PATCH 19/93] Added template to new issue creation [ci skip] --- .gitlab/issue_templates/mytemplate.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .gitlab/issue_templates/mytemplate.md diff --git a/.gitlab/issue_templates/mytemplate.md b/.gitlab/issue_templates/mytemplate.md new file mode 100644 index 0000000..759ecc2 --- /dev/null +++ b/.gitlab/issue_templates/mytemplate.md @@ -0,0 +1,21 @@ +First, please search through the existing issues on GitLab ***and*** read our [known issues](https://veilid.com/chat/knownissues) page before opening a new issue. + +Please provide the following information to the best of your ability: + +## Platform in use (Apple or Android) + + +## Network type (Wifi or Cell) +### If you know it, what type of NAT? + + +## Paste in relevant logs +1. Long press the signal meter in VeilidChat to open the console logs +2. Switch the logs to debug +3. Make the issue happen again +4. Go back into the logs and hit the copy all button +5. Paste the logs somewhere you can make edits -- remove all IPs (v4 and v6) +6. Paste or attach that redacted log here + + +## Description of the issue \ No newline at end of file From abf31369a1da8831e351adf17cf3c9d48623fd7e Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Fri, 9 Aug 2024 10:54:54 -0700 Subject: [PATCH 20/93] fix stupid colors --- lib/chat/views/empty_chat_widget.dart | 2 +- lib/theme/models/contrast_generator.dart | 3 +++ lib/theme/models/radix_generator.dart | 3 +++ lib/theme/models/scale_scheme.dart | 8 ++++---- lib/theme/models/slider_tile.dart | 4 ++-- lib/theme/models/theme_preference.dart | 2 +- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/chat/views/empty_chat_widget.dart b/lib/chat/views/empty_chat_widget.dart index e441a46..946fc3f 100644 --- a/lib/chat/views/empty_chat_widget.dart +++ b/lib/chat/views/empty_chat_widget.dart @@ -17,7 +17,7 @@ class EmptyChatWidget extends StatelessWidget { width: double.infinity, height: double.infinity, decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, + color: scale.primaryScale.appBackground, ), child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/theme/models/contrast_generator.dart b/lib/theme/models/contrast_generator.dart index 09cef2b..308b6b3 100644 --- a/lib/theme/models/contrast_generator.dart +++ b/lib/theme/models/contrast_generator.dart @@ -265,6 +265,9 @@ ThemeData contrastGenerator({ final themeData = ThemeData.from( colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true); return themeData.copyWith( + appBarTheme: themeData.appBarTheme.copyWith( + backgroundColor: scaleScheme.primaryScale.border, + foregroundColor: scaleScheme.primaryScale.borderText), bottomSheetTheme: themeData.bottomSheetTheme.copyWith( elevation: 0, modalElevation: 0, diff --git a/lib/theme/models/radix_generator.dart b/lib/theme/models/radix_generator.dart index 92d52c8..c3802e6 100644 --- a/lib/theme/models/radix_generator.dart +++ b/lib/theme/models/radix_generator.dart @@ -636,6 +636,9 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) { } return scaleScheme.primaryScale.subtleBorder; })), + appBarTheme: themeData.appBarTheme.copyWith( + backgroundColor: scaleScheme.primaryScale.border, + foregroundColor: scaleScheme.primaryScale.borderText), bottomSheetTheme: themeData.bottomSheetTheme.copyWith( elevation: 0, modalElevation: 0, diff --git a/lib/theme/models/scale_scheme.dart b/lib/theme/models/scale_scheme.dart index 512fda6..ac266bc 100644 --- a/lib/theme/models/scale_scheme.dart +++ b/lib/theme/models/scale_scheme.dart @@ -94,10 +94,10 @@ class ScaleScheme extends ThemeExtension { // onErrorContainer: errorScale.subtleText, background: grayScale.appBackground, // reviewed onBackground: grayScale.appText, // reviewed - surface: primaryScale.primary, // reviewed - onSurface: primaryScale.primaryText, // reviewed - surfaceVariant: secondaryScale.primary, - onSurfaceVariant: secondaryScale.primaryText, // ?? reviewed a little + surface: primaryScale.appBackground, // reviewed + onSurface: primaryScale.appText, // reviewed + surfaceVariant: secondaryScale.appBackground, + onSurfaceVariant: secondaryScale.appText, outline: primaryScale.border, outlineVariant: secondaryScale.border, shadow: primaryScale.primary.darken(80), diff --git a/lib/theme/models/slider_tile.dart b/lib/theme/models/slider_tile.dart index e70c6bd..2ce81c9 100644 --- a/lib/theme/models/slider_tile.dart +++ b/lib/theme/models/slider_tile.dart @@ -113,7 +113,7 @@ class SliderTile extends StatelessWidget { ? tileColor.border : tileColor.borderText) : scale.scale(a.actionScale).primaryText, - icon: subtitle.isNotEmpty ? a.icon : null, + icon: subtitle.isEmpty ? a.icon : null, label: a.label, padding: const EdgeInsets.all(2)), ) @@ -136,7 +136,7 @@ class SliderTile extends StatelessWidget { ? tileColor.border : tileColor.borderText) : scale.scale(a.actionScale).primaryText, - icon: subtitle.isNotEmpty ? a.icon : null, + icon: subtitle.isEmpty ? a.icon : null, label: a.label, padding: const EdgeInsets.all(2)), ) diff --git a/lib/theme/models/theme_preference.dart b/lib/theme/models/theme_preference.dart index cfa05c9..ec7a40e 100644 --- a/lib/theme/models/theme_preference.dart +++ b/lib/theme/models/theme_preference.dart @@ -123,7 +123,7 @@ extension ThemePreferencesExt on ThemePreferences { scaleConfig: ScaleConfig( useVisualIndicators: true, preferBorders: true, - borderRadiusScale: 0.5), + borderRadiusScale: 0.2), primaryFront: const Color(0xFF000000), primaryBack: const Color(0xFF00FF00), secondaryFront: const Color(0xFF000000), From 4966349ac9e9e4c6c111bddc8c3470f85e5a8046 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 10 Aug 2024 17:37:12 -0700 Subject: [PATCH 21/93] turn off automatic link preview --- lib/chat/views/chat_component_widget.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/chat/views/chat_component_widget.dart b/lib/chat/views/chat_component_widget.dart index 3349f7f..c4816ae 100644 --- a/lib/chat/views/chat_component_widget.dart +++ b/lib/chat/views/chat_component_widget.dart @@ -224,6 +224,7 @@ class ChatComponentWidget extends StatelessWidget { //onAttachmentPressed: _handleAttachmentPressed, //onMessageTap: _handleMessageTap, //onPreviewDataFetched: _handlePreviewDataFetched, + usePreviewData: false, // onSendPressed: (pt) { try { if (!messageIsValid) { From e8810d208d0cc1c5458fb8413666c8f4715a797f Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 20 Oct 2024 14:57:35 -0400 Subject: [PATCH 22/93] fixes for veilid 0.4.0 --- ios/Podfile.lock | 16 +- .../views/edit_profile_form.dart | 6 +- .../chat_single_contact_item_widget.dart | 9 +- lib/contacts/views/availability_widget.dart | 19 +- lib/contacts/views/edit_contact_form.dart | 8 +- lib/theme/models/scale_scheme.dart | 30 ++ .../repository/processor_repository.dart | 10 +- lib/veilid_processor/views/developer.dart | 2 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- macos/Podfile.lock | 18 +- .../example/macos/Runner/AppDelegate.swift | 2 +- packages/veilid_support/example/pubspec.lock | 169 +++++----- .../lib/dht_support/src/dht_log/dht_log.dart | 9 +- .../src/dht_log/dht_log_spine.dart | 9 +- packages/veilid_support/pubspec.lock | 197 ++++++------ pubspec.lock | 289 ++++++++++-------- 16 files changed, 454 insertions(+), 341 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 536f7a4..81058ea 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -50,7 +50,7 @@ PODS: - GTMSessionFetcher/Core (< 4.0, >= 3.3.2) - MLImage (= 1.0.0-beta5) - MLKitCommon (~> 11.0) - - mobile_scanner (5.1.1): + - mobile_scanner (5.2.3): - Flutter - GoogleMLKit/BarcodeScanning (~> 6.0.0) - nanopb (2.30910.0): @@ -77,7 +77,7 @@ PODS: - FlutterMacOS - smart_auth (0.0.1): - Flutter - - sqflite (0.0.3): + - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - system_info_plus (0.0.1): @@ -101,7 +101,7 @@ DEPENDENCIES: - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - smart_auth (from `.symlinks/plugins/smart_auth/ios`) - - sqflite (from `.symlinks/plugins/sqflite/darwin`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - system_info_plus (from `.symlinks/plugins/system_info_plus/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - veilid (from `.symlinks/plugins/veilid/ios`) @@ -148,8 +148,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" smart_auth: :path: ".symlinks/plugins/smart_auth/ios" - sqflite: - :path: ".symlinks/plugins/sqflite/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" system_info_plus: :path: ".symlinks/plugins/system_info_plus/ios" url_launcher_ios: @@ -172,10 +172,10 @@ SPEC CHECKSUMS: MLKitBarcodeScanning: 10ca0845a6d15f2f6e911f682a1998b68b973e8b MLKitCommon: afec63980417d29ffbb4790529a1b0a2291699e1 MLKitVision: e858c5f125ecc288e4a31127928301eaba9ae0c1 - mobile_scanner: 8564358885a9253c43f822435b70f9345c87224f + mobile_scanner: 96e91f2e1fb396bb7df8da40429ba8dfad664740 nanopb: 438bc412db1928dac798aa6fd75726007be04262 native_device_orientation: 348b10c346a60ebbc62fb235a4fdb5d1b61a8f55 - package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 pasteboard: 982969ebaa7c78af3e6cc7761e8f5e77565d9ce0 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 printing: 233e1b73bd1f4a05615548e9b5a324c98588640b @@ -183,7 +183,7 @@ SPEC CHECKSUMS: share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec + sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13 system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe veilid: f5c2e662f91907b30cf95762619526ac3e4512fd diff --git a/lib/account_manager/views/edit_profile_form.dart b/lib/account_manager/views/edit_profile_form.dart index 54e61d1..1774bff 100644 --- a/lib/account_manager/views/edit_profile_form.dart +++ b/lib/account_manager/views/edit_profile_form.dart @@ -79,6 +79,9 @@ class _EditProfileFormState extends State { FormBuilderDropdown _availabilityDropDown( BuildContext context) { + final theme = Theme.of(context); + final scale = theme.extension()!; + final initialValueX = widget.initialValueCallback(EditProfileForm.formFieldAvailability) as proto.Availability; @@ -105,7 +108,8 @@ class _EditProfileFormState extends State { .map((x) => DropdownMenuItem( value: x, child: Row(mainAxisSize: MainAxisSize.min, children: [ - AvailabilityWidget.availabilityIcon(x), + AvailabilityWidget.availabilityIcon( + x, scale.primaryScale.primaryText), Text(x == proto.Availability.AVAILABILITY_OFFLINE ? translate('availability.always_show_offline') : AvailabilityWidget.availabilityName(x)) diff --git a/lib/chat_list/views/chat_single_contact_item_widget.dart b/lib/chat_list/views/chat_single_contact_item_widget.dart index 4ac992f..d3db153 100644 --- a/lib/chat_list/views/chat_single_contact_item_widget.dart +++ b/lib/chat_list/views/chat_single_contact_item_widget.dart @@ -67,7 +67,12 @@ class ChatSingleContactItemWidget extends StatelessWidget { title: title, subtitle: subtitle, leading: avatar, - trailing: AvailabilityWidget(availability: availability), + trailing: AvailabilityWidget( + availability: availability, + color: _disabled + ? scale.grayScale.primaryText + : scale.secondaryScale.primaryText, + ), onTap: () { singleFuture(activeChatCubit, () async { activeChatCubit.setActiveChat(_localConversationRecordKey); @@ -75,7 +80,7 @@ class ChatSingleContactItemWidget extends StatelessWidget { }, endActions: [ SliderTileAction( - icon: Icons.delete, + //icon: Icons.delete, label: translate('button.delete'), actionScale: ScaleKind.tertiary, onPressed: (context) async { diff --git a/lib/contacts/views/availability_widget.dart b/lib/contacts/views/availability_widget.dart index d50e323..cf3e51a 100644 --- a/lib/contacts/views/availability_widget.dart +++ b/lib/contacts/views/availability_widget.dart @@ -8,17 +8,18 @@ import '../../proto/proto.dart' as proto; class AvailabilityWidget extends StatelessWidget { const AvailabilityWidget( {required this.availability, + required this.color, this.vertical = true, this.iconSize = 32, super.key}); - static Widget availabilityIcon(proto.Availability availability, + static Widget availabilityIcon(proto.Availability availability, Color color, {double size = 32}) { late final Widget iconData; switch (availability) { case proto.Availability.AVAILABILITY_AWAY: - iconData = - ImageIcon(const AssetImage('assets/images/toilet.png'), size: size); + iconData = ImageIcon(const AssetImage('assets/images/toilet.png'), + size: size, color: color); case proto.Availability.AVAILABILITY_BUSY: iconData = Icon(Icons.event_busy, size: size); case proto.Availability.AVAILABILITY_FREE: @@ -56,7 +57,7 @@ class AvailabilityWidget extends StatelessWidget { // final scaleConfig = theme.extension()!; final name = availabilityName(availability); - final icon = availabilityIcon(availability, size: iconSize); + final icon = availabilityIcon(availability, color, size: iconSize); return vertical ? Column( @@ -64,17 +65,20 @@ class AvailabilityWidget extends StatelessWidget { //mainAxisAlignment: MainAxisAlignment.center, children: [ icon, - Text(name, style: textTheme.labelSmall).paddingLTRB(0, 0, 0, 0) + Text(name, style: textTheme.labelSmall!.copyWith(color: color)) + .paddingLTRB(0, 0, 0, 0) ]) : Row(mainAxisSize: MainAxisSize.min, children: [ icon, - Text(name, style: textTheme.labelSmall).paddingLTRB(8, 0, 0, 0) + Text(name, style: textTheme.labelSmall!.copyWith(color: color)) + .paddingLTRB(8, 0, 0, 0) ]); } //////////////////////////////////////////////////////////////////////////// final proto.Availability availability; + final Color color; final bool vertical; final double iconSize; @@ -85,6 +89,7 @@ class AvailabilityWidget extends StatelessWidget { ..add( DiagnosticsProperty('availability', availability)) ..add(DiagnosticsProperty('vertical', vertical)) - ..add(DoubleProperty('iconSize', iconSize)); + ..add(DoubleProperty('iconSize', iconSize)) + ..add(ColorProperty('color', color)); } } diff --git a/lib/contacts/views/edit_contact_form.dart b/lib/contacts/views/edit_contact_form.dart index e515f14..7803ab2 100644 --- a/lib/contacts/views/edit_contact_form.dart +++ b/lib/contacts/views/edit_contact_form.dart @@ -46,9 +46,8 @@ class _EditContactFormState extends State { super.initState(); } - Widget _availabilityWidget( - BuildContext context, proto.Availability availability) => - AvailabilityWidget(availability: availability); + Widget _availabilityWidget(proto.Availability availability, Color color) => + AvailabilityWidget(availability: availability, color: color); @override Widget build(BuildContext context) { @@ -95,7 +94,8 @@ class _EditContactFormState extends State { ) .paddingSymmetric(vertical: 4), Row(mainAxisSize: MainAxisSize.min, children: [ - _availabilityWidget(context, widget.contact.profile.availability), + _availabilityWidget(widget.contact.profile.availability, + scale.primaryScale.primaryText), SelectableText(widget.contact.profile.status, style: textTheme.bodyMedium) .paddingSymmetric(horizontal: 8) diff --git a/lib/theme/models/scale_scheme.dart b/lib/theme/models/scale_scheme.dart index ac266bc..6dbc248 100644 --- a/lib/theme/models/scale_scheme.dart +++ b/lib/theme/models/scale_scheme.dart @@ -145,3 +145,33 @@ class ScaleConfig extends ThemeExtension { lerpDouble(borderRadiusScale, other.borderRadiusScale, t) ?? 1); } } + +class ScaleTheme extends ThemeExtension { + ScaleTheme({ + required this.scheme, + required this.config, + }); + + final ScaleScheme scheme; + final ScaleConfig config; + + @override + ScaleTheme copyWith({ + ScaleScheme? scheme, + ScaleConfig? config, + }) => + ScaleTheme( + scheme: scheme ?? this.scheme, + config: config ?? this.config, + ); + + @override + ScaleTheme lerp(ScaleTheme? other, double t) { + if (other is! ScaleTheme) { + return this; + } + return ScaleTheme( + scheme: scheme.lerp(other.scheme, t), + config: config.lerp(other.config, t)); + } +} diff --git a/lib/veilid_processor/repository/processor_repository.dart b/lib/veilid_processor/repository/processor_repository.dart index e021648..3622219 100644 --- a/lib/veilid_processor/repository/processor_repository.dart +++ b/lib/veilid_processor/repository/processor_repository.dart @@ -12,10 +12,12 @@ class ProcessorRepository { : startedUp = false, _controllerConnectionState = StreamController.broadcast(sync: true), processorConnectionState = ProcessorConnectionState( - attachment: const VeilidStateAttachment( + attachment: VeilidStateAttachment( state: AttachmentState.detached, publicInternetReady: false, - localNetworkReady: false), + localNetworkReady: false, + uptime: TimestampDuration(value: BigInt.zero), + attachedUptime: null), network: VeilidStateNetwork( started: false, bpsDown: BigInt.zero, @@ -96,7 +98,9 @@ class ProcessorRepository { attachment: VeilidStateAttachment( state: updateAttachment.state, publicInternetReady: updateAttachment.publicInternetReady, - localNetworkReady: updateAttachment.localNetworkReady)); + localNetworkReady: updateAttachment.localNetworkReady, + uptime: updateAttachment.uptime, + attachedUptime: updateAttachment.attachedUptime)); } void processUpdateConfig(VeilidUpdateConfig updateConfig) { diff --git a/lib/veilid_processor/views/developer.dart b/lib/veilid_processor/views/developer.dart index 549d228..e40677d 100644 --- a/lib/veilid_processor/views/developer.dart +++ b/lib/veilid_processor/views/developer.dart @@ -366,7 +366,7 @@ class _DeveloperPageState extends State { final _terminalController = TerminalController(); late final HistoryTextEditingController _historyController; - final _logLevelController = DropdownController(duration: 250.ms); + final _logLevelController = DropdownController(duration: 250.ms); final List> _logLevelDropdownItems = []; var _logLevelDropDown = log.level.logLevel; var _showEllet = false; diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index c311845..238bf3e 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -15,7 +15,7 @@ import screen_retriever import share_plus import shared_preferences_foundation import smart_auth -import sqflite +import sqflite_darwin import url_launcher_macos import veilid import window_manager diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 3a69524..9078901 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -2,7 +2,7 @@ PODS: - file_saver (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) - - mobile_scanner (5.1.1): + - mobile_scanner (5.2.3): - FlutterMacOS - package_info_plus (0.0.1): - FlutterMacOS @@ -22,7 +22,7 @@ PODS: - FlutterMacOS - smart_auth (0.0.1): - FlutterMacOS - - sqflite (0.0.3): + - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - url_launcher_macos (0.0.1): @@ -44,7 +44,7 @@ DEPENDENCIES: - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - smart_auth (from `Flutter/ephemeral/.symlinks/plugins/smart_auth/macos`) - - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`) + - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - veilid (from `Flutter/ephemeral/.symlinks/plugins/veilid/macos`) - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) @@ -72,8 +72,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin smart_auth: :path: Flutter/ephemeral/.symlinks/plugins/smart_auth/macos - sqflite: - :path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin + sqflite_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos veilid: @@ -84,8 +84,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: file_saver: 44e6fbf666677faf097302460e214e977fdd977b FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - mobile_scanner: 1efac1e53c294b24e3bb55bcc7f4deee0233a86b - package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c + mobile_scanner: 0a05256215b047af27b9495db3b77640055e8824 + package_info_plus: f5790acc797bf17c3e959e9d6cf162cc68ff7523 pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 printing: 1dd6a1fce2209ec240698e2439a4adbb9b427637 @@ -93,8 +93,8 @@ SPEC CHECKSUMS: share_plus: 36537c04ce0c3e3f5bd297ce4318b6d5ee5fd6cf shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 smart_auth: b38e3ab4bfe089eacb1e233aca1a2340f96c28e9 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 + sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13 + url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 veilid: a54f57b7bcf0e4e072fe99272d76ca126b2026d0 window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 diff --git a/packages/veilid_support/example/macos/Runner/AppDelegate.swift b/packages/veilid_support/example/macos/Runner/AppDelegate.swift index d53ef64..8e02df2 100644 --- a/packages/veilid_support/example/macos/Runner/AppDelegate.swift +++ b/packages/veilid_support/example/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/packages/veilid_support/example/pubspec.lock b/packages/veilid_support/example/pubspec.lock index 236861d..91b7eee 100644 --- a/packages/veilid_support/example/pubspec.lock +++ b/packages/veilid_support/example/pubspec.lock @@ -5,26 +5,31 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "72.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "6.7.0" args: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" async: dependency: transitive description: @@ -37,10 +42,10 @@ packages: dependency: "direct dev" description: name: async_tools - sha256: "93df8b92d54d92e3323c630277e902b4ad4f05f798b55cfbc451e98c3e2fb7ba" + sha256: bbded696bfcb1437d0ca510ac047f261f9c7494fea2c488dd32ba2800e7f49e8 url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "0.1.7" bloc: dependency: transitive description: @@ -53,10 +58,10 @@ packages: dependency: transitive description: name: bloc_advanced_tools - sha256: f0b2dbe028792c97d1eb30480ed4e8035b5c70ea3bcc95a9c5255142592857f7 + sha256: d8a680d8a0469456399fb26bae9f7a1d2a1420b5bdf75e204e0fadab9edb0811 url: "https://pub.dev" source: hosted - version: "0.1.7" + version: "0.1.8" boolean_selector: dependency: transitive description: @@ -69,10 +74,10 @@ packages: dependency: transitive description: name: change_case - sha256: "47c48c36f95f20c6d0ba03efabceff261d05026cca322cc2c4c01c343371b5bb" + sha256: "99cfdf2018c627c8a3af5a23ea4c414eb69c75c31322d23b9660ebc3cf30b514" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" characters: dependency: transitive description: @@ -109,26 +114,26 @@ packages: dependency: transitive description: name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" coverage: dependency: transitive description: name: coverage - sha256: "3945034e86ea203af7a056d98e98e42a5518fff200d6e8e6647e1886b07e936e" + sha256: "88b0fddbe4c92910fefc09cc0248f5e7f0cd23e450ded4c28f16ab8ee8f83268" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.10.0" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.6" cupertino_icons: dependency: "direct main" description: @@ -157,18 +162,18 @@ packages: dependency: transitive description: name: fast_immutable_collections - sha256: "533806a7f0c624c2e479d05d3fdce4c87109a7cd0db39b8cc3830d3a2e8dedc7" + sha256: c3c73f4f989d3302066e4ec94e6ec73b5dc872592d02194f49f1352d64126b8c url: "https://pub.dev" source: hosted - version: "10.2.3" + version: "10.2.4" ffi: dependency: transitive description: name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" file: dependency: transitive description: @@ -181,10 +186,10 @@ packages: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -209,10 +214,10 @@ packages: dependency: transitive 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: @@ -291,18 +296,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -323,10 +328,10 @@ packages: dependency: transitive description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" loggy: dependency: transitive description: @@ -335,6 +340,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" matcher: dependency: transitive description: @@ -347,26 +360,26 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "2.0.0" node_preamble: dependency: transitive description: @@ -395,18 +408,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: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.12" path_provider_foundation: dependency: transitive description: @@ -435,18 +448,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: @@ -507,18 +520,18 @@ packages: dependency: transitive description: name: shelf_static - sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" shelf_web_socket: 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 @@ -528,10 +541,10 @@ packages: dependency: transitive description: name: source_map_stack_trace - sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" source_maps: dependency: transitive description: @@ -608,34 +621,34 @@ packages: dependency: "direct dev" description: name: test - sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" + sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" url: "https://pub.dev" source: hosted - version: "1.25.2" + version: "1.25.7" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" test_core: dependency: transitive description: name: test_core - sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" + sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.4" typed_data: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" vector_math: dependency: transitive description: @@ -669,10 +682,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" watcher: dependency: transitive description: @@ -685,18 +698,26 @@ packages: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "3.0.1" webdriver: dependency: transitive description: @@ -713,22 +734,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" - win32: - dependency: transitive - description: - name: win32 - sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 - url: "https://pub.dev" - source: hosted - version: "5.5.1" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" yaml: dependency: transitive description: @@ -738,5 +751,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.19.1" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart index 8fb1999..bba3306 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart @@ -293,11 +293,12 @@ class DHTLog implements DHTDeleteable { //////////////////////////////////////////////////////////////// // Fields - // 56 subkeys * 512 segments * 36 bytes per typedkey = - // 1032192 bytes per record + // 55 subkeys * 512 segments * 36 bytes per typedkey = + // 1013760 bytes per record + // Leaves 34816 bytes for 0th subkey as head, 56 subkeys total // 512*36 = 18432 bytes per subkey - // 28672 shortarrays * 256 elements = 7340032 elements - static const spineSubkeys = 56; + // 28160 shortarrays * 256 elements = 7208960 elements + static const spineSubkeys = 55; static const segmentsPerSubkey = 512; // Internal representation refreshed from spine record diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart index 7d8c519..1ea48be 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart @@ -147,7 +147,7 @@ class _DHTLogSpine { if (!await writeSpineHead(old: (oldHead, oldTail))) { // Failed to write head means head got overwritten so write should // be considered failed - throw DHTExceptionOutdated(); + throw const DHTExceptionOutdated(); } return out; } on Exception { @@ -257,7 +257,7 @@ class _DHTLogSpine { final headDelta = _ringDistance(newHead, oldHead); final tailDelta = _ringDistance(newTail, oldTail); if (headDelta > _positionLimit ~/ 2 || tailDelta > _positionLimit ~/ 2) { - throw DHTExceptionInvalidData(); + throw const DHTExceptionInvalidData(); } } @@ -615,8 +615,9 @@ class _DHTLogSpine { await _spineRecord.watch(subkeys: [ValueSubkeyRange.single(0)]); // Update changes to the head record - // Don't watch for local changes because this class already handles - // notifying listeners and knows when it makes local changes + // xxx: check if this localChanges can be false... + // xxx: Don't watch for local changes because this class already handles + // xxx: notifying listeners and knows when it makes local changes _subscription ??= await _spineRecord.listen(localChanges: true, _onSpineChanged); } on Exception { diff --git a/packages/veilid_support/pubspec.lock b/packages/veilid_support/pubspec.lock index 20798e2..4262a13 100644 --- a/packages/veilid_support/pubspec.lock +++ b/packages/veilid_support/pubspec.lock @@ -5,34 +5,39 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "72.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "6.7.0" args: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" async: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" async_tools: dependency: "direct main" description: @@ -101,18 +106,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "1414d6d733a85d8ad2f1dfcb3ea7945759e35a123cb99ccfac75d0758f75edfa" + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" url: "https://pub.dev" source: hosted - version: "2.4.10" + version: "2.4.13" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.3.2" built_collection: dependency: transitive description: @@ -133,10 +138,10 @@ packages: dependency: transitive description: name: change_case - sha256: "47c48c36f95f20c6d0ba03efabceff261d05026cca322cc2c4c01c343371b5bb" + sha256: "99cfdf2018c627c8a3af5a23ea4c414eb69c75c31322d23b9660ebc3cf30b514" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" characters: dependency: transitive description: @@ -181,34 +186,34 @@ packages: dependency: transitive description: name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" coverage: dependency: transitive description: name: coverage - sha256: "3945034e86ea203af7a056d98e98e42a5518fff200d6e8e6647e1886b07e936e" + sha256: "88b0fddbe4c92910fefc09cc0248f5e7f0cd23e450ded4c28f16ab8ee8f83268" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.10.0" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.6" dart_style: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.7" equatable: dependency: "direct main" description: @@ -221,34 +226,34 @@ packages: dependency: "direct main" description: name: fast_immutable_collections - sha256: "533806a7f0c624c2e479d05d3fdce4c87109a7cd0db39b8cc3830d3a2e8dedc7" + sha256: c3c73f4f989d3302066e4ec94e6ec73b5dc872592d02194f49f1352d64126b8c url: "https://pub.dev" source: hosted - version: "10.2.3" + version: "10.2.4" ffi: dependency: transitive description: name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" file: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" fixnum: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" flutter: dependency: transitive description: flutter @@ -263,18 +268,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: @@ -303,10 +308,10 @@ 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_multi_server: dependency: transitive description: @@ -367,10 +372,10 @@ packages: dependency: transitive description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" loggy: dependency: "direct main" description: @@ -379,6 +384,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" matcher: dependency: transitive description: @@ -391,26 +404,26 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: "direct main" description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "2.0.0" node_preamble: dependency: transitive description: @@ -431,26 +444,26 @@ packages: dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_provider: dependency: "direct main" 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: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.12" path_provider_foundation: dependency: transitive description: @@ -479,18 +492,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: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -527,10 +540,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" shelf: dependency: transitive description: @@ -551,18 +564,18 @@ packages: dependency: transitive description: name: shelf_static - sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" shelf_web_socket: 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 @@ -588,10 +601,10 @@ packages: dependency: transitive description: name: source_map_stack_trace - sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" source_maps: dependency: transitive description: @@ -612,10 +625,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stream_channel: dependency: transitive description: @@ -636,10 +649,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.0" system_info2: dependency: transitive description: @@ -668,26 +681,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" + sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" url: "https://pub.dev" source: hosted - version: "1.25.2" + version: "1.25.8" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" test_core: dependency: transitive description: name: test_core - sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" + sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.5" timing: dependency: transitive description: @@ -700,10 +713,10 @@ packages: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" vector_math: dependency: transitive description: @@ -723,10 +736,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "7475cb4dd713d57b6f7464c0e13f06da0d535d8b2067e188962a59bac2cf280b" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.2" + version: "14.3.0" watcher: dependency: transitive description: @@ -739,18 +752,26 @@ packages: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "3.0.1" webkit_inspection_protocol: dependency: transitive description: @@ -759,22 +780,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" - win32: - dependency: transitive - description: - name: win32 - sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 - url: "https://pub.dev" - source: hosted - version: "5.5.1" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" yaml: dependency: transitive description: @@ -784,5 +797,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.19.1" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.lock b/pubspec.lock index 17cdf9b..4a839f3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,15 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "72.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" accordion: dependency: "direct main" description: @@ -21,10 +26,10 @@ packages: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "6.7.0" animated_bottom_navigation_bar: dependency: "direct main" description: @@ -53,10 +58,10 @@ packages: dependency: "direct main" description: name: ansicolor - sha256: "8bf17a8ff6ea17499e40a2d2542c2f481cd7615760c6d34065cb22bfd22e6880" + sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" archive: dependency: "direct main" description: @@ -69,18 +74,18 @@ packages: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" async: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" async_tools: dependency: "direct main" description: @@ -125,10 +130,10 @@ packages: dependency: transitive description: name: bidi - sha256: "1a7d0c696324b2089f72e7671fd1f1f64fef44c980f3cebc84e803967c597b63" + sha256: "9a712c7ddf708f7c41b1923aa83648a3ed44cfd75b04f72d598c45e5be287f9d" url: "https://pub.dev" source: hosted - version: "2.0.10" + version: "2.0.12" bloc: dependency: "direct main" description: @@ -197,18 +202,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" url: "https://pub.dev" source: hosted - version: "2.4.11" + version: "2.4.13" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 url: "https://pub.dev" source: hosted - version: "7.3.1" + version: "7.3.2" built_collection: dependency: transitive description: @@ -237,10 +242,10 @@ packages: dependency: transitive description: name: cached_network_image_platform_interface - sha256: ff0c949e323d2a1b52be73acce5b4a7b04063e61414c8ca542dbba47281630a7 + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.1.1" cached_network_image_web: dependency: transitive description: @@ -261,18 +266,18 @@ packages: dependency: transitive description: name: camera_android - sha256: "134b83167cc3c83199e8d75e5bcfde677fec843e7b2ca6b754a5b0b96d00d921" + sha256: "387c9861dc026b980e5ff16c949c5bcfecafa8825b8b74e41b24405eb9d39af6" url: "https://pub.dev" source: hosted - version: "0.10.9+10" + version: "0.10.9+14" camera_avfoundation: dependency: transitive description: name: camera_avfoundation - sha256: b5093a82537b64bb88d4244f8e00b5ba69e822a5994f47b31d11400e1db975e5 + sha256: "0d04cec8715b59fb6dc60eefb47e69024f51233c570e475b886dc9290568bca7" url: "https://pub.dev" source: hosted - version: "0.9.17+1" + version: "0.9.17+4" camera_platform_interface: dependency: transitive description: @@ -285,10 +290,10 @@ packages: dependency: transitive description: name: camera_web - sha256: b9235ec0a2ce949daec546f1f3d86f05c3921ed31c7d9ab6b7c03214d152fc2d + sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f" url: "https://pub.dev" source: hosted - version: "0.3.4" + version: "0.3.5" change_case: dependency: "direct main" description: @@ -373,34 +378,34 @@ packages: dependency: transitive description: name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" cool_dropdown: dependency: "direct main" description: name: cool_dropdown - sha256: "24400f57740b4779407586121e014bef241699ad2a52c506a7e1e7616cb68653" + sha256: "23926fd242b625bcb7ab30c1336498d60f78267518db439141ca19de403ab030" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" cross_file: dependency: transitive description: name: cross_file - sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" url: "https://pub.dev" source: hosted - version: "0.3.4+1" + version: "0.3.4+2" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.6" csslib: dependency: transitive description: @@ -421,10 +426,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.7" diffutil_dart: dependency: transitive description: @@ -437,18 +442,18 @@ packages: dependency: transitive description: name: dio - sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714 + sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" url: "https://pub.dev" source: hosted - version: "5.5.0+1" + version: "5.7.0" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac" + sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "2.0.0" equatable: dependency: "direct main" description: @@ -477,18 +482,18 @@ packages: dependency: transitive description: name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" file: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" file_saver: dependency: "direct main" description: @@ -501,10 +506,10 @@ packages: dependency: "direct main" description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -530,10 +535,10 @@ packages: dependency: transitive description: name: flutter_cache_manager - sha256: a77f77806a790eb9ba0118a5a3a936e81c4fea2b61533033b2b0c3d50bbde5ea + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "3.4.1" flutter_chat_types: dependency: "direct main" description: @@ -555,10 +560,10 @@ packages: dependency: "direct main" description: name: flutter_form_builder - sha256: "447f8808f68070f7df968e8063aada3c9d2e90e789b5b70f3b44e4b315212656" + sha256: c278ef69b08957d484f83413f0e77b656a39b7a7bb4eb8a295da3a820ecc6545 url: "https://pub.dev" source: hosted - version: "9.3.0" + version: "9.5.0" flutter_hooks: dependency: "direct main" description: @@ -608,18 +613,18 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de" + sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398" url: "https://pub.dev" source: hosted - version: "2.0.21" + version: "2.0.23" flutter_shaders: dependency: transitive description: name: flutter_shaders - sha256: "02750b545c01ff4d8e9bbe8f27a7731aa3778402506c67daa1de7f5fc3f4befe" + sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2" url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "0.1.3" flutter_slidable: dependency: "direct main" description: @@ -685,10 +690,10 @@ 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: @@ -733,10 +738,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "39dd52168d6c59984454183148dc3a5776960c61083adfc708cc79a7b3ce1ba8" + sha256: "6f1b756f6e863259a99135ff3c95026c3cdca17d10ebef2bba2261a25ddc8bbc" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.3.0" graphs: dependency: transitive description: @@ -805,10 +810,10 @@ packages: dependency: "direct main" description: name: image - sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" + sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.3.0" intl: dependency: "direct main" description: @@ -869,10 +874,10 @@ packages: dependency: transitive description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" loggy: dependency: "direct main" description: @@ -881,14 +886,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -909,26 +922,26 @@ packages: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" mobile_scanner: dependency: "direct main" description: name: mobile_scanner - sha256: b8c0e9afcfd52534f85ec666f3d52156f560b5e6c25b1e3d4fe2087763607926 + sha256: d234581c090526676fd8fab4ada92f35c6746e3fb4f05a399665d75a399fb760 url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "5.2.3" motion_toast: dependency: "direct main" description: name: motion_toast - sha256: "8dc8af93c606d0a08f2592591164f4a761099c5470e589f25689de6c601f124e" + sha256: "5a4775bf5a89a2402456047f194df5a5d6ac717af0d7694c8b9e37f324d1efd7" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" native_device_orientation: dependency: "direct main" description: @@ -965,10 +978,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "4de6c36df77ffbcef0a5aefe04669d33f2d18397fea228277b852a2d4e58e860" + sha256: df3eb3e0aed5c1107bb0fdb80a8e82e778114958b1c5ac5644fb1ac9cae8a998 url: "https://pub.dev" source: hosted - version: "8.0.1" + version: "8.1.0" package_info_plus_platform_interface: dependency: transitive description: @@ -1013,10 +1026,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb" + sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a url: "https://pub.dev" source: hosted - version: "2.2.9" + version: "2.2.12" path_provider_foundation: dependency: transitive description: @@ -1053,10 +1066,10 @@ packages: dependency: "direct main" description: name: pdf - sha256: "81d5522bddc1ef5c28e8f0ee40b71708761753c163e0c93a40df56fd515ea0f0" + sha256: "05df53f8791587402493ac97b9869d3824eccbc77d97855f4545cf72df3cae07" url: "https://pub.dev" source: hosted - version: "3.11.0" + version: "3.11.1" pdf_widget_wrapper: dependency: transitive description: @@ -1093,10 +1106,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -1181,10 +1194,10 @@ packages: dependency: "direct main" description: name: qr_code_dart_scan - sha256: "52912da40f5e40a197b890108af9d2a6baa0c5812b77bfb085c8ee9e3c4f1f52" + sha256: d5511d137f1ca5cb217fe79fa992616e0361a505a74b1e34499e68040a68b0c3 url: "https://pub.dev" source: hosted - version: "0.8.1" + version: "0.8.3" qr_flutter: dependency: "direct main" description: @@ -1197,10 +1210,10 @@ packages: dependency: transitive description: name: quiver - sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" radix_colors: dependency: "direct main" description: @@ -1286,34 +1299,34 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: c272f9cabca5a81adc9b0894381e9c1def363e980f960fa903c604c471b22f68 + sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294" + sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.3" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833" + sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.5.3" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1" + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: @@ -1326,18 +1339,18 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "3a293170d4d9403c3254ee05b84e62e8a9b3c5808ebd17de6a33fe9ea6457936" + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.2" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2" + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" shelf: dependency: transitive description: @@ -1452,26 +1465,50 @@ packages: dependency: transitive description: name: sqflite - sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d + sha256: "79a297dc3cc137e758c6a4baf83342b039e5a6d2436fcdf3f96a00adaaf2ad62" url: "https://pub.dev" source: hosted - version: "2.3.3+1" + version: "2.4.0" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + url: "https://pub.dev" + source: hosted + version: "2.4.0" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" + sha256: "4468b24876d673418a7b7147e5a08a715b4998a7ae69227acafaab762e0e5490" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.4+5" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "769733dddf94622d5541c73e4ddc6aa7b252d865285914b6fcd54a63c4b4f027" + url: "https://pub.dev" + source: hosted + version: "2.4.1-1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: "direct main" description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" star_menu: dependency: "direct main" description: @@ -1500,18 +1537,18 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" synchronized: dependency: transitive description: name: synchronized - sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" url: "https://pub.dev" source: hosted - version: "3.1.0+1" + version: "3.3.0+3" system_info2: dependency: transitive description: @@ -1540,10 +1577,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" timing: dependency: transitive description: @@ -1564,10 +1601,10 @@ packages: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" universal_io: dependency: transitive description: @@ -1588,18 +1625,18 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "94d8ad05f44c6d4e2ffe5567ab4d741b82d62e3c8e288cc1fcea45965edf47c9" + sha256: "8fc3bae0b68c02c47c5c86fa8bfa74471d42687b0eded01b78de87872db745e2" url: "https://pub.dev" source: hosted - version: "6.3.8" + version: "6.3.12" url_launcher_ios: dependency: transitive description: @@ -1612,18 +1649,18 @@ packages: dependency: transitive description: name: url_launcher_linux - sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_platform_interface: dependency: transitive description: @@ -1636,26 +1673,26 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" + sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" uuid: dependency: "direct main" description: name: uuid - sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff url: "https://pub.dev" source: hosted - version: "4.4.2" + version: "4.5.1" value_layout_builder: dependency: transitive description: @@ -1754,10 +1791,10 @@ packages: dependency: transitive description: name: win32 - sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9" + sha256: "2735daae5150e8b1dfeb3eb0544b4d3af0061e9e82cef063adcd583bdae4306a" url: "https://pub.dev" source: hosted - version: "5.5.3" + version: "5.7.0" window_manager: dependency: "direct main" description: @@ -1770,10 +1807,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" xml: dependency: transitive description: @@ -1823,5 +1860,5 @@ packages: source: hosted version: "1.1.2" sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.22.1" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" From e4c0162e3d3d1ca6ddc470c00bf276dca70e8fac Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Mon, 4 Nov 2024 10:44:45 -0600 Subject: [PATCH 23/93] Updated changelog for v0.4.5 release --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6f43c9..f7e369f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## v0.4.5 ## +- Updated veilid-core to v0.4.1 + - See Veilid changelog for specifics +- DHT speed and reliability improvements + ## v0.4.4 ## - Update beta dialog with expectations page - Temporarily disable relay selection aggressiveness From 57504c3625fe8bf1aa69b926759d75298f0ecf55 Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Mon, 4 Nov 2024 10:45:58 -0600 Subject: [PATCH 24/93] =?UTF-8?q?Version=20update:=20v0.4.4=20=E2=86=92=20?= =?UTF-8?q?v0.4.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 9571b62..f122962 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.4.4+0 +current_version = 0.4.5+0 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)\+(?P\d+) diff --git a/pubspec.yaml b/pubspec.yaml index c71d310..8b171f3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: veilidchat description: VeilidChat publish_to: 'none' -version: 0.4.4+17 +version: 0.4.5+18 environment: sdk: '>=3.2.0 <4.0.0' From ad99f703ff6340404a0e0d670b97f6721946fcb0 Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Mon, 4 Nov 2024 11:38:25 -0600 Subject: [PATCH 25/93] =?UTF-8?q?Version=20update:=20v0.4.4=20=E2=86=92=20?= =?UTF-8?q?v0.4.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile.lock | 12 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- pubspec.lock | 123 ++++++------------ 3 files changed, 50 insertions(+), 87 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 81058ea..c92c9bd 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -77,7 +77,7 @@ PODS: - FlutterMacOS - smart_auth (0.0.1): - Flutter - - sqflite_darwin (0.0.4): + - sqflite (0.0.3): - Flutter - FlutterMacOS - system_info_plus (0.0.1): @@ -101,7 +101,7 @@ DEPENDENCIES: - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - smart_auth (from `.symlinks/plugins/smart_auth/ios`) - - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + - sqflite (from `.symlinks/plugins/sqflite/darwin`) - system_info_plus (from `.symlinks/plugins/system_info_plus/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - veilid (from `.symlinks/plugins/veilid/ios`) @@ -148,8 +148,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" smart_auth: :path: ".symlinks/plugins/smart_auth/ios" - sqflite_darwin: - :path: ".symlinks/plugins/sqflite_darwin/darwin" + sqflite: + :path: ".symlinks/plugins/sqflite/darwin" system_info_plus: :path: ".symlinks/plugins/system_info_plus/ios" url_launcher_ios: @@ -183,10 +183,10 @@ SPEC CHECKSUMS: share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2 - sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13 + sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - veilid: f5c2e662f91907b30cf95762619526ac3e4512fd + veilid: 51243c25047dbc1ebbfd87d713560260d802b845 PODFILE CHECKSUM: 5d504085cd7c7a4d71ee600d7af087cb60ab75b2 diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 238bf3e..c311845 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -15,7 +15,7 @@ import screen_retriever import share_plus import shared_preferences_foundation import smart_auth -import sqflite_darwin +import sqflite import url_launcher_macos import veilid import window_manager diff --git a/pubspec.lock b/pubspec.lock index 4a839f3..b8cd074 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,15 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "72.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.2" + version: "67.0.0" accordion: dependency: "direct main" description: @@ -26,10 +21,10 @@ packages: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.4.1" animated_bottom_navigation_bar: dependency: "direct main" description: @@ -202,18 +197,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.4.11" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe url: "https://pub.dev" source: hosted - version: "7.3.2" + version: "7.3.1" built_collection: dependency: transitive description: @@ -266,10 +261,10 @@ packages: dependency: transitive description: name: camera_android - sha256: "387c9861dc026b980e5ff16c949c5bcfecafa8825b8b74e41b24405eb9d39af6" + sha256: "32f04948a284b71d938fe275616faf4957d07f9b3aab8021bfc8c418301a289e" url: "https://pub.dev" source: hosted - version: "0.10.9+14" + version: "0.10.9+11" camera_avfoundation: dependency: transitive description: @@ -426,10 +421,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "2.3.6" diffutil_dart: dependency: transitive description: @@ -560,10 +555,10 @@ packages: dependency: "direct main" description: name: flutter_form_builder - sha256: c278ef69b08957d484f83413f0e77b656a39b7a7bb4eb8a295da3a820ecc6545 + sha256: "447f8808f68070f7df968e8063aada3c9d2e90e789b5b70f3b44e4b315212656" url: "https://pub.dev" source: hosted - version: "9.5.0" + version: "9.3.0" flutter_hooks: dependency: "direct main" description: @@ -613,10 +608,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398" + sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" url: "https://pub.dev" source: hosted - version: "2.0.23" + version: "2.0.22" flutter_shaders: dependency: transitive description: @@ -690,10 +685,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" + sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 url: "https://pub.dev" source: hosted - version: "2.5.7" + version: "2.5.2" freezed_annotation: dependency: "direct main" description: @@ -886,14 +881,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" - macros: - dependency: transitive - description: - name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" - url: "https://pub.dev" - source: hosted - version: "0.1.2-main.4" matcher: dependency: transitive description: @@ -906,18 +893,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.8.0" meta: dependency: "direct main" description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.12.0" mime: dependency: transitive description: @@ -1026,10 +1013,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" url: "https://pub.dev" source: hosted - version: "2.2.12" + version: "2.2.10" path_provider_foundation: dependency: transitive description: @@ -1307,10 +1294,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab" + sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.3.2" shared_preferences_foundation: dependency: transitive description: @@ -1465,42 +1452,18 @@ packages: dependency: transitive description: name: sqflite - sha256: "79a297dc3cc137e758c6a4baf83342b039e5a6d2436fcdf3f96a00adaaf2ad62" + sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d url: "https://pub.dev" source: hosted - version: "2.4.0" - sqflite_android: - dependency: transitive - description: - name: sqflite_android - sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" - url: "https://pub.dev" - source: hosted - version: "2.4.0" + version: "2.3.3+1" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "4468b24876d673418a7b7147e5a08a715b4998a7ae69227acafaab762e0e5490" + sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" url: "https://pub.dev" source: hosted - version: "2.5.4+5" - sqflite_darwin: - dependency: transitive - description: - name: sqflite_darwin - sha256: "769733dddf94622d5541c73e4ddc6aa7b252d865285914b6fcd54a63c4b4f027" - url: "https://pub.dev" - source: hosted - version: "2.4.1-1" - sqflite_platform_interface: - dependency: transitive - description: - name: sqflite_platform_interface - sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" - url: "https://pub.dev" - source: hosted - version: "2.4.0" + version: "2.5.4" stack_trace: dependency: "direct main" description: @@ -1545,10 +1508,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" url: "https://pub.dev" source: hosted - version: "3.3.0+3" + version: "3.1.0+1" system_info2: dependency: transitive description: @@ -1577,10 +1540,10 @@ packages: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.0" timing: dependency: transitive description: @@ -1601,10 +1564,10 @@ packages: dependency: transitive description: name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.3.2" universal_io: dependency: transitive description: @@ -1633,10 +1596,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "8fc3bae0b68c02c47c5c86fa8bfa74471d42687b0eded01b78de87872db745e2" + sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 url: "https://pub.dev" source: hosted - version: "6.3.12" + version: "6.3.9" url_launcher_ios: dependency: transitive description: @@ -1739,7 +1702,7 @@ packages: path: "../veilid/veilid-flutter" relative: true source: path - version: "0.3.4" + version: "0.4.1" veilid_support: dependency: "direct main" description: @@ -1791,10 +1754,10 @@ packages: dependency: transitive description: name: win32 - sha256: "2735daae5150e8b1dfeb3eb0544b4d3af0061e9e82cef063adcd583bdae4306a" + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" url: "https://pub.dev" source: hosted - version: "5.7.0" + version: "5.5.4" window_manager: dependency: "direct main" description: @@ -1860,5 +1823,5 @@ packages: source: hosted version: "1.1.2" sdks: - dart: ">=3.5.0 <4.0.0" - flutter: ">=3.24.0" + dart: ">=3.4.1 <4.0.0" + flutter: ">=3.22.1" From c3cc1b6a6fae11883d9535f6876066c378c31058 Mon Sep 17 00:00:00 2001 From: Dumont Date: Tue, 12 Nov 2024 10:56:14 -0500 Subject: [PATCH 26/93] fix linux desktop builds Fixes the "No target file veilid-flutter-shared" error documented here: https://gitlab.com/veilid/veilid/-/issues/397 --- linux/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index b669b11..e71febc 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -122,6 +122,12 @@ foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) COMPONENT Runtime) endforeach(bundled_library) +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") From 768c649a555164398092b685d3fce0b6dd4bb0d8 Mon Sep 17 00:00:00 2001 From: "kimmy.zip" <15420938-kimmydotzip@users.noreply.gitlab.com> Date: Mon, 10 Feb 2025 03:00:31 +0000 Subject: [PATCH 27/93] Fix compatibility issues with Flutter 3.27.0 --- macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- pubspec.lock | 383 ++++++++++-------- pubspec.yaml | 2 +- windows/flutter/CMakeLists.txt | 7 +- 4 files changed, 218 insertions(+), 176 deletions(-) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index c311845..238bf3e 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -15,7 +15,7 @@ import screen_retriever import share_plus import shared_preferences_foundation import smart_auth -import sqflite +import sqflite_darwin import url_launcher_macos import veilid import window_manager diff --git a/pubspec.lock b/pubspec.lock index b8cd074..7e541f2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,15 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: "03f6da266a27a4538a69295ec142cb5717d7d4e5727b84658b63e1e1509bac9c" url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "79.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.3" accordion: dependency: "direct main" description: @@ -21,10 +26,10 @@ packages: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: c9040fc56483c22a5e04a9f6a251313118b1a3c42423770623128fa484115643 url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "7.2.0" animated_bottom_navigation_bar: dependency: "direct main" description: @@ -109,10 +114,10 @@ packages: dependency: transitive description: name: barcode - sha256: ab180ce22c6555d77d45f0178a523669db67f95856e3378259ef2ffeb43e6003 + sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" url: "https://pub.dev" source: hosted - version: "2.2.8" + version: "2.2.9" basic_utils: dependency: "direct main" description: @@ -157,58 +162,58 @@ packages: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" build: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.3" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" + sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573" url: "https://pub.dev" source: hosted - version: "2.4.11" + version: "2.4.14" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" url: "https://pub.dev" source: hosted - version: "7.3.1" + version: "8.0.0" built_collection: dependency: transitive description: @@ -221,10 +226,10 @@ packages: dependency: transitive description: name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" url: "https://pub.dev" source: hosted - version: "8.9.2" + version: "8.9.3" cached_network_image: dependency: transitive description: @@ -261,26 +266,26 @@ packages: dependency: transitive description: name: camera_android - sha256: "32f04948a284b71d938fe275616faf4957d07f9b3aab8021bfc8c418301a289e" + sha256: "007c57cdcace4751014071e3d42f2eb8a64a519254abed35b714223d81d66234" url: "https://pub.dev" source: hosted - version: "0.10.9+11" + version: "0.10.10" camera_avfoundation: dependency: transitive description: name: camera_avfoundation - sha256: "0d04cec8715b59fb6dc60eefb47e69024f51233c570e475b886dc9290568bca7" + sha256: "55eb9c216f25339a3faa55fc42826e2c4a45becefa1387fd50fce6ae9dd0c574" url: "https://pub.dev" source: hosted - version: "0.9.17+4" + version: "0.9.18+1" camera_platform_interface: dependency: transitive description: name: camera_platform_interface - sha256: b3ede1f171532e0d83111fe0980b46d17f1aa9788a07a2fbed07366bbdbb9061 + sha256: "953e7baed3a7c8fae92f7200afeb2be503ff1a17c3b4e4ed7b76f008c2810a31" url: "https://pub.dev" source: hosted - version: "2.8.0" + version: "2.9.0" camera_web: dependency: transitive description: @@ -293,10 +298,10 @@ packages: dependency: "direct main" description: name: change_case - sha256: "99cfdf2018c627c8a3af5a23ea4c414eb69c75c31322d23b9660ebc3cf30b514" + sha256: e41ef3df58521194ef8d7649928954805aeb08061917cf658322305e61568003 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" characters: dependency: transitive description: @@ -309,10 +314,10 @@ packages: dependency: "direct main" description: name: charcode - sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.0" charset: dependency: transitive description: @@ -357,18 +362,18 @@ packages: dependency: transitive description: name: code_builder - sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" url: "https://pub.dev" source: hosted - version: "4.10.0" + version: "4.10.1" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -405,10 +410,10 @@ packages: dependency: transitive description: name: csslib - sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -421,10 +426,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "3.0.1" diffutil_dart: dependency: transitive description: @@ -437,26 +442,26 @@ packages: dependency: transitive description: name: dio - sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" url: "https://pub.dev" source: hosted - version: "5.7.0" + version: "5.8.0+1" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" + sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" equatable: dependency: "direct main" description: name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.7" expansion_tile_group: dependency: "direct main" description: @@ -514,10 +519,10 @@ packages: dependency: "direct main" description: name: flutter_animate - sha256: "7c8a6594a9252dad30cc2ef16e33270b6248c4dedc3b3d06c86c4f3f4dc05ae5" + sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" url: "https://pub.dev" source: hosted - version: "4.5.0" + version: "4.5.2" flutter_bloc: dependency: "direct main" description: @@ -555,10 +560,10 @@ packages: dependency: "direct main" description: name: flutter_form_builder - sha256: "447f8808f68070f7df968e8063aada3c9d2e90e789b5b70f3b44e4b315212656" + sha256: "375da52998c72f80dec9187bd93afa7ab202b89d5d066699368ff96d39fd4876" url: "https://pub.dev" source: hosted - version: "9.3.0" + version: "9.7.0" flutter_hooks: dependency: "direct main" description: @@ -592,10 +597,10 @@ packages: dependency: "direct main" description: name: flutter_native_splash - sha256: aa06fec78de2190f3db4319dd60fdc8d12b2626e93ef9828633928c2dcaea840 + sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.4" flutter_parsed_text: dependency: transitive description: @@ -608,10 +613,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" + sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" url: "https://pub.dev" source: hosted - version: "2.0.22" + version: "2.0.24" flutter_shaders: dependency: transitive description: @@ -624,10 +629,10 @@ packages: dependency: "direct main" description: name: flutter_slidable - sha256: "2c5611c0b44e20d180e4342318e1bbc28b0a44ad2c442f5df16962606fd3e8e3" + sha256: a857de7ea701f276fd6a6c4c67ae885b60729a3449e42766bb0e655171042801 url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" flutter_spinkit: dependency: "direct main" description: @@ -640,18 +645,18 @@ packages: dependency: "direct main" description: name: flutter_sticky_header - sha256: "017f398fbb45a589e01491861ca20eb6570a763fd9f3888165a978e11248c709" + sha256: "7f76d24d119424ca0c95c146b8627a457e8de8169b0d584f766c2c545db8f8be" url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.7.0" flutter_svg: dependency: "direct main" description: name: flutter_svg - sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b url: "https://pub.dev" source: hosted - version: "2.0.10+1" + version: "2.0.17" flutter_translate: dependency: "direct main" description: @@ -677,18 +682,18 @@ packages: dependency: "direct main" description: name: form_builder_validators - sha256: c61ed7b1deecf0e1ebe49e2fa79e3283937c5a21c7e48e3ed9856a4a14e1191a + sha256: "517fb884183fff7a0ef3db7d375981011da26ee452f20fb3d2e788ad527ad01d" url: "https://pub.dev" source: hosted - version: "11.0.0" + version: "11.1.1" freezed: dependency: "direct dev" description: name: freezed - sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.8" freezed_annotation: dependency: "direct main" description: @@ -717,10 +722,10 @@ packages: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" globbing: dependency: transitive description: @@ -733,10 +738,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "6f1b756f6e863259a99135ff3c95026c3cdca17d10ebef2bba2261a25ddc8bbc" + sha256: "9b736a9fa879d8ad6df7932cbdcc58237c173ab004ef90d8377923d7ad731eaa" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.7.2" graphs: dependency: transitive description: @@ -757,34 +762,34 @@ packages: dependency: transitive description: name: html - sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" url: "https://pub.dev" source: hosted - version: "0.15.4" + version: "0.15.5" http: dependency: transitive description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" http_multi_server: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" hydrated_bloc: dependency: "direct main" description: @@ -821,10 +826,10 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" js: dependency: transitive description: @@ -845,10 +850,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + sha256: b0a98230538fe5d0b60a22fb6bf1b6cb03471b53e3324ff6069c591679dd59c9 url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.9.3" linkify: dependency: transitive description: @@ -881,6 +886,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + macros: + dependency: transitive + description: + name: macros + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" + url: "https://pub.dev" + source: hosted + version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -893,18 +906,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: "direct main" description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -957,26 +970,26 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: df3eb3e0aed5c1107bb0fdb80a8e82e778114958b1c5ac5644fb1ac9cae8a998 + sha256: b15fad91c4d3d1f2b48c053dd41cb82da007c27407dc9ab5f9aa59881d0e39d4 url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "8.1.4" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" pasteboard: dependency: "direct main" description: @@ -997,34 +1010,34 @@ packages: dependency: transitive description: name: path_parsing - sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" path_provider: dependency: "direct main" description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted - version: "2.2.10" + version: "2.2.15" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -1053,10 +1066,10 @@ packages: dependency: "direct main" description: name: pdf - sha256: "05df53f8791587402493ac97b9869d3824eccbc77d97855f4545cf72df3cae07" + sha256: adbdec5bc84d20e6c8d67f9c64270aa64d1e9e1ed529f0fef7e7bc7e9400f894 url: "https://pub.dev" source: hosted - version: "3.11.1" + version: "3.11.2" pdf_widget_wrapper: dependency: transitive description: @@ -1157,18 +1170,18 @@ packages: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.0" qr: dependency: transitive description: @@ -1286,26 +1299,26 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + sha256: "688ee90fbfb6989c980254a56cb26ebe9bb30a3a2dff439a78894211f73de67a" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.5.1" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" + sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.4" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -1342,18 +1355,18 @@ packages: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" signal_strength_indicator: dependency: "direct main" description: @@ -1366,7 +1379,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" sliver_expandable: dependency: "direct main" description: @@ -1412,26 +1425,26 @@ packages: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "2.0.0" source_helper: dependency: transitive description: name: source_helper - sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" url: "https://pub.dev" source: hosted - version: "1.3.4" + version: "1.3.5" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" split_view: dependency: "direct main" description: @@ -1452,26 +1465,50 @@ packages: dependency: transitive description: name: sqflite - sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d + sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" url: "https://pub.dev" source: hosted - version: "2.3.3+1" + version: "2.4.1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + url: "https://pub.dev" + source: hosted + version: "2.4.0" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" + sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.4+6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" + url: "https://pub.dev" + source: hosted + version: "2.4.1+1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: "direct main" description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" star_menu: dependency: "direct main" description: @@ -1484,34 +1521,34 @@ packages: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: "direct main" description: name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" synchronized: dependency: transitive description: name: synchronized - sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" url: "https://pub.dev" source: hosted - version: "3.1.0+1" + version: "3.3.0+3" system_info2: dependency: transitive description: @@ -1532,26 +1569,26 @@ packages: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.4" timing: dependency: transitive description: name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" transitioned_indexed_stack: dependency: "direct main" description: @@ -1564,10 +1601,10 @@ packages: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" universal_io: dependency: transitive description: @@ -1596,34 +1633,34 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" url: "https://pub.dev" source: hosted - version: "6.3.9" + version: "6.3.14" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -1636,18 +1673,18 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" uuid: dependency: "direct main" description: @@ -1660,34 +1697,34 @@ packages: dependency: transitive description: name: value_layout_builder - sha256: "98202ec1807e94ac72725b7f0d15027afde513c55c69ff3f41bcfccb950831bc" + sha256: c02511ea91ca5c643b514a33a38fa52536f74aa939ec367d02938b5ede6807fa url: "https://pub.dev" source: hosted - version: "0.3.1" + version: "0.4.0" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + sha256: "7ed22c21d7fdcc88dd6ba7860384af438cd220b251ad65dfc142ab722fabef61" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.16" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.13" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.16" vector_math: dependency: transitive description: @@ -1722,10 +1759,10 @@ packages: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web: dependency: transitive description: @@ -1746,18 +1783,18 @@ packages: dependency: transitive description: name: web_socket_channel - sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" win32: dependency: transitive description: name: win32 - sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e url: "https://pub.dev" source: hosted - version: "5.5.4" + version: "5.10.1" window_manager: dependency: "direct main" description: @@ -1794,10 +1831,10 @@ packages: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" zmodem: dependency: transitive description: @@ -1823,5 +1860,5 @@ packages: source: hosted version: "1.1.2" sdks: - dart: ">=3.4.1 <4.0.0" - flutter: ">=3.22.1" + dart: ">=3.6.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 8b171f3..6027eee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,7 +48,7 @@ dependencies: flutter_native_splash: ^2.4.0 flutter_slidable: ^3.1.0 flutter_spinkit: ^5.2.1 - flutter_sticky_header: ^0.6.5 + flutter_sticky_header: ^0.7.0 flutter_svg: ^2.0.10+1 flutter_translate: ^4.1.0 flutter_zoom_drawer: ^3.2.0 diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt index 930d207..903f489 100644 --- a/windows/flutter/CMakeLists.txt +++ b/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS From 39b0262d0e636dffa9964f90a44e192efd08fabf Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 18 Feb 2025 21:37:46 -0500 Subject: [PATCH 28/93] more log work --- .gitignore | 2 + android/app/build.gradle | 33 +- android/build.gradle | 13 - .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/settings.gradle | 30 +- dev-setup/flutter_config.sh | 10 +- ios/Podfile | 2 +- ios/Podfile.lock | 134 +++---- ios/Runner.xcodeproj/project.pbxproj | 3 + .../chat_single_contact_item_widget.dart | 27 +- lib/theme/models/contrast_generator.dart | 16 +- lib/theme/models/models.dart | 1 + lib/theme/models/radix_generator.dart | 25 +- lib/theme/models/scale_scheme.dart | 30 -- lib/theme/models/scale_theme.dart | 88 +++++ lib/theme/models/slider_tile.dart | 95 ++--- lib/tools/loggy.dart | 3 +- linux/flutter/generated_plugin_registrant.cc | 12 +- linux/flutter/generated_plugins.cmake | 3 +- macos/Flutter/GeneratedPluginRegistrant.swift | 6 +- macos/Podfile.lock | 44 +-- macos/Runner/AppDelegate.swift | 4 + .../example/android/app/build.gradle | 10 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../example/dev-setup/flutter_config.sh | 10 +- packages/veilid_support/example/pubspec.lock | 142 +++---- packages/veilid_support/example/pubspec.yaml | 2 +- .../veilid_support/lib/src/veilid_log.dart | 5 - packages/veilid_support/pubspec.lock | 235 ++++++------ packages/veilid_support/pubspec.yaml | 2 +- pubspec.lock | 355 +++++++++++++----- pubspec.yaml | 22 +- web/index.html | 132 ++++++- .../flutter/generated_plugin_registrant.cc | 9 +- windows/flutter/generated_plugins.cmake | 3 +- 35 files changed, 906 insertions(+), 606 deletions(-) create mode 100644 lib/theme/models/scale_theme.dart diff --git a/.gitignore b/.gitignore index 46dd51f..79e7a9c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/android/app/build.gradle b/android/app/build.gradle index f5e4e3d..9d84346 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,3 +1,9 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -21,10 +22,6 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - def buildConfig = 'debug' def keystoreProperties = new Properties() @@ -35,16 +32,16 @@ if (keystorePropertiesFile.exists()) { } android { - ndkVersion "26.3.11579264" + ndkVersion "27.0.12077973" compileSdkVersion flutter.compileSdkVersion - + compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } sourceSets { @@ -70,7 +67,7 @@ android { storePassword keystoreProperties['storePassword'] } } - + buildTypes { release { shrinkResources false @@ -82,7 +79,7 @@ android { } } } - + namespace 'com.veilid.veilidchat' } @@ -90,6 +87,4 @@ flutter { source '../..' } -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" -} +dependencies {} diff --git a/android/build.gradle b/android/build.gradle index dbb249e..bc157bd 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.9.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index e1ca574..afa1e8e 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 44e62bc..b1ae36a 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.8.0" apply false + id "org.jetbrains.kotlin.android" version "1.9.25" apply false +} + +include ":app" \ No newline at end of file diff --git a/dev-setup/flutter_config.sh b/dev-setup/flutter_config.sh index 1168d74..a1b8c8d 100755 --- a/dev-setup/flutter_config.sh +++ b/dev-setup/flutter_config.sh @@ -14,13 +14,13 @@ sed -i '' 's/MACOSX_DEPLOYMENT_TARGET = [^;]*/MACOSX_DEPLOYMENT_TARGET = 10.14.6 sed -i '' "s/platform :osx, '[^']*'/platform :osx, '10.14.6'/g" $APPDIR/macos/Podfile # Android: Set NDK version -if [[ "$TMPDIR" != "" ]]; then +if [[ "$TMPDIR" != "" ]]; then ANDTMP=$TMPDIR/andtmp_$(date +%s) -else +else ANDTMP=/tmp/andtmp_$(date +%s) fi cat < $ANDTMP - ndkVersion '26.3.11579264' + ndkVersion '27.0.12077973' EOF sed -i '' -e "/android {/r $ANDTMP" $APPDIR/android/app/build.gradle rm -- $ANDTMP @@ -29,7 +29,7 @@ rm -- $ANDTMP sed -i '' 's/minSdkVersion .*/minSdkVersion Math.max(flutter.minSdkVersion, 24)/g' $APPDIR/android/app/build.gradle # Android: Set gradle plugin version -sed -i '' "s/classpath \'com.android.tools.build:gradle:[^\']*\'/classpath 'com.android.tools.build:gradle:7.2.0'/g" $APPDIR/android/build.gradle +sed -i '' "s/classpath \'com.android.tools.build:gradle:[^\']*\'/classpath 'com.android.tools.build:gradle:8.8.0'/g" $APPDIR/android/build.gradle # Android: Set gradle version -sed -i '' 's/distributionUrl=.*/distributionUrl=https:\/\/services.gradle.org\/distributions\/gradle-7.6.3-all.zip/g' $APPDIR/android/gradle/wrapper/gradle-wrapper.properties +sed -i '' 's/distributionUrl=.*/distributionUrl=https:\/\/services.gradle.org\/distributions\/gradle-8.10.2-all.zip/g' $APPDIR/android/gradle/wrapper/gradle-wrapper.properties diff --git a/ios/Podfile b/ios/Podfile index 2cbcaa2..572283f 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '12.4' +platform :ios, '15.6' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c92c9bd..c00efec 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -4,60 +4,56 @@ PODS: - file_saver (0.0.1): - Flutter - Flutter (1.0.0) - - flutter_native_splash (0.0.1): + - flutter_native_splash (2.4.3): - Flutter - - GoogleDataTransport (9.4.1): - - GoogleUtilities/Environment (~> 7.7) - - nanopb (< 2.30911.0, >= 2.30908.0) - - PromisesObjC (< 3.0, >= 1.2) - - GoogleMLKit/BarcodeScanning (6.0.0): + - GoogleDataTransport (10.1.0): + - nanopb (~> 3.30910.0) + - PromisesObjC (~> 2.4) + - GoogleMLKit/BarcodeScanning (7.0.0): - GoogleMLKit/MLKitCore - - MLKitBarcodeScanning (~> 5.0.0) - - GoogleMLKit/MLKitCore (6.0.0): - - MLKitCommon (~> 11.0.0) + - MLKitBarcodeScanning (~> 6.0.0) + - GoogleMLKit/MLKitCore (7.0.0): + - MLKitCommon (~> 12.0.0) - GoogleToolboxForMac/Defines (4.2.1) - GoogleToolboxForMac/Logger (4.2.1): - GoogleToolboxForMac/Defines (= 4.2.1) - "GoogleToolboxForMac/NSData+zlib (4.2.1)": - GoogleToolboxForMac/Defines (= 4.2.1) - - GoogleUtilities/Environment (7.13.3): + - GoogleUtilities/Environment (8.0.2): - GoogleUtilities/Privacy - - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.13.3): + - GoogleUtilities/Logger (8.0.2): - GoogleUtilities/Environment - GoogleUtilities/Privacy - - GoogleUtilities/Privacy (7.13.3) - - GoogleUtilities/UserDefaults (7.13.3): + - GoogleUtilities/Privacy (8.0.2) + - GoogleUtilities/UserDefaults (8.0.2): - GoogleUtilities/Logger - GoogleUtilities/Privacy - - GoogleUtilitiesComponents (1.1.0): - - GoogleUtilities/Logger - - GTMSessionFetcher/Core (3.4.1) - - MLImage (1.0.0-beta5) - - MLKitBarcodeScanning (5.0.0): - - MLKitCommon (~> 11.0) - - MLKitVision (~> 7.0) - - MLKitCommon (11.0.0): - - GoogleDataTransport (< 10.0, >= 9.4.1) + - GTMSessionFetcher/Core (3.5.0) + - MLImage (1.0.0-beta6) + - MLKitBarcodeScanning (6.0.0): + - MLKitCommon (~> 12.0) + - MLKitVision (~> 8.0) + - MLKitCommon (12.0.0): + - GoogleDataTransport (~> 10.0) - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1) - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)" - - GoogleUtilities/UserDefaults (< 8.0, >= 7.13.0) - - GoogleUtilitiesComponents (~> 1.0) + - GoogleUtilities/Logger (~> 8.0) + - GoogleUtilities/UserDefaults (~> 8.0) - GTMSessionFetcher/Core (< 4.0, >= 3.3.2) - - MLKitVision (7.0.0): + - MLKitVision (8.0.0): - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1) - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)" - GTMSessionFetcher/Core (< 4.0, >= 3.3.2) - - MLImage (= 1.0.0-beta5) - - MLKitCommon (~> 11.0) - - mobile_scanner (5.2.3): + - MLImage (= 1.0.0-beta6) + - MLKitCommon (~> 12.0) + - mobile_scanner (6.0.2): - Flutter - - GoogleMLKit/BarcodeScanning (~> 6.0.0) - - nanopb (2.30910.0): - - nanopb/decode (= 2.30910.0) - - nanopb/encode (= 2.30910.0) - - nanopb/decode (2.30910.0) - - nanopb/encode (2.30910.0) + - GoogleMLKit/BarcodeScanning (~> 7.0.0) + - nanopb (3.30910.0): + - nanopb/decode (= 3.30910.0) + - nanopb/encode (= 3.30910.0) + - nanopb/decode (3.30910.0) + - nanopb/encode (3.30910.0) - native_device_orientation (0.0.1): - Flutter - package_info_plus (0.4.5): @@ -75,9 +71,7 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - - smart_auth (0.0.1): - - Flutter - - sqflite (0.0.3): + - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - system_info_plus (0.0.1): @@ -100,8 +94,7 @@ DEPENDENCIES: - printing (from `.symlinks/plugins/printing/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - - smart_auth (from `.symlinks/plugins/smart_auth/ios`) - - sqflite (from `.symlinks/plugins/sqflite/darwin`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - system_info_plus (from `.symlinks/plugins/system_info_plus/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - veilid (from `.symlinks/plugins/veilid/ios`) @@ -112,7 +105,6 @@ SPEC REPOS: - GoogleMLKit - GoogleToolboxForMac - GoogleUtilities - - GoogleUtilitiesComponents - GTMSessionFetcher - MLImage - MLKitBarcodeScanning @@ -146,10 +138,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" - smart_auth: - :path: ".symlinks/plugins/smart_auth/ios" - sqflite: - :path: ".symlinks/plugins/sqflite/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" system_info_plus: :path: ".symlinks/plugins/system_info_plus/ios" url_launcher_ios: @@ -158,36 +148,34 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/veilid/ios" SPEC CHECKSUMS: - camera_avfoundation: dd002b0330f4981e1bbcb46ae9b62829237459a4 - file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 + camera_avfoundation: 04b44aeb14070126c6529e5ab82cc7c9fca107cf + file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 - GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a - GoogleMLKit: 97ac7af399057e99182ee8edfa8249e3226a4065 + flutter_native_splash: 6cad9122ea0fad137d23137dd14b937f3e90b145 + GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 + GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318 GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8 - GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 - GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe - GTMSessionFetcher: 8000756fc1c19d2e5697b90311f7832d2e33f6cd - MLImage: 1824212150da33ef225fbd3dc49f184cf611046c - MLKitBarcodeScanning: 10ca0845a6d15f2f6e911f682a1998b68b973e8b - MLKitCommon: afec63980417d29ffbb4790529a1b0a2291699e1 - MLKitVision: e858c5f125ecc288e4a31127928301eaba9ae0c1 - mobile_scanner: 96e91f2e1fb396bb7df8da40429ba8dfad664740 - nanopb: 438bc412db1928dac798aa6fd75726007be04262 - native_device_orientation: 348b10c346a60ebbc62fb235a4fdb5d1b61a8f55 - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - pasteboard: 982969ebaa7c78af3e6cc7761e8f5e77565d9ce0 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - printing: 233e1b73bd1f4a05615548e9b5a324c98588640b + GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d + GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 + MLImage: 0ad1c5f50edd027672d8b26b0fee78a8b4a0fc56 + MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2 + MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d + MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e + mobile_scanner: af8f71879eaba2bbcb4d86c6a462c3c0e7f23036 + nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + native_device_orientation: e3580675687d5034770da198f6839ebf2122ef94 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + printing: 54ff03f28fe9ba3aa93358afb80a8595a071dd07 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - veilid: 51243c25047dbc1ebbfd87d713560260d802b845 + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + system_info_plus: 555ce7047fbbf29154726db942ae785c29211740 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + veilid: 3ce560a4f2b568a77a9fd5e23090f2fa97581019 -PODFILE CHECKSUM: 5d504085cd7c7a4d71ee600d7af087cb60ab75b2 +PODFILE CHECKSUM: c8bf5b16c34712d5790b0b8d2472cc66ac0a8487 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 417e34c..06556a5 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -380,6 +380,7 @@ INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = VeilidChat; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -510,6 +511,7 @@ INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = VeilidChat; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -534,6 +536,7 @@ INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = VeilidChat; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/lib/chat_list/views/chat_single_contact_item_widget.dart b/lib/chat_list/views/chat_single_contact_item_widget.dart index d3db153..826fe37 100644 --- a/lib/chat_list/views/chat_single_contact_item_widget.dart +++ b/lib/chat_list/views/chat_single_contact_item_widget.dart @@ -29,8 +29,7 @@ class ChatSingleContactItemWidget extends StatelessWidget { BuildContext context, ) { final theme = Theme.of(context); - final scale = theme.extension()!; - final scaleConfig = theme.extension()!; + final scaleTheme = Theme.of(context).extension()!; final activeChatCubit = context.watch(); final selected = activeChatCubit.state == _localConversationRecordKey; @@ -44,18 +43,22 @@ class ChatSingleContactItemWidget extends StatelessWidget { ? proto.Availability.AVAILABILITY_UNSPECIFIED : _contact.profile.availability; + final scaleTileTheme = scaleTheme.tileTheme( + disabled: _disabled, + selected: selected, + scaleKind: ScaleKind.secondary); + final avatar = AvatarWidget( name: name, size: 34, - borderColor: _disabled - ? scale.grayScale.primaryText - : scale.secondaryScale.primaryText, + borderColor: scaleTileTheme.borderColor, foregroundColor: _disabled - ? scale.grayScale.primaryText - : scale.secondaryScale.primaryText, - backgroundColor: - _disabled ? scale.grayScale.primary : scale.secondaryScale.primary, - scaleConfig: scaleConfig, + ? scaleTheme.scheme.grayScale.primaryText + : scaleTheme.scheme.secondaryScale.primaryText, + backgroundColor: _disabled + ? scaleTheme.scheme.grayScale.primary + : scaleTheme.scheme.secondaryScale.primary, + scaleConfig: scaleTheme.config, textStyle: theme.textTheme.titleLarge!, ); @@ -69,9 +72,7 @@ class ChatSingleContactItemWidget extends StatelessWidget { leading: avatar, trailing: AvailabilityWidget( availability: availability, - color: _disabled - ? scale.grayScale.primaryText - : scale.secondaryScale.primaryText, + color: scaleTileTheme.textColor, ), onTap: () { singleFuture(activeChatCubit, () async { diff --git a/lib/theme/models/contrast_generator.dart b/lib/theme/models/contrast_generator.dart index 308b6b3..4271c1a 100644 --- a/lib/theme/models/contrast_generator.dart +++ b/lib/theme/models/contrast_generator.dart @@ -4,6 +4,7 @@ import 'radix_generator.dart'; import 'scale_color.dart'; import 'scale_input_decorator_theme.dart'; import 'scale_scheme.dart'; +import 'scale_theme.dart'; ScaleColor _contrastScaleColor( {required Brightness brightness, @@ -261,14 +262,16 @@ ThemeData contrastGenerator({ final colorScheme = scaleScheme.toColorScheme( brightness, ); + final scaleTheme = ScaleTheme( + textTheme: textTheme, scheme: scaleScheme, config: scaleConfig); - final themeData = ThemeData.from( + final baseThemeData = ThemeData.from( colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true); - return themeData.copyWith( - appBarTheme: themeData.appBarTheme.copyWith( + final themeData = baseThemeData.copyWith( + appBarTheme: baseThemeData.appBarTheme.copyWith( backgroundColor: scaleScheme.primaryScale.border, foregroundColor: scaleScheme.primaryScale.borderText), - bottomSheetTheme: themeData.bottomSheetTheme.copyWith( + bottomSheetTheme: baseThemeData.bottomSheetTheme.copyWith( elevation: 0, modalElevation: 0, shape: RoundedRectangleBorder( @@ -277,7 +280,7 @@ ThemeData contrastGenerator({ topRight: Radius.circular(16 * scaleConfig.borderRadiusScale)))), canvasColor: scaleScheme.primaryScale.subtleBackground, - chipTheme: themeData.chipTheme.copyWith( + chipTheme: baseThemeData.chipTheme.copyWith( backgroundColor: scaleScheme.primaryScale.elementBackground, selectedColor: scaleScheme.primaryScale.activeElementBackground, surfaceTintColor: scaleScheme.primaryScale.hoverElementBackground, @@ -303,5 +306,8 @@ ThemeData contrastGenerator({ extensions: >[ scaleScheme, scaleConfig, + scaleTheme ]); + + return themeData; } diff --git a/lib/theme/models/models.dart b/lib/theme/models/models.dart index e0ba490..45b54f9 100644 --- a/lib/theme/models/models.dart +++ b/lib/theme/models/models.dart @@ -2,5 +2,6 @@ export 'chat_theme.dart'; export 'radix_generator.dart'; export 'scale_color.dart'; export 'scale_scheme.dart'; +export 'scale_theme.dart'; export 'slider_tile.dart'; export 'theme_preference.dart'; diff --git a/lib/theme/models/radix_generator.dart b/lib/theme/models/radix_generator.dart index c3802e6..a2bdbb1 100644 --- a/lib/theme/models/radix_generator.dart +++ b/lib/theme/models/radix_generator.dart @@ -7,6 +7,7 @@ import '../../tools/tools.dart'; import 'scale_color.dart'; import 'scale_input_decorator_theme.dart'; import 'scale_scheme.dart'; +import 'scale_theme.dart'; enum RadixThemeColor { scarlet, // red + violet + tomato @@ -610,10 +611,14 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) { borderRadiusScale: 1, ); - final themeData = ThemeData.from( + final scaleTheme = ScaleTheme( + textTheme: textTheme, scheme: scaleScheme, config: scaleConfig); + + final baseThemeData = ThemeData.from( colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true); - return themeData.copyWith( - scrollbarTheme: themeData.scrollbarTheme.copyWith( + + final themeData = baseThemeData.copyWith( + scrollbarTheme: baseThemeData.scrollbarTheme.copyWith( thumbColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.pressed)) { return scaleScheme.primaryScale.border; @@ -636,10 +641,10 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) { } return scaleScheme.primaryScale.subtleBorder; })), - appBarTheme: themeData.appBarTheme.copyWith( + appBarTheme: baseThemeData.appBarTheme.copyWith( backgroundColor: scaleScheme.primaryScale.border, foregroundColor: scaleScheme.primaryScale.borderText), - bottomSheetTheme: themeData.bottomSheetTheme.copyWith( + bottomSheetTheme: baseThemeData.bottomSheetTheme.copyWith( elevation: 0, modalElevation: 0, shape: const RoundedRectangleBorder( @@ -647,7 +652,7 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) { topLeft: Radius.circular(16), topRight: Radius.circular(16)))), canvasColor: scaleScheme.primaryScale.subtleBackground, - chipTheme: themeData.chipTheme.copyWith( + chipTheme: baseThemeData.chipTheme.copyWith( backgroundColor: scaleScheme.primaryScale.elementBackground, selectedColor: scaleScheme.primaryScale.activeElementBackground, surfaceTintColor: scaleScheme.primaryScale.hoverElementBackground, @@ -666,5 +671,11 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) { ), inputDecorationTheme: ScaleInputDecoratorTheme(scaleScheme, scaleConfig, textTheme), - extensions: >[scaleScheme, scaleConfig]); + extensions: >[ + scaleScheme, + scaleConfig, + scaleTheme + ]); + + return themeData; } diff --git a/lib/theme/models/scale_scheme.dart b/lib/theme/models/scale_scheme.dart index 6dbc248..ac266bc 100644 --- a/lib/theme/models/scale_scheme.dart +++ b/lib/theme/models/scale_scheme.dart @@ -145,33 +145,3 @@ class ScaleConfig extends ThemeExtension { lerpDouble(borderRadiusScale, other.borderRadiusScale, t) ?? 1); } } - -class ScaleTheme extends ThemeExtension { - ScaleTheme({ - required this.scheme, - required this.config, - }); - - final ScaleScheme scheme; - final ScaleConfig config; - - @override - ScaleTheme copyWith({ - ScaleScheme? scheme, - ScaleConfig? config, - }) => - ScaleTheme( - scheme: scheme ?? this.scheme, - config: config ?? this.config, - ); - - @override - ScaleTheme lerp(ScaleTheme? other, double t) { - if (other is! ScaleTheme) { - return this; - } - return ScaleTheme( - scheme: scheme.lerp(other.scheme, t), - config: config.lerp(other.config, t)); - } -} diff --git a/lib/theme/models/scale_theme.dart b/lib/theme/models/scale_theme.dart new file mode 100644 index 0000000..95f7db9 --- /dev/null +++ b/lib/theme/models/scale_theme.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; + +import 'scale_scheme.dart'; + +class ScaleTileTheme { + ScaleTileTheme( + {required this.textColor, + required this.backgroundColor, + required this.borderColor, + required this.shapeBorder, + required this.largeTextStyle, + required this.smallTextStyle}); + + final Color textColor; + final Color backgroundColor; + final Color borderColor; + final ShapeBorder shapeBorder; + final TextStyle largeTextStyle; + final TextStyle smallTextStyle; +} + +class ScaleTheme extends ThemeExtension { + ScaleTheme({ + required this.textTheme, + required this.scheme, + required this.config, + }); + + final TextTheme textTheme; + final ScaleScheme scheme; + final ScaleConfig config; + + @override + ScaleTheme copyWith({ + TextTheme? textTheme, + ScaleScheme? scheme, + ScaleConfig? config, + }) => + ScaleTheme( + textTheme: textTheme ?? this.textTheme, + scheme: scheme ?? this.scheme, + config: config ?? this.config, + ); + + @override + ScaleTheme lerp(ScaleTheme? other, double t) { + if (other is! ScaleTheme) { + return this; + } + return ScaleTheme( + textTheme: TextTheme.lerp(textTheme, other.textTheme, t), + scheme: scheme.lerp(other.scheme, t), + config: config.lerp(other.config, t)); + } + + ScaleTileTheme tileTheme( + {bool disabled = false, + bool selected = false, + ScaleKind scaleKind = ScaleKind.primary}) { + final tileColor = scheme.scale(!disabled ? scaleKind : ScaleKind.gray); + + final borderColor = selected ? tileColor.hoverBorder : tileColor.border; + final backgroundColor = config.useVisualIndicators && !selected + ? tileColor.borderText + : borderColor; + final textColor = config.useVisualIndicators && !selected + ? borderColor + : tileColor.borderText; + + final largeTextStyle = textTheme.labelMedium!.copyWith(color: textColor); + final smallTextStyle = textTheme.labelSmall!.copyWith(color: textColor); + + final shapeBorder = RoundedRectangleBorder( + side: config.useVisualIndicators + ? BorderSide(width: 2, color: borderColor, strokeAlign: 0) + : BorderSide.none, + borderRadius: BorderRadius.circular(8 * config.borderRadiusScale)); + + return ScaleTileTheme( + textColor: textColor, + backgroundColor: backgroundColor, + borderColor: borderColor, + shapeBorder: shapeBorder, + largeTextStyle: largeTextStyle, + smallTextStyle: smallTextStyle, + ); + } +} diff --git a/lib/theme/models/slider_tile.dart b/lib/theme/models/slider_tile.dart index 2ce81c9..9b6957a 100644 --- a/lib/theme/models/slider_tile.dart +++ b/lib/theme/models/slider_tile.dart @@ -69,29 +69,15 @@ class SliderTile extends StatelessWidget { // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { final theme = Theme.of(context); - final scale = theme.extension()!; - final tileColor = scale.scale(!disabled ? tileScale : ScaleKind.gray); - final scaleConfig = theme.extension()!; - - final borderColor = selected ? tileColor.hoverBorder : tileColor.border; - final backgroundColor = scaleConfig.useVisualIndicators && !selected - ? tileColor.borderText - : borderColor; - final textColor = scaleConfig.useVisualIndicators && !selected - ? borderColor - : tileColor.borderText; + final scaleTheme = theme.extension()!; + final scaleTileTheme = scaleTheme.tileTheme( + disabled: disabled, selected: selected, scaleKind: tileScale); return Container( clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( - color: backgroundColor, - shape: RoundedRectangleBorder( - side: scaleConfig.useVisualIndicators - ? BorderSide(width: 2, color: borderColor, strokeAlign: 0) - : BorderSide.none, - borderRadius: - BorderRadius.circular(8 * scaleConfig.borderRadiusScale), - )), + color: scaleTileTheme.backgroundColor, + shape: scaleTileTheme.shapeBorder), child: Slidable( // Specify a key if the Slidable is dismissible. key: key, @@ -99,50 +85,39 @@ class SliderTile extends StatelessWidget { ? null : ActionPane( motion: const DrawerMotion(), - children: endActions - .map( - (a) => SlidableAction( - onPressed: disabled ? null : a.onPressed, - backgroundColor: scaleConfig.useVisualIndicators - ? (selected - ? tileColor.borderText - : tileColor.border) - : scale.scale(a.actionScale).primary, - foregroundColor: scaleConfig.useVisualIndicators - ? (selected - ? tileColor.border - : tileColor.borderText) - : scale.scale(a.actionScale).primaryText, - icon: subtitle.isEmpty ? a.icon : null, - label: a.label, - padding: const EdgeInsets.all(2)), - ) - .toList()), + children: endActions.map((a) { + final scaleActionTheme = scaleTheme.tileTheme( + disabled: disabled, + selected: true, + scaleKind: a.actionScale); + return SlidableAction( + onPressed: disabled ? null : a.onPressed, + backgroundColor: scaleActionTheme.backgroundColor, + foregroundColor: scaleActionTheme.textColor, + icon: subtitle.isEmpty ? a.icon : null, + label: a.label, + padding: const EdgeInsets.all(2)); + }).toList()), startActionPane: startActions.isEmpty ? null : ActionPane( motion: const DrawerMotion(), - children: startActions - .map( - (a) => SlidableAction( - onPressed: disabled ? null : a.onPressed, - backgroundColor: scaleConfig.useVisualIndicators - ? (selected - ? tileColor.borderText - : tileColor.border) - : scale.scale(a.actionScale).primary, - foregroundColor: scaleConfig.useVisualIndicators - ? (selected - ? tileColor.border - : tileColor.borderText) - : scale.scale(a.actionScale).primaryText, - icon: subtitle.isEmpty ? a.icon : null, - label: a.label, - padding: const EdgeInsets.all(2)), - ) - .toList()), + children: startActions.map((a) { + final scaleActionTheme = scaleTheme.tileTheme( + disabled: disabled, + selected: true, + scaleKind: a.actionScale); + + return SlidableAction( + onPressed: disabled ? null : a.onPressed, + backgroundColor: scaleActionTheme.backgroundColor, + foregroundColor: scaleActionTheme.textColor, + icon: subtitle.isEmpty ? a.icon : null, + label: a.label, + padding: const EdgeInsets.all(2)); + }).toList()), child: Padding( - padding: scaleConfig.useVisualIndicators + padding: scaleTheme.config.useVisualIndicators ? EdgeInsets.zero : const EdgeInsets.fromLTRB(0, 2, 0, 2), child: GestureDetector( @@ -157,8 +132,8 @@ class SliderTile extends StatelessWidget { softWrap: false, ), subtitle: subtitle.isNotEmpty ? Text(subtitle) : null, - iconColor: textColor, - textColor: textColor, + iconColor: scaleTileTheme.textColor, + textColor: scaleTileTheme.textColor, leading: FittedBox(child: leading), trailing: FittedBox(child: trailing)))))); } diff --git a/lib/tools/loggy.dart b/lib/tools/loggy.dart index 0bb259c..d8d4880 100644 --- a/lib/tools/loggy.dart +++ b/lib/tools/loggy.dart @@ -110,7 +110,8 @@ class CallbackPrinter extends LoggyPrinter { @override void onLog(LogRecord record) { - final out = record.pretty(); + final out = record.pretty().replaceAll('\uFFFD', ''); + if (Platform.isAndroid) { debugPrint(out); } else { diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 3acb238..0ac222b 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -9,8 +9,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -25,12 +24,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) printing_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin"); printing_plugin_register_with_registrar(printing_registrar); - g_autoptr(FlPluginRegistrar) screen_retriever_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); - screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); - g_autoptr(FlPluginRegistrar) smart_auth_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "SmartAuthPlugin"); - smart_auth_plugin_register_with_registrar(smart_auth_registrar); + g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); + screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index d09262f..a48f10f 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -6,8 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_saver pasteboard printing - screen_retriever - smart_auth + screen_retriever_linux url_launcher_linux veilid window_manager diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 238bf3e..a599497 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -11,10 +11,9 @@ import package_info_plus import pasteboard import path_provider_foundation import printing -import screen_retriever +import screen_retriever_macos import share_plus import shared_preferences_foundation -import smart_auth import sqflite_darwin import url_launcher_macos import veilid @@ -27,10 +26,9 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin")) - ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) + ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) - SmartAuthPlugin.register(with: registry.registrar(forPlugin: "SmartAuthPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) VeilidPlugin.register(with: registry.registrar(forPlugin: "VeilidPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 9078901..2d40a21 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -2,7 +2,7 @@ PODS: - file_saver (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) - - mobile_scanner (5.2.3): + - mobile_scanner (6.0.2): - FlutterMacOS - package_info_plus (0.0.1): - FlutterMacOS @@ -13,15 +13,13 @@ PODS: - FlutterMacOS - printing (1.0.0): - FlutterMacOS - - screen_retriever (0.0.1): + - screen_retriever_macos (0.0.1): - FlutterMacOS - share_plus (0.0.1): - FlutterMacOS - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - - smart_auth (0.0.1): - - FlutterMacOS - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS @@ -40,10 +38,9 @@ DEPENDENCIES: - pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - printing (from `Flutter/ephemeral/.symlinks/plugins/printing/macos`) - - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) + - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - - smart_auth (from `Flutter/ephemeral/.symlinks/plugins/smart_auth/macos`) - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - veilid (from `Flutter/ephemeral/.symlinks/plugins/veilid/macos`) @@ -64,14 +61,12 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin printing: :path: Flutter/ephemeral/.symlinks/plugins/printing/macos - screen_retriever: - :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos + screen_retriever_macos: + :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos share_plus: :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos shared_preferences_foundation: :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin - smart_auth: - :path: Flutter/ephemeral/.symlinks/plugins/smart_auth/macos sqflite_darwin: :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin url_launcher_macos: @@ -82,22 +77,21 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: - file_saver: 44e6fbf666677faf097302460e214e977fdd977b + file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - mobile_scanner: 0a05256215b047af27b9495db3b77640055e8824 - package_info_plus: f5790acc797bf17c3e959e9d6cf162cc68ff7523 - pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - printing: 1dd6a1fce2209ec240698e2439a4adbb9b427637 - screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 - share_plus: 36537c04ce0c3e3f5bd297ce4318b6d5ee5fd6cf - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - smart_auth: b38e3ab4bfe089eacb1e233aca1a2340f96c28e9 - sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13 - url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 - veilid: a54f57b7bcf0e4e072fe99272d76ca126b2026d0 - window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 + mobile_scanner: 0e365ed56cad24f28c0fd858ca04edefb40dfac3 + package_info_plus: f0052d280d17aa382b932f399edf32507174e870 + pasteboard: 278d8100149f940fb795d6b3a74f0720c890ecb7 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + printing: c4cf83c78fd684f9bc318e6aadc18972aa48f617 + screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f + share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 + veilid: 319e2e78836d7b3d08203596d0b4a0e244b68d29 + window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index 8e02df2..b3c1761 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/veilid_support/example/android/app/build.gradle b/packages/veilid_support/example/android/app/build.gradle index 033f4ac..2ba6503 100644 --- a/packages/veilid_support/example/android/app/build.gradle +++ b/packages/veilid_support/example/android/app/build.gradle @@ -23,19 +23,19 @@ if (flutterVersionName == null) { } android { - ndkVersion '26.3.11579264' - ndkVersion '26.3.11579264' + ndkVersion '27.0.12077973' + ndkVersion '27.0.12077973' namespace "com.example.example" compileSdk flutter.compileSdkVersion ndkVersion flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } sourceSets { diff --git a/packages/veilid_support/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/veilid_support/example/android/gradle/wrapper/gradle-wrapper.properties index c3433f7..6f8524c 100644 --- a/packages/veilid_support/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/veilid_support/example/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https://services.gradle.org/distributions/gradle-7.3.3-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/packages/veilid_support/example/dev-setup/flutter_config.sh b/packages/veilid_support/example/dev-setup/flutter_config.sh index 9cb53c7..a1b8c8d 100755 --- a/packages/veilid_support/example/dev-setup/flutter_config.sh +++ b/packages/veilid_support/example/dev-setup/flutter_config.sh @@ -14,13 +14,13 @@ sed -i '' 's/MACOSX_DEPLOYMENT_TARGET = [^;]*/MACOSX_DEPLOYMENT_TARGET = 10.14.6 sed -i '' "s/platform :osx, '[^']*'/platform :osx, '10.14.6'/g" $APPDIR/macos/Podfile # Android: Set NDK version -if [[ "$TMPDIR" != "" ]]; then +if [[ "$TMPDIR" != "" ]]; then ANDTMP=$TMPDIR/andtmp_$(date +%s) -else +else ANDTMP=/tmp/andtmp_$(date +%s) fi cat < $ANDTMP - ndkVersion '26.3.11579264' + ndkVersion '27.0.12077973' EOF sed -i '' -e "/android {/r $ANDTMP" $APPDIR/android/app/build.gradle rm -- $ANDTMP @@ -29,7 +29,7 @@ rm -- $ANDTMP sed -i '' 's/minSdkVersion .*/minSdkVersion Math.max(flutter.minSdkVersion, 24)/g' $APPDIR/android/app/build.gradle # Android: Set gradle plugin version -sed -i '' "s/classpath \'com.android.tools.build:gradle:[^\']*\'/classpath 'com.android.tools.build:gradle:7.2.0'/g" $APPDIR/android/build.gradle +sed -i '' "s/classpath \'com.android.tools.build:gradle:[^\']*\'/classpath 'com.android.tools.build:gradle:8.8.0'/g" $APPDIR/android/build.gradle # Android: Set gradle version -sed -i '' 's/distributionUrl=.*/distributionUrl=https:\/\/services.gradle.org\/distributions\/gradle-7.3.3-all.zip/g' $APPDIR/android/gradle/wrapper/gradle-wrapper.properties +sed -i '' 's/distributionUrl=.*/distributionUrl=https:\/\/services.gradle.org\/distributions\/gradle-8.10.2-all.zip/g' $APPDIR/android/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/veilid_support/example/pubspec.lock b/packages/veilid_support/example/pubspec.lock index 91b7eee..ade4030 100644 --- a/packages/veilid_support/example/pubspec.lock +++ b/packages/veilid_support/example/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" args: dependency: transitive description: @@ -74,10 +74,10 @@ packages: dependency: transitive description: name: change_case - sha256: "99cfdf2018c627c8a3af5a23ea4c414eb69c75c31322d23b9660ebc3cf30b514" + sha256: e41ef3df58521194ef8d7649928954805aeb08061917cf658322305e61568003 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" characters: dependency: transitive description: @@ -90,10 +90,10 @@ packages: dependency: transitive description: name: charcode - sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.0" clock: dependency: transitive description: @@ -106,10 +106,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -122,10 +122,10 @@ packages: dependency: transitive description: name: coverage - sha256: "88b0fddbe4c92910fefc09cc0248f5e7f0cd23e450ded4c28f16ab8ee8f83268" + sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.1" crypto: dependency: transitive description: @@ -146,10 +146,10 @@ packages: dependency: transitive description: name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.7" fake_async: dependency: transitive description: @@ -235,10 +235,10 @@ packages: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" globbing: dependency: transitive description: @@ -251,18 +251,18 @@ packages: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" integration_test: dependency: "direct dev" description: flutter @@ -272,10 +272,10 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" js: dependency: transitive description: @@ -296,18 +296,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -320,10 +320,10 @@ packages: dependency: "direct dev" description: name: lint_hard - sha256: "44d15ec309b1a8e1aff99069df9dcb1597f49d5f588f32811ca28fb7b38c32fe" + sha256: "638d2cce6d3d5499826be71311d18cded797a51351eaa1aee7a35a2f0f9bc46e" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" logging: dependency: transitive description: @@ -344,10 +344,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -392,10 +392,10 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" path: dependency: transitive description: @@ -408,26 +408,26 @@ packages: dependency: transitive description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted - version: "2.2.12" + version: "2.2.15" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -496,18 +496,18 @@ packages: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" shelf: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_packages_handler: dependency: transitive description: @@ -528,15 +528,15 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_map_stack_trace: dependency: transitive description: @@ -549,10 +549,10 @@ packages: dependency: transitive description: name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" url: "https://pub.dev" source: hosted - version: "0.10.12" + version: "0.10.13" source_span: dependency: transitive description: @@ -565,10 +565,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stream_channel: dependency: transitive description: @@ -581,10 +581,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" sync_http: dependency: transitive description: @@ -605,10 +605,10 @@ packages: dependency: transitive description: name: system_info_plus - sha256: b915c811c6605b802f3988859bc2bb79c95f735762a75b5451741f7a2b949d1b + sha256: df94187e95527f9cb459e6a9f6e0b1ea20c157d8029bc233de34b3c1e17e1c48 url: "https://pub.dev" source: hosted - version: "0.0.5" + version: "0.0.6" term_glyph: dependency: transitive description: @@ -621,26 +621,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" + sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" url: "https://pub.dev" source: hosted - version: "1.25.7" + version: "1.25.8" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" test_core: dependency: transitive description: name: test_core - sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" + sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" typed_data: dependency: transitive description: @@ -663,7 +663,7 @@ packages: path: "../../../../veilid/veilid-flutter" relative: true source: path - version: "0.3.4" + version: "0.4.1" veilid_support: dependency: "direct main" description: @@ -682,18 +682,18 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.0" watcher: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web: dependency: transitive description: @@ -714,18 +714,18 @@ packages: dependency: transitive description: name: web_socket_channel - sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" webdriver: dependency: transitive description: name: webdriver - sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4" webkit_inspection_protocol: dependency: transitive description: @@ -746,10 +746,10 @@ packages: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" sdks: dart: ">=3.5.0 <4.0.0" flutter: ">=3.24.0" diff --git a/packages/veilid_support/example/pubspec.yaml b/packages/veilid_support/example/pubspec.yaml index 17d17f4..c043a1b 100644 --- a/packages/veilid_support/example/pubspec.yaml +++ b/packages/veilid_support/example/pubspec.yaml @@ -17,7 +17,7 @@ dev_dependencies: async_tools: ^0.1.6 integration_test: sdk: flutter - lint_hard: ^4.0.0 + lint_hard: ^5.0.0 test: ^1.25.2 veilid_test: path: ../../../../veilid/veilid-flutter/packages/veilid_test diff --git a/packages/veilid_support/lib/src/veilid_log.dart b/packages/veilid_support/lib/src/veilid_log.dart index 0007754..3b35b5d 100644 --- a/packages/veilid_support/lib/src/veilid_log.dart +++ b/packages/veilid_support/lib/src/veilid_log.dart @@ -57,19 +57,14 @@ void processLog(VeilidLog log) { switch (log.logLevel) { case VeilidLogLevel.error: veilidLoggy.error(log.message, error, stackTrace); - break; case VeilidLogLevel.warn: veilidLoggy.warning(log.message, error, stackTrace); - break; case VeilidLogLevel.info: veilidLoggy.info(log.message, error, stackTrace); - break; case VeilidLogLevel.debug: veilidLoggy.debug(log.message, error, stackTrace); - break; case VeilidLogLevel.trace: veilidLoggy.trace(log.message, error, stackTrace); - break; } } diff --git a/packages/veilid_support/pubspec.lock b/packages/veilid_support/pubspec.lock index 4262a13..260c991 100644 --- a/packages/veilid_support/pubspec.lock +++ b/packages/veilid_support/pubspec.lock @@ -5,23 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 url: "https://pub.dev" source: hosted - version: "72.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.2" + version: "80.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "7.3.0" args: dependency: transitive description: @@ -34,10 +29,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" async_tools: dependency: "direct main" description: @@ -66,58 +61,58 @@ packages: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" build: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.4" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.4.15" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" url: "https://pub.dev" source: hosted - version: "7.3.2" + version: "8.0.0" built_collection: dependency: transitive description: @@ -130,18 +125,18 @@ packages: dependency: transitive description: name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" url: "https://pub.dev" source: hosted - version: "8.9.2" + version: "8.9.3" change_case: dependency: transitive description: name: change_case - sha256: "99cfdf2018c627c8a3af5a23ea4c414eb69c75c31322d23b9660ebc3cf30b514" + sha256: e41ef3df58521194ef8d7649928954805aeb08061917cf658322305e61568003 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" characters: dependency: transitive description: @@ -154,10 +149,10 @@ packages: dependency: "direct main" description: name: charcode - sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -170,18 +165,18 @@ packages: dependency: transitive description: name: code_builder - sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" url: "https://pub.dev" source: hosted - version: "4.10.0" + version: "4.10.1" collection: dependency: "direct main" description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -194,10 +189,10 @@ packages: dependency: transitive description: name: coverage - sha256: "88b0fddbe4c92910fefc09cc0248f5e7f0cd23e450ded4c28f16ab8ee8f83268" + sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.1" crypto: dependency: transitive description: @@ -210,18 +205,18 @@ packages: dependency: transitive description: name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "3.0.1" equatable: dependency: "direct main" description: name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.7" fast_immutable_collections: dependency: "direct main" description: @@ -268,10 +263,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" url: "https://pub.dev" source: hosted - version: "2.5.7" + version: "2.5.8" freezed_annotation: dependency: "direct main" description: @@ -292,10 +287,10 @@ packages: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" globbing: dependency: transitive description: @@ -312,30 +307,38 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + http: + dependency: transitive + description: + name: http + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + url: "https://pub.dev" + source: hosted + version: "1.3.0" http_multi_server: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" io: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" js: dependency: transitive description: @@ -356,18 +359,18 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a" url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.9.4" lint_hard: dependency: "direct dev" description: name: lint_hard - sha256: "44d15ec309b1a8e1aff99069df9dcb1597f49d5f588f32811ca28fb7b38c32fe" + sha256: "638d2cce6d3d5499826be71311d18cded797a51351eaa1aee7a35a2f0f9bc46e" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" logging: dependency: transitive description: @@ -384,22 +387,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" - macros: - dependency: transitive - description: - name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" - url: "https://pub.dev" - source: hosted - version: "0.1.2-main.4" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -436,10 +431,10 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" path: dependency: "direct main" description: @@ -452,26 +447,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted - version: "2.2.12" + version: "2.2.15" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -532,26 +527,26 @@ packages: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.0" shelf: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_packages_handler: dependency: transitive description: @@ -572,31 +567,31 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_gen: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "2.0.0" source_helper: dependency: transitive description: name: source_helper - sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" url: "https://pub.dev" source: hosted - version: "1.3.4" + version: "1.3.5" source_map_stack_trace: dependency: transitive description: @@ -609,50 +604,50 @@ packages: dependency: transitive description: name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" url: "https://pub.dev" source: hosted - version: "0.10.12" + version: "0.10.13" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" system_info2: dependency: transitive description: @@ -665,50 +660,50 @@ packages: dependency: transitive description: name: system_info_plus - sha256: b915c811c6605b802f3988859bc2bb79c95f735762a75b5451741f7a2b949d1b + sha256: df94187e95527f9cb459e6a9f6e0b1ea20c157d8029bc233de34b3c1e17e1c48 url: "https://pub.dev" source: hosted - version: "0.0.5" + version: "0.0.6" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test: dependency: "direct dev" description: name: test - sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" url: "https://pub.dev" source: hosted - version: "1.25.8" + version: "1.25.15" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.4" test_core: dependency: transitive description: name: test_core - sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.8" timing: dependency: transitive description: name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" typed_data: dependency: transitive description: @@ -731,23 +726,23 @@ packages: path: "../../../veilid/veilid-flutter" relative: true source: path - version: "0.3.4" + version: "0.4.1" vm_service: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "15.0.0" watcher: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web: dependency: transitive description: @@ -768,10 +763,10 @@ packages: dependency: transitive description: name: web_socket_channel - sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" webkit_inspection_protocol: dependency: transitive description: @@ -792,10 +787,10 @@ packages: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" sdks: - dart: ">=3.5.0 <4.0.0" + dart: ">=3.6.0 <4.0.0" flutter: ">=3.24.0" diff --git a/packages/veilid_support/pubspec.yaml b/packages/veilid_support/pubspec.yaml index 2634603..8ed1f58 100644 --- a/packages/veilid_support/pubspec.yaml +++ b/packages/veilid_support/pubspec.yaml @@ -36,5 +36,5 @@ dev_dependencies: build_runner: ^2.4.10 freezed: ^2.5.2 json_serializable: ^6.8.0 - lint_hard: ^4.0.0 + lint_hard: ^5.0.0 test: ^1.25.2 diff --git a/pubspec.lock b/pubspec.lock index 7e541f2..d6492c0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,15 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "03f6da266a27a4538a69295ec142cb5717d7d4e5727b84658b63e1e1509bac9c" + sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 url: "https://pub.dev" source: hosted - version: "79.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" + version: "80.0.0" accordion: dependency: "direct main" description: @@ -26,18 +21,18 @@ packages: dependency: transitive description: name: analyzer - sha256: c9040fc56483c22a5e04a9f6a251313118b1a3c42423770623128fa484115643 + sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" url: "https://pub.dev" source: hosted - version: "7.2.0" + version: "7.3.0" animated_bottom_navigation_bar: dependency: "direct main" description: name: animated_bottom_navigation_bar - sha256: "2b04a2ae4b0742669e60ddf309467d6a354cefd2d0cd20f4737b1efaf9834cda" + sha256: "94971fdfd53acd443acd0d17ce1cb5219ad833f20c75b50c55b205e54a5d6117" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.4.0" animated_switcher_transitions: dependency: "direct main" description: @@ -66,10 +61,10 @@ packages: dependency: "direct main" description: name: archive - sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" url: "https://pub.dev" source: hosted - version: "3.6.1" + version: "4.0.2" args: dependency: transitive description: @@ -82,10 +77,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" async_tools: dependency: "direct main" description: @@ -98,10 +93,10 @@ packages: dependency: "direct main" description: name: awesome_extensions - sha256: "07e52221467e651cab9219a26286245760831c3852ea2c54883a48a54f120d7c" + sha256: "91dc128e8cf01fbd3d3567b8f1dd1e3183cbf9fd6b1850e8b0fafce9a7eee0da" url: "https://pub.dev" source: hosted - version: "2.0.16" + version: "2.0.20" badges: dependency: "direct main" description: @@ -115,9 +110,11 @@ packages: description: name: barcode sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" + sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" url: "https://pub.dev" source: hosted version: "2.2.9" + version: "2.2.9" basic_utils: dependency: "direct main" description: @@ -163,57 +160,67 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted version: "2.1.2" + version: "2.1.2" build: dependency: transitive description: name: build sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted version: "2.4.2" + version: "2.4.2" build_config: dependency: transitive description: name: build_config sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted version: "1.1.2" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948" + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" url: "https://pub.dev" source: hosted - version: "4.0.3" + version: "4.0.4" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e" + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573" + sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573" url: "https://pub.dev" source: hosted version: "2.4.14" + version: "2.4.14" build_runner_core: dependency: transitive description: name: build_runner_core sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" url: "https://pub.dev" source: hosted version: "8.0.0" + version: "8.0.0" built_collection: dependency: transitive description: @@ -227,9 +234,11 @@ packages: description: name: built_value sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" + sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" url: "https://pub.dev" source: hosted version: "8.9.3" + version: "8.9.3" cached_network_image: dependency: transitive description: @@ -258,34 +267,36 @@ packages: dependency: transitive description: name: camera - sha256: dfa8fc5a1adaeb95e7a54d86a5bd56f4bb0e035515354c8ac6d262e35cec2ec8 + sha256: "413d2b34fe28496c35c69ede5b232fb9dd5ca2c3a4cb606b14efc1c7546cc8cb" url: "https://pub.dev" source: hosted - version: "0.10.6" - camera_android: + version: "0.11.1" + camera_android_camerax: dependency: transitive description: - name: camera_android - sha256: "007c57cdcace4751014071e3d42f2eb8a64a519254abed35b714223d81d66234" + name: camera_android_camerax + sha256: "7cc6adf1868bdcf4e63a56b24b41692dfbad2bec1cdceea451c77798f6a605c3" url: "https://pub.dev" source: hosted - version: "0.10.10" + version: "0.6.13" camera_avfoundation: dependency: transitive description: name: camera_avfoundation - sha256: "55eb9c216f25339a3faa55fc42826e2c4a45becefa1387fd50fce6ae9dd0c574" + sha256: "1eeb9ce7c9a397e312343fd7db337d95f35c3e65ad5a62ff637c8abce5102b98" url: "https://pub.dev" source: hosted - version: "0.9.18+1" + version: "0.9.18+8" camera_platform_interface: dependency: transitive description: name: camera_platform_interface sha256: "953e7baed3a7c8fae92f7200afeb2be503ff1a17c3b4e4ed7b76f008c2810a31" + sha256: "953e7baed3a7c8fae92f7200afeb2be503ff1a17c3b4e4ed7b76f008c2810a31" url: "https://pub.dev" source: hosted version: "2.9.0" + version: "2.9.0" camera_web: dependency: transitive description: @@ -299,9 +310,11 @@ packages: description: name: change_case sha256: e41ef3df58521194ef8d7649928954805aeb08061917cf658322305e61568003 + sha256: e41ef3df58521194ef8d7649928954805aeb08061917cf658322305e61568003 url: "https://pub.dev" source: hosted version: "2.2.0" + version: "2.2.0" characters: dependency: transitive description: @@ -315,9 +328,11 @@ packages: description: name: charcode sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a url: "https://pub.dev" source: hosted version: "1.4.0" + version: "1.4.0" charset: dependency: transitive description: @@ -363,17 +378,21 @@ packages: description: name: code_builder sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" url: "https://pub.dev" source: hosted version: "4.10.1" + version: "4.10.1" collection: dependency: transitive description: name: collection sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted version: "1.19.0" + version: "1.19.0" convert: dependency: transitive description: @@ -411,9 +430,11 @@ packages: description: name: csslib sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" url: "https://pub.dev" source: hosted version: "1.0.2" + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -427,9 +448,11 @@ packages: description: name: dart_style sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" url: "https://pub.dev" source: hosted version: "3.0.1" + version: "3.0.1" diffutil_dart: dependency: transitive description: @@ -443,33 +466,39 @@ packages: description: name: dio sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" url: "https://pub.dev" source: hosted version: "5.8.0+1" + version: "5.8.0+1" dio_web_adapter: dependency: transitive description: name: dio_web_adapter sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a + sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a url: "https://pub.dev" source: hosted version: "2.1.0" + version: "2.1.0" equatable: dependency: "direct main" description: name: equatable sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" url: "https://pub.dev" source: hosted version: "2.0.7" + version: "2.0.7" expansion_tile_group: dependency: "direct main" description: name: expansion_tile_group - sha256: "47615665d4e610dee0b6362de9e81003b56b150b5765ea5444a091762b5dc7d5" + sha256: "3be10b81d6d99d1213fe76a285993be0ea6092565ac100152deb6cdf9f5521dc" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "2.2.0" fast_immutable_collections: dependency: "direct main" description: @@ -498,10 +527,10 @@ packages: dependency: "direct main" description: name: file_saver - sha256: d375b351e3331663abbaf99747abd72f159260c58fbbdbca9f926f02c01bdc48 + sha256: "017a127de686af2d2fbbd64afea97052d95f2a0f87d19d25b87e097407bf9c1e" url: "https://pub.dev" source: hosted - version: "0.2.13" + version: "0.2.14" fixnum: dependency: "direct main" description: @@ -520,9 +549,11 @@ packages: description: name: flutter_animate sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" + sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" url: "https://pub.dev" source: hosted version: "4.5.2" + version: "4.5.2" flutter_bloc: dependency: "direct main" description: @@ -561,9 +592,11 @@ packages: description: name: flutter_form_builder sha256: "375da52998c72f80dec9187bd93afa7ab202b89d5d066699368ff96d39fd4876" + sha256: "375da52998c72f80dec9187bd93afa7ab202b89d5d066699368ff96d39fd4876" url: "https://pub.dev" source: hosted version: "9.7.0" + version: "9.7.0" flutter_hooks: dependency: "direct main" description: @@ -598,9 +631,11 @@ packages: description: name: flutter_native_splash sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7" + sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7" url: "https://pub.dev" source: hosted version: "2.4.4" + version: "2.4.4" flutter_parsed_text: dependency: transitive description: @@ -614,9 +649,11 @@ packages: description: name: flutter_plugin_android_lifecycle sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" + sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" url: "https://pub.dev" source: hosted version: "2.0.24" + version: "2.0.24" flutter_shaders: dependency: transitive description: @@ -629,10 +666,10 @@ packages: dependency: "direct main" description: name: flutter_slidable - sha256: a857de7ea701f276fd6a6c4c67ae885b60729a3449e42766bb0e655171042801 + sha256: ab7dbb16f783307c9d7762ede2593ce32c220ba2ba0fd540a3db8e9a3acba71a url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.0.0" flutter_spinkit: dependency: "direct main" description: @@ -646,17 +683,21 @@ packages: description: name: flutter_sticky_header sha256: "7f76d24d119424ca0c95c146b8627a457e8de8169b0d584f766c2c545db8f8be" + sha256: "7f76d24d119424ca0c95c146b8627a457e8de8169b0d584f766c2c545db8f8be" url: "https://pub.dev" source: hosted version: "0.7.0" + version: "0.7.0" flutter_svg: dependency: "direct main" description: name: flutter_svg sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b + sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b url: "https://pub.dev" source: hosted version: "2.0.17" + version: "2.0.17" flutter_translate: dependency: "direct main" description: @@ -683,17 +724,21 @@ packages: description: name: form_builder_validators sha256: "517fb884183fff7a0ef3db7d375981011da26ee452f20fb3d2e788ad527ad01d" + sha256: "517fb884183fff7a0ef3db7d375981011da26ee452f20fb3d2e788ad527ad01d" url: "https://pub.dev" source: hosted version: "11.1.1" + version: "11.1.1" freezed: dependency: "direct dev" description: name: freezed sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" url: "https://pub.dev" source: hosted version: "2.5.8" + version: "2.5.8" freezed_annotation: dependency: "direct main" description: @@ -714,18 +759,20 @@ packages: dependency: transitive description: name: get - sha256: e4e7335ede17452b391ed3b2ede016545706c01a02292a6c97619705e7d2a85e + sha256: c79eeb4339f1f3deffd9ec912f8a923834bec55f7b49c9e882b8fef2c139d425 url: "https://pub.dev" source: hosted - version: "4.6.6" + version: "4.7.2" glob: dependency: transitive description: name: glob sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted version: "2.1.3" + version: "2.1.3" globbing: dependency: transitive description: @@ -738,10 +785,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "9b736a9fa879d8ad6df7932cbdcc58237c173ab004ef90d8377923d7ad731eaa" + sha256: "04539267a740931c6d4479a10d466717ca5901c6fdfd3fcda09391bbb8ebd651" url: "https://pub.dev" source: hosted - version: "14.7.2" + version: "14.8.0" graphs: dependency: transitive description: @@ -763,33 +810,41 @@ packages: description: name: html sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" url: "https://pub.dev" source: hosted version: "0.15.5" + version: "0.15.5" http: dependency: transitive description: name: http sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f url: "https://pub.dev" source: hosted version: "1.3.0" + version: "1.3.0" http_multi_server: dependency: transitive description: name: http_multi_server sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted version: "3.2.2" + version: "3.2.2" http_parser: dependency: transitive description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted version: "4.1.2" + version: "4.1.2" hydrated_bloc: dependency: "direct main" description: @@ -802,18 +857,18 @@ packages: dependency: "direct dev" description: name: icons_launcher - sha256: "9b514ffed6ed69b232fd2bf34c44878c8526be71fc74129a658f35c04c9d4a9d" + sha256: a7c83fbc837dc6f81944ef35c3756f533bb2aba32fcca5cbcdb2dbcd877d5ae9 url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "3.0.0" image: dependency: "direct main" description: name: image - sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d + sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.5.2" intl: dependency: "direct main" description: @@ -827,9 +882,11 @@ packages: description: name: io sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted version: "1.0.5" + version: "1.0.5" js: dependency: transitive description: @@ -850,10 +907,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: b0a98230538fe5d0b60a22fb6bf1b6cb03471b53e3324ff6069c591679dd59c9 + sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a" url: "https://pub.dev" source: hosted - version: "6.9.3" + version: "6.9.4" linkify: dependency: transitive description: @@ -866,10 +923,10 @@ packages: dependency: "direct dev" description: name: lint_hard - sha256: "44d15ec309b1a8e1aff99069df9dcb1597f49d5f588f32811ca28fb7b38c32fe" + sha256: "638d2cce6d3d5499826be71311d18cded797a51351eaa1aee7a35a2f0f9bc46e" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" logging: dependency: transitive description: @@ -886,14 +943,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -930,10 +979,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: d234581c090526676fd8fab4ada92f35c6746e3fb4f05a399665d75a399fb760 + sha256: "91d28b825784e15572fdc39165c5733099ce0e69c6f6f0964ebdbf98a62130fd" url: "https://pub.dev" source: hosted - version: "5.2.3" + version: "6.0.6" motion_toast: dependency: "direct main" description: @@ -971,33 +1020,35 @@ packages: description: name: package_config sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" url: "https://pub.dev" source: hosted version: "2.1.1" + version: "2.1.1" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: b15fad91c4d3d1f2b48c053dd41cb82da007c27407dc9ab5f9aa59881d0e39d4 + sha256: "67eae327b1b0faf761964a1d2e5d323c797f3799db0e85aa232db8d9e922bc35" url: "https://pub.dev" source: hosted - version: "8.1.4" + version: "8.2.1" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b + sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.1.0" pasteboard: dependency: "direct main" description: name: pasteboard - sha256: "1c8b6a8b3f1d12e55d4e9404433cda1b4abe66db6b17bc2d2fb5965772c04674" + sha256: "7bf733f3a00c7188ec1f2c6f0612854248b302cf91ef3611a2b7bb141c0f9d55" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.3.0" path: dependency: "direct main" description: @@ -1011,33 +1062,41 @@ packages: description: name: path_parsing sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" url: "https://pub.dev" source: hosted version: "1.1.0" + version: "1.1.0" path_provider: dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted version: "2.1.5" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted version: "2.2.15" + version: "2.2.15" path_provider_foundation: dependency: transitive description: name: path_provider_foundation sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted version: "2.4.1" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -1066,10 +1125,10 @@ packages: dependency: "direct main" description: name: pdf - sha256: adbdec5bc84d20e6c8d67f9c64270aa64d1e9e1ed529f0fef7e7bc7e9400f894 + sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416" url: "https://pub.dev" source: hosted - version: "3.11.2" + version: "3.11.3" pdf_widget_wrapper: dependency: transitive description: @@ -1098,10 +1157,10 @@ packages: dependency: "direct main" description: name: pinput - sha256: "6d571e38a484f7515a52e89024ef416f11fa6171ac6f32303701374ab9890efa" + sha256: "8a73be426a91fefec90a7f130763ca39772d547e92f19a827cf4aa02e323d35a" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.1" platform: dependency: transitive description: @@ -1134,6 +1193,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + posix: + dependency: transitive + description: + name: posix + sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + url: "https://pub.dev" + source: hosted + version: "6.0.1" preload_page_view: dependency: "direct main" description: @@ -1146,10 +1213,10 @@ packages: dependency: "direct main" description: name: printing - sha256: cc4b256a5a89d5345488e3318897b595867f5181b8c5ed6fc63bfa5f2044aec3 + sha256: "482cd5a5196008f984bb43ed0e47cbfdca7373490b62f3b27b3299275bf22a93" url: "https://pub.dev" source: hosted - version: "5.13.1" + version: "5.14.2" protobuf: dependency: "direct main" description: @@ -1171,17 +1238,21 @@ packages: description: name: pub_semver sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" url: "https://pub.dev" source: hosted version: "2.1.5" + version: "2.1.5" pubspec_parse: dependency: transitive description: name: pubspec_parse sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted version: "1.5.0" + version: "1.5.0" qr: dependency: transitive description: @@ -1194,10 +1265,10 @@ packages: dependency: "direct main" description: name: qr_code_dart_scan - sha256: d5511d137f1ca5cb217fe79fa992616e0361a505a74b1e34499e68040a68b0c3 + sha256: a21340c4a2ca14e2e114915940fcad166f15c1a065fed8b4fede4a4aba5bc4ff url: "https://pub.dev" source: hosted - version: "0.8.3" + version: "0.9.11" qr_flutter: dependency: "direct main" description: @@ -1250,10 +1321,42 @@ packages: dependency: transitive description: name: screen_retriever - sha256: "6ee02c8a1158e6dae7ca430da79436e3b1c9563c8cf02f524af997c201ac2b90" + sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" url: "https://pub.dev" source: hosted - version: "0.1.9" + version: "0.2.0" + screen_retriever_linux: + dependency: transitive + description: + name: screen_retriever_linux + sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_macos: + dependency: transitive + description: + name: screen_retriever_macos + sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_platform_interface: + dependency: transitive + description: + name: screen_retriever_platform_interface + sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_windows: + dependency: transitive + description: + name: screen_retriever_windows + sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" + url: "https://pub.dev" + source: hosted + version: "0.2.0" screenshot: dependency: "direct main" description: @@ -1283,42 +1386,44 @@ packages: dependency: "direct main" description: name: share_plus - sha256: ef3489a969683c4f3d0239010cc8b7a2a46543a8d139e111c06c558875083544 + sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "10.1.4" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "0f9e4418835d1b2c3ae78fdb918251959106cefdbc4dd43526e182f80e82f6d4" + sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.2" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "688ee90fbfb6989c980254a56cb26ebe9bb30a3a2dff439a78894211f73de67a" + sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.5.2" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f" + sha256: ea86be7b7114f9e94fddfbb52649e59a03d6627ccd2387ebddcd6624719e9f16 url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "2.4.5" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted version: "2.5.4" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -1339,10 +1444,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" shared_preferences_windows: dependency: transitive description: @@ -1356,17 +1461,21 @@ packages: description: name: shelf sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted version: "1.4.2" + version: "1.4.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 url: "https://pub.dev" source: hosted version: "2.0.1" + version: "2.0.1" signal_strength_indicator: dependency: "direct main" description: @@ -1380,6 +1489,7 @@ packages: description: flutter source: sdk version: "0.0.0" + version: "0.0.0" sliver_expandable: dependency: "direct main" description: @@ -1404,14 +1514,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.12" - smart_auth: - dependency: transitive - description: - name: smart_auth - sha256: "88aa8fe66e951c78a307f26d1c29672dce2e9eb3da2e12e853864d0e615a73ad" - url: "https://pub.dev" - source: hosted - version: "2.0.0" sorted_list: dependency: "direct main" description: @@ -1426,25 +1528,31 @@ packages: description: name: source_gen sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" url: "https://pub.dev" source: hosted version: "2.0.0" + version: "2.0.0" source_helper: dependency: transitive description: name: source_helper sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" url: "https://pub.dev" source: hosted version: "1.3.5" + version: "1.3.5" source_span: dependency: transitive description: name: source_span sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted version: "1.10.1" + version: "1.10.1" split_view: dependency: "direct main" description: @@ -1466,9 +1574,11 @@ packages: description: name: sqflite sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" + sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" url: "https://pub.dev" source: hosted version: "2.4.1" + version: "2.4.1" sqflite_android: dependency: transitive description: @@ -1482,17 +1592,21 @@ packages: description: name: sqflite_common sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" + sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" url: "https://pub.dev" source: hosted version: "2.5.4+6" + version: "2.5.4+6" sqflite_darwin: dependency: transitive description: name: sqflite_darwin sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" + sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" url: "https://pub.dev" source: hosted version: "2.4.1+1" + version: "2.4.1+1" sqflite_platform_interface: dependency: transitive description: @@ -1506,9 +1620,11 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted version: "1.12.1" + version: "1.12.1" star_menu: dependency: "direct main" description: @@ -1522,25 +1638,31 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted version: "2.1.4" + version: "2.1.4" stream_transform: dependency: "direct main" description: name: stream_transform sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted version: "2.1.1" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted version: "1.4.1" + version: "1.4.1" synchronized: dependency: transitive description: @@ -1561,34 +1683,40 @@ packages: dependency: transitive description: name: system_info_plus - sha256: b915c811c6605b802f3988859bc2bb79c95f735762a75b5451741f7a2b949d1b + sha256: df94187e95527f9cb459e6a9f6e0b1ea20c157d8029bc233de34b3c1e17e1c48 url: "https://pub.dev" source: hosted - version: "0.0.5" + version: "0.0.6" term_glyph: dependency: transitive description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted version: "1.2.2" + version: "1.2.2" test_api: dependency: transitive description: name: test_api sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted version: "0.7.4" + version: "0.7.4" timing: dependency: transitive description: name: timing sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted version: "1.0.2" + version: "1.0.2" transitioned_indexed_stack: dependency: "direct main" description: @@ -1634,33 +1762,41 @@ packages: description: name: url_launcher_android sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" url: "https://pub.dev" source: hosted version: "6.3.14" + version: "6.3.14" url_launcher_ios: dependency: transitive description: name: url_launcher_ios sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" url: "https://pub.dev" source: hosted version: "6.3.2" + version: "6.3.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted version: "3.2.1" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted version: "3.2.2" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -1674,17 +1810,21 @@ packages: description: name: url_launcher_web sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" url: "https://pub.dev" source: hosted version: "2.4.0" + version: "2.4.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted version: "3.1.4" + version: "3.1.4" uuid: dependency: "direct main" description: @@ -1698,33 +1838,39 @@ packages: description: name: value_layout_builder sha256: c02511ea91ca5c643b514a33a38fa52536f74aa939ec367d02938b5ede6807fa + sha256: c02511ea91ca5c643b514a33a38fa52536f74aa939ec367d02938b5ede6807fa url: "https://pub.dev" source: hosted version: "0.4.0" + version: "0.4.0" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "7ed22c21d7fdcc88dd6ba7860384af438cd220b251ad65dfc142ab722fabef61" + sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" url: "https://pub.dev" source: hosted - version: "1.1.16" + version: "1.1.18" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" url: "https://pub.dev" source: hosted version: "1.1.13" + version: "1.1.13" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" url: "https://pub.dev" source: hosted version: "1.1.16" + version: "1.1.16" vector_math: dependency: transitive description: @@ -1740,6 +1886,7 @@ packages: relative: true source: path version: "0.4.1" + version: "0.4.1" veilid_support: dependency: "direct main" description: @@ -1760,17 +1907,19 @@ packages: description: name: watcher sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted version: "1.1.1" + version: "1.1.1" web: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.1.0" web_socket: dependency: transitive description: @@ -1784,25 +1933,29 @@ packages: description: name: web_socket_channel sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" url: "https://pub.dev" source: hosted version: "3.0.2" + version: "3.0.2" win32: dependency: transitive description: name: win32 sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e url: "https://pub.dev" source: hosted version: "5.10.1" + version: "5.10.1" window_manager: dependency: "direct main" description: name: window_manager - sha256: "8699323b30da4cdbe2aa2e7c9de567a6abd8a97d9a5c850a3c86dcd0b34bbfbf" + sha256: "732896e1416297c63c9e3fb95aea72d0355f61390263982a47fd519169dc5059" url: "https://pub.dev" source: hosted - version: "0.3.9" + version: "0.4.3" xdg_directories: dependency: transitive description: @@ -1832,9 +1985,11 @@ packages: description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted version: "3.1.3" + version: "3.1.3" zmodem: dependency: transitive description: @@ -1862,3 +2017,5 @@ packages: sdks: dart: ">=3.6.0 <4.0.0" flutter: ">=3.27.0" + dart: ">=3.6.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 6027eee..5bc5e26 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: animated_switcher_transitions: ^1.0.0 animated_theme_switcher: ^2.0.10 ansicolor: ^2.0.2 - archive: ^3.6.1 + archive: ^4.0.2 async_tools: ^0.1.7 awesome_extensions: ^2.0.16 badges: ^3.1.2 @@ -28,7 +28,7 @@ dependencies: cool_dropdown: ^2.1.0 cupertino_icons: ^1.0.8 equatable: ^2.0.5 - expansion_tile_group: ^1.2.4 + expansion_tile_group: ^2.2.0 fast_immutable_collections: ^10.2.4 file_saver: ^0.2.13 fixnum: ^1.1.0 @@ -46,7 +46,7 @@ dependencies: flutter_localizations: sdk: flutter flutter_native_splash: ^2.4.0 - flutter_slidable: ^3.1.0 + flutter_slidable: ^4.0.0 flutter_spinkit: ^5.2.1 flutter_sticky_header: ^0.7.0 flutter_svg: ^2.0.10+1 @@ -61,20 +61,20 @@ dependencies: json_annotation: ^4.9.0 loggy: ^2.0.3 meta: ^1.12.0 - mobile_scanner: ^5.1.1 + mobile_scanner: ^6.0.6 motion_toast: ^2.10.0 native_device_orientation: ^2.0.3 package_info_plus: ^8.0.0 - pasteboard: ^0.2.0 + pasteboard: ^0.3.0 path: ^1.9.0 path_provider: ^2.1.3 pdf: ^3.11.0 - pinput: ^4.0.0 + pinput: ^5.0.1 preload_page_view: ^0.2.0 printing: ^5.13.1 protobuf: ^3.1.0 provider: ^6.1.2 - qr_code_dart_scan: ^0.8.0 + qr_code_dart_scan: ^0.9.11 qr_flutter: ^4.1.0 radix_colors: ^1.0.4 reorderable_grid: ^1.0.10 @@ -85,7 +85,7 @@ dependencies: git: url: https://gitlab.com/veilid/Searchable-Listview.git ref: main - share_plus: ^9.0.0 + share_plus: ^10.1.4 shared_preferences: ^2.2.3 signal_strength_indicator: ^0.4.1 sliver_expandable: ^1.1.1 @@ -107,7 +107,7 @@ dependencies: path: ../veilid/veilid-flutter veilid_support: path: packages/veilid_support - window_manager: ^0.3.9 + window_manager: ^0.4.3 xterm: ^4.0.0 zxing2: ^0.2.3 @@ -124,9 +124,9 @@ dependencies: dev_dependencies: build_runner: ^2.4.11 freezed: ^2.5.2 - icons_launcher: ^2.1.7 + icons_launcher: ^3.0.0 json_serializable: ^6.8.0 - lint_hard: ^4.0.0 + lint_hard: ^5.0.0 flutter_native_splash: color: "#8588D0" diff --git a/web/index.html b/web/index.html index dfd1680..54ea8ab 100644 --- a/web/index.html +++ b/web/index.html @@ -1,3 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + VeilidChat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -43,10 +138,15 @@ - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 041a716..bb1eee2 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -9,9 +9,8 @@ #include #include #include -#include +#include #include -#include #include #include #include @@ -23,12 +22,10 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("PasteboardPlugin")); PrintingPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PrintingPlugin")); - ScreenRetrieverPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); + ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); - SmartAuthPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("SmartAuthPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); VeilidPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 17a8144..6cba61e 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -6,9 +6,8 @@ list(APPEND FLUTTER_PLUGIN_LIST file_saver pasteboard printing - screen_retriever + screen_retriever_windows share_plus - smart_auth url_launcher_windows veilid window_manager From 892fdffcf1b47ff341296227ec9eacfc6d765713 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 18 Feb 2025 21:46:08 -0500 Subject: [PATCH 29/93] merge conflicts --- pubspec.lock | 166 +++++---------------------------------------------- 1 file changed, 14 insertions(+), 152 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index d6492c0..9555644 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -110,11 +110,9 @@ packages: description: name: barcode sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" - sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" url: "https://pub.dev" source: hosted version: "2.2.9" - version: "2.2.9" basic_utils: dependency: "direct main" description: @@ -160,31 +158,25 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted version: "2.1.2" - version: "2.1.2" build: dependency: transitive description: name: build sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 - sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted version: "2.4.2" - version: "2.4.2" build_config: dependency: transitive description: name: build_config sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted version: "1.1.2" - version: "1.1.2" build_daemon: dependency: transitive description: @@ -205,22 +197,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573" - sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573" + sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" url: "https://pub.dev" source: hosted - version: "2.4.14" - version: "2.4.14" + version: "2.4.15" build_runner_core: dependency: transitive description: name: build_runner_core sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" url: "https://pub.dev" source: hosted version: "8.0.0" - version: "8.0.0" built_collection: dependency: transitive description: @@ -234,19 +222,17 @@ packages: description: name: built_value sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" - sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" url: "https://pub.dev" source: hosted version: "8.9.3" - version: "8.9.3" cached_network_image: dependency: transitive description: name: cached_network_image - sha256: "4a5d8d2c728b0f3d0245f69f921d7be90cae4c2fd5288f773088672c0893f819" + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "3.4.1" cached_network_image_platform_interface: dependency: transitive description: @@ -259,10 +245,10 @@ packages: dependency: transitive description: name: cached_network_image_web - sha256: "6322dde7a5ad92202e64df659241104a43db20ed594c41ca18de1014598d7996" + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.1" camera: dependency: transitive description: @@ -292,11 +278,9 @@ packages: description: name: camera_platform_interface sha256: "953e7baed3a7c8fae92f7200afeb2be503ff1a17c3b4e4ed7b76f008c2810a31" - sha256: "953e7baed3a7c8fae92f7200afeb2be503ff1a17c3b4e4ed7b76f008c2810a31" url: "https://pub.dev" source: hosted version: "2.9.0" - version: "2.9.0" camera_web: dependency: transitive description: @@ -310,11 +294,9 @@ packages: description: name: change_case sha256: e41ef3df58521194ef8d7649928954805aeb08061917cf658322305e61568003 - sha256: e41ef3df58521194ef8d7649928954805aeb08061917cf658322305e61568003 url: "https://pub.dev" source: hosted version: "2.2.0" - version: "2.2.0" characters: dependency: transitive description: @@ -328,11 +310,9 @@ packages: description: name: charcode sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a - sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a url: "https://pub.dev" source: hosted version: "1.4.0" - version: "1.4.0" charset: dependency: transitive description: @@ -378,21 +358,17 @@ packages: description: name: code_builder sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" url: "https://pub.dev" source: hosted version: "4.10.1" - version: "4.10.1" collection: dependency: transitive description: name: collection sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted version: "1.19.0" - version: "1.19.0" convert: dependency: transitive description: @@ -430,11 +406,9 @@ packages: description: name: csslib sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" url: "https://pub.dev" source: hosted version: "1.0.2" - version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -448,11 +422,9 @@ packages: description: name: dart_style sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" - sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" url: "https://pub.dev" source: hosted version: "3.0.1" - version: "3.0.1" diffutil_dart: dependency: transitive description: @@ -466,31 +438,25 @@ packages: description: name: dio sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" - sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" url: "https://pub.dev" source: hosted version: "5.8.0+1" - version: "5.8.0+1" dio_web_adapter: dependency: transitive description: name: dio_web_adapter sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a - sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a url: "https://pub.dev" source: hosted version: "2.1.0" - version: "2.1.0" equatable: dependency: "direct main" description: name: equatable sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" url: "https://pub.dev" source: hosted version: "2.0.7" - version: "2.0.7" expansion_tile_group: dependency: "direct main" description: @@ -549,11 +515,9 @@ packages: description: name: flutter_animate sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" - sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" url: "https://pub.dev" source: hosted version: "4.5.2" - version: "4.5.2" flutter_bloc: dependency: "direct main" description: @@ -592,11 +556,9 @@ packages: description: name: flutter_form_builder sha256: "375da52998c72f80dec9187bd93afa7ab202b89d5d066699368ff96d39fd4876" - sha256: "375da52998c72f80dec9187bd93afa7ab202b89d5d066699368ff96d39fd4876" url: "https://pub.dev" source: hosted version: "9.7.0" - version: "9.7.0" flutter_hooks: dependency: "direct main" description: @@ -631,11 +593,9 @@ packages: description: name: flutter_native_splash sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7" - sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7" url: "https://pub.dev" source: hosted version: "2.4.4" - version: "2.4.4" flutter_parsed_text: dependency: transitive description: @@ -649,11 +609,9 @@ packages: description: name: flutter_plugin_android_lifecycle sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" - sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" url: "https://pub.dev" source: hosted version: "2.0.24" - version: "2.0.24" flutter_shaders: dependency: transitive description: @@ -683,21 +641,17 @@ packages: description: name: flutter_sticky_header sha256: "7f76d24d119424ca0c95c146b8627a457e8de8169b0d584f766c2c545db8f8be" - sha256: "7f76d24d119424ca0c95c146b8627a457e8de8169b0d584f766c2c545db8f8be" url: "https://pub.dev" source: hosted version: "0.7.0" - version: "0.7.0" flutter_svg: dependency: "direct main" description: name: flutter_svg sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b - sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b url: "https://pub.dev" source: hosted version: "2.0.17" - version: "2.0.17" flutter_translate: dependency: "direct main" description: @@ -724,21 +678,17 @@ packages: description: name: form_builder_validators sha256: "517fb884183fff7a0ef3db7d375981011da26ee452f20fb3d2e788ad527ad01d" - sha256: "517fb884183fff7a0ef3db7d375981011da26ee452f20fb3d2e788ad527ad01d" url: "https://pub.dev" source: hosted version: "11.1.1" - version: "11.1.1" freezed: dependency: "direct dev" description: name: freezed sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" - sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" url: "https://pub.dev" source: hosted version: "2.5.8" - version: "2.5.8" freezed_annotation: dependency: "direct main" description: @@ -768,11 +718,9 @@ packages: description: name: glob sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted version: "2.1.3" - version: "2.1.3" globbing: dependency: transitive description: @@ -810,41 +758,33 @@ packages: description: name: html sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" - sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" url: "https://pub.dev" source: hosted version: "0.15.5" - version: "0.15.5" http: dependency: transitive description: name: http sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f - sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f url: "https://pub.dev" source: hosted version: "1.3.0" - version: "1.3.0" http_multi_server: dependency: transitive description: name: http_multi_server sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 - sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted version: "3.2.2" - version: "3.2.2" http_parser: dependency: transitive description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted version: "4.1.2" - version: "4.1.2" hydrated_bloc: dependency: "direct main" description: @@ -882,19 +822,17 @@ packages: description: name: io sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b - sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted version: "1.0.5" - version: "1.0.5" js: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" json_annotation: dependency: "direct main" description: @@ -971,10 +909,10 @@ packages: dependency: transitive description: name: mime - sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "2.0.0" mobile_scanner: dependency: "direct main" description: @@ -1020,11 +958,9 @@ packages: description: name: package_config sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" - sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" url: "https://pub.dev" source: hosted version: "2.1.1" - version: "2.1.1" package_info_plus: dependency: "direct main" description: @@ -1062,41 +998,33 @@ packages: description: name: path_parsing sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" - sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" url: "https://pub.dev" source: hosted version: "1.1.0" - version: "1.1.0" path_provider: dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted version: "2.1.5" - version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" - sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted version: "2.2.15" - version: "2.2.15" path_provider_foundation: dependency: transitive description: name: path_provider_foundation sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted version: "2.4.1" - version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -1238,21 +1166,17 @@ packages: description: name: pub_semver sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" - sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" url: "https://pub.dev" source: hosted version: "2.1.5" - version: "2.1.5" pubspec_parse: dependency: transitive description: name: pubspec_parse sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted version: "1.5.0" - version: "1.5.0" qr: dependency: transitive description: @@ -1410,20 +1334,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: ea86be7b7114f9e94fddfbb52649e59a03d6627ccd2387ebddcd6624719e9f16 + sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22 url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "2.4.6" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted version: "2.5.4" - version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -1461,21 +1383,17 @@ packages: description: name: shelf sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted version: "1.4.2" - version: "1.4.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 - sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "2.0.1" - version: "2.0.1" + version: "3.0.0" signal_strength_indicator: dependency: "direct main" description: @@ -1489,7 +1407,6 @@ packages: description: flutter source: sdk version: "0.0.0" - version: "0.0.0" sliver_expandable: dependency: "direct main" description: @@ -1528,31 +1445,25 @@ packages: description: name: source_gen sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" - sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" url: "https://pub.dev" source: hosted version: "2.0.0" - version: "2.0.0" source_helper: dependency: transitive description: name: source_helper sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" url: "https://pub.dev" source: hosted version: "1.3.5" - version: "1.3.5" source_span: dependency: transitive description: name: source_span sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted version: "1.10.1" - version: "1.10.1" split_view: dependency: "direct main" description: @@ -1574,11 +1485,9 @@ packages: description: name: sqflite sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" - sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" url: "https://pub.dev" source: hosted version: "2.4.1" - version: "2.4.1" sqflite_android: dependency: transitive description: @@ -1592,21 +1501,17 @@ packages: description: name: sqflite_common sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" - sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" url: "https://pub.dev" source: hosted version: "2.5.4+6" - version: "2.5.4+6" sqflite_darwin: dependency: transitive description: name: sqflite_darwin sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" - sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" url: "https://pub.dev" source: hosted version: "2.4.1+1" - version: "2.4.1+1" sqflite_platform_interface: dependency: transitive description: @@ -1620,11 +1525,9 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted version: "1.12.1" - version: "1.12.1" star_menu: dependency: "direct main" description: @@ -1638,31 +1541,25 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted version: "2.1.4" - version: "2.1.4" stream_transform: dependency: "direct main" description: name: stream_transform sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 - sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted version: "2.1.1" - version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted version: "1.4.1" - version: "1.4.1" synchronized: dependency: transitive description: @@ -1692,31 +1589,25 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted version: "1.2.2" - version: "1.2.2" test_api: dependency: transitive description: name: test_api sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted version: "0.7.4" - version: "0.7.4" timing: dependency: transitive description: name: timing sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted version: "1.0.2" - version: "1.0.2" transitioned_indexed_stack: dependency: "direct main" description: @@ -1762,41 +1653,33 @@ packages: description: name: url_launcher_android sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" - sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" url: "https://pub.dev" source: hosted version: "6.3.14" - version: "6.3.14" url_launcher_ios: dependency: transitive description: name: url_launcher_ios sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" - sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" url: "https://pub.dev" source: hosted version: "6.3.2" - version: "6.3.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted version: "3.2.1" - version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" - sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted version: "3.2.2" - version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -1810,21 +1693,17 @@ packages: description: name: url_launcher_web sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" - sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" url: "https://pub.dev" source: hosted version: "2.4.0" - version: "2.4.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted version: "3.1.4" - version: "3.1.4" uuid: dependency: "direct main" description: @@ -1838,11 +1717,9 @@ packages: description: name: value_layout_builder sha256: c02511ea91ca5c643b514a33a38fa52536f74aa939ec367d02938b5ede6807fa - sha256: c02511ea91ca5c643b514a33a38fa52536f74aa939ec367d02938b5ede6807fa url: "https://pub.dev" source: hosted version: "0.4.0" - version: "0.4.0" vector_graphics: dependency: transitive description: @@ -1856,21 +1733,17 @@ packages: description: name: vector_graphics_codec sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" - sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" url: "https://pub.dev" source: hosted version: "1.1.13" - version: "1.1.13" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" - sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" url: "https://pub.dev" source: hosted version: "1.1.16" - version: "1.1.16" vector_math: dependency: transitive description: @@ -1886,7 +1759,6 @@ packages: relative: true source: path version: "0.4.1" - version: "0.4.1" veilid_support: dependency: "direct main" description: @@ -1907,11 +1779,9 @@ packages: description: name: watcher sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted version: "1.1.1" - version: "1.1.1" web: dependency: transitive description: @@ -1933,21 +1803,17 @@ packages: description: name: web_socket_channel sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" - sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" url: "https://pub.dev" source: hosted version: "3.0.2" - version: "3.0.2" win32: dependency: transitive description: name: win32 sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e - sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e url: "https://pub.dev" source: hosted version: "5.10.1" - version: "5.10.1" window_manager: dependency: "direct main" description: @@ -1985,11 +1851,9 @@ packages: description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted version: "3.1.3" - version: "3.1.3" zmodem: dependency: transitive description: @@ -2017,5 +1881,3 @@ packages: sdks: dart: ">=3.6.0 <4.0.0" flutter: ">=3.27.0" - dart: ">=3.6.0 <4.0.0" - flutter: ">=3.27.0" From 604ec9cfdd5e264884b327e5c6e5efee9308cf32 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 19 Feb 2025 14:30:26 -0500 Subject: [PATCH 30/93] minor cleanups --- .../veilid_support/example/android/settings.gradle | 11 +++++------ packages/veilid_support/lib/src/config.dart | 3 +-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/veilid_support/example/android/settings.gradle b/packages/veilid_support/example/android/settings.gradle index 1d6d19b..b1ae36a 100644 --- a/packages/veilid_support/example/android/settings.gradle +++ b/packages/veilid_support/example/android/settings.gradle @@ -5,10 +5,9 @@ pluginManagement { def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" return flutterSdkPath - } - settings.ext.flutterSdkPath = flutterSdkPath() + }() - includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { google() @@ -19,8 +18,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false + id "com.android.application" version "8.8.0" apply false + id "org.jetbrains.kotlin.android" version "1.9.25" apply false } -include ":app" +include ":app" \ No newline at end of file diff --git a/packages/veilid_support/lib/src/config.dart b/packages/veilid_support/lib/src/config.dart index 9f7703c..bedc743 100644 --- a/packages/veilid_support/lib/src/config.dart +++ b/packages/veilid_support/lib/src/config.dart @@ -49,8 +49,7 @@ Future> getDefaultVeilidPlatformConfig( return VeilidFFIConfig( logging: VeilidFFIConfigLogging( terminal: VeilidFFIConfigLoggingTerminal( - enabled: - kIsDebugMode && (Platform.isIOS || Platform.isAndroid), + enabled: false, level: kIsDebugMode ? VeilidConfigLogLevel.debug : VeilidConfigLogLevel.info, From d460a0388ce5a7087f0db5e508a3d618e7756494 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 13 Mar 2025 21:34:12 -0400 Subject: [PATCH 31/93] debugging and cleanup --- android/app/.gitignore | 1 + assets/i18n/en.json | 5 +- build.yaml | 3 + .../xcshareddata/xcschemes/Runner.xcscheme | 1 + .../local_account/local_account.freezed.dart | 58 +- .../per_account_collection_state.freezed.dart | 19 +- .../models/user_login/user_login.freezed.dart | 43 +- .../views/edit_account_page.dart | 9 +- .../views/new_account_page.dart | 5 +- .../views/show_recovery_key_page.dart | 4 +- .../models/chat_component_state.freezed.dart | 59 +- lib/chat/models/message_state.freezed.dart | 40 +- lib/chat/models/window_state.freezed.dart | 38 +- lib/chat/views/no_conversation_widget.dart | 2 +- lib/contacts/views/no_contact_widget.dart | 2 +- lib/layout/default_app_bar.dart | 1 + .../notifications_preference.freezed.dart | 21 +- .../models/notifications_state.freezed.dart | 34 +- .../views/notifications_widget.dart | 64 +- lib/proto/veilidchat.pb.dart | 844 +++++++++++++++++- lib/proto/veilidchat.pbenum.dart | 5 +- lib/proto/veilidchat.pbjson.dart | 2 +- lib/proto/veilidchat.pbserver.dart | 2 +- lib/router/cubits/router_cubit.freezed.dart | 21 +- lib/settings/models/preferences.freezed.dart | 48 +- lib/theme/models/chat_theme.dart | 2 +- lib/theme/models/contrast_generator.dart | 8 +- lib/theme/models/models.dart | 5 +- lib/theme/models/radix_generator.dart | 15 +- .../models/{ => scale_theme}/scale_color.dart | 0 .../scale_custom_dropdown_theme.dart | 93 ++ .../scale_input_decorator_theme.dart | 93 +- .../{ => scale_theme}/scale_scheme.dart | 0 lib/theme/models/scale_theme/scale_theme.dart | 44 + .../scale_tile_theme.dart} | 36 +- .../models/scale_theme/scale_toast_theme.dart | 72 ++ lib/theme/models/theme_preference.dart | 2 +- .../models/theme_preference.freezed.dart | 21 +- lib/theme/views/option_box.dart | 4 +- lib/theme/{models => views}/slider_tile.dart | 0 lib/theme/views/styled_scaffold.dart | 21 +- lib/theme/views/views.dart | 1 + lib/theme/views/widget_helpers.dart | 26 +- lib/tools/loggy.dart | 2 +- .../processor_connection_state.freezed.dart | 21 +- lib/veilid_processor/views/developer.dart | 173 ++-- .../views/signal_strength_meter.dart | 19 +- .../xcshareddata/xcschemes/Runner.xcscheme | 1 + .../example/integration_test/app_test.dart | 208 ++--- packages/veilid_support/example/pubspec.lock | 96 +- .../dht_record/dht_record_pool.freezed.dart | 42 +- .../dht_record/dht_record_pool_private.dart | 3 - .../account_record_info.freezed.dart | 28 +- .../identity_support/identity.freezed.dart | 26 +- .../identity_instance.freezed.dart | 50 +- .../super_identity.freezed.dart | 38 +- packages/veilid_support/lib/proto/dht.pb.dart | 103 ++- .../veilid_support/lib/proto/dht.pbenum.dart | 2 +- .../veilid_support/lib/proto/dht.pbjson.dart | 2 +- .../lib/proto/dht.pbserver.dart | 2 +- .../veilid_support/lib/proto/veilid.pb.dart | 177 +++- .../lib/proto/veilid.pbenum.dart | 2 +- .../lib/proto/veilid.pbjson.dart | 2 +- .../lib/proto/veilid.pbserver.dart | 2 +- packages/veilid_support/lib/src/config.dart | 3 +- packages/veilid_support/pubspec.lock | 23 +- packages/veilid_support/pubspec.yaml | 6 +- pubspec.lock | 211 +++-- pubspec.yaml | 80 +- 69 files changed, 2306 insertions(+), 790 deletions(-) create mode 100644 android/app/.gitignore rename lib/theme/models/{ => scale_theme}/scale_color.dart (100%) create mode 100644 lib/theme/models/scale_theme/scale_custom_dropdown_theme.dart rename lib/theme/models/{ => scale_theme}/scale_input_decorator_theme.dart (60%) rename lib/theme/models/{ => scale_theme}/scale_scheme.dart (100%) create mode 100644 lib/theme/models/scale_theme/scale_theme.dart rename lib/theme/models/{scale_theme.dart => scale_theme/scale_tile_theme.dart} (67%) create mode 100644 lib/theme/models/scale_theme/scale_toast_theme.dart rename lib/theme/{models => views}/slider_tile.dart (100%) diff --git a/android/app/.gitignore b/android/app/.gitignore new file mode 100644 index 0000000..0e60033 --- /dev/null +++ b/android/app/.gitignore @@ -0,0 +1 @@ +.cxx diff --git a/assets/i18n/en.json b/assets/i18n/en.json index d45a746..4334a6b 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -65,8 +65,9 @@ "destroy_account_confirm_message": "This action is PERMANENT, and your VeilidChat account will no longer be recoverable with the recovery key. Restoring from backups will not recover your account!", "destroy_account_confirm_message_details": "You will lose access to:\n • Your entire message history\n • Your contacts\n • This will not remove your messages you have sent from other people's devices\n", "confirm_are_you_sure": "Are you sure you want to do this?", - "failed_to_remove": "Failed to remove account.\n\nTry again when you have a more stable network connection.", - "failed_to_destroy": "Failed to destroy account.\n\nTry again when you have a more stable network connection.", + "failed_to_remove_title": "Failed to remove account", + "try_again_network": "Try again when you have a more stable network connection", + "failed_to_destroy_title": "Failed to destroy account", "account_removed": "Account removed successfully", "account_destroyed": "Account destroyed successfully" }, diff --git a/build.yaml b/build.yaml index 950fe95..77b9050 100644 --- a/build.yaml +++ b/build.yaml @@ -1,6 +1,9 @@ targets: $default: builders: + freezed: + options: + generic_argument_factories: true json_serializable: options: explicit_to_json: true diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8b3e7d0..3e31b44 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -48,6 +48,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/lib/account_manager/models/local_account/local_account.freezed.dart b/lib/account_manager/models/local_account/local_account.freezed.dart index 92e376f..effc69a 100644 --- a/lib/account_manager/models/local_account/local_account.freezed.dart +++ b/lib/account_manager/models/local_account/local_account.freezed.dart @@ -37,8 +37,12 @@ mixin _$LocalAccount { throw _privateConstructorUsedError; // Display name for account until it is unlocked String get name => throw _privateConstructorUsedError; + /// Serializes this LocalAccount to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of LocalAccount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $LocalAccountCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -70,6 +74,8 @@ class _$LocalAccountCopyWithImpl<$Res, $Val extends LocalAccount> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of LocalAccount + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -108,6 +114,8 @@ class _$LocalAccountCopyWithImpl<$Res, $Val extends LocalAccount> ) as $Val); } + /// Create a copy of LocalAccount + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $SuperIdentityCopyWith<$Res> get superIdentity { @@ -145,6 +153,8 @@ class __$$LocalAccountImplCopyWithImpl<$Res> _$LocalAccountImpl _value, $Res Function(_$LocalAccountImpl) _then) : super(_value, _then); + /// Create a copy of LocalAccount + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -244,7 +254,7 @@ class _$LocalAccountImpl implements _LocalAccount { (identical(other.name, name) || other.name == name)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -255,7 +265,9 @@ class _$LocalAccountImpl implements _LocalAccount { hiddenAccount, name); - @JsonKey(ignore: true) + /// Create a copy of LocalAccount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$LocalAccountImplCopyWith<_$LocalAccountImpl> get copyWith => @@ -281,24 +293,32 @@ abstract class _LocalAccount implements LocalAccount { factory _LocalAccount.fromJson(Map json) = _$LocalAccountImpl.fromJson; - @override // The super identity key record for the account, +// The super identity key record for the account, // containing the publicKey in the currentIdentity - SuperIdentity get superIdentity; - @override // The encrypted currentIdentity secret that goes with -// the identityPublicKey with appended salt - @Uint8ListJsonConverter() - Uint8List get identitySecretBytes; - @override // The kind of encryption input used on the account - EncryptionKeyType get encryptionKeyType; - @override // If account is not hidden, password can be retrieved via - bool get biometricsEnabled; - @override // Keep account hidden unless account password is entered -// (tries all hidden accounts with auth method (no biometrics)) - bool get hiddenAccount; - @override // Display name for account until it is unlocked - String get name; @override - @JsonKey(ignore: true) + SuperIdentity + get superIdentity; // The encrypted currentIdentity secret that goes with +// the identityPublicKey with appended salt + @override + @Uint8ListJsonConverter() + Uint8List + get identitySecretBytes; // The kind of encryption input used on the account + @override + EncryptionKeyType + get encryptionKeyType; // If account is not hidden, password can be retrieved via + @override + bool + get biometricsEnabled; // Keep account hidden unless account password is entered +// (tries all hidden accounts with auth method (no biometrics)) + @override + bool get hiddenAccount; // Display name for account until it is unlocked + @override + String get name; + + /// Create a copy of LocalAccount + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) _$$LocalAccountImplCopyWith<_$LocalAccountImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/account_manager/models/per_account_collection_state/per_account_collection_state.freezed.dart b/lib/account_manager/models/per_account_collection_state/per_account_collection_state.freezed.dart index 8dcc549..1aa0c7e 100644 --- a/lib/account_manager/models/per_account_collection_state/per_account_collection_state.freezed.dart +++ b/lib/account_manager/models/per_account_collection_state/per_account_collection_state.freezed.dart @@ -35,7 +35,9 @@ mixin _$PerAccountCollectionState { get activeSingleContactChatBlocMapCubit => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of PerAccountCollectionState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $PerAccountCollectionStateCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -74,6 +76,8 @@ class _$PerAccountCollectionStateCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of PerAccountCollectionState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -139,6 +143,8 @@ class _$PerAccountCollectionStateCopyWithImpl<$Res, ) as $Val); } + /// Create a copy of PerAccountCollectionState + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $AsyncValueCopyWith? get avAccountRecordState { @@ -190,6 +196,8 @@ class __$$PerAccountCollectionStateImplCopyWithImpl<$Res> $Res Function(_$PerAccountCollectionStateImpl) _then) : super(_value, _then); + /// Create a copy of PerAccountCollectionState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -353,7 +361,9 @@ class _$PerAccountCollectionStateImpl implements _PerAccountCollectionState { activeConversationsBlocMapCubit, activeSingleContactChatBlocMapCubit); - @JsonKey(ignore: true) + /// Create a copy of PerAccountCollectionState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$PerAccountCollectionStateImplCopyWith<_$PerAccountCollectionStateImpl> @@ -401,8 +411,11 @@ abstract class _PerAccountCollectionState implements PerAccountCollectionState { ActiveConversationsBlocMapCubit? get activeConversationsBlocMapCubit; @override ActiveSingleContactChatBlocMapCubit? get activeSingleContactChatBlocMapCubit; + + /// Create a copy of PerAccountCollectionState + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$PerAccountCollectionStateImplCopyWith<_$PerAccountCollectionStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/account_manager/models/user_login/user_login.freezed.dart b/lib/account_manager/models/user_login/user_login.freezed.dart index c93ee7b..2804a77 100644 --- a/lib/account_manager/models/user_login/user_login.freezed.dart +++ b/lib/account_manager/models/user_login/user_login.freezed.dart @@ -30,8 +30,12 @@ mixin _$UserLogin { throw _privateConstructorUsedError; // The time this login was most recently used Timestamp get lastActive => throw _privateConstructorUsedError; + /// Serializes this UserLogin to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of UserLogin + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $UserLoginCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -60,6 +64,8 @@ class _$UserLoginCopyWithImpl<$Res, $Val extends UserLogin> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of UserLogin + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -88,6 +94,8 @@ class _$UserLoginCopyWithImpl<$Res, $Val extends UserLogin> ) as $Val); } + /// Create a copy of UserLogin + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $AccountRecordInfoCopyWith<$Res> get accountRecordInfo { @@ -123,6 +131,8 @@ class __$$UserLoginImplCopyWithImpl<$Res> _$UserLoginImpl _value, $Res Function(_$UserLoginImpl) _then) : super(_value, _then); + /// Create a copy of UserLogin + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -198,12 +208,14 @@ class _$UserLoginImpl implements _UserLogin { other.lastActive == lastActive)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, superIdentityRecordKey, identitySecret, accountRecordInfo, lastActive); - @JsonKey(ignore: true) + /// Create a copy of UserLogin + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$UserLoginImplCopyWith<_$UserLoginImpl> get copyWith => @@ -227,17 +239,24 @@ abstract class _UserLogin implements UserLogin { factory _UserLogin.fromJson(Map json) = _$UserLoginImpl.fromJson; - @override // SuperIdentity record key for the user +// SuperIdentity record key for the user // used to index the local accounts table - Typed get superIdentityRecordKey; - @override // The identity secret as unlocked from the local accounts table - Typed get identitySecret; - @override // The account record key, owner key and secret pulled from the identity - AccountRecordInfo get accountRecordInfo; - @override // The time this login was most recently used - Timestamp get lastActive; @override - @JsonKey(ignore: true) + Typed + get superIdentityRecordKey; // The identity secret as unlocked from the local accounts table + @override + Typed + get identitySecret; // The account record key, owner key and secret pulled from the identity + @override + AccountRecordInfo + get accountRecordInfo; // The time this login was most recently used + @override + Timestamp get lastActive; + + /// Create a copy of UserLogin + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) _$$UserLoginImplCopyWith<_$UserLoginImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/account_manager/views/edit_account_page.dart b/lib/account_manager/views/edit_account_page.dart index 3ba0f65..918c4ca 100644 --- a/lib/account_manager/views/edit_account_page.dart +++ b/lib/account_manager/views/edit_account_page.dart @@ -127,9 +127,9 @@ class _EditAccountPageState extends WindowSetupState { .info(text: translate('edit_account_page.account_removed')); GoRouterHelper(context).pop(); } else { - context - .read() - .error(text: translate('edit_account_page.failed_to_remove')); + context.read().error( + title: translate('edit_account_page.failed_to_remove_title'), + text: translate('edit_account_page.try_again_network')); } } } finally { @@ -196,7 +196,8 @@ class _EditAccountPageState extends WindowSetupState { GoRouterHelper(context).pop(); } else { context.read().error( - text: translate('edit_account_page.failed_to_destroy')); + title: translate('edit_account_page.failed_to_destroy_title'), + text: translate('edit_account_page.try_again_network')); } } } finally { diff --git a/lib/account_manager/views/new_account_page.dart b/lib/account_manager/views/new_account_page.dart index 69c75ae..ccd5b00 100644 --- a/lib/account_manager/views/new_account_page.dart +++ b/lib/account_manager/views/new_account_page.dart @@ -135,9 +135,10 @@ class _NewAccountPageState extends WindowSetupState { }) ]), body: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: _newAccountForm( - context, - )).paddingSymmetric(horizontal: 24, vertical: 8), + context, + )).paddingAll(2), ).withModalHUD(context, displayModalHUD); } diff --git a/lib/account_manager/views/show_recovery_key_page.dart b/lib/account_manager/views/show_recovery_key_page.dart index 2649bcf..acbb3f3 100644 --- a/lib/account_manager/views/show_recovery_key_page.dart +++ b/lib/account_manager/views/show_recovery_key_page.dart @@ -1,11 +1,11 @@ import 'dart:async'; import 'dart:io'; import 'dart:math'; -import 'dart:typed_data'; import 'package:async_tools/async_tools.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:file_saver/file_saver.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:go_router/go_router.dart'; @@ -61,7 +61,7 @@ class _ShowRecoveryKeyPageState extends WindowSetupState { _isInAsyncCall = false; }); - if (Platform.isLinux) { + if (!kIsWeb && Platform.isLinux) { // Share plus doesn't do Linux yet await FileSaver.instance.saveFile(name: 'recovery_key.png', bytes: bytes); } else { diff --git a/lib/chat/models/chat_component_state.freezed.dart b/lib/chat/models/chat_component_state.freezed.dart index 41ab7e2..f967997 100644 --- a/lib/chat/models/chat_component_state.freezed.dart +++ b/lib/chat/models/chat_component_state.freezed.dart @@ -35,7 +35,9 @@ mixin _$ChatComponentState { throw _privateConstructorUsedError; // Title of the chat String get title => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of ChatComponentState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ChatComponentStateCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -70,6 +72,8 @@ class _$ChatComponentStateCopyWithImpl<$Res, $Val extends ChatComponentState> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ChatComponentState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -123,6 +127,8 @@ class _$ChatComponentStateCopyWithImpl<$Res, $Val extends ChatComponentState> ) as $Val); } + /// Create a copy of ChatComponentState + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $AsyncValueCopyWith, $Res> get messageWindow { @@ -164,6 +170,8 @@ class __$$ChatComponentStateImplCopyWithImpl<$Res> $Res Function(_$ChatComponentStateImpl) _then) : super(_value, _then); + /// Create a copy of ChatComponentState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -301,7 +309,9 @@ class _$ChatComponentStateImpl implements _ChatComponentState { messageWindow, title); - @JsonKey(ignore: true) + /// Create a copy of ChatComponentState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ChatComponentStateImplCopyWith<_$ChatComponentStateImpl> get copyWith => @@ -322,26 +332,33 @@ abstract class _ChatComponentState implements ChatComponentState { required final AsyncValue> messageWindow, required final String title}) = _$ChatComponentStateImpl; - @override // GlobalKey for the chat - GlobalKey get chatKey; - @override // ScrollController for the chat - AutoScrollController get scrollController; - @override // TextEditingController for the chat - InputTextFieldController get textEditingController; - @override // Local user - User? get localUser; - @override // Active remote users - IMap, User> get remoteUsers; - @override // Historical remote users - IMap, User> get historicalRemoteUsers; - @override // Unknown users - IMap, User> get unknownUsers; - @override // Messages state - AsyncValue> get messageWindow; - @override // Title of the chat - String get title; +// GlobalKey for the chat @override - @JsonKey(ignore: true) + GlobalKey get chatKey; // ScrollController for the chat + @override + AutoScrollController + get scrollController; // TextEditingController for the chat + @override + InputTextFieldController get textEditingController; // Local user + @override + User? get localUser; // Active remote users + @override + IMap, User> + get remoteUsers; // Historical remote users + @override + IMap, User> + get historicalRemoteUsers; // Unknown users + @override + IMap, User> get unknownUsers; // Messages state + @override + AsyncValue> get messageWindow; // Title of the chat + @override + String get title; + + /// Create a copy of ChatComponentState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) _$$ChatComponentStateImplCopyWith<_$ChatComponentStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/chat/models/message_state.freezed.dart b/lib/chat/models/message_state.freezed.dart index 96c98e2..baafea6 100644 --- a/lib/chat/models/message_state.freezed.dart +++ b/lib/chat/models/message_state.freezed.dart @@ -30,8 +30,12 @@ mixin _$MessageState { throw _privateConstructorUsedError; // The state of the message MessageSendState? get sendState => throw _privateConstructorUsedError; + /// Serializes this MessageState to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of MessageState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $MessageStateCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -60,6 +64,8 @@ class _$MessageStateCopyWithImpl<$Res, $Val extends MessageState> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of MessageState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -113,6 +119,8 @@ class __$$MessageStateImplCopyWithImpl<$Res> _$MessageStateImpl _value, $Res Function(_$MessageStateImpl) _then) : super(_value, _then); + /// Create a copy of MessageState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -199,12 +207,14 @@ class _$MessageStateImpl with DiagnosticableTreeMixin implements _MessageState { other.sendState == sendState)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, content, sentTimestamp, reconciledTimestamp, sendState); - @JsonKey(ignore: true) + /// Create a copy of MessageState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$MessageStateImplCopyWith<_$MessageStateImpl> get copyWith => @@ -229,17 +239,21 @@ abstract class _MessageState implements MessageState { factory _MessageState.fromJson(Map json) = _$MessageStateImpl.fromJson; - @override // Content of the message - @JsonKey(fromJson: messageFromJson, toJson: messageToJson) - proto.Message get content; - @override // Sent timestamp - Timestamp get sentTimestamp; - @override // Reconciled timestamp - Timestamp? get reconciledTimestamp; - @override // The state of the message - MessageSendState? get sendState; +// Content of the message @override - @JsonKey(ignore: true) + @JsonKey(fromJson: messageFromJson, toJson: messageToJson) + proto.Message get content; // Sent timestamp + @override + Timestamp get sentTimestamp; // Reconciled timestamp + @override + Timestamp? get reconciledTimestamp; // The state of the message + @override + MessageSendState? get sendState; + + /// Create a copy of MessageState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) _$$MessageStateImplCopyWith<_$MessageStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/chat/models/window_state.freezed.dart b/lib/chat/models/window_state.freezed.dart index 604931d..59ff754 100644 --- a/lib/chat/models/window_state.freezed.dart +++ b/lib/chat/models/window_state.freezed.dart @@ -27,7 +27,9 @@ mixin _$WindowState { throw _privateConstructorUsedError; // If we should have the tail following the array bool get follow => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of WindowState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $WindowStateCopyWith> get copyWith => throw _privateConstructorUsedError; } @@ -56,6 +58,8 @@ class _$WindowStateCopyWithImpl> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of WindowState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -114,6 +118,8 @@ class __$$WindowStateImplCopyWithImpl _$WindowStateImpl _value, $Res Function(_$WindowStateImpl) _then) : super(_value, _then); + /// Create a copy of WindowState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -216,7 +222,9 @@ class _$WindowStateImpl windowCount, follow); - @JsonKey(ignore: true) + /// Create a copy of WindowState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$WindowStateImplCopyWith> get copyWith => @@ -232,18 +240,22 @@ abstract class _WindowState implements WindowState { required final int windowCount, required final bool follow}) = _$WindowStateImpl; - @override // List of objects in the window - IList get window; - @override // Total number of objects (windowTail max) - int get length; - @override // One past the end of the last element - int get windowTail; - @override // The total number of elements to try to keep in the window - int get windowCount; - @override // If we should have the tail following the array - bool get follow; +// List of objects in the window @override - @JsonKey(ignore: true) + IList get window; // Total number of objects (windowTail max) + @override + int get length; // One past the end of the last element + @override + int get windowTail; // The total number of elements to try to keep in the window + @override + int get windowCount; // If we should have the tail following the array + @override + bool get follow; + + /// Create a copy of WindowState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) _$$WindowStateImplCopyWith> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/chat/views/no_conversation_widget.dart b/lib/chat/views/no_conversation_widget.dart index 77502e1..e246fee 100644 --- a/lib/chat/views/no_conversation_widget.dart +++ b/lib/chat/views/no_conversation_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import '../../theme/models/scale_scheme.dart'; +import '../../theme/models/scale_theme/scale_scheme.dart'; class NoConversationWidget extends StatelessWidget { const NoConversationWidget({super.key}); diff --git a/lib/contacts/views/no_contact_widget.dart b/lib/contacts/views/no_contact_widget.dart index c559d8b..8edb5d5 100644 --- a/lib/contacts/views/no_contact_widget.dart +++ b/lib/contacts/views/no_contact_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import '../../theme/models/scale_scheme.dart'; +import '../../theme/models/scale_theme/scale_scheme.dart'; class NoContactWidget extends StatelessWidget { const NoContactWidget({super.key}); diff --git a/lib/layout/default_app_bar.dart b/lib/layout/default_app_bar.dart index 41e3601..fbf2360 100644 --- a/lib/layout/default_app_bar.dart +++ b/lib/layout/default_app_bar.dart @@ -6,6 +6,7 @@ class DefaultAppBar extends AppBar { DefaultAppBar( {required super.title, super.key, Widget? leading, super.actions}) : super( + titleSpacing: 0, leading: leading ?? Container( margin: const EdgeInsets.all(4), diff --git a/lib/notifications/models/notifications_preference.freezed.dart b/lib/notifications/models/notifications_preference.freezed.dart index b2cbc67..0335ad8 100644 --- a/lib/notifications/models/notifications_preference.freezed.dart +++ b/lib/notifications/models/notifications_preference.freezed.dart @@ -35,8 +35,12 @@ mixin _$NotificationsPreference { SoundEffect get onMessageReceivedSound => throw _privateConstructorUsedError; SoundEffect get onMessageSentSound => throw _privateConstructorUsedError; + /// Serializes this NotificationsPreference to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of NotificationsPreference + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $NotificationsPreferenceCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -70,6 +74,8 @@ class _$NotificationsPreferenceCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of NotificationsPreference + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -155,6 +161,8 @@ class __$$NotificationsPreferenceImplCopyWithImpl<$Res> $Res Function(_$NotificationsPreferenceImpl) _then) : super(_value, _then); + /// Create a copy of NotificationsPreference + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -289,7 +297,7 @@ class _$NotificationsPreferenceImpl implements _NotificationsPreference { other.onMessageSentSound == onMessageSentSound)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -303,7 +311,9 @@ class _$NotificationsPreferenceImpl implements _NotificationsPreference { onMessageReceivedSound, onMessageSentSound); - @JsonKey(ignore: true) + /// Create a copy of NotificationsPreference + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$NotificationsPreferenceImplCopyWith<_$NotificationsPreferenceImpl> @@ -351,8 +361,11 @@ abstract class _NotificationsPreference implements NotificationsPreference { SoundEffect get onMessageReceivedSound; @override SoundEffect get onMessageSentSound; + + /// Create a copy of NotificationsPreference + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$NotificationsPreferenceImplCopyWith<_$NotificationsPreferenceImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/notifications/models/notifications_state.freezed.dart b/lib/notifications/models/notifications_state.freezed.dart index 90893e6..e052e7a 100644 --- a/lib/notifications/models/notifications_state.freezed.dart +++ b/lib/notifications/models/notifications_state.freezed.dart @@ -20,7 +20,9 @@ mixin _$NotificationItem { String get text => throw _privateConstructorUsedError; String? get title => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of NotificationItem + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $NotificationItemCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -44,6 +46,8 @@ class _$NotificationItemCopyWithImpl<$Res, $Val extends NotificationItem> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of NotificationItem + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -87,6 +91,8 @@ class __$$NotificationItemImplCopyWithImpl<$Res> $Res Function(_$NotificationItemImpl) _then) : super(_value, _then); + /// Create a copy of NotificationItem + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -142,7 +148,9 @@ class _$NotificationItemImpl implements _NotificationItem { @override int get hashCode => Object.hash(runtimeType, type, text, title); - @JsonKey(ignore: true) + /// Create a copy of NotificationItem + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$NotificationItemImplCopyWith<_$NotificationItemImpl> get copyWith => @@ -162,8 +170,11 @@ abstract class _NotificationItem implements NotificationItem { String get text; @override String? get title; + + /// Create a copy of NotificationItem + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$NotificationItemImplCopyWith<_$NotificationItemImpl> get copyWith => throw _privateConstructorUsedError; } @@ -172,7 +183,9 @@ abstract class _NotificationItem implements NotificationItem { mixin _$NotificationsState { IList get queue => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of NotificationsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $NotificationsStateCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -196,6 +209,8 @@ class _$NotificationsStateCopyWithImpl<$Res, $Val extends NotificationsState> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of NotificationsState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -229,6 +244,8 @@ class __$$NotificationsStateImplCopyWithImpl<$Res> $Res Function(_$NotificationsStateImpl) _then) : super(_value, _then); + /// Create a copy of NotificationsState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -268,7 +285,9 @@ class _$NotificationsStateImpl implements _NotificationsState { int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(queue)); - @JsonKey(ignore: true) + /// Create a copy of NotificationsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$NotificationsStateImplCopyWith<_$NotificationsStateImpl> get copyWith => @@ -283,8 +302,11 @@ abstract class _NotificationsState implements NotificationsState { @override IList get queue; + + /// Create a copy of NotificationsState + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$NotificationsStateImplCopyWith<_$NotificationsStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/notifications/views/notifications_widget.dart b/lib/notifications/views/notifications_widget.dart index 246a570..73fac5f 100644 --- a/lib/notifications/views/notifications_widget.dart +++ b/lib/notifications/views/notifications_widget.dart @@ -1,6 +1,7 @@ +import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:motion_toast/motion_toast.dart'; +import 'package:toastification/toastification.dart'; import '../../theme/theme.dart'; import '../notifications.dart'; @@ -43,46 +44,47 @@ class NotificationsWidget extends StatelessWidget { //////////////////////////////////////////////////////////////////////////// // Private Implementation + void _toast( + {required BuildContext context, + required String text, + required ScaleToastTheme toastTheme, + String? title}) { + toastification.show( + context: context, + title: title != null + ? Text(title) + .copyWith(style: toastTheme.titleTextStyle) + .paddingLTRB(0, 0, 0, 8) + : null, + description: Text(text).copyWith(style: toastTheme.descriptionTextStyle), + icon: toastTheme.icon, + primaryColor: toastTheme.primaryColor, + backgroundColor: toastTheme.backgroundColor, + foregroundColor: toastTheme.foregroundColor, + padding: toastTheme.padding, + borderRadius: toastTheme.borderRadius, + borderSide: toastTheme.borderSide, + autoCloseDuration: const Duration(seconds: 2), + animationDuration: const Duration(milliseconds: 500), + ); + } + void _info( {required BuildContext context, required String text, String? title}) { final theme = Theme.of(context); - final scale = theme.extension()!; - final scaleConfig = theme.extension()!; + final toastTheme = + theme.extension()!.toastTheme(ScaleToastKind.info); - MotionToast( - title: title != null ? Text(title) : null, - description: Text(text), - constraints: BoxConstraints.loose(const Size(400, 100)), - contentPadding: const EdgeInsets.all(16), - primaryColor: scale.tertiaryScale.elementBackground, - secondaryColor: scale.tertiaryScale.calloutBackground, - borderRadius: 12 * scaleConfig.borderRadiusScale, - toastDuration: const Duration(seconds: 2), - animationDuration: const Duration(milliseconds: 500), - displayBorder: scaleConfig.useVisualIndicators, - icon: Icons.info, - ).show(context); + _toast(context: context, text: text, toastTheme: toastTheme, title: title); } void _error( {required BuildContext context, required String text, String? title}) { final theme = Theme.of(context); - final scale = theme.extension()!; - final scaleConfig = theme.extension()!; + final toastTheme = + theme.extension()!.toastTheme(ScaleToastKind.error); - MotionToast( - title: title != null ? Text(title) : null, - description: Text(text), - constraints: BoxConstraints.loose(const Size(400, 100)), - contentPadding: const EdgeInsets.all(16), - primaryColor: scale.errorScale.elementBackground, - secondaryColor: scale.errorScale.calloutBackground, - borderRadius: 12 * scaleConfig.borderRadiusScale, - toastDuration: const Duration(seconds: 4), - animationDuration: const Duration(milliseconds: 1000), - displayBorder: scaleConfig.useVisualIndicators, - icon: Icons.error, - ).show(context); + _toast(context: context, text: text, toastTheme: toastTheme, title: title); } //////////////////////////////////////////////////////////////////////////// diff --git a/lib/proto/veilidchat.pb.dart b/lib/proto/veilidchat.pb.dart index 5152594..3947baf 100644 --- a/lib/proto/veilidchat.pb.dart +++ b/lib/proto/veilidchat.pb.dart @@ -4,7 +4,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields // ignore_for_file: unnecessary_import, unnecessary_this, unused_import @@ -20,8 +20,21 @@ import 'veilidchat.pbenum.dart'; export 'veilidchat.pbenum.dart'; +/// Reference to data on the DHT class DHTDataReference extends $pb.GeneratedMessage { - factory DHTDataReference() => create(); + factory DHTDataReference({ + $0.TypedKey? dhtData, + $0.TypedKey? hash, + }) { + final $result = create(); + if (dhtData != null) { + $result.dhtData = dhtData; + } + if (hash != null) { + $result.hash = hash; + } + return $result; + } DHTDataReference._() : super(); factory DHTDataReference.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory DHTDataReference.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -76,8 +89,17 @@ class DHTDataReference extends $pb.GeneratedMessage { $0.TypedKey ensureHash() => $_ensure(1); } +/// Reference to data on the BlockStore class BlockStoreDataReference extends $pb.GeneratedMessage { - factory BlockStoreDataReference() => create(); + factory BlockStoreDataReference({ + $0.TypedKey? block, + }) { + final $result = create(); + if (block != null) { + $result.block = block; + } + return $result; + } BlockStoreDataReference._() : super(); factory BlockStoreDataReference.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory BlockStoreDataReference.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -126,8 +148,23 @@ enum DataReference_Kind { notSet } +/// DataReference +/// Pointer to data somewhere in Veilid +/// Abstraction over DHTData and BlockStore class DataReference extends $pb.GeneratedMessage { - factory DataReference() => create(); + factory DataReference({ + DHTDataReference? dhtData, + BlockStoreDataReference? blockStoreData, + }) { + final $result = create(); + if (dhtData != null) { + $result.dhtData = dhtData; + } + if (blockStoreData != null) { + $result.blockStoreData = blockStoreData; + } + return $result; + } DataReference._() : super(); factory DataReference.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory DataReference.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -196,8 +233,21 @@ enum Attachment_Kind { notSet } +/// A single attachment class Attachment extends $pb.GeneratedMessage { - factory Attachment() => create(); + factory Attachment({ + AttachmentMedia? media, + $0.Signature? signature, + }) { + final $result = create(); + if (media != null) { + $result.media = media; + } + if (signature != null) { + $result.signature = signature; + } + return $result; + } Attachment._() : super(); factory Attachment.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Attachment.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -248,6 +298,7 @@ class Attachment extends $pb.GeneratedMessage { @$pb.TagNumber(1) AttachmentMedia ensureMedia() => $_ensure(0); + /// Author signature over all attachment fields and content fields and bytes @$pb.TagNumber(2) $0.Signature get signature => $_getN(1); @$pb.TagNumber(2) @@ -260,8 +311,25 @@ class Attachment extends $pb.GeneratedMessage { $0.Signature ensureSignature() => $_ensure(1); } +/// A file, audio, image, or video attachment class AttachmentMedia extends $pb.GeneratedMessage { - factory AttachmentMedia() => create(); + factory AttachmentMedia({ + $core.String? mime, + $core.String? name, + DataReference? content, + }) { + final $result = create(); + if (mime != null) { + $result.mime = mime; + } + if (name != null) { + $result.name = name; + } + if (content != null) { + $result.content = content; + } + return $result; + } AttachmentMedia._() : super(); factory AttachmentMedia.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory AttachmentMedia.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -294,6 +362,7 @@ class AttachmentMedia extends $pb.GeneratedMessage { static AttachmentMedia getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static AttachmentMedia? _defaultInstance; + /// MIME type of the data @$pb.TagNumber(1) $core.String get mime => $_getSZ(0); @$pb.TagNumber(1) @@ -303,6 +372,7 @@ class AttachmentMedia extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearMime() => clearField(1); + /// Title or filename @$pb.TagNumber(2) $core.String get name => $_getSZ(1); @$pb.TagNumber(2) @@ -312,6 +382,7 @@ class AttachmentMedia extends $pb.GeneratedMessage { @$pb.TagNumber(2) void clearName() => clearField(2); + /// Pointer to the data content @$pb.TagNumber(3) DataReference get content => $_getN(2); @$pb.TagNumber(3) @@ -324,8 +395,25 @@ class AttachmentMedia extends $pb.GeneratedMessage { DataReference ensureContent() => $_ensure(2); } +/// Permissions of a chat class Permissions extends $pb.GeneratedMessage { - factory Permissions() => create(); + factory Permissions({ + Scope? canAddMembers, + Scope? canEditInfo, + $core.bool? moderated, + }) { + final $result = create(); + if (canAddMembers != null) { + $result.canAddMembers = canAddMembers; + } + if (canEditInfo != null) { + $result.canEditInfo = canEditInfo; + } + if (moderated != null) { + $result.moderated = moderated; + } + return $result; + } Permissions._() : super(); factory Permissions.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Permissions.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -358,6 +446,7 @@ class Permissions extends $pb.GeneratedMessage { static Permissions getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static Permissions? _defaultInstance; + /// Parties in this scope or higher can add members to their own group or lower @$pb.TagNumber(1) Scope get canAddMembers => $_getN(0); @$pb.TagNumber(1) @@ -367,6 +456,7 @@ class Permissions extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearCanAddMembers() => clearField(1); + /// Parties in this scope or higher can change the 'info' of a group @$pb.TagNumber(2) Scope get canEditInfo => $_getN(1); @$pb.TagNumber(2) @@ -376,6 +466,7 @@ class Permissions extends $pb.GeneratedMessage { @$pb.TagNumber(2) void clearCanEditInfo() => clearField(2); + /// If moderation is enabled or not. @$pb.TagNumber(3) $core.bool get moderated => $_getBF(2); @$pb.TagNumber(3) @@ -386,8 +477,33 @@ class Permissions extends $pb.GeneratedMessage { void clearModerated() => clearField(3); } +/// The membership of a chat class Membership extends $pb.GeneratedMessage { - factory Membership() => create(); + factory Membership({ + $core.Iterable<$0.TypedKey>? watchers, + $core.Iterable<$0.TypedKey>? moderated, + $core.Iterable<$0.TypedKey>? talkers, + $core.Iterable<$0.TypedKey>? moderators, + $core.Iterable<$0.TypedKey>? admins, + }) { + final $result = create(); + if (watchers != null) { + $result.watchers.addAll(watchers); + } + if (moderated != null) { + $result.moderated.addAll(moderated); + } + if (talkers != null) { + $result.talkers.addAll(talkers); + } + if (moderators != null) { + $result.moderators.addAll(moderators); + } + if (admins != null) { + $result.admins.addAll(admins); + } + return $result; + } Membership._() : super(); factory Membership.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Membership.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -422,24 +538,50 @@ class Membership extends $pb.GeneratedMessage { static Membership getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static Membership? _defaultInstance; + /// Conversation keys for parties in the 'watchers' group @$pb.TagNumber(1) $core.List<$0.TypedKey> get watchers => $_getList(0); + /// Conversation keys for parties in the 'moderated' group @$pb.TagNumber(2) $core.List<$0.TypedKey> get moderated => $_getList(1); + /// Conversation keys for parties in the 'talkers' group @$pb.TagNumber(3) $core.List<$0.TypedKey> get talkers => $_getList(2); + /// Conversation keys for parties in the 'moderators' group @$pb.TagNumber(4) $core.List<$0.TypedKey> get moderators => $_getList(3); + /// Conversation keys for parties in the 'admins' group @$pb.TagNumber(5) $core.List<$0.TypedKey> get admins => $_getList(4); } +/// The chat settings class ChatSettings extends $pb.GeneratedMessage { - factory ChatSettings() => create(); + factory ChatSettings({ + $core.String? title, + $core.String? description, + DataReference? icon, + $fixnum.Int64? defaultExpiration, + }) { + final $result = create(); + if (title != null) { + $result.title = title; + } + if (description != null) { + $result.description = description; + } + if (icon != null) { + $result.icon = icon; + } + if (defaultExpiration != null) { + $result.defaultExpiration = defaultExpiration; + } + return $result; + } ChatSettings._() : super(); factory ChatSettings.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory ChatSettings.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -473,6 +615,7 @@ class ChatSettings extends $pb.GeneratedMessage { static ChatSettings getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static ChatSettings? _defaultInstance; + /// Title for the chat @$pb.TagNumber(1) $core.String get title => $_getSZ(0); @$pb.TagNumber(1) @@ -482,6 +625,7 @@ class ChatSettings extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearTitle() => clearField(1); + /// Description for the chat @$pb.TagNumber(2) $core.String get description => $_getSZ(1); @$pb.TagNumber(2) @@ -491,6 +635,7 @@ class ChatSettings extends $pb.GeneratedMessage { @$pb.TagNumber(2) void clearDescription() => clearField(2); + /// Icon for the chat @$pb.TagNumber(3) DataReference get icon => $_getN(2); @$pb.TagNumber(3) @@ -502,6 +647,7 @@ class ChatSettings extends $pb.GeneratedMessage { @$pb.TagNumber(3) DataReference ensureIcon() => $_ensure(2); + /// Default message expiration duration (in us) @$pb.TagNumber(4) $fixnum.Int64 get defaultExpiration => $_getI64(3); @$pb.TagNumber(4) @@ -512,8 +658,37 @@ class ChatSettings extends $pb.GeneratedMessage { void clearDefaultExpiration() => clearField(4); } +/// A text message class Message_Text extends $pb.GeneratedMessage { - factory Message_Text() => create(); + factory Message_Text({ + $core.String? text, + $core.String? topic, + $core.List<$core.int>? replyId, + $fixnum.Int64? expiration, + $core.int? viewLimit, + $core.Iterable? attachments, + }) { + final $result = create(); + if (text != null) { + $result.text = text; + } + if (topic != null) { + $result.topic = topic; + } + if (replyId != null) { + $result.replyId = replyId; + } + if (expiration != null) { + $result.expiration = expiration; + } + if (viewLimit != null) { + $result.viewLimit = viewLimit; + } + if (attachments != null) { + $result.attachments.addAll(attachments); + } + return $result; + } Message_Text._() : super(); factory Message_Text.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Message_Text.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -549,6 +724,7 @@ class Message_Text extends $pb.GeneratedMessage { static Message_Text getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static Message_Text? _defaultInstance; + /// Text of the message @$pb.TagNumber(1) $core.String get text => $_getSZ(0); @$pb.TagNumber(1) @@ -558,6 +734,7 @@ class Message_Text extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearText() => clearField(1); + /// Topic of the message / Content warning @$pb.TagNumber(2) $core.String get topic => $_getSZ(1); @$pb.TagNumber(2) @@ -567,6 +744,7 @@ class Message_Text extends $pb.GeneratedMessage { @$pb.TagNumber(2) void clearTopic() => clearField(2); + /// Message id replied to (author id + message id) @$pb.TagNumber(3) $core.List<$core.int> get replyId => $_getN(2); @$pb.TagNumber(3) @@ -576,6 +754,7 @@ class Message_Text extends $pb.GeneratedMessage { @$pb.TagNumber(3) void clearReplyId() => clearField(3); + /// Message expiration timestamp @$pb.TagNumber(4) $fixnum.Int64 get expiration => $_getI64(3); @$pb.TagNumber(4) @@ -585,6 +764,7 @@ class Message_Text extends $pb.GeneratedMessage { @$pb.TagNumber(4) void clearExpiration() => clearField(4); + /// Message view limit before deletion @$pb.TagNumber(5) $core.int get viewLimit => $_getIZ(4); @$pb.TagNumber(5) @@ -594,12 +774,26 @@ class Message_Text extends $pb.GeneratedMessage { @$pb.TagNumber(5) void clearViewLimit() => clearField(5); + /// Attachments on the message @$pb.TagNumber(6) $core.List get attachments => $_getList(5); } +/// A secret message class Message_Secret extends $pb.GeneratedMessage { - factory Message_Secret() => create(); + factory Message_Secret({ + $core.List<$core.int>? ciphertext, + $fixnum.Int64? expiration, + }) { + final $result = create(); + if (ciphertext != null) { + $result.ciphertext = ciphertext; + } + if (expiration != null) { + $result.expiration = expiration; + } + return $result; + } Message_Secret._() : super(); factory Message_Secret.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Message_Secret.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -631,6 +825,7 @@ class Message_Secret extends $pb.GeneratedMessage { static Message_Secret getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static Message_Secret? _defaultInstance; + /// Text message protobuf encrypted by a key @$pb.TagNumber(1) $core.List<$core.int> get ciphertext => $_getN(0); @$pb.TagNumber(1) @@ -640,6 +835,8 @@ class Message_Secret extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearCiphertext() => clearField(1); + /// Secret expiration timestamp + /// This is the time after which an un-revealed secret will get deleted @$pb.TagNumber(2) $fixnum.Int64 get expiration => $_getI64(1); @$pb.TagNumber(2) @@ -650,8 +847,18 @@ class Message_Secret extends $pb.GeneratedMessage { void clearExpiration() => clearField(2); } +/// A 'delete' control message +/// Deletes a set of messages by their ids class Message_ControlDelete extends $pb.GeneratedMessage { - factory Message_ControlDelete() => create(); + factory Message_ControlDelete({ + $core.Iterable<$core.List<$core.int>>? ids, + }) { + final $result = create(); + if (ids != null) { + $result.ids.addAll(ids); + } + return $result; + } Message_ControlDelete._() : super(); factory Message_ControlDelete.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Message_ControlDelete.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -686,8 +893,18 @@ class Message_ControlDelete extends $pb.GeneratedMessage { $core.List<$core.List<$core.int>> get ids => $_getList(0); } +/// An 'erase' control message +/// Deletes a set of messages from before some timestamp class Message_ControlErase extends $pb.GeneratedMessage { - factory Message_ControlErase() => create(); + factory Message_ControlErase({ + $fixnum.Int64? timestamp, + }) { + final $result = create(); + if (timestamp != null) { + $result.timestamp = timestamp; + } + return $result; + } Message_ControlErase._() : super(); factory Message_ControlErase.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Message_ControlErase.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -718,6 +935,8 @@ class Message_ControlErase extends $pb.GeneratedMessage { static Message_ControlErase getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static Message_ControlErase? _defaultInstance; + /// The latest timestamp to delete messages before + /// If this is zero then all messages are cleared @$pb.TagNumber(1) $fixnum.Int64 get timestamp => $_getI64(0); @$pb.TagNumber(1) @@ -728,8 +947,17 @@ class Message_ControlErase extends $pb.GeneratedMessage { void clearTimestamp() => clearField(1); } +/// A 'change settings' control message class Message_ControlSettings extends $pb.GeneratedMessage { - factory Message_ControlSettings() => create(); + factory Message_ControlSettings({ + ChatSettings? settings, + }) { + final $result = create(); + if (settings != null) { + $result.settings = settings; + } + return $result; + } Message_ControlSettings._() : super(); factory Message_ControlSettings.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Message_ControlSettings.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -772,8 +1000,18 @@ class Message_ControlSettings extends $pb.GeneratedMessage { ChatSettings ensureSettings() => $_ensure(0); } +/// A 'change permissions' control message +/// Changes the permissions of a chat class Message_ControlPermissions extends $pb.GeneratedMessage { - factory Message_ControlPermissions() => create(); + factory Message_ControlPermissions({ + Permissions? permissions, + }) { + final $result = create(); + if (permissions != null) { + $result.permissions = permissions; + } + return $result; + } Message_ControlPermissions._() : super(); factory Message_ControlPermissions.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Message_ControlPermissions.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -816,8 +1054,18 @@ class Message_ControlPermissions extends $pb.GeneratedMessage { Permissions ensurePermissions() => $_ensure(0); } +/// A 'change membership' control message +/// Changes the class Message_ControlMembership extends $pb.GeneratedMessage { - factory Message_ControlMembership() => create(); + factory Message_ControlMembership({ + Membership? membership, + }) { + final $result = create(); + if (membership != null) { + $result.membership = membership; + } + return $result; + } Message_ControlMembership._() : super(); factory Message_ControlMembership.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Message_ControlMembership.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -860,8 +1108,22 @@ class Message_ControlMembership extends $pb.GeneratedMessage { Membership ensureMembership() => $_ensure(0); } +/// A 'moderation' control message +/// Accepts or rejects a set of messages class Message_ControlModeration extends $pb.GeneratedMessage { - factory Message_ControlModeration() => create(); + factory Message_ControlModeration({ + $core.Iterable<$core.List<$core.int>>? acceptedIds, + $core.Iterable<$core.List<$core.int>>? rejectedIds, + }) { + final $result = create(); + if (acceptedIds != null) { + $result.acceptedIds.addAll(acceptedIds); + } + if (rejectedIds != null) { + $result.rejectedIds.addAll(rejectedIds); + } + return $result; + } Message_ControlModeration._() : super(); factory Message_ControlModeration.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Message_ControlModeration.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -900,8 +1162,17 @@ class Message_ControlModeration extends $pb.GeneratedMessage { $core.List<$core.List<$core.int>> get rejectedIds => $_getList(1); } +/// A 'read receipt' control message class Message_ControlReadReceipt extends $pb.GeneratedMessage { - factory Message_ControlReadReceipt() => create(); + factory Message_ControlReadReceipt({ + $core.Iterable<$core.List<$core.int>>? readIds, + }) { + final $result = create(); + if (readIds != null) { + $result.readIds.addAll(readIds); + } + return $result; + } Message_ControlReadReceipt._() : super(); factory Message_ControlReadReceipt.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Message_ControlReadReceipt.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -948,8 +1219,61 @@ enum Message_Kind { notSet } +/// A single message as part of a series of messages class Message extends $pb.GeneratedMessage { - factory Message() => create(); + factory Message({ + $core.List<$core.int>? id, + $0.TypedKey? author, + $fixnum.Int64? timestamp, + Message_Text? text, + Message_Secret? secret, + Message_ControlDelete? delete, + Message_ControlErase? erase, + Message_ControlSettings? settings, + Message_ControlPermissions? permissions, + Message_ControlMembership? membership, + Message_ControlModeration? moderation, + $0.Signature? signature, + }) { + final $result = create(); + if (id != null) { + $result.id = id; + } + if (author != null) { + $result.author = author; + } + if (timestamp != null) { + $result.timestamp = timestamp; + } + if (text != null) { + $result.text = text; + } + if (secret != null) { + $result.secret = secret; + } + if (delete != null) { + $result.delete = delete; + } + if (erase != null) { + $result.erase = erase; + } + if (settings != null) { + $result.settings = settings; + } + if (permissions != null) { + $result.permissions = permissions; + } + if (membership != null) { + $result.membership = membership; + } + if (moderation != null) { + $result.moderation = moderation; + } + if (signature != null) { + $result.signature = signature; + } + return $result; + } Message._() : super(); factory Message.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Message.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -1006,6 +1330,8 @@ class Message extends $pb.GeneratedMessage { Message_Kind whichKind() => _Message_KindByTag[$_whichOneof(0)]!; void clearKind() => clearField($_whichOneof(0)); + /// Unique id for this author stream + /// Calculated from the hash of the previous message from this author @$pb.TagNumber(1) $core.List<$core.int> get id => $_getN(0); @$pb.TagNumber(1) @@ -1015,6 +1341,7 @@ class Message extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearId() => clearField(1); + /// Author of the message (identity public key) @$pb.TagNumber(2) $0.TypedKey get author => $_getN(1); @$pb.TagNumber(2) @@ -1026,6 +1353,7 @@ class Message extends $pb.GeneratedMessage { @$pb.TagNumber(2) $0.TypedKey ensureAuthor() => $_ensure(1); + /// Time the message was sent according to sender @$pb.TagNumber(3) $fixnum.Int64 get timestamp => $_getI64(2); @$pb.TagNumber(3) @@ -1123,6 +1451,7 @@ class Message extends $pb.GeneratedMessage { @$pb.TagNumber(11) Message_ControlModeration ensureModeration() => $_ensure(10); + /// Author signature over all of the fields and attachment signatures @$pb.TagNumber(12) $0.Signature get signature => $_getN(11); @$pb.TagNumber(12) @@ -1135,8 +1464,21 @@ class Message extends $pb.GeneratedMessage { $0.Signature ensureSignature() => $_ensure(11); } +/// Locally stored messages for chats class ReconciledMessage extends $pb.GeneratedMessage { - factory ReconciledMessage() => create(); + factory ReconciledMessage({ + Message? content, + $fixnum.Int64? reconciledTime, + }) { + final $result = create(); + if (content != null) { + $result.content = content; + } + if (reconciledTime != null) { + $result.reconciledTime = reconciledTime; + } + return $result; + } ReconciledMessage._() : super(); factory ReconciledMessage.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory ReconciledMessage.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -1168,6 +1510,7 @@ class ReconciledMessage extends $pb.GeneratedMessage { static ReconciledMessage getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static ReconciledMessage? _defaultInstance; + /// The message as sent @$pb.TagNumber(1) Message get content => $_getN(0); @$pb.TagNumber(1) @@ -1179,6 +1522,7 @@ class ReconciledMessage extends $pb.GeneratedMessage { @$pb.TagNumber(1) Message ensureContent() => $_ensure(0); + /// The timestamp the message was reconciled @$pb.TagNumber(2) $fixnum.Int64 get reconciledTime => $_getI64(1); @$pb.TagNumber(2) @@ -1189,8 +1533,36 @@ class ReconciledMessage extends $pb.GeneratedMessage { void clearReconciledTime() => clearField(2); } +/// The means of direct communications that is synchronized between +/// two users. Visible and encrypted for the other party. +/// Includes communications for: +/// * Profile changes +/// * Identity changes +/// * 1-1 chat messages +/// * Group chat messages +/// +/// DHT Schema: SMPL(0,1,[identityPublicKey]) +/// DHT Key (UnicastOutbox): localConversation +/// DHT Secret: None +/// Encryption: DH(IdentityA, IdentityB) class Conversation extends $pb.GeneratedMessage { - factory Conversation() => create(); + factory Conversation({ + Profile? profile, + $core.String? superIdentityJson, + $0.TypedKey? messages, + }) { + final $result = create(); + if (profile != null) { + $result.profile = profile; + } + if (superIdentityJson != null) { + $result.superIdentityJson = superIdentityJson; + } + if (messages != null) { + $result.messages = messages; + } + return $result; + } Conversation._() : super(); factory Conversation.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Conversation.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -1223,6 +1595,7 @@ class Conversation extends $pb.GeneratedMessage { static Conversation getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static Conversation? _defaultInstance; + /// Profile to publish to friend @$pb.TagNumber(1) Profile get profile => $_getN(0); @$pb.TagNumber(1) @@ -1234,6 +1607,7 @@ class Conversation extends $pb.GeneratedMessage { @$pb.TagNumber(1) Profile ensureProfile() => $_ensure(0); + /// SuperIdentity (JSON) to publish to friend or chat room @$pb.TagNumber(2) $core.String get superIdentityJson => $_getSZ(1); @$pb.TagNumber(2) @@ -1243,6 +1617,7 @@ class Conversation extends $pb.GeneratedMessage { @$pb.TagNumber(2) void clearSuperIdentityJson() => clearField(2); + /// Messages DHTLog @$pb.TagNumber(3) $0.TypedKey get messages => $_getN(2); @$pb.TagNumber(3) @@ -1255,8 +1630,21 @@ class Conversation extends $pb.GeneratedMessage { $0.TypedKey ensureMessages() => $_ensure(2); } +/// A member of chat which may or may not be associated with a contact class ChatMember extends $pb.GeneratedMessage { - factory ChatMember() => create(); + factory ChatMember({ + $0.TypedKey? remoteIdentityPublicKey, + $0.TypedKey? remoteConversationRecordKey, + }) { + final $result = create(); + if (remoteIdentityPublicKey != null) { + $result.remoteIdentityPublicKey = remoteIdentityPublicKey; + } + if (remoteConversationRecordKey != null) { + $result.remoteConversationRecordKey = remoteConversationRecordKey; + } + return $result; + } ChatMember._() : super(); factory ChatMember.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory ChatMember.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -1288,6 +1676,7 @@ class ChatMember extends $pb.GeneratedMessage { static ChatMember getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static ChatMember? _defaultInstance; + /// The identity public key most recently associated with the chat member @$pb.TagNumber(1) $0.TypedKey get remoteIdentityPublicKey => $_getN(0); @$pb.TagNumber(1) @@ -1299,6 +1688,7 @@ class ChatMember extends $pb.GeneratedMessage { @$pb.TagNumber(1) $0.TypedKey ensureRemoteIdentityPublicKey() => $_ensure(0); + /// Conversation key for the other party @$pb.TagNumber(2) $0.TypedKey get remoteConversationRecordKey => $_getN(1); @$pb.TagNumber(2) @@ -1311,8 +1701,26 @@ class ChatMember extends $pb.GeneratedMessage { $0.TypedKey ensureRemoteConversationRecordKey() => $_ensure(1); } +/// A 1-1 chat +/// Privately encrypted, this is the local user's copy of the chat class DirectChat extends $pb.GeneratedMessage { - factory DirectChat() => create(); + factory DirectChat({ + ChatSettings? settings, + $0.TypedKey? localConversationRecordKey, + ChatMember? remoteMember, + }) { + final $result = create(); + if (settings != null) { + $result.settings = settings; + } + if (localConversationRecordKey != null) { + $result.localConversationRecordKey = localConversationRecordKey; + } + if (remoteMember != null) { + $result.remoteMember = remoteMember; + } + return $result; + } DirectChat._() : super(); factory DirectChat.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory DirectChat.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -1345,6 +1753,7 @@ class DirectChat extends $pb.GeneratedMessage { static DirectChat getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static DirectChat? _defaultInstance; + /// Settings @$pb.TagNumber(1) ChatSettings get settings => $_getN(0); @$pb.TagNumber(1) @@ -1356,6 +1765,7 @@ class DirectChat extends $pb.GeneratedMessage { @$pb.TagNumber(1) ChatSettings ensureSettings() => $_ensure(0); + /// Conversation key for this user @$pb.TagNumber(2) $0.TypedKey get localConversationRecordKey => $_getN(1); @$pb.TagNumber(2) @@ -1367,6 +1777,7 @@ class DirectChat extends $pb.GeneratedMessage { @$pb.TagNumber(2) $0.TypedKey ensureLocalConversationRecordKey() => $_ensure(1); + /// Conversation key for the other party @$pb.TagNumber(3) ChatMember get remoteMember => $_getN(2); @$pb.TagNumber(3) @@ -1379,8 +1790,34 @@ class DirectChat extends $pb.GeneratedMessage { ChatMember ensureRemoteMember() => $_ensure(2); } +/// A group chat +/// Privately encrypted, this is the local user's copy of the chat class GroupChat extends $pb.GeneratedMessage { - factory GroupChat() => create(); + factory GroupChat({ + ChatSettings? settings, + Membership? membership, + Permissions? permissions, + $0.TypedKey? localConversationRecordKey, + $core.Iterable? remoteMembers, + }) { + final $result = create(); + if (settings != null) { + $result.settings = settings; + } + if (membership != null) { + $result.membership = membership; + } + if (permissions != null) { + $result.permissions = permissions; + } + if (localConversationRecordKey != null) { + $result.localConversationRecordKey = localConversationRecordKey; + } + if (remoteMembers != null) { + $result.remoteMembers.addAll(remoteMembers); + } + return $result; + } GroupChat._() : super(); factory GroupChat.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory GroupChat.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -1415,6 +1852,7 @@ class GroupChat extends $pb.GeneratedMessage { static GroupChat getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static GroupChat? _defaultInstance; + /// Settings @$pb.TagNumber(1) ChatSettings get settings => $_getN(0); @$pb.TagNumber(1) @@ -1426,6 +1864,7 @@ class GroupChat extends $pb.GeneratedMessage { @$pb.TagNumber(1) ChatSettings ensureSettings() => $_ensure(0); + /// Membership @$pb.TagNumber(2) Membership get membership => $_getN(1); @$pb.TagNumber(2) @@ -1437,6 +1876,7 @@ class GroupChat extends $pb.GeneratedMessage { @$pb.TagNumber(2) Membership ensureMembership() => $_ensure(1); + /// Permissions @$pb.TagNumber(3) Permissions get permissions => $_getN(2); @$pb.TagNumber(3) @@ -1448,6 +1888,7 @@ class GroupChat extends $pb.GeneratedMessage { @$pb.TagNumber(3) Permissions ensurePermissions() => $_ensure(2); + /// Conversation key for this user @$pb.TagNumber(4) $0.TypedKey get localConversationRecordKey => $_getN(3); @$pb.TagNumber(4) @@ -1459,6 +1900,7 @@ class GroupChat extends $pb.GeneratedMessage { @$pb.TagNumber(4) $0.TypedKey ensureLocalConversationRecordKey() => $_ensure(3); + /// Conversation keys for the other parties @$pb.TagNumber(5) $core.List get remoteMembers => $_getList(4); } @@ -1469,8 +1911,21 @@ enum Chat_Kind { notSet } +/// Some kind of chat class Chat extends $pb.GeneratedMessage { - factory Chat() => create(); + factory Chat({ + DirectChat? direct, + GroupChat? group, + }) { + final $result = create(); + if (direct != null) { + $result.direct = direct; + } + if (group != null) { + $result.group = group; + } + return $result; + } Chat._() : super(); factory Chat.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Chat.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -1534,8 +1989,45 @@ class Chat extends $pb.GeneratedMessage { GroupChat ensureGroup() => $_ensure(1); } +/// Publicly shared profile information for both contacts and accounts +/// Contains: +/// Name - Friendly name +/// Pronouns - Pronouns of user +/// Icon - Little picture to represent user in contact list class Profile extends $pb.GeneratedMessage { - factory Profile() => create(); + factory Profile({ + $core.String? name, + $core.String? pronouns, + $core.String? about, + $core.String? status, + Availability? availability, + DataReference? avatar, + $fixnum.Int64? timestamp, + }) { + final $result = create(); + if (name != null) { + $result.name = name; + } + if (pronouns != null) { + $result.pronouns = pronouns; + } + if (about != null) { + $result.about = about; + } + if (status != null) { + $result.status = status; + } + if (availability != null) { + $result.availability = availability; + } + if (avatar != null) { + $result.avatar = avatar; + } + if (timestamp != null) { + $result.timestamp = timestamp; + } + return $result; + } Profile._() : super(); factory Profile.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Profile.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -1572,6 +2064,7 @@ class Profile extends $pb.GeneratedMessage { static Profile getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static Profile? _defaultInstance; + /// Friendy name (max length 64) @$pb.TagNumber(1) $core.String get name => $_getSZ(0); @$pb.TagNumber(1) @@ -1581,6 +2074,7 @@ class Profile extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearName() => clearField(1); + /// Pronouns of user (max length 64) @$pb.TagNumber(2) $core.String get pronouns => $_getSZ(1); @$pb.TagNumber(2) @@ -1590,6 +2084,7 @@ class Profile extends $pb.GeneratedMessage { @$pb.TagNumber(2) void clearPronouns() => clearField(2); + /// Description of the user (max length 1024) @$pb.TagNumber(3) $core.String get about => $_getSZ(2); @$pb.TagNumber(3) @@ -1599,6 +2094,7 @@ class Profile extends $pb.GeneratedMessage { @$pb.TagNumber(3) void clearAbout() => clearField(3); + /// Status/away message (max length 128) @$pb.TagNumber(4) $core.String get status => $_getSZ(3); @$pb.TagNumber(4) @@ -1608,6 +2104,7 @@ class Profile extends $pb.GeneratedMessage { @$pb.TagNumber(4) void clearStatus() => clearField(4); + /// Availability @$pb.TagNumber(5) Availability get availability => $_getN(4); @$pb.TagNumber(5) @@ -1617,6 +2114,7 @@ class Profile extends $pb.GeneratedMessage { @$pb.TagNumber(5) void clearAvailability() => clearField(5); + /// Avatar @$pb.TagNumber(6) DataReference get avatar => $_getN(5); @$pb.TagNumber(6) @@ -1628,6 +2126,7 @@ class Profile extends $pb.GeneratedMessage { @$pb.TagNumber(6) DataReference ensureAvatar() => $_ensure(5); + /// Timestamp of last change @$pb.TagNumber(7) $fixnum.Int64 get timestamp => $_getI64(6); @$pb.TagNumber(7) @@ -1638,8 +2137,61 @@ class Profile extends $pb.GeneratedMessage { void clearTimestamp() => clearField(7); } +/// A record of an individual account +/// Pointed to by the identity account map in the identity key +/// +/// DHT Schema: DFLT(1) +/// DHT Private: accountSecretKey class Account extends $pb.GeneratedMessage { - factory Account() => create(); + factory Account({ + Profile? profile, + $core.bool? invisible, + $core.int? autoAwayTimeoutMin, + $1.OwnedDHTRecordPointer? contactList, + $1.OwnedDHTRecordPointer? contactInvitationRecords, + $1.OwnedDHTRecordPointer? chatList, + $1.OwnedDHTRecordPointer? groupChatList, + $core.String? freeMessage, + $core.String? busyMessage, + $core.String? awayMessage, + $core.bool? autodetectAway, + }) { + final $result = create(); + if (profile != null) { + $result.profile = profile; + } + if (invisible != null) { + $result.invisible = invisible; + } + if (autoAwayTimeoutMin != null) { + $result.autoAwayTimeoutMin = autoAwayTimeoutMin; + } + if (contactList != null) { + $result.contactList = contactList; + } + if (contactInvitationRecords != null) { + $result.contactInvitationRecords = contactInvitationRecords; + } + if (chatList != null) { + $result.chatList = chatList; + } + if (groupChatList != null) { + $result.groupChatList = groupChatList; + } + if (freeMessage != null) { + $result.freeMessage = freeMessage; + } + if (busyMessage != null) { + $result.busyMessage = busyMessage; + } + if (awayMessage != null) { + $result.awayMessage = awayMessage; + } + if (autodetectAway != null) { + $result.autodetectAway = autodetectAway; + } + return $result; + } Account._() : super(); factory Account.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Account.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -1680,6 +2232,7 @@ class Account extends $pb.GeneratedMessage { static Account getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static Account? _defaultInstance; + /// The user's profile that gets shared with contacts @$pb.TagNumber(1) Profile get profile => $_getN(0); @$pb.TagNumber(1) @@ -1691,6 +2244,7 @@ class Account extends $pb.GeneratedMessage { @$pb.TagNumber(1) Profile ensureProfile() => $_ensure(0); + /// Invisibility makes you always look 'Offline' @$pb.TagNumber(2) $core.bool get invisible => $_getBF(1); @$pb.TagNumber(2) @@ -1700,6 +2254,7 @@ class Account extends $pb.GeneratedMessage { @$pb.TagNumber(2) void clearInvisible() => clearField(2); + /// Auto-away sets 'away' mode after an inactivity time (only if autodetect_away is set) @$pb.TagNumber(3) $core.int get autoAwayTimeoutMin => $_getIZ(2); @$pb.TagNumber(3) @@ -1709,6 +2264,8 @@ class Account extends $pb.GeneratedMessage { @$pb.TagNumber(3) void clearAutoAwayTimeoutMin() => clearField(3); + /// The contacts DHTList for this account + /// DHT Private @$pb.TagNumber(4) $1.OwnedDHTRecordPointer get contactList => $_getN(3); @$pb.TagNumber(4) @@ -1720,6 +2277,8 @@ class Account extends $pb.GeneratedMessage { @$pb.TagNumber(4) $1.OwnedDHTRecordPointer ensureContactList() => $_ensure(3); + /// The ContactInvitationRecord DHTShortArray for this account + /// DHT Private @$pb.TagNumber(5) $1.OwnedDHTRecordPointer get contactInvitationRecords => $_getN(4); @$pb.TagNumber(5) @@ -1731,6 +2290,8 @@ class Account extends $pb.GeneratedMessage { @$pb.TagNumber(5) $1.OwnedDHTRecordPointer ensureContactInvitationRecords() => $_ensure(4); + /// The Chats DHTList for this account + /// DHT Private @$pb.TagNumber(6) $1.OwnedDHTRecordPointer get chatList => $_getN(5); @$pb.TagNumber(6) @@ -1742,6 +2303,8 @@ class Account extends $pb.GeneratedMessage { @$pb.TagNumber(6) $1.OwnedDHTRecordPointer ensureChatList() => $_ensure(5); + /// The GroupChats DHTList for this account + /// DHT Private @$pb.TagNumber(7) $1.OwnedDHTRecordPointer get groupChatList => $_getN(6); @$pb.TagNumber(7) @@ -1753,6 +2316,7 @@ class Account extends $pb.GeneratedMessage { @$pb.TagNumber(7) $1.OwnedDHTRecordPointer ensureGroupChatList() => $_ensure(6); + /// Free message (max length 128) @$pb.TagNumber(8) $core.String get freeMessage => $_getSZ(7); @$pb.TagNumber(8) @@ -1762,6 +2326,7 @@ class Account extends $pb.GeneratedMessage { @$pb.TagNumber(8) void clearFreeMessage() => clearField(8); + /// Busy message (max length 128) @$pb.TagNumber(9) $core.String get busyMessage => $_getSZ(8); @$pb.TagNumber(9) @@ -1771,6 +2336,7 @@ class Account extends $pb.GeneratedMessage { @$pb.TagNumber(9) void clearBusyMessage() => clearField(9); + /// Away message (max length 128) @$pb.TagNumber(10) $core.String get awayMessage => $_getSZ(9); @$pb.TagNumber(10) @@ -1780,6 +2346,7 @@ class Account extends $pb.GeneratedMessage { @$pb.TagNumber(10) void clearAwayMessage() => clearField(10); + /// Auto-detect away @$pb.TagNumber(11) $core.bool get autodetectAway => $_getBF(10); @$pb.TagNumber(11) @@ -1790,8 +2357,51 @@ class Account extends $pb.GeneratedMessage { void clearAutodetectAway() => clearField(11); } +/// A record of a contact that has accepted a contact invitation +/// Contains a copy of the most recent remote profile as well as +/// a locally edited profile. +/// Contains a copy of the most recent identity from the contact's +/// Master identity dht key +/// +/// Stored in ContactList DHTList class Contact extends $pb.GeneratedMessage { - factory Contact() => create(); + factory Contact({ + $core.String? nickname, + Profile? profile, + $core.String? superIdentityJson, + $0.TypedKey? identityPublicKey, + $0.TypedKey? remoteConversationRecordKey, + $0.TypedKey? localConversationRecordKey, + $core.bool? showAvailability, + $core.String? notes, + }) { + final $result = create(); + if (nickname != null) { + $result.nickname = nickname; + } + if (profile != null) { + $result.profile = profile; + } + if (superIdentityJson != null) { + $result.superIdentityJson = superIdentityJson; + } + if (identityPublicKey != null) { + $result.identityPublicKey = identityPublicKey; + } + if (remoteConversationRecordKey != null) { + $result.remoteConversationRecordKey = remoteConversationRecordKey; + } + if (localConversationRecordKey != null) { + $result.localConversationRecordKey = localConversationRecordKey; + } + if (showAvailability != null) { + $result.showAvailability = showAvailability; + } + if (notes != null) { + $result.notes = notes; + } + return $result; + } Contact._() : super(); factory Contact.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Contact.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -1829,6 +2439,7 @@ class Contact extends $pb.GeneratedMessage { static Contact getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static Contact? _defaultInstance; + /// Friend's nickname @$pb.TagNumber(1) $core.String get nickname => $_getSZ(0); @$pb.TagNumber(1) @@ -1838,6 +2449,7 @@ class Contact extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearNickname() => clearField(1); + /// Copy of friend's profile from remote conversation @$pb.TagNumber(2) Profile get profile => $_getN(1); @$pb.TagNumber(2) @@ -1849,6 +2461,7 @@ class Contact extends $pb.GeneratedMessage { @$pb.TagNumber(2) Profile ensureProfile() => $_ensure(1); + /// Copy of friend's SuperIdentity in JSON from remote conversation @$pb.TagNumber(3) $core.String get superIdentityJson => $_getSZ(2); @$pb.TagNumber(3) @@ -1858,6 +2471,7 @@ class Contact extends $pb.GeneratedMessage { @$pb.TagNumber(3) void clearSuperIdentityJson() => clearField(3); + /// Copy of friend's most recent identity public key from their identityMaster @$pb.TagNumber(4) $0.TypedKey get identityPublicKey => $_getN(3); @$pb.TagNumber(4) @@ -1869,6 +2483,7 @@ class Contact extends $pb.GeneratedMessage { @$pb.TagNumber(4) $0.TypedKey ensureIdentityPublicKey() => $_ensure(3); + /// Remote conversation key to sync from friend @$pb.TagNumber(5) $0.TypedKey get remoteConversationRecordKey => $_getN(4); @$pb.TagNumber(5) @@ -1880,6 +2495,7 @@ class Contact extends $pb.GeneratedMessage { @$pb.TagNumber(5) $0.TypedKey ensureRemoteConversationRecordKey() => $_ensure(4); + /// Our conversation key for friend to sync @$pb.TagNumber(6) $0.TypedKey get localConversationRecordKey => $_getN(5); @$pb.TagNumber(6) @@ -1891,6 +2507,7 @@ class Contact extends $pb.GeneratedMessage { @$pb.TagNumber(6) $0.TypedKey ensureLocalConversationRecordKey() => $_ensure(5); + /// Show availability to this contact @$pb.TagNumber(7) $core.bool get showAvailability => $_getBF(6); @$pb.TagNumber(7) @@ -1900,6 +2517,7 @@ class Contact extends $pb.GeneratedMessage { @$pb.TagNumber(7) void clearShowAvailability() => clearField(7); + /// Notes about this friend @$pb.TagNumber(8) $core.String get notes => $_getSZ(7); @$pb.TagNumber(8) @@ -1910,8 +2528,24 @@ class Contact extends $pb.GeneratedMessage { void clearNotes() => clearField(8); } +/// Invitation that is shared for VeilidChat contact connections +/// serialized to QR code or data blob, not send over DHT, out of band. +/// Writer secret is unique to this invitation. Writer public key is in the ContactRequestPrivate +/// in the ContactRequestInbox subkey 0 DHT key class ContactInvitation extends $pb.GeneratedMessage { - factory ContactInvitation() => create(); + factory ContactInvitation({ + $0.TypedKey? contactRequestInboxKey, + $core.List<$core.int>? writerSecret, + }) { + final $result = create(); + if (contactRequestInboxKey != null) { + $result.contactRequestInboxKey = contactRequestInboxKey; + } + if (writerSecret != null) { + $result.writerSecret = writerSecret; + } + return $result; + } ContactInvitation._() : super(); factory ContactInvitation.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory ContactInvitation.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -1943,6 +2577,7 @@ class ContactInvitation extends $pb.GeneratedMessage { static ContactInvitation getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static ContactInvitation? _defaultInstance; + /// Contact request DHT record key @$pb.TagNumber(1) $0.TypedKey get contactRequestInboxKey => $_getN(0); @$pb.TagNumber(1) @@ -1954,6 +2589,7 @@ class ContactInvitation extends $pb.GeneratedMessage { @$pb.TagNumber(1) $0.TypedKey ensureContactRequestInboxKey() => $_ensure(0); + /// Writer secret key bytes possibly encrypted with nonce appended @$pb.TagNumber(2) $core.List<$core.int> get writerSecret => $_getN(1); @$pb.TagNumber(2) @@ -1964,8 +2600,21 @@ class ContactInvitation extends $pb.GeneratedMessage { void clearWriterSecret() => clearField(2); } +/// Signature of invitation with identity class SignedContactInvitation extends $pb.GeneratedMessage { - factory SignedContactInvitation() => create(); + factory SignedContactInvitation({ + $core.List<$core.int>? contactInvitation, + $0.Signature? identitySignature, + }) { + final $result = create(); + if (contactInvitation != null) { + $result.contactInvitation = contactInvitation; + } + if (identitySignature != null) { + $result.identitySignature = identitySignature; + } + return $result; + } SignedContactInvitation._() : super(); factory SignedContactInvitation.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory SignedContactInvitation.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -1997,6 +2646,7 @@ class SignedContactInvitation extends $pb.GeneratedMessage { static SignedContactInvitation getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static SignedContactInvitation? _defaultInstance; + /// The serialized bytes for the contact invitation @$pb.TagNumber(1) $core.List<$core.int> get contactInvitation => $_getN(0); @$pb.TagNumber(1) @@ -2006,6 +2656,7 @@ class SignedContactInvitation extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearContactInvitation() => clearField(1); + /// The signature of the contact_invitation bytes with the identity @$pb.TagNumber(2) $0.Signature get identitySignature => $_getN(1); @$pb.TagNumber(2) @@ -2018,8 +2669,22 @@ class SignedContactInvitation extends $pb.GeneratedMessage { $0.Signature ensureIdentitySignature() => $_ensure(1); } +/// Contact request unicastinbox on the DHT +/// DHTSchema: SMPL 1 owner key, 1 writer key symmetrically encrypted with writer secret class ContactRequest extends $pb.GeneratedMessage { - factory ContactRequest() => create(); + factory ContactRequest({ + EncryptionKeyType? encryptionKeyType, + $core.List<$core.int>? private, + }) { + final $result = create(); + if (encryptionKeyType != null) { + $result.encryptionKeyType = encryptionKeyType; + } + if (private != null) { + $result.private = private; + } + return $result; + } ContactRequest._() : super(); factory ContactRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory ContactRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -2051,6 +2716,7 @@ class ContactRequest extends $pb.GeneratedMessage { static ContactRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static ContactRequest? _defaultInstance; + /// The kind of encryption used on the unicastinbox writer key @$pb.TagNumber(1) EncryptionKeyType get encryptionKeyType => $_getN(0); @$pb.TagNumber(1) @@ -2060,6 +2726,7 @@ class ContactRequest extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearEncryptionKeyType() => clearField(1); + /// The private part encoded and symmetrically encrypted with the unicastinbox writer secret @$pb.TagNumber(2) $core.List<$core.int> get private => $_getN(1); @$pb.TagNumber(2) @@ -2070,8 +2737,34 @@ class ContactRequest extends $pb.GeneratedMessage { void clearPrivate() => clearField(2); } +/// The private part of a possibly encrypted contact request +/// Symmetrically encrypted with writer secret class ContactRequestPrivate extends $pb.GeneratedMessage { - factory ContactRequestPrivate() => create(); + factory ContactRequestPrivate({ + $0.CryptoKey? writerKey, + Profile? profile, + $0.TypedKey? superIdentityRecordKey, + $0.TypedKey? chatRecordKey, + $fixnum.Int64? expiration, + }) { + final $result = create(); + if (writerKey != null) { + $result.writerKey = writerKey; + } + if (profile != null) { + $result.profile = profile; + } + if (superIdentityRecordKey != null) { + $result.superIdentityRecordKey = superIdentityRecordKey; + } + if (chatRecordKey != null) { + $result.chatRecordKey = chatRecordKey; + } + if (expiration != null) { + $result.expiration = expiration; + } + return $result; + } ContactRequestPrivate._() : super(); factory ContactRequestPrivate.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory ContactRequestPrivate.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -2106,6 +2799,7 @@ class ContactRequestPrivate extends $pb.GeneratedMessage { static ContactRequestPrivate getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static ContactRequestPrivate? _defaultInstance; + /// Writer public key for signing writes to contact request unicastinbox @$pb.TagNumber(1) $0.CryptoKey get writerKey => $_getN(0); @$pb.TagNumber(1) @@ -2117,6 +2811,7 @@ class ContactRequestPrivate extends $pb.GeneratedMessage { @$pb.TagNumber(1) $0.CryptoKey ensureWriterKey() => $_ensure(0); + /// Snapshot of profile @$pb.TagNumber(2) Profile get profile => $_getN(1); @$pb.TagNumber(2) @@ -2128,6 +2823,7 @@ class ContactRequestPrivate extends $pb.GeneratedMessage { @$pb.TagNumber(2) Profile ensureProfile() => $_ensure(1); + /// SuperIdentity DHT record key @$pb.TagNumber(3) $0.TypedKey get superIdentityRecordKey => $_getN(2); @$pb.TagNumber(3) @@ -2139,6 +2835,7 @@ class ContactRequestPrivate extends $pb.GeneratedMessage { @$pb.TagNumber(3) $0.TypedKey ensureSuperIdentityRecordKey() => $_ensure(2); + /// Local chat DHT record key @$pb.TagNumber(4) $0.TypedKey get chatRecordKey => $_getN(3); @$pb.TagNumber(4) @@ -2150,6 +2847,7 @@ class ContactRequestPrivate extends $pb.GeneratedMessage { @$pb.TagNumber(4) $0.TypedKey ensureChatRecordKey() => $_ensure(3); + /// Expiration timestamp @$pb.TagNumber(5) $fixnum.Int64 get expiration => $_getI64(4); @$pb.TagNumber(5) @@ -2160,8 +2858,25 @@ class ContactRequestPrivate extends $pb.GeneratedMessage { void clearExpiration() => clearField(5); } +/// To accept or reject a contact request, fill this out and send to the ContactRequest unicastinbox class ContactResponse extends $pb.GeneratedMessage { - factory ContactResponse() => create(); + factory ContactResponse({ + $core.bool? accept, + $0.TypedKey? superIdentityRecordKey, + $0.TypedKey? remoteConversationRecordKey, + }) { + final $result = create(); + if (accept != null) { + $result.accept = accept; + } + if (superIdentityRecordKey != null) { + $result.superIdentityRecordKey = superIdentityRecordKey; + } + if (remoteConversationRecordKey != null) { + $result.remoteConversationRecordKey = remoteConversationRecordKey; + } + return $result; + } ContactResponse._() : super(); factory ContactResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory ContactResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -2194,6 +2909,7 @@ class ContactResponse extends $pb.GeneratedMessage { static ContactResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static ContactResponse? _defaultInstance; + /// Accept or reject @$pb.TagNumber(1) $core.bool get accept => $_getBF(0); @$pb.TagNumber(1) @@ -2203,6 +2919,7 @@ class ContactResponse extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearAccept() => clearField(1); + /// Remote SuperIdentity DHT record key @$pb.TagNumber(2) $0.TypedKey get superIdentityRecordKey => $_getN(1); @$pb.TagNumber(2) @@ -2214,6 +2931,7 @@ class ContactResponse extends $pb.GeneratedMessage { @$pb.TagNumber(2) $0.TypedKey ensureSuperIdentityRecordKey() => $_ensure(1); + /// Remote chat DHT record key if accepted @$pb.TagNumber(3) $0.TypedKey get remoteConversationRecordKey => $_getN(2); @$pb.TagNumber(3) @@ -2226,8 +2944,22 @@ class ContactResponse extends $pb.GeneratedMessage { $0.TypedKey ensureRemoteConversationRecordKey() => $_ensure(2); } +/// Signature of response with identity +/// Symmetrically encrypted with writer secret class SignedContactResponse extends $pb.GeneratedMessage { - factory SignedContactResponse() => create(); + factory SignedContactResponse({ + $core.List<$core.int>? contactResponse, + $0.Signature? identitySignature, + }) { + final $result = create(); + if (contactResponse != null) { + $result.contactResponse = contactResponse; + } + if (identitySignature != null) { + $result.identitySignature = identitySignature; + } + return $result; + } SignedContactResponse._() : super(); factory SignedContactResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory SignedContactResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -2259,6 +2991,7 @@ class SignedContactResponse extends $pb.GeneratedMessage { static SignedContactResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static SignedContactResponse? _defaultInstance; + /// Serialized bytes for ContactResponse @$pb.TagNumber(1) $core.List<$core.int> get contactResponse => $_getN(0); @$pb.TagNumber(1) @@ -2268,6 +3001,7 @@ class SignedContactResponse extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearContactResponse() => clearField(1); + /// Signature of the contact_accept bytes with the identity @$pb.TagNumber(2) $0.Signature get identitySignature => $_getN(1); @$pb.TagNumber(2) @@ -2280,8 +3014,41 @@ class SignedContactResponse extends $pb.GeneratedMessage { $0.Signature ensureIdentitySignature() => $_ensure(1); } +/// Contact request record kept in Account DHTList to keep track of extant contact invitations class ContactInvitationRecord extends $pb.GeneratedMessage { - factory ContactInvitationRecord() => create(); + factory ContactInvitationRecord({ + $1.OwnedDHTRecordPointer? contactRequestInbox, + $0.CryptoKey? writerKey, + $0.CryptoKey? writerSecret, + $0.TypedKey? localConversationRecordKey, + $fixnum.Int64? expiration, + $core.List<$core.int>? invitation, + $core.String? message, + }) { + final $result = create(); + if (contactRequestInbox != null) { + $result.contactRequestInbox = contactRequestInbox; + } + if (writerKey != null) { + $result.writerKey = writerKey; + } + if (writerSecret != null) { + $result.writerSecret = writerSecret; + } + if (localConversationRecordKey != null) { + $result.localConversationRecordKey = localConversationRecordKey; + } + if (expiration != null) { + $result.expiration = expiration; + } + if (invitation != null) { + $result.invitation = invitation; + } + if (message != null) { + $result.message = message; + } + return $result; + } ContactInvitationRecord._() : super(); factory ContactInvitationRecord.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory ContactInvitationRecord.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -2318,6 +3085,7 @@ class ContactInvitationRecord extends $pb.GeneratedMessage { static ContactInvitationRecord getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static ContactInvitationRecord? _defaultInstance; + /// Contact request unicastinbox DHT record key (parent is accountkey) @$pb.TagNumber(1) $1.OwnedDHTRecordPointer get contactRequestInbox => $_getN(0); @$pb.TagNumber(1) @@ -2329,6 +3097,7 @@ class ContactInvitationRecord extends $pb.GeneratedMessage { @$pb.TagNumber(1) $1.OwnedDHTRecordPointer ensureContactRequestInbox() => $_ensure(0); + /// Writer key sent to contact for the contact_request_inbox smpl inbox subkey @$pb.TagNumber(2) $0.CryptoKey get writerKey => $_getN(1); @$pb.TagNumber(2) @@ -2340,6 +3109,7 @@ class ContactInvitationRecord extends $pb.GeneratedMessage { @$pb.TagNumber(2) $0.CryptoKey ensureWriterKey() => $_ensure(1); + /// Writer secret sent encrypted in the invitation @$pb.TagNumber(3) $0.CryptoKey get writerSecret => $_getN(2); @$pb.TagNumber(3) @@ -2351,6 +3121,7 @@ class ContactInvitationRecord extends $pb.GeneratedMessage { @$pb.TagNumber(3) $0.CryptoKey ensureWriterSecret() => $_ensure(2); + /// Local chat DHT record key (parent is accountkey, will be moved to Contact if accepted) @$pb.TagNumber(4) $0.TypedKey get localConversationRecordKey => $_getN(3); @$pb.TagNumber(4) @@ -2362,6 +3133,7 @@ class ContactInvitationRecord extends $pb.GeneratedMessage { @$pb.TagNumber(4) $0.TypedKey ensureLocalConversationRecordKey() => $_ensure(3); + /// Expiration timestamp @$pb.TagNumber(5) $fixnum.Int64 get expiration => $_getI64(4); @$pb.TagNumber(5) @@ -2371,6 +3143,7 @@ class ContactInvitationRecord extends $pb.GeneratedMessage { @$pb.TagNumber(5) void clearExpiration() => clearField(5); + /// A copy of the raw SignedContactInvitation invitation bytes post-encryption and signing @$pb.TagNumber(6) $core.List<$core.int> get invitation => $_getN(5); @$pb.TagNumber(6) @@ -2380,6 +3153,7 @@ class ContactInvitationRecord extends $pb.GeneratedMessage { @$pb.TagNumber(6) void clearInvitation() => clearField(6); + /// The message sent along with the invitation @$pb.TagNumber(7) $core.String get message => $_getSZ(6); @$pb.TagNumber(7) diff --git a/lib/proto/veilidchat.pbenum.dart b/lib/proto/veilidchat.pbenum.dart index 9133788..42009e8 100644 --- a/lib/proto/veilidchat.pbenum.dart +++ b/lib/proto/veilidchat.pbenum.dart @@ -4,7 +4,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields // ignore_for_file: unnecessary_import, unnecessary_this, unused_import @@ -13,6 +13,7 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; +/// Contact availability class Availability extends $pb.ProtobufEnum { static const Availability AVAILABILITY_UNSPECIFIED = Availability._(0, _omitEnumNames ? '' : 'AVAILABILITY_UNSPECIFIED'); static const Availability AVAILABILITY_OFFLINE = Availability._(1, _omitEnumNames ? '' : 'AVAILABILITY_OFFLINE'); @@ -34,6 +35,7 @@ class Availability extends $pb.ProtobufEnum { const Availability._($core.int v, $core.String n) : super(v, n); } +/// Encryption used on secret keys class EncryptionKeyType extends $pb.ProtobufEnum { static const EncryptionKeyType ENCRYPTION_KEY_TYPE_UNSPECIFIED = EncryptionKeyType._(0, _omitEnumNames ? '' : 'ENCRYPTION_KEY_TYPE_UNSPECIFIED'); static const EncryptionKeyType ENCRYPTION_KEY_TYPE_NONE = EncryptionKeyType._(1, _omitEnumNames ? '' : 'ENCRYPTION_KEY_TYPE_NONE'); @@ -53,6 +55,7 @@ class EncryptionKeyType extends $pb.ProtobufEnum { const EncryptionKeyType._($core.int v, $core.String n) : super(v, n); } +/// Scope of a chat class Scope extends $pb.ProtobufEnum { static const Scope WATCHERS = Scope._(0, _omitEnumNames ? '' : 'WATCHERS'); static const Scope MODERATED = Scope._(1, _omitEnumNames ? '' : 'MODERATED'); diff --git a/lib/proto/veilidchat.pbjson.dart b/lib/proto/veilidchat.pbjson.dart index ec327f4..e102d40 100644 --- a/lib/proto/veilidchat.pbjson.dart +++ b/lib/proto/veilidchat.pbjson.dart @@ -4,7 +4,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields // ignore_for_file: unnecessary_import, unnecessary_this, unused_import diff --git a/lib/proto/veilidchat.pbserver.dart b/lib/proto/veilidchat.pbserver.dart index 02a9ae4..047feed 100644 --- a/lib/proto/veilidchat.pbserver.dart +++ b/lib/proto/veilidchat.pbserver.dart @@ -4,7 +4,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names // ignore_for_file: deprecated_member_use_from_same_package, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields diff --git a/lib/router/cubits/router_cubit.freezed.dart b/lib/router/cubits/router_cubit.freezed.dart index e44cd91..8377607 100644 --- a/lib/router/cubits/router_cubit.freezed.dart +++ b/lib/router/cubits/router_cubit.freezed.dart @@ -22,8 +22,12 @@ RouterState _$RouterStateFromJson(Map json) { mixin _$RouterState { bool get hasAnyAccount => throw _privateConstructorUsedError; + /// Serializes this RouterState to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of RouterState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $RouterStateCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -47,6 +51,8 @@ class _$RouterStateCopyWithImpl<$Res, $Val extends RouterState> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of RouterState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -80,6 +86,8 @@ class __$$RouterStateImplCopyWithImpl<$Res> _$RouterStateImpl _value, $Res Function(_$RouterStateImpl) _then) : super(_value, _then); + /// Create a copy of RouterState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -127,11 +135,13 @@ class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState { other.hasAnyAccount == hasAnyAccount)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, hasAnyAccount); - @JsonKey(ignore: true) + /// Create a copy of RouterState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$RouterStateImplCopyWith<_$RouterStateImpl> get copyWith => @@ -154,8 +164,11 @@ abstract class _RouterState implements RouterState { @override bool get hasAnyAccount; + + /// Create a copy of RouterState + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$RouterStateImplCopyWith<_$RouterStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/settings/models/preferences.freezed.dart b/lib/settings/models/preferences.freezed.dart index 1735d45..a7ebed3 100644 --- a/lib/settings/models/preferences.freezed.dart +++ b/lib/settings/models/preferences.freezed.dart @@ -24,8 +24,12 @@ mixin _$LockPreference { bool get lockWhenSwitching => throw _privateConstructorUsedError; bool get lockWithSystemLock => throw _privateConstructorUsedError; + /// Serializes this LockPreference to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of LockPreference + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $LockPreferenceCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -52,6 +56,8 @@ class _$LockPreferenceCopyWithImpl<$Res, $Val extends LockPreference> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of LockPreference + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -98,6 +104,8 @@ class __$$LockPreferenceImplCopyWithImpl<$Res> _$LockPreferenceImpl _value, $Res Function(_$LockPreferenceImpl) _then) : super(_value, _then); + /// Create a copy of LockPreference + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -161,12 +169,14 @@ class _$LockPreferenceImpl implements _LockPreference { other.lockWithSystemLock == lockWithSystemLock)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, inactivityLockSecs, lockWhenSwitching, lockWithSystemLock); - @JsonKey(ignore: true) + /// Create a copy of LockPreference + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$LockPreferenceImplCopyWith<_$LockPreferenceImpl> get copyWith => @@ -196,8 +206,11 @@ abstract class _LockPreference implements LockPreference { bool get lockWhenSwitching; @override bool get lockWithSystemLock; + + /// Create a copy of LockPreference + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$LockPreferenceImplCopyWith<_$LockPreferenceImpl> get copyWith => throw _privateConstructorUsedError; } @@ -215,8 +228,12 @@ mixin _$Preferences { NotificationsPreference get notificationsPreference => throw _privateConstructorUsedError; + /// Serializes this Preferences to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $PreferencesCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -248,6 +265,8 @@ class _$PreferencesCopyWithImpl<$Res, $Val extends Preferences> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -276,6 +295,8 @@ class _$PreferencesCopyWithImpl<$Res, $Val extends Preferences> ) as $Val); } + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $ThemePreferencesCopyWith<$Res> get themePreference { @@ -284,6 +305,8 @@ class _$PreferencesCopyWithImpl<$Res, $Val extends Preferences> }); } + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $LockPreferenceCopyWith<$Res> get lockPreference { @@ -292,6 +315,8 @@ class _$PreferencesCopyWithImpl<$Res, $Val extends Preferences> }); } + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $NotificationsPreferenceCopyWith<$Res> get notificationsPreference { @@ -332,6 +357,8 @@ class __$$PreferencesImplCopyWithImpl<$Res> _$PreferencesImpl _value, $Res Function(_$PreferencesImpl) _then) : super(_value, _then); + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -407,12 +434,14 @@ class _$PreferencesImpl implements _Preferences { other.notificationsPreference == notificationsPreference)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, themePreference, languagePreference, lockPreference, notificationsPreference); - @JsonKey(ignore: true) + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$PreferencesImplCopyWith<_$PreferencesImpl> get copyWith => @@ -445,8 +474,11 @@ abstract class _Preferences implements Preferences { LockPreference get lockPreference; @override NotificationsPreference get notificationsPreference; + + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$PreferencesImplCopyWith<_$PreferencesImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/theme/models/chat_theme.dart b/lib/theme/models/chat_theme.dart index 8623427..a7039e5 100644 --- a/lib/theme/models/chat_theme.dart +++ b/lib/theme/models/chat_theme.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_chat_ui/flutter_chat_ui.dart'; -import 'scale_scheme.dart'; +import 'scale_theme/scale_scheme.dart'; ChatTheme makeChatTheme( ScaleScheme scale, ScaleConfig scaleConfig, TextTheme textTheme) => diff --git a/lib/theme/models/contrast_generator.dart b/lib/theme/models/contrast_generator.dart index 4271c1a..b71ebea 100644 --- a/lib/theme/models/contrast_generator.dart +++ b/lib/theme/models/contrast_generator.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'radix_generator.dart'; -import 'scale_color.dart'; -import 'scale_input_decorator_theme.dart'; -import 'scale_scheme.dart'; -import 'scale_theme.dart'; +import 'scale_theme/scale_color.dart'; +import 'scale_theme/scale_input_decorator_theme.dart'; +import 'scale_theme/scale_scheme.dart'; +import 'scale_theme/scale_theme.dart'; ScaleColor _contrastScaleColor( {required Brightness brightness, diff --git a/lib/theme/models/models.dart b/lib/theme/models/models.dart index 45b54f9..be80542 100644 --- a/lib/theme/models/models.dart +++ b/lib/theme/models/models.dart @@ -1,7 +1,4 @@ export 'chat_theme.dart'; export 'radix_generator.dart'; -export 'scale_color.dart'; -export 'scale_scheme.dart'; -export 'scale_theme.dart'; -export 'slider_tile.dart'; +export 'scale_theme/scale_theme.dart'; export 'theme_preference.dart'; diff --git a/lib/theme/models/radix_generator.dart b/lib/theme/models/radix_generator.dart index a2bdbb1..3dac7bc 100644 --- a/lib/theme/models/radix_generator.dart +++ b/lib/theme/models/radix_generator.dart @@ -1,13 +1,14 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:radix_colors/radix_colors.dart'; import '../../tools/tools.dart'; -import 'scale_color.dart'; -import 'scale_input_decorator_theme.dart'; -import 'scale_scheme.dart'; -import 'scale_theme.dart'; +import 'scale_theme/scale_color.dart'; +import 'scale_theme/scale_input_decorator_theme.dart'; +import 'scale_theme/scale_scheme.dart'; +import 'scale_theme/scale_theme.dart'; enum RadixThemeColor { scarlet, // red + violet + tomato @@ -571,7 +572,11 @@ RadixScheme _radixScheme(Brightness brightness, RadixThemeColor themeColor) { TextTheme makeRadixTextTheme(Brightness brightness) { late final TextTheme textTheme; - if (Platform.isIOS) { + if (kIsWeb) { + textTheme = (brightness == Brightness.light) + ? Typography.blackHelsinki + : Typography.whiteHelsinki; + } else if (Platform.isIOS) { textTheme = (brightness == Brightness.light) ? Typography.blackCupertino : Typography.whiteCupertino; diff --git a/lib/theme/models/scale_color.dart b/lib/theme/models/scale_theme/scale_color.dart similarity index 100% rename from lib/theme/models/scale_color.dart rename to lib/theme/models/scale_theme/scale_color.dart diff --git a/lib/theme/models/scale_theme/scale_custom_dropdown_theme.dart b/lib/theme/models/scale_theme/scale_custom_dropdown_theme.dart new file mode 100644 index 0000000..94764a5 --- /dev/null +++ b/lib/theme/models/scale_theme/scale_custom_dropdown_theme.dart @@ -0,0 +1,93 @@ +import 'package:animated_custom_dropdown/custom_dropdown.dart'; +import 'package:flutter/material.dart'; + +import 'scale_scheme.dart'; +import 'scale_theme.dart'; + +class ScaleCustomDropdownTheme { + ScaleCustomDropdownTheme({ + required this.decoration, + required this.closedHeaderPadding, + required this.expandedHeaderPadding, + required this.itemsListPadding, + required this.listItemPadding, + required this.disabledDecoration, + required this.textStyle, + }); + + final CustomDropdownDecoration decoration; + final EdgeInsets closedHeaderPadding; + final EdgeInsets expandedHeaderPadding; + final EdgeInsets itemsListPadding; + final EdgeInsets listItemPadding; + final CustomDropdownDisabledDecoration disabledDecoration; + final TextStyle textStyle; +} + +extension ScaleCustomDropdownThemeExt on ScaleTheme { + ScaleCustomDropdownTheme customDropdownTheme() { + final scale = scheme.primaryScale; + final borderColor = scale.borderText; + final fillColor = scale.subtleBorder; + + // final backgroundColor = config.useVisualIndicators && !selected + // ? tileColor.borderText + // : borderColor; + // final textColor = config.useVisualIndicators && !selected + // ? borderColor + // : tileColor.borderText; + + // final largeTextStyle = textTheme.labelMedium!.copyWith(color: textColor); + // final smallTextStyle = textTheme.labelSmall!.copyWith(color: textColor); + + final border = Border.fromBorderSide(config.useVisualIndicators + ? BorderSide(width: 2, color: borderColor, strokeAlign: 0) + : BorderSide.none); + final borderRadius = BorderRadius.circular(8 * config.borderRadiusScale); + + final decoration = CustomDropdownDecoration( + closedFillColor: fillColor, + expandedFillColor: fillColor, + closedShadow: [], + expandedShadow: [], + closedSuffixIcon: Icon(Icons.arrow_drop_down, color: borderColor), + expandedSuffixIcon: Icon(Icons.arrow_drop_up, color: borderColor), + prefixIcon: null, + closedBorder: border, + closedBorderRadius: borderRadius, + closedErrorBorder: null, + closedErrorBorderRadius: null, + expandedBorder: border, + expandedBorderRadius: borderRadius, + hintStyle: null, + headerStyle: null, + noResultFoundStyle: null, + errorStyle: null, + listItemStyle: null, + overlayScrollbarDecoration: null, + searchFieldDecoration: null, + listItemDecoration: null, + ); + + final disabledDecoration = CustomDropdownDisabledDecoration( + fillColor: null, + shadow: null, + suffixIcon: null, + prefixIcon: null, + border: null, + borderRadius: null, + headerStyle: null, + hintStyle: null, + ); + + return ScaleCustomDropdownTheme( + textStyle: textTheme.labelSmall!.copyWith(color: borderColor), + decoration: decoration, + closedHeaderPadding: const EdgeInsets.all(4), + expandedHeaderPadding: const EdgeInsets.all(4), + itemsListPadding: const EdgeInsets.all(4), + listItemPadding: const EdgeInsets.all(4), + disabledDecoration: disabledDecoration, + ); + } +} diff --git a/lib/theme/models/scale_input_decorator_theme.dart b/lib/theme/models/scale_theme/scale_input_decorator_theme.dart similarity index 60% rename from lib/theme/models/scale_input_decorator_theme.dart rename to lib/theme/models/scale_theme/scale_input_decorator_theme.dart index 265670e..3af1f15 100644 --- a/lib/theme/models/scale_input_decorator_theme.dart +++ b/lib/theme/models/scale_theme/scale_input_decorator_theme.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'scale_scheme.dart'; +import 'scale_theme.dart'; class ScaleInputDecoratorTheme extends InputDecorationTheme { ScaleInputDecoratorTheme( @@ -25,41 +26,41 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme { final TextTheme _textTheme; @override - TextStyle? get hintStyle => MaterialStateTextStyle.resolveWith((states) { - if (states.contains(MaterialState.disabled)) { + TextStyle? get hintStyle => WidgetStateTextStyle.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { return TextStyle(color: _scaleScheme.grayScale.border); } return TextStyle(color: _scaleScheme.primaryScale.border); }); @override - Color? get fillColor => MaterialStateColor.resolveWith((states) { - if (states.contains(MaterialState.disabled)) { - return _scaleScheme.grayScale.primary.withOpacity(0.04); + Color? get fillColor => WidgetStateColor.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return _scaleScheme.grayScale.primary.withAlpha(10); } - return _scaleScheme.primaryScale.primary.withOpacity(0.04); + return _scaleScheme.primaryScale.primary.withAlpha(10); }); @override BorderSide? get activeIndicatorBorder => - MaterialStateBorderSide.resolveWith((states) { - if (states.contains(MaterialState.disabled)) { + WidgetStateBorderSide.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { return BorderSide( - color: _scaleScheme.grayScale.border.withAlpha(0x7F)); + color: _scaleScheme.grayScale.border.withAlpha(127)); } - if (states.contains(MaterialState.error)) { - if (states.contains(MaterialState.hovered)) { + if (states.contains(WidgetState.error)) { + if (states.contains(WidgetState.hovered)) { return BorderSide(color: _scaleScheme.errorScale.hoverBorder); } - if (states.contains(MaterialState.focused)) { + if (states.contains(WidgetState.focused)) { return BorderSide(color: _scaleScheme.errorScale.border, width: 2); } return BorderSide(color: _scaleScheme.errorScale.subtleBorder); } - if (states.contains(MaterialState.hovered)) { + if (states.contains(WidgetState.hovered)) { return BorderSide(color: _scaleScheme.secondaryScale.hoverBorder); } - if (states.contains(MaterialState.focused)) { + if (states.contains(WidgetState.focused)) { return BorderSide( color: _scaleScheme.secondaryScale.border, width: 2); } @@ -67,25 +68,24 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme { }); @override - BorderSide? get outlineBorder => - MaterialStateBorderSide.resolveWith((states) { - if (states.contains(MaterialState.disabled)) { + BorderSide? get outlineBorder => WidgetStateBorderSide.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { return BorderSide( - color: _scaleScheme.grayScale.border.withAlpha(0x7F)); + color: _scaleScheme.grayScale.border.withAlpha(127)); } - if (states.contains(MaterialState.error)) { - if (states.contains(MaterialState.hovered)) { + if (states.contains(WidgetState.error)) { + if (states.contains(WidgetState.hovered)) { return BorderSide(color: _scaleScheme.errorScale.hoverBorder); } - if (states.contains(MaterialState.focused)) { + if (states.contains(WidgetState.focused)) { return BorderSide(color: _scaleScheme.errorScale.border, width: 2); } return BorderSide(color: _scaleScheme.errorScale.subtleBorder); } - if (states.contains(MaterialState.hovered)) { + if (states.contains(WidgetState.hovered)) { return BorderSide(color: _scaleScheme.primaryScale.hoverBorder); } - if (states.contains(MaterialState.focused)) { + if (states.contains(WidgetState.focused)) { return BorderSide(color: _scaleScheme.primaryScale.border, width: 2); } return BorderSide(color: _scaleScheme.primaryScale.subtleBorder); @@ -95,51 +95,51 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme { Color? get iconColor => _scaleScheme.primaryScale.primary; @override - Color? get prefixIconColor => MaterialStateColor.resolveWith((states) { - if (states.contains(MaterialState.disabled)) { - return _scaleScheme.primaryScale.primary.withAlpha(0x3F); + Color? get prefixIconColor => WidgetStateColor.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return _scaleScheme.primaryScale.primary.withAlpha(127); } - if (states.contains(MaterialState.error)) { + if (states.contains(WidgetState.error)) { return _scaleScheme.errorScale.primary; } return _scaleScheme.primaryScale.primary; }); @override - Color? get suffixIconColor => MaterialStateColor.resolveWith((states) { - if (states.contains(MaterialState.disabled)) { - return _scaleScheme.primaryScale.primary.withAlpha(0x3F); + Color? get suffixIconColor => WidgetStateColor.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return _scaleScheme.primaryScale.primary.withAlpha(127); } - if (states.contains(MaterialState.error)) { + if (states.contains(WidgetState.error)) { return _scaleScheme.errorScale.primary; } return _scaleScheme.primaryScale.primary; }); @override - TextStyle? get labelStyle => MaterialStateTextStyle.resolveWith((states) { + TextStyle? get labelStyle => WidgetStateTextStyle.resolveWith((states) { final textStyle = _textTheme.bodyLarge ?? const TextStyle(); - if (states.contains(MaterialState.disabled)) { + if (states.contains(WidgetState.disabled)) { return textStyle.copyWith( - color: _scaleScheme.grayScale.border.withAlpha(0x7F)); + color: _scaleScheme.grayScale.border.withAlpha(127)); } - if (states.contains(MaterialState.error)) { - if (states.contains(MaterialState.hovered)) { + if (states.contains(WidgetState.error)) { + if (states.contains(WidgetState.hovered)) { return textStyle.copyWith( color: _scaleScheme.errorScale.hoverBorder); } - if (states.contains(MaterialState.focused)) { + if (states.contains(WidgetState.focused)) { return textStyle.copyWith( color: _scaleScheme.errorScale.hoverBorder); } return textStyle.copyWith( color: _scaleScheme.errorScale.subtleBorder); } - if (states.contains(MaterialState.hovered)) { + if (states.contains(WidgetState.hovered)) { return textStyle.copyWith( color: _scaleScheme.primaryScale.hoverBorder); } - if (states.contains(MaterialState.focused)) { + if (states.contains(WidgetState.focused)) { return textStyle.copyWith( color: _scaleScheme.primaryScale.hoverBorder); } @@ -150,19 +150,24 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme { TextStyle? get floatingLabelStyle => labelStyle; @override - TextStyle? get helperStyle => MaterialStateTextStyle.resolveWith((states) { + TextStyle? get helperStyle => WidgetStateTextStyle.resolveWith((states) { final textStyle = _textTheme.bodySmall ?? const TextStyle(); - if (states.contains(MaterialState.disabled)) { + if (states.contains(WidgetState.disabled)) { return textStyle.copyWith( - color: _scaleScheme.grayScale.border.withAlpha(0x7F)); + color: _scaleScheme.grayScale.border.withAlpha(127)); } return textStyle.copyWith( - color: _scaleScheme.secondaryScale.border.withAlpha(0x7F)); + color: _scaleScheme.secondaryScale.border.withAlpha(127)); }); @override - TextStyle? get errorStyle => MaterialStateTextStyle.resolveWith((states) { + TextStyle? get errorStyle => WidgetStateTextStyle.resolveWith((states) { final textStyle = _textTheme.bodySmall ?? const TextStyle(); return textStyle.copyWith(color: _scaleScheme.errorScale.primary); }); } + +extension ScaleInputDecoratorThemeExt on ScaleTheme { + ScaleInputDecoratorTheme inputDecoratorTheme() => + ScaleInputDecoratorTheme(scheme, config, textTheme); +} diff --git a/lib/theme/models/scale_scheme.dart b/lib/theme/models/scale_theme/scale_scheme.dart similarity index 100% rename from lib/theme/models/scale_scheme.dart rename to lib/theme/models/scale_theme/scale_scheme.dart diff --git a/lib/theme/models/scale_theme/scale_theme.dart b/lib/theme/models/scale_theme/scale_theme.dart new file mode 100644 index 0000000..d539c86 --- /dev/null +++ b/lib/theme/models/scale_theme/scale_theme.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +import 'scale_scheme.dart'; + +export 'scale_color.dart'; +export 'scale_input_decorator_theme.dart'; +export 'scale_scheme.dart'; +export 'scale_tile_theme.dart'; +export 'scale_toast_theme.dart'; + +class ScaleTheme extends ThemeExtension { + ScaleTheme({ + required this.textTheme, + required this.scheme, + required this.config, + }); + + final TextTheme textTheme; + final ScaleScheme scheme; + final ScaleConfig config; + + @override + ScaleTheme copyWith({ + TextTheme? textTheme, + ScaleScheme? scheme, + ScaleConfig? config, + }) => + ScaleTheme( + textTheme: textTheme ?? this.textTheme, + scheme: scheme ?? this.scheme, + config: config ?? this.config, + ); + + @override + ScaleTheme lerp(ScaleTheme? other, double t) { + if (other is! ScaleTheme) { + return this; + } + return ScaleTheme( + textTheme: TextTheme.lerp(textTheme, other.textTheme, t), + scheme: scheme.lerp(other.scheme, t), + config: config.lerp(other.config, t)); + } +} diff --git a/lib/theme/models/scale_theme.dart b/lib/theme/models/scale_theme/scale_tile_theme.dart similarity index 67% rename from lib/theme/models/scale_theme.dart rename to lib/theme/models/scale_theme/scale_tile_theme.dart index 95f7db9..e7339d1 100644 --- a/lib/theme/models/scale_theme.dart +++ b/lib/theme/models/scale_theme/scale_tile_theme.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'scale_scheme.dart'; +import 'scale_theme.dart'; class ScaleTileTheme { ScaleTileTheme( @@ -19,40 +20,7 @@ class ScaleTileTheme { final TextStyle smallTextStyle; } -class ScaleTheme extends ThemeExtension { - ScaleTheme({ - required this.textTheme, - required this.scheme, - required this.config, - }); - - final TextTheme textTheme; - final ScaleScheme scheme; - final ScaleConfig config; - - @override - ScaleTheme copyWith({ - TextTheme? textTheme, - ScaleScheme? scheme, - ScaleConfig? config, - }) => - ScaleTheme( - textTheme: textTheme ?? this.textTheme, - scheme: scheme ?? this.scheme, - config: config ?? this.config, - ); - - @override - ScaleTheme lerp(ScaleTheme? other, double t) { - if (other is! ScaleTheme) { - return this; - } - return ScaleTheme( - textTheme: TextTheme.lerp(textTheme, other.textTheme, t), - scheme: scheme.lerp(other.scheme, t), - config: config.lerp(other.config, t)); - } - +extension ScaleTileThemeExt on ScaleTheme { ScaleTileTheme tileTheme( {bool disabled = false, bool selected = false, diff --git a/lib/theme/models/scale_theme/scale_toast_theme.dart b/lib/theme/models/scale_theme/scale_toast_theme.dart new file mode 100644 index 0000000..de310d4 --- /dev/null +++ b/lib/theme/models/scale_theme/scale_toast_theme.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; + +import 'scale_scheme.dart'; +import 'scale_theme.dart'; + +enum ScaleToastKind { + info, + error, +} + +class ScaleToastTheme { + ScaleToastTheme( + {required this.primaryColor, + required this.backgroundColor, + required this.foregroundColor, + required this.borderSide, + required this.borderRadius, + required this.padding, + required this.icon, + required this.titleTextStyle, + required this.descriptionTextStyle}); + + final Color primaryColor; + final Color backgroundColor; + final Color foregroundColor; + final BorderSide? borderSide; + final BorderRadiusGeometry borderRadius; + final EdgeInsetsGeometry padding; + final Icon icon; + final TextStyle titleTextStyle; + final TextStyle descriptionTextStyle; +} + +extension ScaleToastThemeExt on ScaleTheme { + ScaleToastTheme toastTheme(ScaleToastKind kind) { + final toastScaleColor = scheme.scale(ScaleKind.tertiary); + + Icon icon; + switch (kind) { + case ScaleToastKind.info: + icon = const Icon(Icons.info, size: 32); + case ScaleToastKind.error: + icon = const Icon(Icons.dangerous, size: 32); + } + + final primaryColor = toastScaleColor.calloutText; + final borderColor = toastScaleColor.border; + final backgroundColor = config.useVisualIndicators + ? toastScaleColor.calloutText + : toastScaleColor.calloutBackground; + final textColor = config.useVisualIndicators + ? toastScaleColor.calloutBackground + : toastScaleColor.calloutText; + final titleColor = config.useVisualIndicators + ? toastScaleColor.calloutBackground + : toastScaleColor.calloutText; + + return ScaleToastTheme( + primaryColor: primaryColor, + backgroundColor: backgroundColor, + foregroundColor: textColor, + borderSide: (config.useVisualIndicators || config.preferBorders) + ? BorderSide(color: borderColor, width: 2) + : const BorderSide(color: Colors.transparent, width: 0), + borderRadius: BorderRadius.circular(12 * config.borderRadiusScale), + padding: const EdgeInsets.all(8), + icon: icon, + titleTextStyle: textTheme.labelMedium!.copyWith(color: titleColor), + descriptionTextStyle: + textTheme.labelMedium!.copyWith(color: textColor)); + } +} diff --git a/lib/theme/models/theme_preference.dart b/lib/theme/models/theme_preference.dart index ec7a40e..9d6f6dc 100644 --- a/lib/theme/models/theme_preference.dart +++ b/lib/theme/models/theme_preference.dart @@ -5,7 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import '../views/widget_helpers.dart'; import 'contrast_generator.dart'; import 'radix_generator.dart'; -import 'scale_scheme.dart'; +import 'scale_theme/scale_scheme.dart'; part 'theme_preference.freezed.dart'; part 'theme_preference.g.dart'; diff --git a/lib/theme/models/theme_preference.freezed.dart b/lib/theme/models/theme_preference.freezed.dart index 97e3f81..657d021 100644 --- a/lib/theme/models/theme_preference.freezed.dart +++ b/lib/theme/models/theme_preference.freezed.dart @@ -25,8 +25,12 @@ mixin _$ThemePreferences { ColorPreference get colorPreference => throw _privateConstructorUsedError; double get displayScale => throw _privateConstructorUsedError; + /// Serializes this ThemePreferences to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ThemePreferences + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ThemePreferencesCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -53,6 +57,8 @@ class _$ThemePreferencesCopyWithImpl<$Res, $Val extends ThemePreferences> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ThemePreferences + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -99,6 +105,8 @@ class __$$ThemePreferencesImplCopyWithImpl<$Res> $Res Function(_$ThemePreferencesImpl) _then) : super(_value, _then); + /// Create a copy of ThemePreferences + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -162,12 +170,14 @@ class _$ThemePreferencesImpl implements _ThemePreferences { other.displayScale == displayScale)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, brightnessPreference, colorPreference, displayScale); - @JsonKey(ignore: true) + /// Create a copy of ThemePreferences + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ThemePreferencesImplCopyWith<_$ThemePreferencesImpl> get copyWith => @@ -197,8 +207,11 @@ abstract class _ThemePreferences implements ThemePreferences { ColorPreference get colorPreference; @override double get displayScale; + + /// Create a copy of ThemePreferences + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ThemePreferencesImplCopyWith<_$ThemePreferencesImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/theme/views/option_box.dart b/lib/theme/views/option_box.dart index 508c7ba..06a3293 100644 --- a/lib/theme/views/option_box.dart +++ b/lib/theme/views/option_box.dart @@ -40,7 +40,9 @@ class OptionBox extends StatelessWidget { ElevatedButton( onPressed: _onClick, child: Row(mainAxisSize: MainAxisSize.min, children: [ - Icon(_buttonIcon, size: 24).paddingLTRB(0, 8, 8, 8), + Icon(_buttonIcon, + size: 24, color: scale.primaryScale.appText) + .paddingLTRB(0, 8, 8, 8), Text(textAlign: TextAlign.center, _buttonText) ])).paddingLTRB(0, 12, 0, 0).toCenter() ]).paddingAll(12)) diff --git a/lib/theme/models/slider_tile.dart b/lib/theme/views/slider_tile.dart similarity index 100% rename from lib/theme/models/slider_tile.dart rename to lib/theme/views/slider_tile.dart diff --git a/lib/theme/views/styled_scaffold.dart b/lib/theme/views/styled_scaffold.dart index 9560ecc..9ee3d36 100644 --- a/lib/theme/views/styled_scaffold.dart +++ b/lib/theme/views/styled_scaffold.dart @@ -14,17 +14,22 @@ class StyledScaffold extends StatelessWidget { final enableBorder = !isMobileSize(context); - final scaffold = clipBorder( - clipEnabled: enableBorder, - borderEnabled: scaleConfig.useVisualIndicators, - borderRadius: 16 * scaleConfig.borderRadiusScale, - borderColor: scale.primaryScale.border, - child: Scaffold(appBar: appBar, body: body, key: key)) - .paddingAll(enableBorder ? 32 : 0); + var scaffold = clipBorder( + clipEnabled: enableBorder, + borderEnabled: scaleConfig.useVisualIndicators, + borderRadius: 16 * scaleConfig.borderRadiusScale, + borderColor: scale.primaryScale.border, + child: Scaffold(appBar: appBar, body: body, key: key)); + + if (!scaleConfig.useVisualIndicators) { + scaffold = scaffold.withShadow( + offset: const Offset(0, 16), + shadowColor: scale.primaryScale.primary.withAlpha(0x3F).darken(60)); + } return GestureDetector( onTap: () => FocusManager.instance.primaryFocus?.unfocus(), - child: scaffold); + child: scaffold.paddingAll(enableBorder ? 32 : 0)); } //////////////////////////////////////////////////////////////////////////// diff --git a/lib/theme/views/views.dart b/lib/theme/views/views.dart index e8aa1d8..b5aa809 100644 --- a/lib/theme/views/views.dart +++ b/lib/theme/views/views.dart @@ -8,6 +8,7 @@ export 'pop_control.dart'; export 'recovery_key_widget.dart'; export 'responsive.dart'; export 'scanner_error_widget.dart'; +export 'slider_tile.dart'; export 'styled_alert.dart'; export 'styled_dialog.dart'; export 'styled_scaffold.dart'; diff --git a/lib/theme/views/widget_helpers.dart b/lib/theme/views/widget_helpers.dart index 970e065..1b9e80f 100644 --- a/lib/theme/views/widget_helpers.dart +++ b/lib/theme/views/widget_helpers.dart @@ -500,24 +500,26 @@ const grayColorFilter = ColorFilter.matrix([ 0, ]); -Widget clipBorder({ +Container clipBorder({ required bool clipEnabled, required bool borderEnabled, required double borderRadius, required Color borderColor, required Widget child, }) => - ClipRRect( - borderRadius: clipEnabled - ? BorderRadius.circular(borderRadius) - : BorderRadius.zero, - child: DecoratedBox( - decoration: BoxDecoration(boxShadow: [ - if (borderEnabled) BoxShadow(color: borderColor, spreadRadius: 2) - ]), - child: ClipRRect( + // ignore: avoid_unnecessary_containers, use_decorated_box + Container( + decoration: ShapeDecoration( + color: borderColor, + shape: RoundedRectangleBorder( borderRadius: clipEnabled ? BorderRadius.circular(borderRadius) : BorderRadius.zero, - child: child, - )).paddingAll(clipEnabled && borderEnabled ? 2 : 0)); + )), + child: ClipRRect( + clipBehavior: Clip.hardEdge, + borderRadius: clipEnabled + ? BorderRadius.circular(borderRadius) + : BorderRadius.zero, + child: child) + .paddingAll(clipEnabled && borderEnabled ? 2 : 0)); diff --git a/lib/tools/loggy.dart b/lib/tools/loggy.dart index d8d4880..2730888 100644 --- a/lib/tools/loggy.dart +++ b/lib/tools/loggy.dart @@ -112,7 +112,7 @@ class CallbackPrinter extends LoggyPrinter { void onLog(LogRecord record) { final out = record.pretty().replaceAll('\uFFFD', ''); - if (Platform.isAndroid) { + if (!kIsWeb && Platform.isAndroid) { debugPrint(out); } else { debugPrintSynchronously(out); diff --git a/lib/veilid_processor/models/processor_connection_state.freezed.dart b/lib/veilid_processor/models/processor_connection_state.freezed.dart index d857318..87ad295 100644 --- a/lib/veilid_processor/models/processor_connection_state.freezed.dart +++ b/lib/veilid_processor/models/processor_connection_state.freezed.dart @@ -19,7 +19,9 @@ mixin _$ProcessorConnectionState { VeilidStateAttachment get attachment => throw _privateConstructorUsedError; VeilidStateNetwork get network => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of ProcessorConnectionState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ProcessorConnectionStateCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -47,6 +49,8 @@ class _$ProcessorConnectionStateCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ProcessorConnectionState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -65,6 +69,8 @@ class _$ProcessorConnectionStateCopyWithImpl<$Res, ) as $Val); } + /// Create a copy of ProcessorConnectionState + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $VeilidStateAttachmentCopyWith<$Res> get attachment { @@ -73,6 +79,8 @@ class _$ProcessorConnectionStateCopyWithImpl<$Res, }); } + /// Create a copy of ProcessorConnectionState + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $VeilidStateNetworkCopyWith<$Res> get network { @@ -109,6 +117,8 @@ class __$$ProcessorConnectionStateImplCopyWithImpl<$Res> $Res Function(_$ProcessorConnectionStateImpl) _then) : super(_value, _then); + /// Create a copy of ProcessorConnectionState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -158,7 +168,9 @@ class _$ProcessorConnectionStateImpl extends _ProcessorConnectionState { @override int get hashCode => Object.hash(runtimeType, attachment, network); - @JsonKey(ignore: true) + /// Create a copy of ProcessorConnectionState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ProcessorConnectionStateImplCopyWith<_$ProcessorConnectionStateImpl> @@ -177,8 +189,11 @@ abstract class _ProcessorConnectionState extends ProcessorConnectionState { VeilidStateAttachment get attachment; @override VeilidStateNetwork get network; + + /// Create a copy of ProcessorConnectionState + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ProcessorConnectionStateImplCopyWith<_$ProcessorConnectionStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/veilid_processor/views/developer.dart b/lib/veilid_processor/views/developer.dart index e40677d..f561f07 100644 --- a/lib/veilid_processor/views/developer.dart +++ b/lib/veilid_processor/views/developer.dart @@ -1,13 +1,11 @@ import 'dart:async'; +import 'package:animated_custom_dropdown/custom_dropdown.dart'; import 'package:ansicolor/ansicolor.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; -import 'package:cool_dropdown/cool_dropdown.dart'; -import 'package:cool_dropdown/models/cool_dropdown_item.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:go_router/go_router.dart'; @@ -17,6 +15,7 @@ import 'package:xterm/xterm.dart'; import '../../layout/layout.dart'; import '../../notifications/notifications.dart'; +import '../../theme/models/scale_theme/scale_custom_dropdown_theme.dart'; import '../../theme/theme.dart'; import '../../tools/tools.dart'; import 'history_text_editing_controller.dart'; @@ -30,6 +29,15 @@ const kDefaultTerminalStyle = TerminalStyle( // height: 1.2, fontFamily: 'Source Code Pro'); +class LogLevelDropdownItem { + const LogLevelDropdownItem( + {required this.label, required this.icon, required this.value}); + + final String label; + final Widget icon; + final LogLevel value; +} + class DeveloperPage extends StatefulWidget { const DeveloperPage({super.key}); @@ -49,7 +57,7 @@ class _DeveloperPageState extends State { }); for (var i = 0; i < logLevels.length; i++) { - _logLevelDropdownItems.add(CoolDropdownItem( + _logLevelDropdownItems.add(LogLevelDropdownItem( label: logLevelName(logLevels[i]), icon: Text(logLevelEmoji(logLevels[i])), value: logLevels[i])); @@ -167,29 +175,28 @@ class _DeveloperPageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final textTheme = theme.textTheme; final scale = theme.extension()!; + final scaleTheme = theme.extension()!; + final dropdownTheme = scaleTheme.customDropdownTheme(); final scaleConfig = theme.extension()!; - // WidgetsBinding.instance.addPostFrameCallback((_) { - // if (!_isScrolling && _wantsBottom) { - // _scrollToBottom(); - // } - // }); + final hintColor = scaleConfig.useVisualIndicators + ? scale.primaryScale.primaryText + : scale.primaryScale.primary; return Scaffold( - backgroundColor: scale.primaryScale.primary, + backgroundColor: scale.primaryScale.border, appBar: DefaultAppBar( title: Text(translate('developer.title')), leading: IconButton( - icon: Icon(Icons.arrow_back, color: scale.primaryScale.primaryText), + icon: Icon(Icons.arrow_back, color: scale.primaryScale.borderText), onPressed: () => GoRouterHelper(context).pop(), ), actions: [ IconButton( icon: const Icon(Icons.copy), - color: scale.primaryScale.primaryText, - disabledColor: scale.primaryScale.primaryText.withAlpha(0x3F), + color: scale.primaryScale.borderText, + disabledColor: scale.primaryScale.borderText.withAlpha(0x3F), onPressed: _terminalController.selection == null ? null : () async { @@ -197,15 +204,15 @@ class _DeveloperPageState extends State { }), IconButton( icon: const Icon(Icons.copy_all), - color: scale.primaryScale.primaryText, - disabledColor: scale.primaryScale.primaryText.withAlpha(0x3F), + color: scale.primaryScale.borderText, + disabledColor: scale.primaryScale.borderText.withAlpha(0x3F), onPressed: () async { await copyAll(context); }), IconButton( icon: const Icon(Icons.clear_all), - color: scale.primaryScale.primaryText, - disabledColor: scale.primaryScale.primaryText.withAlpha(0x3F), + color: scale.primaryScale.borderText, + disabledColor: scale.primaryScale.borderText.withAlpha(0x3F), onPressed: () async { final confirm = await showConfirmModal( context: context, @@ -216,74 +223,39 @@ class _DeveloperPageState extends State { await clear(context); } }), - CoolDropdown( - controller: _logLevelController, - defaultItem: _logLevelDropdownItems - .singleWhere((x) => x.value == _logLevelDropDown), - onChange: (value) { - setState(() { - _logLevelDropDown = value; - Loggy('').level = getLogOptions(value); - setVeilidLogLevel(value); - _logLevelController.close(); - }); - }, - resultOptions: ResultOptions( - width: 64, - height: 40, - render: ResultRender.icon, - icon: SizedBox( - width: 10, - height: 10, - child: CustomPaint( - painter: DropdownArrowPainter( - color: scale.primaryScale.primaryText))), - textStyle: textTheme.labelMedium! - .copyWith(color: scale.primaryScale.primaryText), - padding: const EdgeInsets.fromLTRB(8, 4, 8, 4), - openBoxDecoration: BoxDecoration( - //color: scale.primaryScale.border, - border: Border.all( - color: scaleConfig.useVisualIndicators - ? scale.primaryScale.hoverBorder - : scale.primaryScale.borderText), - borderRadius: - BorderRadius.circular(8 * scaleConfig.borderRadiusScale), - ), - boxDecoration: BoxDecoration( - //color: scale.primaryScale.hoverBorder, - border: Border.all( - color: scaleConfig.useVisualIndicators - ? scale.primaryScale.hoverBorder - : scale.primaryScale.borderText), - borderRadius: - BorderRadius.circular(8 * scaleConfig.borderRadiusScale), - ), - ), - dropdownOptions: DropdownOptions( - width: 160, - align: DropdownAlign.right, - duration: 150.ms, - color: scale.primaryScale.elementBackground, - borderSide: BorderSide(color: scale.primaryScale.border), - borderRadius: - BorderRadius.circular(8 * scaleConfig.borderRadiusScale), - padding: const EdgeInsets.fromLTRB(8, 4, 8, 4), - ), - dropdownTriangleOptions: const DropdownTriangleOptions( - align: DropdownTriangleAlign.right), - dropdownItemOptions: DropdownItemOptions( - selectedTextStyle: textTheme.labelMedium! - .copyWith(color: scale.primaryScale.appText), - textStyle: textTheme.labelMedium! - .copyWith(color: scale.primaryScale.appText), - selectedBoxDecoration: BoxDecoration( - color: scale.primaryScale.activeElementBackground), - mainAxisAlignment: MainAxisAlignment.spaceBetween, - padding: const EdgeInsets.fromLTRB(8, 4, 8, 4), - selectedPadding: const EdgeInsets.fromLTRB(8, 4, 8, 4)), - dropdownList: _logLevelDropdownItems, - ).paddingLTRB(0, 0, 8, 0) + SizedBox.fromSize( + size: const Size(120, 48), + child: CustomDropdown( + items: _logLevelDropdownItems, + initialItem: _logLevelDropdownItems + .singleWhere((x) => x.value == _logLevelDropDown), + onChanged: (item) { + if (item != null) { + setState(() { + _logLevelDropDown = item.value; + Loggy('').level = getLogOptions(item.value); + setVeilidLogLevel(item.value); + }); + } + }, + headerBuilder: (context, item, enabled) => Row(children: [ + item.icon, + const Spacer(), + Text(item.label).copyWith(style: dropdownTheme.textStyle) + ]), + listItemBuilder: (context, item, enabled, onItemSelect) => + Row(children: [ + item.icon, + const Spacer(), + Text(item.label).copyWith(style: dropdownTheme.textStyle) + ]), + decoration: dropdownTheme.decoration, + disabledDecoration: dropdownTheme.disabledDecoration, + listItemPadding: dropdownTheme.listItemPadding, + itemsListPadding: dropdownTheme.itemsListPadding, + expandedHeaderPadding: dropdownTheme.expandedHeaderPadding, + closedHeaderPadding: dropdownTheme.closedHeaderPadding, + )).paddingLTRB(0, 4, 8, 4), ], ), body: GestureDetector( @@ -312,21 +284,24 @@ class _DeveloperPageState extends State { decoration: InputDecoration( filled: true, contentPadding: const EdgeInsets.fromLTRB(8, 2, 8, 2), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular( - 8 * scaleConfig.borderRadiusScale), - borderSide: BorderSide.none), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular( - 8 * scaleConfig.borderRadiusScale), - ), - fillColor: scale.primaryScale.subtleBackground, + enabledBorder: + const OutlineInputBorder(borderSide: BorderSide.none), + border: + const OutlineInputBorder(borderSide: BorderSide.none), + focusedBorder: + const OutlineInputBorder(borderSide: BorderSide.none), + fillColor: scale.primaryScale.elementBackground, + hoverColor: scale.primaryScale.elementBackground, + hintStyle: scaleTheme.textTheme.labelMedium!.copyWith( + color: scaleConfig.useVisualIndicators + ? hintColor.withAlpha(0x7F) + : hintColor), hintText: translate('developer.command'), suffixIcon: IconButton( icon: Icon(Icons.send, color: _historyController.controller.text.isEmpty - ? scale.primaryScale.primary.withAlpha(0x3F) - : scale.primaryScale.primary), + ? hintColor.withAlpha(0x7F) + : hintColor), onPressed: (_historyController.controller.text.isEmpty || _busy) ? null @@ -366,9 +341,9 @@ class _DeveloperPageState extends State { final _terminalController = TerminalController(); late final HistoryTextEditingController _historyController; - final _logLevelController = DropdownController(duration: 250.ms); - final List> _logLevelDropdownItems = []; + final List _logLevelDropdownItems = []; var _logLevelDropDown = log.level.logLevel; + var _showEllet = false; var _busy = false; diff --git a/lib/veilid_processor/views/signal_strength_meter.dart b/lib/veilid_processor/views/signal_strength_meter.dart index 74230ed..5385bb1 100644 --- a/lib/veilid_processor/views/signal_strength_meter.dart +++ b/lib/veilid_processor/views/signal_strength_meter.dart @@ -34,34 +34,33 @@ class SignalStrengthMeterWidget extends StatelessWidget { case AttachmentState.detached: iconWidget = Icon(Icons.signal_cellular_nodata, size: iconSize, - color: this.color ?? scale.primaryScale.primaryText); + color: this.color ?? scale.primaryScale.borderText); return; case AttachmentState.detaching: iconWidget = Icon(Icons.signal_cellular_off, size: iconSize, - color: this.color ?? scale.primaryScale.primaryText); + color: this.color ?? scale.primaryScale.borderText); return; case AttachmentState.attaching: value = 0; - color = this.color ?? scale.primaryScale.primaryText; + color = this.color ?? scale.primaryScale.borderText; case AttachmentState.attachedWeak: value = 1; - color = this.color ?? scale.primaryScale.primaryText; + color = this.color ?? scale.primaryScale.borderText; case AttachmentState.attachedStrong: value = 2; - color = this.color ?? scale.primaryScale.primaryText; + color = this.color ?? scale.primaryScale.borderText; case AttachmentState.attachedGood: value = 3; - color = this.color ?? scale.primaryScale.primaryText; + color = this.color ?? scale.primaryScale.borderText; case AttachmentState.fullyAttached: value = 4; - color = this.color ?? scale.primaryScale.primaryText; + color = this.color ?? scale.primaryScale.borderText; case AttachmentState.overAttached: value = 4; - color = this.color ?? scale.primaryScale.primaryText; + color = this.color ?? scale.primaryScale.borderText; } - inactiveColor = - this.inactiveColor ?? scale.primaryScale.primaryText; + inactiveColor = this.inactiveColor ?? scale.grayScale.borderText; iconWidget = SignalStrengthIndicator.bars( value: value, diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c94b139..408e781 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -48,6 +48,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/packages/veilid_support/example/integration_test/app_test.dart b/packages/veilid_support/example/integration_test/app_test.dart index a0f3b7f..6912fd3 100644 --- a/packages/veilid_support/example/integration_test/app_test.dart +++ b/packages/veilid_support/example/integration_test/app_test.dart @@ -36,116 +36,116 @@ void main() { setUpAll(veilidFixture.attach); tearDownAll(veilidFixture.detach); - // group('TableDB Tests', () { - // group('TableDBArray Tests', () { - // // test('create/delete TableDBArray', testTableDBArrayCreateDelete); + group('TableDB Tests', () { + group('TableDBArray Tests', () { + // test('create/delete TableDBArray', testTableDBArrayCreateDelete); - // group('TableDBArray Add/Get Tests', () { - // for (final params in [ - // // - // (99, 3, 15), - // (100, 4, 16), - // (101, 5, 17), - // // - // (511, 3, 127), - // (512, 4, 128), - // (513, 5, 129), - // // - // (4095, 3, 1023), - // (4096, 4, 1024), - // (4097, 5, 1025), - // // - // (65535, 3, 16383), - // (65536, 4, 16384), - // (65537, 5, 16385), - // ]) { - // final count = params.$1; - // final singles = params.$2; - // final batchSize = params.$3; + group('TableDBArray Add/Get Tests', () { + for (final params in [ + // + (99, 3, 15), + (100, 4, 16), + (101, 5, 17), + // + (511, 3, 127), + (512, 4, 128), + (513, 5, 129), + // + (4095, 3, 1023), + (4096, 4, 1024), + (4097, 5, 1025), + // + (65535, 3, 16383), + (65536, 4, 16384), + (65537, 5, 16385), + ]) { + final count = params.$1; + final singles = params.$2; + final batchSize = params.$3; - // test( - // timeout: const Timeout(Duration(seconds: 480)), - // 'add/remove TableDBArray count = $count batchSize=$batchSize', - // makeTestTableDBArrayAddGetClear( - // count: count, - // singles: singles, - // batchSize: batchSize, - // crypto: const VeilidCryptoPublic()), - // ); - // } - // }); + test( + timeout: const Timeout(Duration(seconds: 480)), + 'add/remove TableDBArray count = $count batchSize=$batchSize', + makeTestTableDBArrayAddGetClear( + count: count, + singles: singles, + batchSize: batchSize, + crypto: const VeilidCryptoPublic()), + ); + } + }); - // group('TableDBArray Insert Tests', () { - // for (final params in [ - // // - // (99, 3, 15), - // (100, 4, 16), - // (101, 5, 17), - // // - // (511, 3, 127), - // (512, 4, 128), - // (513, 5, 129), - // // - // (4095, 3, 1023), - // (4096, 4, 1024), - // (4097, 5, 1025), - // // - // (65535, 3, 16383), - // (65536, 4, 16384), - // (65537, 5, 16385), - // ]) { - // final count = params.$1; - // final singles = params.$2; - // final batchSize = params.$3; + group('TableDBArray Insert Tests', () { + for (final params in [ + // + (99, 3, 15), + (100, 4, 16), + (101, 5, 17), + // + (511, 3, 127), + (512, 4, 128), + (513, 5, 129), + // + (4095, 3, 1023), + (4096, 4, 1024), + (4097, 5, 1025), + // + (65535, 3, 16383), + (65536, 4, 16384), + (65537, 5, 16385), + ]) { + final count = params.$1; + final singles = params.$2; + final batchSize = params.$3; - // test( - // timeout: const Timeout(Duration(seconds: 480)), - // 'insert TableDBArray count=$count singles=$singles batchSize=$batchSize', - // makeTestTableDBArrayInsert( - // count: count, - // singles: singles, - // batchSize: batchSize, - // crypto: const VeilidCryptoPublic()), - // ); - // } - // }); + test( + timeout: const Timeout(Duration(seconds: 480)), + 'insert TableDBArray count=$count singles=$singles batchSize=$batchSize', + makeTestTableDBArrayInsert( + count: count, + singles: singles, + batchSize: batchSize, + crypto: const VeilidCryptoPublic()), + ); + } + }); - // group('TableDBArray Remove Tests', () { - // for (final params in [ - // // - // (99, 3, 15), - // (100, 4, 16), - // (101, 5, 17), - // // - // (511, 3, 127), - // (512, 4, 128), - // (513, 5, 129), - // // - // (4095, 3, 1023), - // (4096, 4, 1024), - // (4097, 5, 1025), - // // - // (16383, 3, 4095), - // (16384, 4, 4096), - // (16385, 5, 4097), - // ]) { - // final count = params.$1; - // final singles = params.$2; - // final batchSize = params.$3; + group('TableDBArray Remove Tests', () { + for (final params in [ + // + (99, 3, 15), + (100, 4, 16), + (101, 5, 17), + // + (511, 3, 127), + (512, 4, 128), + (513, 5, 129), + // + (4095, 3, 1023), + (4096, 4, 1024), + (4097, 5, 1025), + // + (16383, 3, 4095), + (16384, 4, 4096), + (16385, 5, 4097), + ]) { + final count = params.$1; + final singles = params.$2; + final batchSize = params.$3; - // test( - // timeout: const Timeout(Duration(seconds: 480)), - // 'remove TableDBArray count=$count singles=$singles batchSize=$batchSize', - // makeTestTableDBArrayRemove( - // count: count, - // singles: singles, - // batchSize: batchSize, - // crypto: const VeilidCryptoPublic()), - // ); - // } - // }); - // }); - // }); + test( + timeout: const Timeout(Duration(seconds: 480)), + 'remove TableDBArray count=$count singles=$singles batchSize=$batchSize', + makeTestTableDBArrayRemove( + count: count, + singles: singles, + batchSize: batchSize, + crypto: const VeilidCryptoPublic()), + ); + } + }); + }); + }); group('DHT Support Tests', () { setUpAll(updateProcessorFixture.setUp); diff --git a/packages/veilid_support/example/pubspec.lock b/packages/veilid_support/example/pubspec.lock index ade4030..3844db3 100644 --- a/packages/veilid_support/example/pubspec.lock +++ b/packages/veilid_support/example/pubspec.lock @@ -34,10 +34,10 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" async_tools: dependency: "direct dev" description: @@ -66,10 +66,10 @@ packages: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" change_case: dependency: transitive description: @@ -82,10 +82,10 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" charcode: dependency: transitive description: @@ -98,18 +98,18 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" convert: dependency: transitive description: @@ -154,10 +154,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" fast_immutable_collections: dependency: transitive description: @@ -178,10 +178,10 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" fixnum: dependency: transitive description: @@ -296,18 +296,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -352,10 +352,10 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -368,10 +368,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mime: dependency: transitive description: @@ -400,10 +400,10 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_provider: dependency: transitive description: @@ -456,10 +456,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -480,10 +480,10 @@ packages: dependency: transitive description: name: process - sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.3" protobuf: dependency: transitive description: @@ -557,34 +557,34 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" sync_http: dependency: transitive description: @@ -613,34 +613,34 @@ packages: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test: dependency: "direct dev" description: name: test - sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" url: "https://pub.dev" source: hosted - version: "1.25.8" + version: "1.25.15" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.4" test_core: dependency: transitive description: name: test_core - sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.8" typed_data: dependency: transitive description: @@ -663,7 +663,7 @@ packages: path: "../../../../veilid/veilid-flutter" relative: true source: path - version: "0.4.1" + version: "0.4.3" veilid_support: dependency: "direct main" description: @@ -682,10 +682,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.3.1" watcher: dependency: transitive description: @@ -751,5 +751,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.5.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.24.0" diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.freezed.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.freezed.dart index e09fc0c..9e51ef8 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.freezed.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.freezed.dart @@ -29,8 +29,12 @@ mixin _$DHTRecordPoolAllocations { throw _privateConstructorUsedError; IMap get debugNames => throw _privateConstructorUsedError; + /// Serializes this DHTRecordPoolAllocations to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of DHTRecordPoolAllocations + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $DHTRecordPoolAllocationsCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -59,6 +63,8 @@ class _$DHTRecordPoolAllocationsCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of DHTRecordPoolAllocations + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -114,6 +120,8 @@ class __$$DHTRecordPoolAllocationsImplCopyWithImpl<$Res> $Res Function(_$DHTRecordPoolAllocationsImpl) _then) : super(_value, _then); + /// Create a copy of DHTRecordPoolAllocations + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -188,12 +196,14 @@ class _$DHTRecordPoolAllocationsImpl implements _DHTRecordPoolAllocations { other.debugNames == debugNames)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, childrenByParent, parentByChild, const DeepCollectionEquality().hash(rootRecords), debugNames); - @JsonKey(ignore: true) + /// Create a copy of DHTRecordPoolAllocations + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$DHTRecordPoolAllocationsImplCopyWith<_$DHTRecordPoolAllocationsImpl> @@ -226,8 +236,11 @@ abstract class _DHTRecordPoolAllocations implements DHTRecordPoolAllocations { ISet> get rootRecords; @override IMap get debugNames; + + /// Create a copy of DHTRecordPoolAllocations + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$DHTRecordPoolAllocationsImplCopyWith<_$DHTRecordPoolAllocationsImpl> get copyWith => throw _privateConstructorUsedError; } @@ -243,8 +256,12 @@ mixin _$OwnedDHTRecordPointer { throw _privateConstructorUsedError; KeyPair get owner => throw _privateConstructorUsedError; + /// Serializes this OwnedDHTRecordPointer to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of OwnedDHTRecordPointer + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $OwnedDHTRecordPointerCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -269,6 +286,8 @@ class _$OwnedDHTRecordPointerCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of OwnedDHTRecordPointer + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -309,6 +328,8 @@ class __$$OwnedDHTRecordPointerImplCopyWithImpl<$Res> $Res Function(_$OwnedDHTRecordPointerImpl) _then) : super(_value, _then); + /// Create a copy of OwnedDHTRecordPointer + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -357,11 +378,13 @@ class _$OwnedDHTRecordPointerImpl implements _OwnedDHTRecordPointer { (identical(other.owner, owner) || other.owner == owner)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, recordKey, owner); - @JsonKey(ignore: true) + /// Create a copy of OwnedDHTRecordPointer + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$OwnedDHTRecordPointerImplCopyWith<_$OwnedDHTRecordPointerImpl> @@ -388,8 +411,11 @@ abstract class _OwnedDHTRecordPointer implements OwnedDHTRecordPointer { Typed get recordKey; @override KeyPair get owner; + + /// Create a copy of OwnedDHTRecordPointer + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$OwnedDHTRecordPointerImplCopyWith<_$OwnedDHTRecordPointerImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool_private.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool_private.dart index b7cbba8..d1fb5d1 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool_private.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool_private.dart @@ -1,8 +1,5 @@ part of 'dht_record_pool.dart'; -const int _watchBackoffMultiplier = 2; -const int _watchBackoffMax = 30; - const int? _defaultWatchDurationSecs = null; // 600 const int _watchRenewalNumerator = 4; const int _watchRenewalDenominator = 5; diff --git a/packages/veilid_support/lib/identity_support/account_record_info.freezed.dart b/packages/veilid_support/lib/identity_support/account_record_info.freezed.dart index 0d5b327..a266230 100644 --- a/packages/veilid_support/lib/identity_support/account_record_info.freezed.dart +++ b/packages/veilid_support/lib/identity_support/account_record_info.freezed.dart @@ -23,8 +23,12 @@ mixin _$AccountRecordInfo { // Top level account keys and secrets OwnedDHTRecordPointer get accountRecord => throw _privateConstructorUsedError; + /// Serializes this AccountRecordInfo to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of AccountRecordInfo + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $AccountRecordInfoCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -50,6 +54,8 @@ class _$AccountRecordInfoCopyWithImpl<$Res, $Val extends AccountRecordInfo> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of AccountRecordInfo + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -63,6 +69,8 @@ class _$AccountRecordInfoCopyWithImpl<$Res, $Val extends AccountRecordInfo> ) as $Val); } + /// Create a copy of AccountRecordInfo + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $OwnedDHTRecordPointerCopyWith<$Res> get accountRecord { @@ -94,6 +102,8 @@ class __$$AccountRecordInfoImplCopyWithImpl<$Res> $Res Function(_$AccountRecordInfoImpl) _then) : super(_value, _then); + /// Create a copy of AccountRecordInfo + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -134,11 +144,13 @@ class _$AccountRecordInfoImpl implements _AccountRecordInfo { other.accountRecord == accountRecord)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, accountRecord); - @JsonKey(ignore: true) + /// Create a copy of AccountRecordInfo + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$AccountRecordInfoImplCopyWith<_$AccountRecordInfoImpl> get copyWith => @@ -161,10 +173,14 @@ abstract class _AccountRecordInfo implements AccountRecordInfo { factory _AccountRecordInfo.fromJson(Map json) = _$AccountRecordInfoImpl.fromJson; - @override // Top level account keys and secrets - OwnedDHTRecordPointer get accountRecord; +// Top level account keys and secrets @override - @JsonKey(ignore: true) + OwnedDHTRecordPointer get accountRecord; + + /// Create a copy of AccountRecordInfo + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) _$$AccountRecordInfoImplCopyWith<_$AccountRecordInfoImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/packages/veilid_support/lib/identity_support/identity.freezed.dart b/packages/veilid_support/lib/identity_support/identity.freezed.dart index 5977a26..3a276b0 100644 --- a/packages/veilid_support/lib/identity_support/identity.freezed.dart +++ b/packages/veilid_support/lib/identity_support/identity.freezed.dart @@ -24,8 +24,12 @@ mixin _$Identity { IMap> get accountRecords => throw _privateConstructorUsedError; + /// Serializes this Identity to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of Identity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $IdentityCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -48,6 +52,8 @@ class _$IdentityCopyWithImpl<$Res, $Val extends Identity> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of Identity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -81,6 +87,8 @@ class __$$IdentityImplCopyWithImpl<$Res> _$IdentityImpl _value, $Res Function(_$IdentityImpl) _then) : super(_value, _then); + /// Create a copy of Identity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -121,11 +129,13 @@ class _$IdentityImpl implements _Identity { other.accountRecords == accountRecords)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, accountRecords); - @JsonKey(ignore: true) + /// Create a copy of Identity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$IdentityImplCopyWith<_$IdentityImpl> get copyWith => @@ -147,10 +157,14 @@ abstract class _Identity implements Identity { factory _Identity.fromJson(Map json) = _$IdentityImpl.fromJson; - @override // Top level account keys and secrets - IMap> get accountRecords; +// Top level account keys and secrets @override - @JsonKey(ignore: true) + IMap> get accountRecords; + + /// Create a copy of Identity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) _$$IdentityImplCopyWith<_$IdentityImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/packages/veilid_support/lib/identity_support/identity_instance.freezed.dart b/packages/veilid_support/lib/identity_support/identity_instance.freezed.dart index a7c3e78..28bbad4 100644 --- a/packages/veilid_support/lib/identity_support/identity_instance.freezed.dart +++ b/packages/veilid_support/lib/identity_support/identity_instance.freezed.dart @@ -38,8 +38,12 @@ mixin _$IdentityInstance { // by SuperIdentity publicKey FixedEncodedString86 get signature => throw _privateConstructorUsedError; + /// Serializes this IdentityInstance to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of IdentityInstance + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $IdentityInstanceCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -68,6 +72,8 @@ class _$IdentityInstanceCopyWithImpl<$Res, $Val extends IdentityInstance> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of IdentityInstance + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -126,6 +132,8 @@ class __$$IdentityInstanceImplCopyWithImpl<$Res> $Res Function(_$IdentityInstanceImpl) _then) : super(_value, _then); + /// Create a copy of IdentityInstance + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -219,7 +227,7 @@ class _$IdentityInstanceImpl extends _IdentityInstance { other.signature == signature)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -229,7 +237,9 @@ class _$IdentityInstanceImpl extends _IdentityInstance { superSignature, signature); - @JsonKey(ignore: true) + /// Create a copy of IdentityInstance + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$IdentityInstanceImplCopyWith<_$IdentityInstanceImpl> get copyWith => @@ -256,25 +266,31 @@ abstract class _IdentityInstance extends IdentityInstance { factory _IdentityInstance.fromJson(Map json) = _$IdentityInstanceImpl.fromJson; - @override // Private DHT record storing identity account mapping - Typed get recordKey; - @override // Public key of identity instance - FixedEncodedString43 get publicKey; - @override // Secret key of identity instance +// Private DHT record storing identity account mapping + @override + Typed get recordKey; // Public key of identity instance + @override + FixedEncodedString43 get publicKey; // Secret key of identity instance // Encrypted with appended salt, key is DeriveSharedSecret( // password = SuperIdentity.secret, // salt = publicKey) // Used to recover accounts without generating a new instance - @Uint8ListJsonConverter() - Uint8List get encryptedSecretKey; - @override // Signature of SuperInstance recordKey and SuperInstance publicKey -// by publicKey - FixedEncodedString86 get superSignature; - @override // Signature of recordKey, publicKey, encryptedSecretKey, and superSignature -// by SuperIdentity publicKey - FixedEncodedString86 get signature; @override - @JsonKey(ignore: true) + @Uint8ListJsonConverter() + Uint8List + get encryptedSecretKey; // Signature of SuperInstance recordKey and SuperInstance publicKey +// by publicKey + @override + FixedEncodedString86 + get superSignature; // Signature of recordKey, publicKey, encryptedSecretKey, and superSignature +// by SuperIdentity publicKey + @override + FixedEncodedString86 get signature; + + /// Create a copy of IdentityInstance + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) _$$IdentityInstanceImplCopyWith<_$IdentityInstanceImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/packages/veilid_support/lib/identity_support/super_identity.freezed.dart b/packages/veilid_support/lib/identity_support/super_identity.freezed.dart index dc1c69a..9c5c6a7 100644 --- a/packages/veilid_support/lib/identity_support/super_identity.freezed.dart +++ b/packages/veilid_support/lib/identity_support/super_identity.freezed.dart @@ -53,8 +53,12 @@ mixin _$SuperIdentity { /// by publicKey FixedEncodedString86 get signature => throw _privateConstructorUsedError; + /// Serializes this SuperIdentity to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of SuperIdentity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SuperIdentityCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -86,6 +90,8 @@ class _$SuperIdentityCopyWithImpl<$Res, $Val extends SuperIdentity> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of SuperIdentity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -124,6 +130,8 @@ class _$SuperIdentityCopyWithImpl<$Res, $Val extends SuperIdentity> ) as $Val); } + /// Create a copy of SuperIdentity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $IdentityInstanceCopyWith<$Res> get currentInstance { @@ -161,6 +169,8 @@ class __$$SuperIdentityImplCopyWithImpl<$Res> _$SuperIdentityImpl _value, $Res Function(_$SuperIdentityImpl) _then) : super(_value, _then); + /// Create a copy of SuperIdentity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -298,7 +308,7 @@ class _$SuperIdentityImpl extends _SuperIdentity { other.signature == signature)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -309,7 +319,9 @@ class _$SuperIdentityImpl extends _SuperIdentity { const DeepCollectionEquality().hash(_deprecatedSuperRecordKeys), signature); - @JsonKey(ignore: true) + /// Create a copy of SuperIdentity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SuperIdentityImplCopyWith<_$SuperIdentityImpl> get copyWith => @@ -337,44 +349,46 @@ abstract class _SuperIdentity extends SuperIdentity { factory _SuperIdentity.fromJson(Map json) = _$SuperIdentityImpl.fromJson; - @override - /// Public DHT record storing this structure for account recovery /// changing this can migrate/forward the SuperIdentity to a new DHT record /// Instances should not hash this recordKey, rather the actual record /// key used to store the superIdentity, as this may change. - Typed get recordKey; @override + Typed get recordKey; /// Public key of the SuperIdentity used to sign identity keys for recovery /// This must match the owner of the superRecord DHT record and can not be /// changed without changing the record - FixedEncodedString43 get publicKey; @override + FixedEncodedString43 get publicKey; /// Current identity instance /// The most recently generated identity instance for this SuperIdentity - IdentityInstance get currentInstance; @override + IdentityInstance get currentInstance; /// Deprecated identity instances /// These may be compromised and should not be considered valid for /// new signatures, but may be used to validate old signatures - List get deprecatedInstances; @override + List get deprecatedInstances; /// Deprecated superRecords /// These may be compromised and should not be considered valid for /// new signatures, but may be used to validate old signatures - List> get deprecatedSuperRecordKeys; @override + List> get deprecatedSuperRecordKeys; /// Signature of recordKey, currentInstance signature, /// signatures of deprecatedInstances, and deprecatedSuperRecordKeys /// by publicKey - FixedEncodedString86 get signature; @override - @JsonKey(ignore: true) + FixedEncodedString86 get signature; + + /// Create a copy of SuperIdentity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) _$$SuperIdentityImplCopyWith<_$SuperIdentityImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/packages/veilid_support/lib/proto/dht.pb.dart b/packages/veilid_support/lib/proto/dht.pb.dart index 7a9ac9a..b1c0b47 100644 --- a/packages/veilid_support/lib/proto/dht.pb.dart +++ b/packages/veilid_support/lib/proto/dht.pb.dart @@ -4,7 +4,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields // ignore_for_file: unnecessary_import, unnecessary_this, unused_import @@ -16,7 +16,27 @@ import 'package:protobuf/protobuf.dart' as $pb; import 'veilid.pb.dart' as $0; class DHTData extends $pb.GeneratedMessage { - factory DHTData() => create(); + factory DHTData({ + $core.Iterable<$0.TypedKey>? keys, + $0.TypedKey? hash, + $core.int? chunk, + $core.int? size, + }) { + final $result = create(); + if (keys != null) { + $result.keys.addAll(keys); + } + if (hash != null) { + $result.hash = hash; + } + if (chunk != null) { + $result.chunk = chunk; + } + if (size != null) { + $result.size = size; + } + return $result; + } DHTData._() : super(); factory DHTData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory DHTData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -50,9 +70,12 @@ class DHTData extends $pb.GeneratedMessage { static DHTData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static DHTData? _defaultInstance; + /// Other keys to concatenate + /// Uses the same writer as this DHTList with SMPL schema @$pb.TagNumber(1) $core.List<$0.TypedKey> get keys => $_getList(0); + /// Hash of reassembled data to verify contents @$pb.TagNumber(2) $0.TypedKey get hash => $_getN(1); @$pb.TagNumber(2) @@ -64,6 +87,7 @@ class DHTData extends $pb.GeneratedMessage { @$pb.TagNumber(2) $0.TypedKey ensureHash() => $_ensure(1); + /// Chunk size per subkey @$pb.TagNumber(3) $core.int get chunk => $_getIZ(2); @$pb.TagNumber(3) @@ -73,6 +97,7 @@ class DHTData extends $pb.GeneratedMessage { @$pb.TagNumber(3) void clearChunk() => clearField(3); + /// Total data size @$pb.TagNumber(4) $core.int get size => $_getIZ(3); @$pb.TagNumber(4) @@ -83,8 +108,26 @@ class DHTData extends $pb.GeneratedMessage { void clearSize() => clearField(4); } +/// DHTLog - represents a ring buffer of many elements with append/truncate semantics +/// Header in subkey 0 of first key follows this structure class DHTLog extends $pb.GeneratedMessage { - factory DHTLog() => create(); + factory DHTLog({ + $core.int? head, + $core.int? tail, + $core.int? stride, + }) { + final $result = create(); + if (head != null) { + $result.head = head; + } + if (tail != null) { + $result.tail = tail; + } + if (stride != null) { + $result.stride = stride; + } + return $result; + } DHTLog._() : super(); factory DHTLog.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory DHTLog.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -117,6 +160,7 @@ class DHTLog extends $pb.GeneratedMessage { static DHTLog getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static DHTLog? _defaultInstance; + /// Position of the start of the log (oldest items) @$pb.TagNumber(1) $core.int get head => $_getIZ(0); @$pb.TagNumber(1) @@ -126,6 +170,7 @@ class DHTLog extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearHead() => clearField(1); + /// Position of the end of the log (newest items) @$pb.TagNumber(2) $core.int get tail => $_getIZ(1); @$pb.TagNumber(2) @@ -135,6 +180,7 @@ class DHTLog extends $pb.GeneratedMessage { @$pb.TagNumber(2) void clearTail() => clearField(2); + /// Stride of each segment of the dhtlog @$pb.TagNumber(3) $core.int get stride => $_getIZ(2); @$pb.TagNumber(3) @@ -145,8 +191,32 @@ class DHTLog extends $pb.GeneratedMessage { void clearStride() => clearField(3); } +/// DHTShortArray - represents a re-orderable collection of up to 256 individual elements +/// Header in subkey 0 of first key follows this structure +/// +/// stride = descriptor subkey count on first key - 1 +/// Subkeys 1..=stride on the first key are individual elements +/// Subkeys 0..stride on the 'keys' keys are also individual elements +/// +/// Keys must use writable schema in order to make this list mutable class DHTShortArray extends $pb.GeneratedMessage { - factory DHTShortArray() => create(); + factory DHTShortArray({ + $core.Iterable<$0.TypedKey>? keys, + $core.List<$core.int>? index, + $core.Iterable<$core.int>? seqs, + }) { + final $result = create(); + if (keys != null) { + $result.keys.addAll(keys); + } + if (index != null) { + $result.index = index; + } + if (seqs != null) { + $result.seqs.addAll(seqs); + } + return $result; + } DHTShortArray._() : super(); factory DHTShortArray.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory DHTShortArray.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -179,9 +249,16 @@ class DHTShortArray extends $pb.GeneratedMessage { static DHTShortArray getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static DHTShortArray? _defaultInstance; + /// Other keys to concatenate + /// Uses the same writer as this DHTList with SMPL schema @$pb.TagNumber(1) $core.List<$0.TypedKey> get keys => $_getList(0); + /// Item position index (uint8[256./]) + /// Actual item location is: + /// idx = index[n] + 1 (offset for header at idx 0) + /// key = idx / stride + /// subkey = idx % stride @$pb.TagNumber(2) $core.List<$core.int> get index => $_getN(1); @$pb.TagNumber(2) @@ -191,12 +268,26 @@ class DHTShortArray extends $pb.GeneratedMessage { @$pb.TagNumber(2) void clearIndex() => clearField(2); + /// Most recent sequence numbers for elements @$pb.TagNumber(3) $core.List<$core.int> get seqs => $_getList(2); } +/// A pointer to an child DHT record class OwnedDHTRecordPointer extends $pb.GeneratedMessage { - factory OwnedDHTRecordPointer() => create(); + factory OwnedDHTRecordPointer({ + $0.TypedKey? recordKey, + $0.KeyPair? owner, + }) { + final $result = create(); + if (recordKey != null) { + $result.recordKey = recordKey; + } + if (owner != null) { + $result.owner = owner; + } + return $result; + } OwnedDHTRecordPointer._() : super(); factory OwnedDHTRecordPointer.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory OwnedDHTRecordPointer.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -228,6 +319,7 @@ class OwnedDHTRecordPointer extends $pb.GeneratedMessage { static OwnedDHTRecordPointer getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static OwnedDHTRecordPointer? _defaultInstance; + /// DHT Record key @$pb.TagNumber(1) $0.TypedKey get recordKey => $_getN(0); @$pb.TagNumber(1) @@ -239,6 +331,7 @@ class OwnedDHTRecordPointer extends $pb.GeneratedMessage { @$pb.TagNumber(1) $0.TypedKey ensureRecordKey() => $_ensure(0); + /// DHT record owner key @$pb.TagNumber(2) $0.KeyPair get owner => $_getN(1); @$pb.TagNumber(2) diff --git a/packages/veilid_support/lib/proto/dht.pbenum.dart b/packages/veilid_support/lib/proto/dht.pbenum.dart index f76992d..7059e85 100644 --- a/packages/veilid_support/lib/proto/dht.pbenum.dart +++ b/packages/veilid_support/lib/proto/dht.pbenum.dart @@ -4,7 +4,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields // ignore_for_file: unnecessary_import, unnecessary_this, unused_import diff --git a/packages/veilid_support/lib/proto/dht.pbjson.dart b/packages/veilid_support/lib/proto/dht.pbjson.dart index 9d505f0..dd14566 100644 --- a/packages/veilid_support/lib/proto/dht.pbjson.dart +++ b/packages/veilid_support/lib/proto/dht.pbjson.dart @@ -4,7 +4,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields // ignore_for_file: unnecessary_import, unnecessary_this, unused_import diff --git a/packages/veilid_support/lib/proto/dht.pbserver.dart b/packages/veilid_support/lib/proto/dht.pbserver.dart index ffbf990..02e8c03 100644 --- a/packages/veilid_support/lib/proto/dht.pbserver.dart +++ b/packages/veilid_support/lib/proto/dht.pbserver.dart @@ -4,7 +4,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names // ignore_for_file: deprecated_member_use_from_same_package, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields diff --git a/packages/veilid_support/lib/proto/veilid.pb.dart b/packages/veilid_support/lib/proto/veilid.pb.dart index a53133a..5431b80 100644 --- a/packages/veilid_support/lib/proto/veilid.pb.dart +++ b/packages/veilid_support/lib/proto/veilid.pb.dart @@ -4,7 +4,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields // ignore_for_file: unnecessary_import, unnecessary_this, unused_import @@ -13,8 +13,45 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; +/// 32-byte value in bigendian format class CryptoKey extends $pb.GeneratedMessage { - factory CryptoKey() => create(); + factory CryptoKey({ + $core.int? u0, + $core.int? u1, + $core.int? u2, + $core.int? u3, + $core.int? u4, + $core.int? u5, + $core.int? u6, + $core.int? u7, + }) { + final $result = create(); + if (u0 != null) { + $result.u0 = u0; + } + if (u1 != null) { + $result.u1 = u1; + } + if (u2 != null) { + $result.u2 = u2; + } + if (u3 != null) { + $result.u3 = u3; + } + if (u4 != null) { + $result.u4 = u4; + } + if (u5 != null) { + $result.u5 = u5; + } + if (u6 != null) { + $result.u6 = u6; + } + if (u7 != null) { + $result.u7 = u7; + } + return $result; + } CryptoKey._() : super(); factory CryptoKey.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory CryptoKey.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -125,8 +162,77 @@ class CryptoKey extends $pb.GeneratedMessage { void clearU7() => clearField(8); } +/// 64-byte value in bigendian format class Signature extends $pb.GeneratedMessage { - factory Signature() => create(); + factory Signature({ + $core.int? u0, + $core.int? u1, + $core.int? u2, + $core.int? u3, + $core.int? u4, + $core.int? u5, + $core.int? u6, + $core.int? u7, + $core.int? u8, + $core.int? u9, + $core.int? u10, + $core.int? u11, + $core.int? u12, + $core.int? u13, + $core.int? u14, + $core.int? u15, + }) { + final $result = create(); + if (u0 != null) { + $result.u0 = u0; + } + if (u1 != null) { + $result.u1 = u1; + } + if (u2 != null) { + $result.u2 = u2; + } + if (u3 != null) { + $result.u3 = u3; + } + if (u4 != null) { + $result.u4 = u4; + } + if (u5 != null) { + $result.u5 = u5; + } + if (u6 != null) { + $result.u6 = u6; + } + if (u7 != null) { + $result.u7 = u7; + } + if (u8 != null) { + $result.u8 = u8; + } + if (u9 != null) { + $result.u9 = u9; + } + if (u10 != null) { + $result.u10 = u10; + } + if (u11 != null) { + $result.u11 = u11; + } + if (u12 != null) { + $result.u12 = u12; + } + if (u13 != null) { + $result.u13 = u13; + } + if (u14 != null) { + $result.u14 = u14; + } + if (u15 != null) { + $result.u15 = u15; + } + return $result; + } Signature._() : super(); factory Signature.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Signature.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -317,8 +423,37 @@ class Signature extends $pb.GeneratedMessage { void clearU15() => clearField(16); } +/// 24-byte value in bigendian format class Nonce extends $pb.GeneratedMessage { - factory Nonce() => create(); + factory Nonce({ + $core.int? u0, + $core.int? u1, + $core.int? u2, + $core.int? u3, + $core.int? u4, + $core.int? u5, + }) { + final $result = create(); + if (u0 != null) { + $result.u0 = u0; + } + if (u1 != null) { + $result.u1 = u1; + } + if (u2 != null) { + $result.u2 = u2; + } + if (u3 != null) { + $result.u3 = u3; + } + if (u4 != null) { + $result.u4 = u4; + } + if (u5 != null) { + $result.u5 = u5; + } + return $result; + } Nonce._() : super(); factory Nonce.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory Nonce.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -409,8 +544,21 @@ class Nonce extends $pb.GeneratedMessage { void clearU5() => clearField(6); } +/// 36-byte typed crypto key class TypedKey extends $pb.GeneratedMessage { - factory TypedKey() => create(); + factory TypedKey({ + $core.int? kind, + CryptoKey? value, + }) { + final $result = create(); + if (kind != null) { + $result.kind = kind; + } + if (value != null) { + $result.value = value; + } + return $result; + } TypedKey._() : super(); factory TypedKey.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory TypedKey.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -442,6 +590,7 @@ class TypedKey extends $pb.GeneratedMessage { static TypedKey getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static TypedKey? _defaultInstance; + /// CryptoKind FourCC in bigendian format @$pb.TagNumber(1) $core.int get kind => $_getIZ(0); @$pb.TagNumber(1) @@ -451,6 +600,7 @@ class TypedKey extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearKind() => clearField(1); + /// Key value @$pb.TagNumber(2) CryptoKey get value => $_getN(1); @$pb.TagNumber(2) @@ -463,8 +613,21 @@ class TypedKey extends $pb.GeneratedMessage { CryptoKey ensureValue() => $_ensure(1); } +/// Key pair class KeyPair extends $pb.GeneratedMessage { - factory KeyPair() => create(); + factory KeyPair({ + CryptoKey? key, + CryptoKey? secret, + }) { + final $result = create(); + if (key != null) { + $result.key = key; + } + if (secret != null) { + $result.secret = secret; + } + return $result; + } KeyPair._() : super(); factory KeyPair.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory KeyPair.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); @@ -496,6 +659,7 @@ class KeyPair extends $pb.GeneratedMessage { static KeyPair getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static KeyPair? _defaultInstance; + /// Public key @$pb.TagNumber(1) CryptoKey get key => $_getN(0); @$pb.TagNumber(1) @@ -507,6 +671,7 @@ class KeyPair extends $pb.GeneratedMessage { @$pb.TagNumber(1) CryptoKey ensureKey() => $_ensure(0); + /// Private key @$pb.TagNumber(2) CryptoKey get secret => $_getN(1); @$pb.TagNumber(2) diff --git a/packages/veilid_support/lib/proto/veilid.pbenum.dart b/packages/veilid_support/lib/proto/veilid.pbenum.dart index 1ade7e9..89c0019 100644 --- a/packages/veilid_support/lib/proto/veilid.pbenum.dart +++ b/packages/veilid_support/lib/proto/veilid.pbenum.dart @@ -4,7 +4,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields // ignore_for_file: unnecessary_import, unnecessary_this, unused_import diff --git a/packages/veilid_support/lib/proto/veilid.pbjson.dart b/packages/veilid_support/lib/proto/veilid.pbjson.dart index b92b4e5..db8318e 100644 --- a/packages/veilid_support/lib/proto/veilid.pbjson.dart +++ b/packages/veilid_support/lib/proto/veilid.pbjson.dart @@ -4,7 +4,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields // ignore_for_file: unnecessary_import, unnecessary_this, unused_import diff --git a/packages/veilid_support/lib/proto/veilid.pbserver.dart b/packages/veilid_support/lib/proto/veilid.pbserver.dart index 2de2834..f799a3f 100644 --- a/packages/veilid_support/lib/proto/veilid.pbserver.dart +++ b/packages/veilid_support/lib/proto/veilid.pbserver.dart @@ -4,7 +4,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names // ignore_for_file: deprecated_member_use_from_same_package, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields diff --git a/packages/veilid_support/lib/src/config.dart b/packages/veilid_support/lib/src/config.dart index bedc743..47bd84e 100644 --- a/packages/veilid_support/lib/src/config.dart +++ b/packages/veilid_support/lib/src/config.dart @@ -115,6 +115,7 @@ Future getVeilidConfig(bool isWeb, String programName) async { const VeilidConfigCapabilities(disable: ['DHTV', 'DHTW', 'TUNL']), protectedStore: // XXX: Linux often does not have a secret storage mechanism installed - config.protectedStore.copyWith(allowInsecureFallback: Platform.isLinux), + config.protectedStore + .copyWith(allowInsecureFallback: !isWeb && Platform.isLinux), ); } diff --git a/packages/veilid_support/pubspec.lock b/packages/veilid_support/pubspec.lock index 260c991..447a27c 100644 --- a/packages/veilid_support/pubspec.lock +++ b/packages/veilid_support/pubspec.lock @@ -36,10 +36,9 @@ packages: async_tools: dependency: "direct main" description: - name: async_tools - sha256: bbded696bfcb1437d0ca510ac047f261f9c7494fea2c488dd32ba2800e7f49e8 - url: "https://pub.dev" - source: hosted + path: "../../../dart_async_tools" + relative: true + source: path version: "0.1.7" bloc: dependency: "direct main" @@ -141,10 +140,10 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" charcode: dependency: "direct main" description: @@ -173,10 +172,10 @@ packages: dependency: "direct main" description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" convert: dependency: transitive description: @@ -407,10 +406,10 @@ packages: dependency: "direct main" description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mime: dependency: transitive description: @@ -726,7 +725,7 @@ packages: path: "../../../veilid/veilid-flutter" relative: true source: path - version: "0.4.1" + version: "0.4.3" vm_service: dependency: transitive description: @@ -792,5 +791,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.24.0" diff --git a/packages/veilid_support/pubspec.yaml b/packages/veilid_support/pubspec.yaml index 8ed1f58..bcd965d 100644 --- a/packages/veilid_support/pubspec.yaml +++ b/packages/veilid_support/pubspec.yaml @@ -26,9 +26,9 @@ dependencies: # veilid: ^0.0.1 path: ../../../veilid/veilid-flutter -# dependency_overrides: -# async_tools: -# path: ../../../dart_async_tools +dependency_overrides: + async_tools: + path: ../../../dart_async_tools # bloc_advanced_tools: # path: ../../../bloc_advanced_tools diff --git a/pubspec.lock b/pubspec.lock index 9555644..b98424d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + animated_custom_dropdown: + dependency: "direct main" + description: + name: animated_custom_dropdown + sha256: "5a72dc209041bb53f6c7164bc2e366552d5197cdb032b1c9b2c36e3013024486" + url: "https://pub.dev" + source: hosted + version: "3.1.1" animated_switcher_transitions: dependency: "direct main" description: @@ -61,18 +69,18 @@ packages: dependency: "direct main" description: name: archive - sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" + sha256: "0c64e928dcbefddecd234205422bcfc2b5e6d31be0b86fef0d0dd48d7b4c9742" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.4" args: dependency: transitive description: name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.0" async: dependency: transitive description: @@ -84,19 +92,18 @@ packages: async_tools: dependency: "direct main" description: - name: async_tools - sha256: bbded696bfcb1437d0ca510ac047f261f9c7494fea2c488dd32ba2800e7f49e8 - url: "https://pub.dev" - source: hosted + path: "../dart_async_tools" + relative: true + source: path version: "0.1.7" awesome_extensions: dependency: "direct main" description: name: awesome_extensions - sha256: "91dc128e8cf01fbd3d3567b8f1dd1e3183cbf9fd6b1850e8b0fafce9a7eee0da" + sha256: "9b1693e986e4045141add298fa2d7f9aa6cdd3c125b951e2cde739a5058ed879" url: "https://pub.dev" source: hosted - version: "2.0.20" + version: "2.0.21" badges: dependency: "direct main" description: @@ -117,10 +124,10 @@ packages: dependency: "direct main" description: name: basic_utils - sha256: "2064b21d3c41ed7654bc82cc476fd65542e04d60059b74d5eed490a4da08fc6c" + sha256: "548047bef0b3b697be19fa62f46de54d99c9019a69fb7db92c69e19d87f633c7" url: "https://pub.dev" source: hosted - version: "5.7.0" + version: "5.8.2" bidi: dependency: transitive description: @@ -221,10 +228,10 @@ packages: dependency: transitive description: name: built_value - sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" + sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 url: "https://pub.dev" source: hosted - version: "8.9.3" + version: "8.9.5" cached_network_image: dependency: transitive description: @@ -261,18 +268,18 @@ packages: dependency: transitive description: name: camera_android_camerax - sha256: "7cc6adf1868bdcf4e63a56b24b41692dfbad2bec1cdceea451c77798f6a605c3" + sha256: "13784f539c7f104766bff84e4479a70f03b29d78b208278be45c939250d9d7f5" url: "https://pub.dev" source: hosted - version: "0.6.13" + version: "0.6.14+1" camera_avfoundation: dependency: transitive description: name: camera_avfoundation - sha256: "1eeb9ce7c9a397e312343fd7db337d95f35c3e65ad5a62ff637c8abce5102b98" + sha256: "3057ada0b30402e3a9b6dffec365c9736a36edbf04abaecc67c4309eadc86b49" url: "https://pub.dev" source: hosted - version: "0.9.18+8" + version: "0.9.18+9" camera_platform_interface: dependency: transitive description: @@ -301,10 +308,10 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" charcode: dependency: "direct main" description: @@ -349,10 +356,10 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: @@ -365,10 +372,10 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" convert: dependency: transitive description: @@ -377,14 +384,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" - cool_dropdown: - dependency: "direct main" - description: - name: cool_dropdown - sha256: "23926fd242b625bcb7ab30c1336498d60f78267518db439141ca19de403ab030" - url: "https://pub.dev" - source: hosted - version: "2.1.1" cross_file: dependency: transitive description: @@ -445,10 +444,10 @@ packages: dependency: transitive description: name: dio_web_adapter - sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" equatable: dependency: "direct main" description: @@ -477,10 +476,10 @@ packages: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" file: dependency: transitive description: @@ -592,10 +591,10 @@ packages: dependency: "direct main" description: name: flutter_native_splash - sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7" + sha256: edb09c35ee9230c4b03f13dd45bb3a276d0801865f0a4650b7e2a3bba61a803a url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "2.4.5" flutter_parsed_text: dependency: transitive description: @@ -608,10 +607,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" + sha256: "5a1e6fb2c0561958d7e4c33574674bda7b77caaca7a33b758876956f2902eea3" url: "https://pub.dev" source: hosted - version: "2.0.24" + version: "2.0.27" flutter_shaders: dependency: transitive description: @@ -677,10 +676,10 @@ packages: dependency: "direct main" description: name: form_builder_validators - sha256: "517fb884183fff7a0ef3db7d375981011da26ee452f20fb3d2e788ad527ad01d" + sha256: cd617fa346250293ff3e2709961d0faf7b80e6e4f0ff7b500126b28d7422dd67 url: "https://pub.dev" source: hosted - version: "11.1.1" + version: "11.1.2" freezed: dependency: "direct dev" description: @@ -733,10 +732,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "04539267a740931c6d4479a10d466717ca5901c6fdfd3fcda09391bbb8ebd651" + sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 url: "https://pub.dev" source: hosted - version: "14.8.0" + version: "14.8.1" graphs: dependency: transitive description: @@ -797,18 +796,26 @@ packages: dependency: "direct dev" description: name: icons_launcher - sha256: a7c83fbc837dc6f81944ef35c3756f533bb2aba32fcca5cbcdb2dbcd877d5ae9 + sha256: "2949eef3d336028d89133f69ef221d877e09deed04ebd8e738ab4a427850a7a2" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" + iconsax_flutter: + dependency: transitive + description: + name: iconsax_flutter + sha256: "95b65699da8ea98f87c5d232f06b0debaaf1ec1332b697e4d90969ec9a93037d" + url: "https://pub.dev" + source: hosted + version: "1.0.0" image: dependency: "direct main" description: name: image - sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6" + sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3" url: "https://pub.dev" source: hosted - version: "4.5.2" + version: "4.5.3" intl: dependency: "direct main" description: @@ -829,10 +836,10 @@ packages: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" json_annotation: dependency: "direct main" description: @@ -901,10 +908,10 @@ packages: dependency: "direct main" description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mime: dependency: transitive description: @@ -917,18 +924,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: "91d28b825784e15572fdc39165c5733099ce0e69c6f6f0964ebdbf98a62130fd" + sha256: "9cb9e371ee9b5b548714f9ab5fd33b530d799745c83d5729ecd1e8ab2935dbd1" url: "https://pub.dev" source: hosted - version: "6.0.6" - motion_toast: - dependency: "direct main" - description: - name: motion_toast - sha256: "5a4775bf5a89a2402456047f194df5a5d6ac717af0d7694c8b9e37f324d1efd7" - url: "https://pub.dev" - source: hosted - version: "2.11.0" + version: "6.0.7" native_device_orientation: dependency: "direct main" description: @@ -957,26 +956,26 @@ packages: dependency: transitive description: name: package_config - sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: "67eae327b1b0faf761964a1d2e5d323c797f3799db0e85aa232db8d9e922bc35" + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" url: "https://pub.dev" source: hosted - version: "8.2.1" + version: "8.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76" + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" pasteboard: dependency: "direct main" description: @@ -989,10 +988,10 @@ packages: dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_parsing: dependency: transitive description: @@ -1013,10 +1012,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" url: "https://pub.dev" source: hosted - version: "2.2.15" + version: "2.2.16" path_provider_foundation: dependency: transitive description: @@ -1049,6 +1048,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + pausable_timer: + dependency: transitive + description: + name: pausable_timer + sha256: "6ef1a95441ec3439de6fb63f39a011b67e693198e7dae14e20675c3c00e86074" + url: "https://pub.dev" + source: hosted + version: "3.1.0+3" pdf: dependency: "direct main" description: @@ -1069,10 +1076,10 @@ packages: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.1.0" photo_view: dependency: transitive description: @@ -1109,10 +1116,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5" url: "https://pub.dev" source: hosted - version: "3.9.1" + version: "4.0.0" pool: dependency: transitive description: @@ -1165,10 +1172,10 @@ packages: dependency: transitive description: name: pub_semver - sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" pubspec_parse: dependency: transitive description: @@ -1334,10 +1341,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22 + sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad" url: "https://pub.dev" source: hosted - version: "2.4.6" + version: "2.4.8" shared_preferences_foundation: dependency: transitive description: @@ -1484,34 +1491,34 @@ packages: dependency: transitive description: name: sqflite - sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" sqflite_android: dependency: transitive description: name: sqflite_android - sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" url: "https://pub.dev" source: hosted - version: "2.5.4+6" + version: "2.5.5" sqflite_darwin: dependency: transitive description: name: sqflite_darwin - sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" url: "https://pub.dev" source: hosted - version: "2.4.1+1" + version: "2.4.2" sqflite_platform_interface: dependency: transitive description: @@ -1564,10 +1571,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" url: "https://pub.dev" source: hosted - version: "3.3.0+3" + version: "3.3.1" system_info2: dependency: transitive description: @@ -1608,6 +1615,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + toastification: + dependency: "direct main" + description: + name: toastification + sha256: "9713989549d60754fd0522425d1251501919cfb7bab4ffbbb36ef40de5ea72b9" + url: "https://pub.dev" + source: hosted + version: "3.0.2" transitioned_indexed_stack: dependency: "direct main" description: @@ -1652,10 +1667,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4" url: "https://pub.dev" source: hosted - version: "6.3.14" + version: "6.3.15" url_launcher_ios: dependency: transitive description: @@ -1758,7 +1773,7 @@ packages: path: "../veilid/veilid-flutter" relative: true source: path - version: "0.4.1" + version: "0.4.3" veilid_support: dependency: "direct main" description: @@ -1786,10 +1801,10 @@ packages: dependency: transitive description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web_socket: dependency: transitive description: @@ -1810,10 +1825,10 @@ packages: dependency: transitive description: name: win32 - sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f url: "https://pub.dev" source: hosted - version: "5.10.1" + version: "5.12.0" window_manager: dependency: "direct main" description: @@ -1879,5 +1894,5 @@ packages: source: hosted version: "1.1.2" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.7.0 <4.0.0" flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5bc5e26..1e0a526 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,69 +9,68 @@ environment: dependencies: accordion: ^2.6.0 - animated_bottom_navigation_bar: ^1.3.3 + animated_bottom_navigation_bar: ^1.4.0 + animated_custom_dropdown: ^3.1.1 animated_switcher_transitions: ^1.0.0 animated_theme_switcher: ^2.0.10 - ansicolor: ^2.0.2 - archive: ^4.0.2 + ansicolor: ^2.0.3 + archive: ^4.0.4 async_tools: ^0.1.7 - awesome_extensions: ^2.0.16 + awesome_extensions: ^2.0.21 badges: ^3.1.2 - basic_utils: ^5.7.0 + basic_utils: ^5.8.2 bloc: ^8.1.4 bloc_advanced_tools: ^0.1.8 blurry_modal_progress_hud: ^1.1.1 - change_case: ^2.1.0 - charcode: ^1.3.1 + change_case: ^2.2.0 + charcode: ^1.4.0 circular_profile_avatar: ^2.0.5 circular_reveal_animation: ^2.0.1 - cool_dropdown: ^2.1.0 cupertino_icons: ^1.0.8 - equatable: ^2.0.5 + equatable: ^2.0.7 expansion_tile_group: ^2.2.0 fast_immutable_collections: ^10.2.4 - file_saver: ^0.2.13 - fixnum: ^1.1.0 + file_saver: ^0.2.14 + fixnum: ^1.1.1 flutter: sdk: flutter - flutter_animate: ^4.5.0 - flutter_bloc: ^8.1.5 + flutter_animate: ^4.5.2 + flutter_bloc: ^8.1.6 flutter_chat_types: ^3.6.2 flutter_chat_ui: git: url: https://gitlab.com/veilid/flutter-chat-ui.git ref: main - flutter_form_builder: ^9.3.0 + flutter_form_builder: ^9.7.0 flutter_hooks: ^0.20.5 flutter_localizations: sdk: flutter - flutter_native_splash: ^2.4.0 + flutter_native_splash: ^2.4.5 flutter_slidable: ^4.0.0 flutter_spinkit: ^5.2.1 flutter_sticky_header: ^0.7.0 - flutter_svg: ^2.0.10+1 + flutter_svg: ^2.0.17 flutter_translate: ^4.1.0 flutter_zoom_drawer: ^3.2.0 - form_builder_validators: ^11.0.0 - freezed_annotation: ^2.4.1 - go_router: ^14.1.4 + form_builder_validators: ^11.1.2 + freezed_annotation: ^2.4.4 + go_router: ^14.8.1 hydrated_bloc: ^9.1.5 - image: ^4.2.0 + image: ^4.5.3 intl: ^0.19.0 json_annotation: ^4.9.0 loggy: ^2.0.3 - meta: ^1.12.0 - mobile_scanner: ^6.0.6 - motion_toast: ^2.10.0 + meta: ^1.16.0 + mobile_scanner: ^6.0.7 native_device_orientation: ^2.0.3 - package_info_plus: ^8.0.0 + package_info_plus: ^8.3.0 pasteboard: ^0.3.0 - path: ^1.9.0 - path_provider: ^2.1.3 - pdf: ^3.11.0 + path: ^1.9.1 + path_provider: ^2.1.5 + pdf: ^3.11.3 pinput: ^5.0.1 preload_page_view: ^0.2.0 - printing: ^5.13.1 + printing: ^5.14.2 protobuf: ^3.1.0 provider: ^6.1.2 qr_code_dart_scan: ^0.9.11 @@ -86,7 +85,7 @@ dependencies: url: https://gitlab.com/veilid/Searchable-Listview.git ref: main share_plus: ^10.1.4 - shared_preferences: ^2.2.3 + shared_preferences: ^2.5.2 signal_strength_indicator: ^0.4.1 sliver_expandable: ^1.1.1 sliver_fill_remaining_box_adapter: ^1.0.0 @@ -96,12 +95,13 @@ dependencies: url: https://gitlab.com/veilid/dart-sorted-list-improved.git ref: main split_view: ^3.2.1 - stack_trace: ^1.11.1 + stack_trace: ^1.12.1 star_menu: ^4.0.1 - stream_transform: ^2.1.0 + stream_transform: ^2.1.1 + toastification: ^3.0.2 transitioned_indexed_stack: ^1.0.2 - url_launcher: ^6.3.0 - uuid: ^4.4.0 + url_launcher: ^6.3.1 + uuid: ^4.5.1 veilid: # veilid: ^0.0.1 path: ../veilid/veilid-flutter @@ -111,9 +111,9 @@ dependencies: xterm: ^4.0.0 zxing2: ^0.2.3 -# dependency_overrides: -# async_tools: -# path: ../dart_async_tools +dependency_overrides: + async_tools: + path: ../dart_async_tools # bloc_advanced_tools: # path: ../bloc_advanced_tools # searchable_listview: @@ -122,10 +122,10 @@ dependencies: # path: ../flutter_chat_ui dev_dependencies: - build_runner: ^2.4.11 - freezed: ^2.5.2 - icons_launcher: ^3.0.0 - json_serializable: ^6.8.0 + build_runner: ^2.4.15 + freezed: ^2.5.8 + icons_launcher: ^3.0.1 + json_serializable: ^6.9.4 lint_hard: ^5.0.0 flutter_native_splash: From 77c68aa45f4c94ce10383b2fdd34a3e81c6f35ac Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 17 Mar 2025 00:51:16 -0400 Subject: [PATCH 32/93] ui cleanup --- assets/i18n/en.json | 28 +- assets/images/grid.svg | 1 + .../cubits/account_record_cubit.dart | 46 +--- lib/account_manager/models/account_spec.dart | 102 +++++-- .../views/edit_account_page.dart | 134 +++++----- .../views/edit_profile_form.dart | 186 ++++++------- .../views/new_account_page.dart | 37 +-- lib/app.dart | 46 ++-- lib/chat/views/chat_component_widget.dart | 3 +- lib/chat/views/no_conversation_widget.dart | 6 +- .../chat_single_contact_item_widget.dart | 16 +- .../cubits/contact_invitation_list_cubit.dart | 4 +- .../cubits/invitation_generator_cubit.dart | 2 +- .../views/contact_invitation_display.dart | 45 +++- .../views/contact_invitation_item_widget.dart | 14 +- .../views/create_invitation_dialog.dart | 47 ++-- .../views/new_contact_bottom_sheet.dart | 71 ----- lib/contact_invitation/views/views.dart | 1 - lib/contacts/contacts.dart | 1 + lib/contacts/cubits/contact_list_cubit.dart | 17 +- lib/contacts/models/contact_spec.dart | 37 +++ lib/contacts/models/models.dart | 1 + lib/contacts/views/availability_widget.dart | 6 +- .../views/contact_details_widget.dart | 39 ++- lib/contacts/views/contact_item_widget.dart | 6 +- lib/contacts/views/contacts_browser.dart | 22 +- lib/contacts/views/contacts_dialog.dart | 126 +++++---- lib/contacts/views/edit_contact_form.dart | 252 +++++++++++------- lib/layout/home/drawer_menu/drawer_menu.dart | 118 +++----- .../home/drawer_menu/menu_item_widget.dart | 61 +++-- lib/layout/home/home_screen.dart | 5 +- .../views/notifications_preferences.dart | 6 + lib/proto/veilidchat.pb.dart | 15 ++ lib/proto/veilidchat.pbjson.dart | 4 +- lib/proto/veilidchat.proto | 2 + lib/router/cubits/router_cubit.dart | 5 +- lib/settings/settings_page.dart | 3 +- lib/theme/models/chat_theme.dart | 2 +- lib/theme/models/contrast_generator.dart | 94 +++---- lib/theme/models/radix_generator.dart | 68 +---- lib/theme/models/scale_theme/scale_color.dart | 7 + .../scale_custom_dropdown_theme.dart | 1 - .../scale_input_decorator_theme.dart | 117 ++++++-- .../models/scale_theme/scale_scheme.dart | 13 +- lib/theme/models/scale_theme/scale_theme.dart | 83 ++++++ .../models/scale_theme/scale_tile_theme.dart | 5 +- .../models/scale_theme/scale_toast_theme.dart | 16 +- lib/theme/views/avatar_widget.dart | 21 +- lib/theme/views/slider_tile.dart | 11 +- lib/theme/views/styled_alert.dart | 35 --- lib/theme/views/styled_dialog.dart | 2 +- lib/theme/views/widget_helpers.dart | 61 +++-- lib/veilid_processor/views/developer.dart | 4 +- pubspec.lock | 8 + pubspec.yaml | 9 +- build.bat => update_generated_files.bat | 0 build.sh => update_generated_files.sh | 0 57 files changed, 1158 insertions(+), 914 deletions(-) create mode 100644 assets/images/grid.svg delete mode 100644 lib/contact_invitation/views/new_contact_bottom_sheet.dart create mode 100644 lib/contacts/models/contact_spec.dart create mode 100644 lib/contacts/models/models.dart rename build.bat => update_generated_files.bat (100%) rename build.sh => update_generated_files.sh (100%) diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 4334a6b..ef4c44c 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -50,7 +50,6 @@ "edit_account_page": { "titlebar": "Edit Account", "header": "Account Profile", - "update": "Update", "instructions": "This information will be shared with the people you invite to connect with you on VeilidChat.", "error": "Account modification error", "name": "Name", @@ -64,7 +63,6 @@ "destroy_account_description": "Destroy account, removing it completely from all devices everywhere", "destroy_account_confirm_message": "This action is PERMANENT, and your VeilidChat account will no longer be recoverable with the recovery key. Restoring from backups will not recover your account!", "destroy_account_confirm_message_details": "You will lose access to:\n • Your entire message history\n • Your contacts\n • This will not remove your messages you have sent from other people's devices\n", - "confirm_are_you_sure": "Are you sure you want to do this?", "failed_to_remove_title": "Failed to remove account", "try_again_network": "Try again when you have a more stable network connection", "failed_to_destroy_title": "Failed to destroy account", @@ -84,6 +82,12 @@ "view": "View", "share": "Share" }, + "confirmation": { + "confirm": "Confirm", + "discard_changes": "Discard changes?", + "are_you_sure_discard": "Are you sure you want to discard your changes?", + "are_you_sure": "Are you sure you want to do this?" + }, "button": { "ok": "Ok", "cancel": "Cancel", @@ -95,10 +99,10 @@ "close": "Close", "yes": "Yes", "no": "No", + "update": "Update", "waiting_for_network": "Waiting For Network" }, "toast": { - "confirm": "Confirm", "error": "Error", "info": "Info" }, @@ -142,9 +146,7 @@ "form_nickname": "Nickname", "form_notes": "Notes", "form_fingerprint": "Fingerprint", - "form_show_availability": "Show availability", - "save": "Save", - "save_disabled": "Save" + "form_show_availability": "Show availability" }, "availability": { "unspecified": "Unspecified", @@ -172,18 +174,21 @@ "create_invitation_dialog": { "title": "Create Contact Invitation", "me": "me", - "fingerprint": "Fingerprint:", + "recipient_name": "Contact Name", + "recipient_hint": "Enter the recipient's name", + "recipient_helper": "Name of the person you are inviting to chat", + "message_hint": "Enter message for contact (optional)", + "message_label": "Message", + "message_helper": "Message to send with invitation", + "fingerprint": "Fingerprint", "connect_with_me": "Connect with {name} on VeilidChat!", - "enter_message_hint": "Enter message for contact (optional)", - "message_to_contact": "Message to send with invitation (not encrypted)", "generate": "Generate Invitation", - "message": "Message", "unlocked": "Unlocked", "pin": "PIN", "password": "Password", "protect_this_invitation": "Protect this invitation:", "note": "Note:", - "note_text": "Contact invitations can be used by anyone. Make sure you send the invitation to your contact over a secure medium, and preferably use a password or pin to ensure that they are the only ones who can unlock the invitation and accept it.", + "note_text": "Do not post contact invitations publicly.\n\nContact invitations can be used by anyone. Make sure you send the invitation to your contact over a secure medium, and preferably use a password or pin to ensure that they are the only ones who can unlock the invitation and accept it.", "pin_description": "Choose a PIN to protect the contact invite.\n\nThis level of security is appropriate only for casual connections in public environments for 'shoulder surfing' protection.", "password_description": "Choose a strong password to protect the contact invite.\n\nThis level of security is appropriate when you must be sure the contact invitation is only accepted by its intended recipient. Share this password over a different medium than the invite itself.", "pin_does_not_match": "PIN does not match", @@ -193,6 +198,7 @@ "invitation_copied": "Invitation Copied" }, "invitation_dialog": { + "to": "To", "message_from_contact": "Message from contact", "validating": "Validating...", "failed_to_accept": "Failed to accept contact invitation", diff --git a/assets/images/grid.svg b/assets/images/grid.svg new file mode 100644 index 0000000..f30d577 --- /dev/null +++ b/assets/images/grid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/account_manager/cubits/account_record_cubit.dart b/lib/account_manager/cubits/account_record_cubit.dart index 9a73246..16ab2e0 100644 --- a/lib/account_manager/cubits/account_record_cubit.dart +++ b/lib/account_manager/cubits/account_record_cubit.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:async_tools/async_tools.dart'; -import 'package:protobuf/protobuf.dart'; import 'package:veilid_support/veilid_support.dart'; import '../../proto/proto.dart' as proto; @@ -47,53 +46,30 @@ class AccountRecordCubit extends DefaultDHTRecordCubit { // Public Interface void updateAccount( - AccountSpec accountSpec, Future Function() onSuccess) { - _sspUpdate.updateState((accountSpec, onSuccess), (state) async { + AccountSpec accountSpec, Future Function() onChanged) { + _sspUpdate.updateState((accountSpec, onChanged), (state) async { await _updateAccountAsync(state.$1, state.$2); }); } Future _updateAccountAsync( - AccountSpec accountSpec, Future Function() onSuccess) async { - var changed = false; - + AccountSpec accountSpec, Future Function() onChanged) async { + var changed = true; await record?.eventualUpdateProtobuf(proto.Account.fromBuffer, (old) async { - changed = false; if (old == null) { return null; } - final newAccount = old.deepCopy() - ..profile.name = accountSpec.name - ..profile.pronouns = accountSpec.pronouns - ..profile.about = accountSpec.about - ..profile.availability = accountSpec.availability - ..profile.status = accountSpec.status - //..profile.avatar = - ..profile.timestamp = Veilid.instance.now().toInt64() - ..invisible = accountSpec.invisible - ..autodetectAway = accountSpec.autoAway - ..autoAwayTimeoutMin = accountSpec.autoAwayTimeout - ..freeMessage = accountSpec.freeMessage - ..awayMessage = accountSpec.awayMessage - ..busyMessage = accountSpec.busyMessage; + final oldAccountSpec = AccountSpec.fromProto(old); + changed = oldAccountSpec != accountSpec; + if (!changed) { + return null; + } - if (newAccount.profile != old.profile || - newAccount.invisible != old.invisible || - newAccount.autodetectAway != old.autodetectAway || - newAccount.autoAwayTimeoutMin != old.autoAwayTimeoutMin || - newAccount.freeMessage != old.freeMessage || - newAccount.busyMessage != old.busyMessage || - newAccount.awayMessage != old.awayMessage) { - changed = true; - } - if (changed) { - return newAccount; - } - return null; + return accountSpec.updateProto(old); }); if (changed) { - await onSuccess(); + await onChanged(); } } diff --git a/lib/account_manager/models/account_spec.dart b/lib/account_manager/models/account_spec.dart index 539b8d0..918e192 100644 --- a/lib/account_manager/models/account_spec.dart +++ b/lib/account_manager/models/account_spec.dart @@ -1,12 +1,16 @@ -import 'package:flutter/widgets.dart'; +import 'package:equatable/equatable.dart'; +import 'package:meta/meta.dart'; +import 'package:protobuf/protobuf.dart'; +import 'package:veilid_support/veilid_support.dart'; import '../../proto/proto.dart' as proto; /// Profile and Account configurable fields /// Some are publicly visible via the proto.Profile /// Some are privately held as proto.Account configurations -class AccountSpec { - AccountSpec( +@immutable +class AccountSpec extends Equatable { + const AccountSpec( {required this.name, required this.pronouns, required this.about, @@ -19,37 +23,99 @@ class AccountSpec { required this.autoAway, required this.autoAwayTimeout}); + const AccountSpec.empty() + : name = '', + pronouns = '', + about = '', + availability = proto.Availability.AVAILABILITY_FREE, + invisible = false, + freeMessage = '', + awayMessage = '', + busyMessage = '', + avatar = null, + autoAway = false, + autoAwayTimeout = 15; + + AccountSpec.fromProto(proto.Account p) + : name = p.profile.name, + pronouns = p.profile.pronouns, + about = p.profile.about, + availability = p.profile.availability, + invisible = p.invisible, + freeMessage = p.freeMessage, + awayMessage = p.awayMessage, + busyMessage = p.busyMessage, + avatar = p.profile.hasAvatar() ? p.profile.avatar : null, + autoAway = p.autodetectAway, + autoAwayTimeout = p.autoAwayTimeoutMin; + String get status { late final String status; switch (availability) { case proto.Availability.AVAILABILITY_AWAY: status = awayMessage; - break; case proto.Availability.AVAILABILITY_BUSY: status = busyMessage; - break; case proto.Availability.AVAILABILITY_FREE: status = freeMessage; - break; case proto.Availability.AVAILABILITY_UNSPECIFIED: case proto.Availability.AVAILABILITY_OFFLINE: status = ''; - break; } return status; } + Future updateProto(proto.Account old) async { + final newProto = old.deepCopy() + ..profile.name = name + ..profile.pronouns = pronouns + ..profile.about = about + ..profile.availability = availability + ..profile.status = status + ..profile.timestamp = Veilid.instance.now().toInt64() + ..invisible = invisible + ..autodetectAway = autoAway + ..autoAwayTimeoutMin = autoAwayTimeout + ..freeMessage = freeMessage + ..awayMessage = awayMessage + ..busyMessage = busyMessage; + + final newAvatar = avatar; + if (newAvatar != null) { + newProto.profile.avatar = newAvatar; + } else { + newProto.profile.clearAvatar(); + } + + return newProto; + } + //////////////////////////////////////////////////////////////////////////// - String name; - String pronouns; - String about; - proto.Availability availability; - bool invisible; - String freeMessage; - String awayMessage; - String busyMessage; - ImageProvider? avatar; - bool autoAway; - int autoAwayTimeout; + final String name; + final String pronouns; + final String about; + final proto.Availability availability; + final bool invisible; + final String freeMessage; + final String awayMessage; + final String busyMessage; + final proto.DataReference? avatar; + final bool autoAway; + final int autoAwayTimeout; + + @override + List get props => [ + name, + pronouns, + about, + availability, + invisible, + freeMessage, + awayMessage, + busyMessage, + avatar, + autoAway, + autoAwayTimeout + ]; } diff --git a/lib/account_manager/views/edit_account_page.dart b/lib/account_manager/views/edit_account_page.dart index 918c4ca..81b67eb 100644 --- a/lib/account_manager/views/edit_account_page.dart +++ b/lib/account_manager/views/edit_account_page.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:async_tools/async_tools.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -10,17 +11,18 @@ import 'package:veilid_support/veilid_support.dart'; import '../../layout/default_app_bar.dart'; import '../../notifications/notifications.dart'; -import '../../proto/proto.dart' as proto; import '../../theme/theme.dart'; import '../../tools/tools.dart'; import '../../veilid_processor/veilid_processor.dart'; import '../account_manager.dart'; import 'edit_profile_form.dart'; +const _kDoBackArrow = 'doBackArrow'; + class EditAccountPage extends StatefulWidget { const EditAccountPage( {required this.superIdentityRecordKey, - required this.existingAccount, + required this.initialValue, required this.accountRecord, super.key}); @@ -28,7 +30,7 @@ class EditAccountPage extends StatefulWidget { State createState() => _EditAccountPageState(); final TypedKey superIdentityRecordKey; - final proto.Account existingAccount; + final AccountSpec initialValue; final OwnedDHTRecordPointer accountRecord; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -36,8 +38,7 @@ class EditAccountPage extends StatefulWidget { properties ..add(DiagnosticsProperty( 'superIdentityRecordKey', superIdentityRecordKey)) - ..add(DiagnosticsProperty( - 'existingAccount', existingAccount)) + ..add(DiagnosticsProperty('initialValue', initialValue)) ..add(DiagnosticsProperty( 'accountRecord', accountRecord)); } @@ -49,36 +50,14 @@ class _EditAccountPageState extends WindowSetupState { titleBarStyle: TitleBarStyle.normal, orientationCapability: OrientationCapability.portraitOnly); - Widget _editAccountForm(BuildContext context, - {required Future Function(AccountSpec) onUpdate}) => - EditProfileForm( + EditProfileForm _editAccountForm(BuildContext context) => EditProfileForm( header: translate('edit_account_page.header'), instructions: translate('edit_account_page.instructions'), - submitText: translate('edit_account_page.update'), + submitText: translate('button.update'), submitDisabledText: translate('button.waiting_for_network'), - onUpdate: onUpdate, - initialValueCallback: (key) => switch (key) { - EditProfileForm.formFieldName => widget.existingAccount.profile.name, - EditProfileForm.formFieldPronouns => - widget.existingAccount.profile.pronouns, - EditProfileForm.formFieldAbout => - widget.existingAccount.profile.about, - EditProfileForm.formFieldAvailability => - widget.existingAccount.profile.availability, - EditProfileForm.formFieldFreeMessage => - widget.existingAccount.freeMessage, - EditProfileForm.formFieldAwayMessage => - widget.existingAccount.awayMessage, - EditProfileForm.formFieldBusyMessage => - widget.existingAccount.busyMessage, - EditProfileForm.formFieldAvatar => - widget.existingAccount.profile.avatar, - EditProfileForm.formFieldAutoAway => - widget.existingAccount.autodetectAway, - EditProfileForm.formFieldAutoAwayTimeout => - widget.existingAccount.autoAwayTimeoutMin.toString(), - String() => throw UnimplementedError(), - }, + onSubmit: _onSubmit, + onModifiedState: _onModifiedState, + initialValue: widget.initialValue, ); Future _onRemoveAccount() async { @@ -88,8 +67,7 @@ class _EditAccountPageState extends WindowSetupState { child: Column(mainAxisSize: MainAxisSize.min, children: [ Text(translate('edit_account_page.remove_account_confirm_message')) .paddingLTRB(24, 24, 24, 0), - Text(translate('edit_account_page.confirm_are_you_sure')) - .paddingAll(8), + Text(translate('confirmation.are_you_sure')).paddingAll(8), Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( onPressed: () { @@ -156,8 +134,7 @@ class _EditAccountPageState extends WindowSetupState { Text(translate( 'edit_account_page.destroy_account_confirm_message_details')) .paddingLTRB(24, 24, 24, 0), - Text(translate('edit_account_page.confirm_are_you_sure')) - .paddingAll(8), + Text(translate('confirmation.are_you_sure')).paddingAll(8), Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( onPressed: () { @@ -214,26 +191,51 @@ class _EditAccountPageState extends WindowSetupState { } } - Future _onUpdate(AccountSpec accountSpec) async { - // Look up account cubit for this specific account - final perAccountCollectionBlocMapCubit = - context.read(); - final accountRecordCubit = await perAccountCollectionBlocMapCubit.operate( - widget.superIdentityRecordKey, - closure: (c) async => c.accountRecordCubit); - if (accountRecordCubit == null) { - return; - } - - // Update account profile DHT record - // This triggers ConversationCubits to update - accountRecordCubit.updateAccount(accountSpec, () async { - // Update local account profile - await AccountRepository.instance - .updateLocalAccount(widget.superIdentityRecordKey, accountSpec); + void _onModifiedState(bool isModified) { + setState(() { + _isModified = isModified; }); } + Future _onSubmit(AccountSpec accountSpec) async { + try { + setState(() { + _isInAsyncCall = true; + }); + try { + // Look up account cubit for this specific account + final perAccountCollectionBlocMapCubit = + context.read(); + final accountRecordCubit = await perAccountCollectionBlocMapCubit + .operate(widget.superIdentityRecordKey, + closure: (c) async => c.accountRecordCubit); + if (accountRecordCubit == null) { + return false; + } + + // Update account profile DHT record + // This triggers ConversationCubits to update + accountRecordCubit.updateAccount(accountSpec, () async { + // Update local account profile + await AccountRepository.instance + .updateLocalAccount(widget.superIdentityRecordKey, accountSpec); + }); + + return true; + } finally { + setState(() { + _isInAsyncCall = false; + }); + } + } on Exception catch (e, st) { + if (mounted) { + await showErrorStacktraceModal( + context: context, error: e, stackTrace: st); + } + } + return false; + } + @override Widget build(BuildContext context) { final displayModalHUD = _isInAsyncCall; @@ -246,9 +248,23 @@ class _EditAccountPageState extends WindowSetupState { ? IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { - Navigator.pop(context); - }, - ) + singleFuture((this, _kDoBackArrow), () async { + if (_isModified) { + final ok = await showConfirmModal( + context: context, + title: + translate('confirmation.discard_changes'), + text: translate( + 'confirmation.are_you_sure_discard')); + if (!ok) { + return; + } + } + if (context.mounted) { + Navigator.pop(context); + } + }); + }) : null, actions: [ const SignalStrengthMeterWidget(), @@ -261,10 +277,7 @@ class _EditAccountPageState extends WindowSetupState { ]), body: SingleChildScrollView( child: Column(children: [ - _editAccountForm( - context, - onUpdate: _onUpdate, - ).paddingLTRB(0, 0, 0, 32), + _editAccountForm(context).paddingLTRB(0, 0, 0, 32), OptionBox( instructions: translate('edit_account_page.remove_account_description'), @@ -286,4 +299,5 @@ class _EditAccountPageState extends WindowSetupState { //////////////////////////////////////////////////////////////////////////// bool _isInAsyncCall = false; + bool _isModified = false; } diff --git a/lib/account_manager/views/edit_profile_form.dart b/lib/account_manager/views/edit_profile_form.dart index 1774bff..d6bb504 100644 --- a/lib/account_manager/views/edit_profile_form.dart +++ b/lib/account_manager/views/edit_profile_form.dart @@ -13,7 +13,7 @@ import '../../theme/theme.dart'; import '../../veilid_processor/veilid_processor.dart'; import '../models/models.dart'; -const _kDoUpdateSubmit = 'doUpdateSubmit'; +const _kDoSubmitEditProfile = 'doSubmitEditProfile'; class EditProfileForm extends StatefulWidget { const EditProfileForm({ @@ -21,9 +21,9 @@ class EditProfileForm extends StatefulWidget { required this.instructions, required this.submitText, required this.submitDisabledText, - required this.initialValueCallback, - this.onUpdate, - this.onSubmit, + required this.initialValue, + required this.onSubmit, + this.onModifiedState, super.key, }); @@ -32,11 +32,11 @@ class EditProfileForm extends StatefulWidget { final String header; final String instructions; - final Future Function(AccountSpec)? onUpdate; - final Future Function(AccountSpec)? onSubmit; + final Future Function(AccountSpec) onSubmit; + final void Function(bool)? onModifiedState; final String submitText; final String submitDisabledText; - final Object Function(String key) initialValueCallback; + final AccountSpec initialValue; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -44,14 +44,13 @@ class EditProfileForm extends StatefulWidget { properties ..add(StringProperty('header', header)) ..add(StringProperty('instructions', instructions)) - ..add(ObjectFlagProperty Function(AccountSpec)?>.has( - 'onUpdate', onUpdate)) ..add(StringProperty('submitText', submitText)) ..add(StringProperty('submitDisabledText', submitDisabledText)) - ..add(ObjectFlagProperty.has( - 'initialValueCallback', initialValueCallback)) - ..add(ObjectFlagProperty Function(AccountSpec)?>.has( - 'onSubmit', onSubmit)); + ..add(ObjectFlagProperty Function(AccountSpec)>.has( + 'onSubmit', onSubmit)) + ..add(ObjectFlagProperty.has( + 'onModifiedState', onModifiedState)) + ..add(DiagnosticsProperty('initialValue', initialValue)); } static const String formFieldName = 'name'; @@ -71,8 +70,9 @@ class _EditProfileFormState extends State { @override void initState() { - _autoAwayEnabled = - widget.initialValueCallback(EditProfileForm.formFieldAutoAway) as bool; + _savedValue = widget.initialValue; + _currentValueName = widget.initialValue.name; + _currentValueAutoAway = widget.initialValue.autoAway; super.initState(); } @@ -82,13 +82,10 @@ class _EditProfileFormState extends State { final theme = Theme.of(context); final scale = theme.extension()!; - final initialValueX = - widget.initialValueCallback(EditProfileForm.formFieldAvailability) - as proto.Availability; final initialValue = - initialValueX == proto.Availability.AVAILABILITY_UNSPECIFIED + _savedValue.availability == proto.Availability.AVAILABILITY_UNSPECIFIED ? proto.Availability.AVAILABILITY_FREE - : initialValueX; + : _savedValue.availability; final availabilities = [ proto.Availability.AVAILABILITY_FREE, @@ -109,7 +106,7 @@ class _EditProfileFormState extends State { value: x, child: Row(mainAxisSize: MainAxisSize.min, children: [ AvailabilityWidget.availabilityIcon( - x, scale.primaryScale.primaryText), + x, scale.primaryScale.appText), Text(x == proto.Availability.AVAILABILITY_OFFLINE ? translate('availability.always_show_offline') : AvailabilityWidget.availabilityName(x)) @@ -138,6 +135,12 @@ class _EditProfileFormState extends State { .fields[EditProfileForm.formFieldAwayMessage]!.value as String; final busyMessage = _formKey.currentState! .fields[EditProfileForm.formFieldBusyMessage]!.value as String; + + const proto.DataReference? avatar = null; + // final avatar = _formKey.currentState! + // .fields[EditProfileForm.formFieldAvatar]!.value + //as proto.DataReference?; + final autoAway = _formKey .currentState!.fields[EditProfileForm.formFieldAutoAway]!.value as bool; final autoAwayTimeoutString = _formKey.currentState! @@ -153,11 +156,21 @@ class _EditProfileFormState extends State { freeMessage: freeMessage, awayMessage: awayMessage, busyMessage: busyMessage, - avatar: null, + avatar: avatar, autoAway: autoAway, autoAwayTimeout: autoAwayTimeout); } + // Check if everything is the same and update state + void _onChanged() { + final currentValue = _makeAccountSpec(); + _isModified = currentValue != _savedValue; + final onModifiedState = widget.onModifiedState; + if (onModifiedState != null) { + onModifiedState(_isModified); + } + } + Widget _editProfileForm( BuildContext context, ) { @@ -176,24 +189,32 @@ class _EditProfileFormState extends State { return FormBuilder( key: _formKey, autovalidateMode: AutovalidateMode.onUserInteraction, + onChanged: _onChanged, child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - AvatarWidget( - name: _formKey.currentState?.value[EditProfileForm.formFieldName] - as String? ?? - '?', - size: 128, - borderColor: border, - foregroundColor: scale.primaryScale.primaryText, - backgroundColor: scale.primaryScale.primary, - scaleConfig: scaleConfig, - textStyle: theme.textTheme.titleLarge!.copyWith(fontSize: 64), - ).paddingLTRB(0, 0, 0, 16), + Row(children: [ + const Spacer(), + AvatarWidget( + name: _currentValueName, + size: 128, + borderColor: border, + foregroundColor: scale.primaryScale.primaryText, + backgroundColor: scale.primaryScale.primary, + scaleConfig: scaleConfig, + textStyle: theme.textTheme.titleLarge!.copyWith(fontSize: 64), + ).paddingLTRB(0, 0, 0, 16), + const Spacer() + ]), FormBuilderTextField( autofocus: true, name: EditProfileForm.formFieldName, - initialValue: widget - .initialValueCallback(EditProfileForm.formFieldName) as String, + initialValue: _savedValue.name, + onChanged: (x) { + setState(() { + _currentValueName = x ?? ''; + }); + }, decoration: InputDecoration( floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_name'), @@ -204,23 +225,20 @@ class _EditProfileFormState extends State { FormBuilderValidators.required(), ]), textInputAction: TextInputAction.next, - ).onFocusChange(_onFocusChange), + ), FormBuilderTextField( name: EditProfileForm.formFieldPronouns, - initialValue: - widget.initialValueCallback(EditProfileForm.formFieldPronouns) - as String, + initialValue: _savedValue.pronouns, maxLength: 64, decoration: InputDecoration( floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_pronouns'), hintText: translate('account.empty_pronouns')), textInputAction: TextInputAction.next, - ).onFocusChange(_onFocusChange), + ), FormBuilderTextField( name: EditProfileForm.formFieldAbout, - initialValue: widget - .initialValueCallback(EditProfileForm.formFieldAbout) as String, + initialValue: _savedValue.about, maxLength: 1024, maxLines: 8, minLines: 1, @@ -229,74 +247,69 @@ class _EditProfileFormState extends State { labelText: translate('account.form_about'), hintText: translate('account.empty_about')), textInputAction: TextInputAction.newline, - ).onFocusChange(_onFocusChange), - _availabilityDropDown(context) - .paddingLTRB(0, 0, 0, 16) - .onFocusChange(_onFocusChange), + ), + _availabilityDropDown(context).paddingLTRB(0, 0, 0, 16), FormBuilderTextField( name: EditProfileForm.formFieldFreeMessage, - initialValue: widget.initialValueCallback( - EditProfileForm.formFieldFreeMessage) as String, + initialValue: _savedValue.freeMessage, maxLength: 128, decoration: InputDecoration( floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_free_message'), hintText: translate('account.empty_free_message')), textInputAction: TextInputAction.next, - ).onFocusChange(_onFocusChange), + ), FormBuilderTextField( name: EditProfileForm.formFieldAwayMessage, - initialValue: widget.initialValueCallback( - EditProfileForm.formFieldAwayMessage) as String, + initialValue: _savedValue.awayMessage, maxLength: 128, decoration: InputDecoration( floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_away_message'), hintText: translate('account.empty_away_message')), textInputAction: TextInputAction.next, - ).onFocusChange(_onFocusChange), + ), FormBuilderTextField( name: EditProfileForm.formFieldBusyMessage, - initialValue: widget.initialValueCallback( - EditProfileForm.formFieldBusyMessage) as String, + initialValue: _savedValue.busyMessage, maxLength: 128, decoration: InputDecoration( floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_busy_message'), hintText: translate('account.empty_busy_message')), textInputAction: TextInputAction.next, - ).onFocusChange(_onFocusChange), + ), FormBuilderCheckbox( name: EditProfileForm.formFieldAutoAway, - initialValue: - widget.initialValueCallback(EditProfileForm.formFieldAutoAway) - as bool, + initialValue: _savedValue.autoAway, side: BorderSide(color: scale.primaryScale.border, width: 2), + checkColor: scale.primaryScale.borderText, + activeColor: scale.primaryScale.border, title: Text(translate('account.form_auto_away'), style: textTheme.labelMedium), onChanged: (v) { setState(() { - _autoAwayEnabled = v ?? false; + _currentValueAutoAway = v ?? false; }); }, - ).onFocusChange(_onFocusChange), + ), FormBuilderTextField( name: EditProfileForm.formFieldAutoAwayTimeout, - enabled: _autoAwayEnabled, - initialValue: widget.initialValueCallback( - EditProfileForm.formFieldAutoAwayTimeout) as String, + enabled: _currentValueAutoAway, + initialValue: _savedValue.autoAwayTimeout.toString(), decoration: InputDecoration( labelText: translate('account.form_auto_away_timeout'), ), validator: FormBuilderValidators.positiveNumber(), textInputAction: TextInputAction.next, - ).onFocusChange(_onFocusChange), + ), Row(children: [ const Spacer(), Text(widget.instructions).toCenter().flexible(flex: 6), const Spacer(), - ]).paddingSymmetric(vertical: 4), - if (widget.onSubmit != null) + ]).paddingSymmetric(vertical: 16), + Row(children: [ + const Spacer(), Builder(builder: (context) { final networkReady = context .watch() @@ -307,7 +320,7 @@ class _EditProfileFormState extends State { false; return ElevatedButton( - onPressed: networkReady ? _doSubmit : null, + onPressed: (networkReady && _isModified) ? _doSubmit : null, child: Row(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), Text(networkReady @@ -317,36 +330,24 @@ class _EditProfileFormState extends State { ]), ); }), + const Spacer() + ]) ], ), ); } - void _onFocusChange(bool focused) { - if (!focused) { - _doUpdate(); - } - } - - void _doUpdate() { - final onUpdate = widget.onUpdate; - if (onUpdate != null) { - singleFuture((this, _kDoUpdateSubmit), () async { - if (_formKey.currentState?.saveAndValidate() ?? false) { - final aus = _makeAccountSpec(); - await onUpdate(aus); - } - }); - } - } - void _doSubmit() { final onSubmit = widget.onSubmit; - if (onSubmit != null) { - singleFuture((this, _kDoUpdateSubmit), () async { - if (_formKey.currentState?.saveAndValidate() ?? false) { - final aus = _makeAccountSpec(); - await onSubmit(aus); + if (_formKey.currentState?.saveAndValidate() ?? false) { + singleFuture((this, _kDoSubmitEditProfile), () async { + final updatedAccountSpec = _makeAccountSpec(); + final saved = await onSubmit(updatedAccountSpec); + if (saved) { + setState(() { + _savedValue = updatedAccountSpec; + }); + _onChanged(); } }); } @@ -358,5 +359,8 @@ class _EditProfileFormState extends State { ); /////////////////////////////////////////////////////////////////////////// - late bool _autoAwayEnabled; + late AccountSpec _savedValue; + late bool _currentValueAutoAway; + late String _currentValueName; + bool _isModified = false; } diff --git a/lib/account_manager/views/new_account_page.dart b/lib/account_manager/views/new_account_page.dart index ccd5b00..a739094 100644 --- a/lib/account_manager/views/new_account_page.dart +++ b/lib/account_manager/views/new_account_page.dart @@ -8,7 +8,6 @@ import 'package:go_router/go_router.dart'; import '../../layout/default_app_bar.dart'; import '../../notifications/cubits/notifications_cubit.dart'; -import '../../proto/proto.dart' as proto; import '../../theme/theme.dart'; import '../../tools/tools.dart'; import '../../veilid_processor/veilid_processor.dart'; @@ -28,33 +27,6 @@ class _NewAccountPageState extends WindowSetupState { titleBarStyle: TitleBarStyle.normal, orientationCapability: OrientationCapability.portraitOnly); - Object _defaultAccountValues(String key) { - switch (key) { - case EditProfileForm.formFieldName: - return ''; - case EditProfileForm.formFieldPronouns: - return ''; - case EditProfileForm.formFieldAbout: - return ''; - case EditProfileForm.formFieldAvailability: - return proto.Availability.AVAILABILITY_FREE; - case EditProfileForm.formFieldFreeMessage: - return ''; - case EditProfileForm.formFieldAwayMessage: - return ''; - case EditProfileForm.formFieldBusyMessage: - return ''; - // case EditProfileForm.formFieldAvatar: - // return null; - case EditProfileForm.formFieldAutoAway: - return false; - case EditProfileForm.formFieldAutoAwayTimeout: - return '15'; - default: - throw StateError('missing form element'); - } - } - Widget _newAccountForm( BuildContext context, ) => @@ -63,10 +35,10 @@ class _NewAccountPageState extends WindowSetupState { instructions: translate('new_account_page.instructions'), submitText: translate('new_account_page.create'), submitDisabledText: translate('button.waiting_for_network'), - initialValueCallback: _defaultAccountValues, + initialValue: const AccountSpec.empty(), onSubmit: _onSubmit); - Future _onSubmit(AccountSpec accountSpec) async { + Future _onSubmit(AccountSpec accountSpec) async { // dismiss the keyboard by unfocusing the textfield FocusScope.of(context).unfocus(); @@ -88,13 +60,15 @@ class _NewAccountPageState extends WindowSetupState { context.read().error( text: translate('new_account_page.network_is_offline'), title: translate('new_account_page.error')); - return; + return false; } final writableSuperIdentity = await AccountRepository.instance .createWithNewSuperIdentity(accountSpec); GoRouterHelper(context).pushReplacement('/new_account/recovery_key', extra: [writableSuperIdentity, accountSpec.name]); + + return true; } finally { if (mounted) { setState(() { @@ -108,6 +82,7 @@ class _NewAccountPageState extends WindowSetupState { context: context, error: e, stackTrace: st); } } + return false; } @override diff --git a/lib/app.dart b/lib/app.dart index 7ef0911..fade2c8 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:provider/provider.dart'; @@ -163,23 +164,34 @@ class VeilidChatApp extends StatelessWidget { scale.primaryScale.subtleBackground, ]); - return DecoratedBox( - decoration: BoxDecoration(gradient: gradient), - child: MaterialApp.router( - scrollBehavior: const ScrollBehaviorModified(), - debugShowCheckedModeBanner: false, - routerConfig: context.read().router(), - title: translate('app.title'), - theme: theme, - localizationsDelegates: [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - FormBuilderLocalizations.delegate, - localizationDelegate - ], - supportedLocales: localizationDelegate.supportedLocales, - locale: localizationDelegate.currentLocale, - )); + return Stack( + fit: StackFit.expand, + alignment: Alignment.center, + children: [ + DecoratedBox( + decoration: BoxDecoration(gradient: gradient)), + SvgPicture.asset( + 'assets/images/grid.svg', + fit: BoxFit.cover, + colorFilter: overlayFilter, + ), + MaterialApp.router( + scrollBehavior: const ScrollBehaviorModified(), + debugShowCheckedModeBanner: false, + routerConfig: context.read().router(), + title: translate('app.title'), + theme: theme, + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + FormBuilderLocalizations.delegate, + localizationDelegate + ], + supportedLocales: + localizationDelegate.supportedLocales, + locale: localizationDelegate.currentLocale, + ) + ]); })), )), ); diff --git a/lib/chat/views/chat_component_widget.dart b/lib/chat/views/chat_component_widget.dart index c4816ae..79d1f1c 100644 --- a/lib/chat/views/chat_component_widget.dart +++ b/lib/chat/views/chat_component_widget.dart @@ -147,8 +147,7 @@ class ChatComponentWidget extends StatelessWidget { ]), ), DecoratedBox( - decoration: - BoxDecoration(color: scale.primaryScale.subtleBackground), + decoration: const BoxDecoration(color: Colors.transparent), child: NotificationListener( onNotification: (notification) { if (chatComponentCubit.scrollOffset != 0) { diff --git a/lib/chat/views/no_conversation_widget.dart b/lib/chat/views/no_conversation_widget.dart index e246fee..df1b6d3 100644 --- a/lib/chat/views/no_conversation_widget.dart +++ b/lib/chat/views/no_conversation_widget.dart @@ -16,7 +16,7 @@ class NoConversationWidget extends StatelessWidget { return DecoratedBox( decoration: BoxDecoration( - color: scale.primaryScale.appBackground, + color: scale.primaryScale.appBackground.withAlpha(192), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -24,14 +24,14 @@ class NoConversationWidget extends StatelessWidget { children: [ Icon( Icons.diversity_3, - color: scale.primaryScale.subtleBorder, + color: scale.primaryScale.appText.withAlpha(127), size: 48, ), Text( textAlign: TextAlign.center, translate('chat.start_a_conversation'), style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: scale.primaryScale.subtleBorder, + color: scale.primaryScale.appText.withAlpha(127), ), ), ], diff --git a/lib/chat_list/views/chat_single_contact_item_widget.dart b/lib/chat_list/views/chat_single_contact_item_widget.dart index 826fe37..3bdf645 100644 --- a/lib/chat_list/views/chat_single_contact_item_widget.dart +++ b/lib/chat_list/views/chat_single_contact_item_widget.dart @@ -44,20 +44,22 @@ class ChatSingleContactItemWidget extends StatelessWidget { : _contact.profile.availability; final scaleTileTheme = scaleTheme.tileTheme( - disabled: _disabled, - selected: selected, - scaleKind: ScaleKind.secondary); + disabled: _disabled, + selected: selected, + ); final avatar = AvatarWidget( name: name, size: 34, - borderColor: scaleTileTheme.borderColor, + borderColor: scaleTheme.config.useVisualIndicators + ? scaleTheme.scheme.primaryScale.primaryText + : scaleTheme.scheme.primaryScale.subtleBorder, foregroundColor: _disabled ? scaleTheme.scheme.grayScale.primaryText - : scaleTheme.scheme.secondaryScale.primaryText, + : scaleTheme.scheme.primaryScale.primaryText, backgroundColor: _disabled ? scaleTheme.scheme.grayScale.primary - : scaleTheme.scheme.secondaryScale.primary, + : scaleTheme.scheme.primaryScale.primary, scaleConfig: scaleTheme.config, textStyle: theme.textTheme.titleLarge!, ); @@ -66,7 +68,7 @@ class ChatSingleContactItemWidget extends StatelessWidget { key: ValueKey(_localConversationRecordKey), disabled: _disabled, selected: selected, - tileScale: ScaleKind.secondary, + tileScale: ScaleKind.primary, title: title, subtitle: subtitle, leading: avatar, diff --git a/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart b/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart index 959445c..768cf2f 100644 --- a/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart +++ b/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart @@ -59,6 +59,7 @@ class ContactInvitationListCubit {required proto.Profile profile, required EncryptionKeyType encryptionKeyType, required String encryptionKey, + required String recipient, required String message, required Timestamp? expiration}) async { final pool = DHTRecordPool.instance; @@ -154,7 +155,8 @@ class ContactInvitationListCubit ..localConversationRecordKey = localConversation.key.toProto() ..expiration = expiration?.toInt64() ?? Int64.ZERO ..invitation = signedContactInvitationBytes - ..message = message; + ..message = message + ..recipient = recipient; // Add ContactInvitationRecord to account's list await operateWriteEventual((writer) async { diff --git a/lib/contact_invitation/cubits/invitation_generator_cubit.dart b/lib/contact_invitation/cubits/invitation_generator_cubit.dart index 5c0fa15..8d2226c 100644 --- a/lib/contact_invitation/cubits/invitation_generator_cubit.dart +++ b/lib/contact_invitation/cubits/invitation_generator_cubit.dart @@ -5,5 +5,5 @@ import 'package:veilid_support/veilid_support.dart'; class InvitationGeneratorCubit extends FutureCubit<(Uint8List, TypedKey)> { InvitationGeneratorCubit(super.fut); - InvitationGeneratorCubit.value(super.v) : super.value(); + InvitationGeneratorCubit.value(super.state) : super.value(); } diff --git a/lib/contact_invitation/views/contact_invitation_display.dart b/lib/contact_invitation/views/contact_invitation_display.dart index 83b80d0..b3f048a 100644 --- a/lib/contact_invitation/views/contact_invitation_display.dart +++ b/lib/contact_invitation/views/contact_invitation_display.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:auto_size_text/auto_size_text.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:basic_utils/basic_utils.dart'; import 'package:flutter/foundation.dart'; @@ -20,11 +21,13 @@ import '../contact_invitation.dart'; class ContactInvitationDisplayDialog extends StatelessWidget { const ContactInvitationDisplayDialog._({ required this.locator, + required this.recipient, required this.message, required this.fingerprint, }); final Locator locator; + final String recipient; final String message; final String fingerprint; @@ -32,18 +35,22 @@ class ContactInvitationDisplayDialog extends StatelessWidget { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties + ..add(StringProperty('recipient', recipient)) ..add(StringProperty('message', message)) ..add(DiagnosticsProperty('locator', locator)) ..add(StringProperty('fingerprint', fingerprint)); } - String makeTextInvite(String message, Uint8List data) { + String makeTextInvite(String recipient, String message, Uint8List data) { final invite = StringUtils.addCharAtPosition( base64UrlNoPadEncode(data), '\n', 40, repeat: true); + final to = recipient.isNotEmpty + ? '${translate('invitiation_dialog.to')}: $recipient\n' + : ''; final msg = message.isNotEmpty ? '$message\n' : ''; - - return '$msg' + return '$to' + '$msg' '--- BEGIN VEILIDCHAT CONTACT INVITE ----\n' '$invite\n' '---- END VEILIDCHAT CONTACT INVITE -----\n' @@ -62,6 +69,10 @@ class ContactInvitationDisplayDialog extends StatelessWidget { final cardsize = min(MediaQuery.of(context).size.shortestSide - 48.0, 400); + final fingerprintText = + '${translate('create_invitation_dialog.fingerprint')}\n' + '$fingerprint'; + return BlocListener( bloc: locator(), @@ -110,14 +121,21 @@ class ContactInvitationDisplayDialog extends StatelessWidget { errorCorrectLevel: QrErrorCorrectLevel.L)), ).expanded(), - Text(message, - softWrap: true, - style: textTheme.labelLarge! - .copyWith(color: Colors.black)) - .paddingAll(8), - Text( - '${translate('create_invitation_dialog.fingerprint')}\n' - '$fingerprint', + if (recipient.isNotEmpty) + AutoSizeText(recipient, + softWrap: true, + maxLines: 2, + style: textTheme.labelLarge! + .copyWith(color: Colors.black)) + .paddingAll(8), + if (message.isNotEmpty) + Text(message, + softWrap: true, + maxLines: 2, + style: textTheme.labelMedium! + .copyWith(color: Colors.black)) + .paddingAll(8), + Text(fingerprintText, softWrap: true, textAlign: TextAlign.center, style: textTheme.labelSmall!.copyWith( @@ -137,7 +155,8 @@ class ContactInvitationDisplayDialog extends StatelessWidget { text: translate('create_invitation_dialog' '.invitation_copied')); await Clipboard.setData(ClipboardData( - text: makeTextInvite(message, data.$1))); + text: makeTextInvite( + recipient, message, data.$1))); }, ).paddingAll(16), ]), @@ -148,6 +167,7 @@ class ContactInvitationDisplayDialog extends StatelessWidget { required BuildContext context, required Locator locator, required InvitationGeneratorCubit Function(BuildContext) create, + required String recipient, required String message, }) async { final fingerprint = @@ -159,6 +179,7 @@ class ContactInvitationDisplayDialog extends StatelessWidget { create: create, child: ContactInvitationDisplayDialog._( locator: locator, + recipient: recipient, message: message, fingerprint: fingerprint, ))); diff --git a/lib/contact_invitation/views/contact_invitation_item_widget.dart b/lib/contact_invitation/views/contact_invitation_item_widget.dart index 6e6dfcf..779f962 100644 --- a/lib/contact_invitation/views/contact_invitation_item_widget.dart +++ b/lib/contact_invitation/views/contact_invitation_item_widget.dart @@ -37,14 +37,19 @@ class ContactInvitationItemWidget extends StatelessWidget { final tileDisabled = disabled || context.watch().isBusy; + var title = translate('contact_list.invitation'); + if (contactInvitationRecord.recipient.isNotEmpty) { + title = contactInvitationRecord.recipient; + } else if (contactInvitationRecord.message.isNotEmpty) { + title = contactInvitationRecord.message; + } + return SliderTile( key: ObjectKey(contactInvitationRecord), disabled: tileDisabled, selected: selected, tileScale: ScaleKind.primary, - title: contactInvitationRecord.message.isEmpty - ? translate('contact_list.invitation') - : contactInvitationRecord.message, + title: title, leading: const Icon(Icons.person_add), onTap: () async { if (!context.mounted) { @@ -53,6 +58,7 @@ class ContactInvitationItemWidget extends StatelessWidget { await ContactInvitationDisplayDialog.show( context: context, locator: context.read, + recipient: contactInvitationRecord.recipient, message: contactInvitationRecord.message, create: (context) => InvitationGeneratorCubit.value(( Uint8List.fromList(contactInvitationRecord.invitation), @@ -62,7 +68,7 @@ class ContactInvitationItemWidget extends StatelessWidget { }, endActions: [ SliderTileAction( - icon: Icons.delete, + // icon: Icons.delete, label: translate('button.delete'), actionScale: ScaleKind.tertiary, onPressed: (context) async { diff --git a/lib/contact_invitation/views/create_invitation_dialog.dart b/lib/contact_invitation/views/create_invitation_dialog.dart index f1115d7..d835de8 100644 --- a/lib/contact_invitation/views/create_invitation_dialog.dart +++ b/lib/contact_invitation/views/create_invitation_dialog.dart @@ -18,7 +18,7 @@ class CreateInvitationDialog extends StatefulWidget { const CreateInvitationDialog._({required this.locator}); @override - CreateInvitationDialogState createState() => CreateInvitationDialogState(); + State createState() => _CreateInvitationDialogState(); static Future show(BuildContext context) async { await StyledDialog.show( @@ -36,8 +36,9 @@ class CreateInvitationDialog extends StatefulWidget { } } -class CreateInvitationDialogState extends State { +class _CreateInvitationDialogState extends State { late final TextEditingController _messageTextController; + late final TextEditingController _recipientTextController; EncryptionKeyType _encryptionKeyType = EncryptionKeyType.none; String _encryptionKey = ''; @@ -51,6 +52,7 @@ class CreateInvitationDialogState extends State { _messageTextController = TextEditingController( text: translate('create_invitation_dialog.connect_with_me', args: {'name': name})); + _recipientTextController = TextEditingController(); super.initState(); } @@ -154,6 +156,7 @@ class CreateInvitationDialogState extends State { profile: profile, encryptionKeyType: _encryptionKeyType, encryptionKey: _encryptionKey, + recipient: _recipientTextController.text, message: _messageTextController.text, expiration: _expiration); @@ -162,6 +165,7 @@ class CreateInvitationDialogState extends State { await ContactInvitationDisplayDialog.show( context: context, locator: widget.locator, + recipient: _recipientTextController.text, message: _messageTextController.text, create: (context) => InvitationGeneratorCubit(generator)); } @@ -176,6 +180,7 @@ class CreateInvitationDialogState extends State { final theme = Theme.of(context); //final scale = theme.extension()!; final textTheme = theme.textTheme; + return ConstrainedBox( constraints: BoxConstraints(maxHeight: maxDialogHeight, maxWidth: maxDialogWidth), @@ -185,19 +190,34 @@ class CreateInvitationDialogState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text( - translate('create_invitation_dialog.message_to_contact'), + TextField( + controller: _recipientTextController, + onChanged: (value) { + setState(() {}); + }, + inputFormatters: [ + LengthLimitingTextInputFormatter(128), + ], + decoration: InputDecoration( + hintText: + translate('create_invitation_dialog.recipient_hint'), + labelText: + translate('create_invitation_dialog.recipient_name'), + helperText: + translate('create_invitation_dialog.recipient_helper')), ).paddingAll(8), + const SizedBox(height: 10), TextField( controller: _messageTextController, inputFormatters: [ LengthLimitingTextInputFormatter(128), ], decoration: InputDecoration( - //border: const OutlineInputBorder(), - hintText: - translate('create_invitation_dialog.enter_message_hint'), - labelText: translate('create_invitation_dialog.message')), + hintText: translate('create_invitation_dialog.message_hint'), + labelText: + translate('create_invitation_dialog.message_label'), + helperText: + translate('create_invitation_dialog.message_helper')), ).paddingAll(8), const SizedBox(height: 10), Text(translate('create_invitation_dialog.protect_this_invitation'), @@ -228,7 +248,9 @@ class CreateInvitationDialogState extends State { Container( padding: const EdgeInsets.all(8), child: ElevatedButton( - onPressed: _onGenerateButtonPressed, + onPressed: _recipientTextController.text.isNotEmpty + ? _onGenerateButtonPressed + : null, child: Text( translate('create_invitation_dialog.generate'), ).paddingAll(16), @@ -244,11 +266,4 @@ class CreateInvitationDialogState extends State { ), ); } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties.add(DiagnosticsProperty( - 'messageTextController', _messageTextController)); - } } diff --git a/lib/contact_invitation/views/new_contact_bottom_sheet.dart b/lib/contact_invitation/views/new_contact_bottom_sheet.dart deleted file mode 100644 index a79a07f..0000000 --- a/lib/contact_invitation/views/new_contact_bottom_sheet.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:awesome_extensions/awesome_extensions.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_translate/flutter_translate.dart'; - -import '../../theme/theme.dart'; -import 'create_invitation_dialog.dart'; -import 'paste_invitation_dialog.dart'; -import 'scan_invitation_dialog.dart'; - -Widget newContactBottomSheetBuilder( - BuildContext sheetContext, BuildContext context) { - final theme = Theme.of(sheetContext); - final scale = theme.extension()!; - - return KeyboardListener( - focusNode: FocusNode(), - onKeyEvent: (ke) { - if (ke.logicalKey == LogicalKeyboardKey.escape) { - Navigator.pop(sheetContext); - } - }, - child: styledBottomSheet( - context: context, - title: translate('add_contact_sheet.new_contact'), - child: SizedBox( - height: 160, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Column(children: [ - IconButton( - onPressed: () async { - Navigator.pop(sheetContext); - await CreateInvitationDialog.show(context); - }, - iconSize: 64, - icon: const Icon(Icons.contact_page), - color: scale.primaryScale.hoverBorder), - Text( - translate('add_contact_sheet.create_invite'), - ) - ]), - Column(children: [ - IconButton( - onPressed: () async { - Navigator.pop(sheetContext); - await ScanInvitationDialog.show(context); - }, - iconSize: 64, - icon: const Icon(Icons.qr_code_scanner), - color: scale.primaryScale.hoverBorder), - Text( - translate('add_contact_sheet.scan_invite'), - ) - ]), - Column(children: [ - IconButton( - onPressed: () async { - Navigator.pop(sheetContext); - await PasteInvitationDialog.show(context); - }, - iconSize: 64, - icon: const Icon(Icons.paste), - color: scale.primaryScale.hoverBorder), - Text( - translate('add_contact_sheet.paste_invite'), - ) - ]) - ]).paddingAll(16)))); -} diff --git a/lib/contact_invitation/views/views.dart b/lib/contact_invitation/views/views.dart index 726f0b9..241513d 100644 --- a/lib/contact_invitation/views/views.dart +++ b/lib/contact_invitation/views/views.dart @@ -3,6 +3,5 @@ export 'contact_invitation_item_widget.dart'; export 'contact_invitation_list_widget.dart'; export 'create_invitation_dialog.dart'; export 'invitation_dialog.dart'; -export 'new_contact_bottom_sheet.dart'; export 'paste_invitation_dialog.dart'; export 'scan_invitation_dialog.dart'; diff --git a/lib/contacts/contacts.dart b/lib/contacts/contacts.dart index 6acdd43..08ae2e7 100644 --- a/lib/contacts/contacts.dart +++ b/lib/contacts/contacts.dart @@ -1,2 +1,3 @@ export 'cubits/cubits.dart'; +export 'models/models.dart'; export 'views/views.dart'; diff --git a/lib/contacts/cubits/contact_list_cubit.dart b/lib/contacts/cubits/contact_list_cubit.dart index d3c6483..df70cc0 100644 --- a/lib/contacts/cubits/contact_list_cubit.dart +++ b/lib/contacts/cubits/contact_list_cubit.dart @@ -8,6 +8,7 @@ import 'package:veilid_support/veilid_support.dart'; import '../../account_manager/account_manager.dart'; import '../../proto/proto.dart' as proto; import '../../tools/tools.dart'; +import '../models/models.dart'; ////////////////////////////////////////////////// // Mutable state for per-account contacts @@ -81,9 +82,7 @@ class ContactListCubit extends DHTShortArrayCubit { Future updateContactFields({ required TypedKey localConversationRecordKey, - String? nickname, - String? notes, - bool? showAvailability, + required ContactSpec updatedContactSpec, }) async { // Update contact's locally-modifiable fields await operateWriteEventual((writer) async { @@ -92,17 +91,7 @@ class ContactListCubit extends DHTShortArrayCubit { if (c != null && c.localConversationRecordKey.toVeilid() == localConversationRecordKey) { - final newContact = c.deepCopy(); - - if (nickname != null) { - newContact.nickname = nickname; - } - if (notes != null) { - newContact.notes = notes; - } - if (showAvailability != null) { - newContact.showAvailability = showAvailability; - } + final newContact = await updatedContactSpec.updateProto(c); final updated = await writer.tryWriteItemProtobuf( proto.Contact.fromBuffer, pos, newContact); diff --git a/lib/contacts/models/contact_spec.dart b/lib/contacts/models/contact_spec.dart new file mode 100644 index 0000000..1596434 --- /dev/null +++ b/lib/contacts/models/contact_spec.dart @@ -0,0 +1,37 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/foundation.dart'; +import 'package:protobuf/protobuf.dart'; + +import '../../proto/proto.dart' as proto; + +@immutable +class ContactSpec extends Equatable { + const ContactSpec({ + required this.nickname, + required this.notes, + required this.showAvailability, + }); + + ContactSpec.fromProto(proto.Contact p) + : nickname = p.nickname, + notes = p.notes, + showAvailability = p.showAvailability; + + Future updateProto(proto.Contact old) async { + final newProto = old.deepCopy() + ..nickname = nickname + ..notes = notes + ..showAvailability = showAvailability; + + return newProto; + } + + //////////////////////////////////////////////////////////////////////////// + + final String nickname; + final String notes; + final bool showAvailability; + + @override + List get props => [nickname, notes, showAvailability]; +} diff --git a/lib/contacts/models/models.dart b/lib/contacts/models/models.dart new file mode 100644 index 0000000..d489632 --- /dev/null +++ b/lib/contacts/models/models.dart @@ -0,0 +1 @@ +export 'contact_spec.dart'; diff --git a/lib/contacts/views/availability_widget.dart b/lib/contacts/views/availability_widget.dart index cf3e51a..a79f774 100644 --- a/lib/contacts/views/availability_widget.dart +++ b/lib/contacts/views/availability_widget.dart @@ -10,11 +10,11 @@ class AvailabilityWidget extends StatelessWidget { {required this.availability, required this.color, this.vertical = true, - this.iconSize = 32, + this.iconSize = 24, super.key}); static Widget availabilityIcon(proto.Availability availability, Color color, - {double size = 32}) { + {double size = 24}) { late final Widget iconData; switch (availability) { case proto.Availability.AVAILABILITY_AWAY: @@ -70,7 +70,7 @@ class AvailabilityWidget extends StatelessWidget { ]) : Row(mainAxisSize: MainAxisSize.min, children: [ icon, - Text(name, style: textTheme.labelSmall!.copyWith(color: color)) + Text(name, style: textTheme.labelLarge!.copyWith(color: color)) .paddingLTRB(8, 0, 0, 0) ]); } diff --git a/lib/contacts/views/contact_details_widget.dart b/lib/contacts/views/contact_details_widget.dart index bd4376f..7b5416e 100644 --- a/lib/contacts/views/contact_details_widget.dart +++ b/lib/contacts/views/contact_details_widget.dart @@ -1,13 +1,15 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_translate/flutter_translate.dart'; import '../../proto/proto.dart' as proto; +import '../../tools/tools.dart'; import '../contacts.dart'; class ContactDetailsWidget extends StatefulWidget { - const ContactDetailsWidget({required this.contact, super.key}); - final proto.Contact contact; + const ContactDetailsWidget( + {required this.contact, this.onModifiedState, super.key}); @override State createState() => _ContactDetailsWidgetState(); @@ -15,8 +17,14 @@ class ContactDetailsWidget extends StatefulWidget { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(DiagnosticsProperty('contact', contact)); + properties + ..add(DiagnosticsProperty('contact', contact)) + ..add(ObjectFlagProperty.has( + 'onModifiedState', onModifiedState)); } + + final proto.Contact contact; + final void Function(bool)? onModifiedState; } class _ContactDetailsWidgetState extends State @@ -24,18 +32,21 @@ class _ContactDetailsWidgetState extends State @override Widget build(BuildContext context) => SingleChildScrollView( child: EditContactForm( - formKey: GlobalKey(), contact: widget.contact, - onSubmit: (fbs) async { + submitText: translate('button.update'), + submitDisabledText: translate('button.waiting_for_network'), + onModifiedState: widget.onModifiedState, + onSubmit: (updatedContactSpec) async { final contactList = context.read(); - await contactList.updateContactFields( - localConversationRecordKey: - widget.contact.localConversationRecordKey.toVeilid(), - nickname: fbs.currentState - ?.value[EditContactForm.formFieldNickname] as String, - notes: fbs.currentState?.value[EditContactForm.formFieldNotes] - as String, - showAvailability: fbs.currentState - ?.value[EditContactForm.formFieldShowAvailability] as bool); + try { + await contactList.updateContactFields( + localConversationRecordKey: + widget.contact.localConversationRecordKey.toVeilid(), + updatedContactSpec: updatedContactSpec); + } on Exception catch (e) { + log.debug('error updating contact: $e', e); + return false; + } + return true; })); } diff --git a/lib/contacts/views/contact_item_widget.dart b/lib/contacts/views/contact_item_widget.dart index 4cb874d..a0f2fbc 100644 --- a/lib/contacts/views/contact_item_widget.dart +++ b/lib/contacts/views/contact_item_widget.dart @@ -40,7 +40,7 @@ class ContactItemWidget extends StatelessWidget { size: 34, borderColor: _disabled ? scale.grayScale.primaryText - : scale.primaryScale.primaryText, + : scale.primaryScale.subtleBorder, foregroundColor: _disabled ? scale.grayScale.primaryText : scale.primaryScale.primaryText, @@ -71,7 +71,7 @@ class ContactItemWidget extends StatelessWidget { endActions: [ if (_onDoubleTap != null) SliderTileAction( - icon: Icons.edit, + //icon: Icons.edit, label: translate('button.edit'), actionScale: ScaleKind.secondary, onPressed: (_context) => @@ -81,7 +81,7 @@ class ContactItemWidget extends StatelessWidget { ), if (_onDelete != null) SliderTileAction( - icon: Icons.delete, + //icon: Icons.delete, label: translate('button.delete'), actionScale: ScaleKind.tertiary, onPressed: (_context) => diff --git a/lib/contacts/views/contacts_browser.dart b/lib/contacts/views/contacts_browser.dart index 89cea88..7040af5 100644 --- a/lib/contacts/views/contacts_browser.dart +++ b/lib/contacts/views/contacts_browser.dart @@ -74,13 +74,10 @@ class _ContactsBrowserState extends State final menuIconColor = scaleConfig.preferBorders ? scale.primaryScale.hoverBorder - : scale.primaryScale.borderText; + : scale.primaryScale.hoverBorder; final menuBackgroundColor = scaleConfig.preferBorders ? scale.primaryScale.elementBackground - : scale.primaryScale.border; - // final menuHoverColor = scaleConfig.preferBorders - // ? scale.primaryScale.hoverElementBackground - // : scale.primaryScale.hoverBorder; + : scale.primaryScale.elementBackground; final menuBorderColor = scale.primaryScale.hoverBorder; @@ -149,13 +146,12 @@ class _ContactsBrowserState extends State }, iconSize: 32, icon: const Icon(Icons.contact_page), - color: scale.primaryScale.hoverBorder, + color: menuIconColor, ), Text(translate('add_contact_sheet.create_invite'), maxLines: 2, textAlign: TextAlign.center, - style: textTheme.labelSmall! - .copyWith(color: scale.primaryScale.hoverBorder)) + style: textTheme.labelSmall!.copyWith(color: menuIconColor)) ]), StarMenu( items: receiveInviteMenuItems, @@ -171,13 +167,12 @@ class _ContactsBrowserState extends State icon: ImageIcon( const AssetImage('assets/images/handshake.png'), size: 32, - color: scale.primaryScale.hoverBorder, + color: menuIconColor, )), Text(translate('add_contact_sheet.receive_invite'), maxLines: 2, textAlign: TextAlign.center, - style: textTheme.labelSmall! - .copyWith(color: scale.primaryScale.hoverBorder)) + style: textTheme.labelSmall!.copyWith(color: menuIconColor)) ]), ), ]).paddingAll(16); @@ -274,8 +269,9 @@ class _ContactsBrowserState extends State case ContactsBrowserElementKind.invitation: final invitation = element.invitation!; return invitation.message - .toLowerCase() - .contains(lowerValue); + .toLowerCase() + .contains(lowerValue) || + invitation.recipient.toLowerCase().contains(lowerValue); } }).toList() }; diff --git a/lib/contacts/views/contacts_dialog.dart b/lib/contacts/views/contacts_dialog.dart index e6e5391..ec85df3 100644 --- a/lib/contacts/views/contacts_dialog.dart +++ b/lib/contacts/views/contacts_dialog.dart @@ -1,3 +1,4 @@ +import 'package:async_tools/async_tools.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -11,6 +12,8 @@ import '../../proto/proto.dart' as proto; import '../../theme/theme.dart'; import '../contacts.dart'; +const _kDoBackArrow = 'doBackArrow'; + class ContactsDialog extends StatefulWidget { const ContactsDialog._({required this.modalContext}); @@ -44,13 +47,8 @@ class _ContactsDialogState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - // final textTheme = theme.textTheme; final scale = theme.extension()!; - final scaleConfig = theme.extension()!; - - final appBarIconColor = scaleConfig.useVisualIndicators - ? scale.secondaryScale.border - : scale.secondaryScale.borderText; + final appBarIconColor = scale.primaryScale.borderText; final enableSplit = !isMobileWidth(context); final enableLeft = enableSplit || _selectedContact == null; @@ -63,20 +61,22 @@ class _ContactsDialogState extends State { title: Text(!enableSplit && enableRight ? translate('contacts_dialog.edit_contact') : translate('contacts_dialog.contacts')), - leading: Navigator.canPop(context) - ? IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () { - if (!enableSplit && enableRight) { - setState(() { - _selectedContact = null; - }); - } else { + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + singleFuture((this, _kDoBackArrow), () async { + final confirmed = await _onContactSelected(null); + if (!enableSplit && enableRight) { + } else { + if (confirmed) { + if (context.mounted) { Navigator.pop(context); } - }, - ) - : null, + } + } + }); + }, + ), actions: [ if (_selectedContact != null) FittedBox( @@ -85,9 +85,10 @@ class _ContactsDialogState extends State { Column(mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.chat_bubble), + color: appBarIconColor, tooltip: translate('contacts_dialog.new_chat'), onPressed: () async { - await onChatStarted(_selectedContact!); + await _onChatStarted(_selectedContact!); }), Text(translate('contacts_dialog.new_chat'), style: theme.textTheme.labelSmall! @@ -100,10 +101,11 @@ class _ContactsDialogState extends State { Column(mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.close), + color: appBarIconColor, tooltip: translate('contacts_dialog.close_contact'), onPressed: () async { - await onContactSelected(null); + await _onContactSelected(null); }), Text(translate('contacts_dialog.close_contact'), style: theme.textTheme.labelSmall! @@ -115,41 +117,68 @@ class _ContactsDialogState extends State { return ColoredBox( color: scale.primaryScale.appBackground, - child: Row(children: [ - Offstage( - offstage: !enableLeft, - child: SizedBox( - width: enableLeft && !enableRight - ? maxWidth - : (maxWidth / 3).clamp(200, 500), - child: DecoratedBox( - decoration: BoxDecoration( - color: scale.primaryScale.subtleBackground), - child: ContactsBrowser( - selectedContactRecordKey: _selectedContact - ?.localConversationRecordKey - .toVeilid(), - onContactSelected: onContactSelected, - onChatStarted: onChatStarted, - ).paddingLTRB(8, 0, 8, 8)))), - if (enableRight) - if (_selectedContact == null) - const NoContactWidget().expanded() - else - ContactDetailsWidget(contact: _selectedContact!) - .paddingAll(8) - .expanded(), - ])); + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Offstage( + offstage: !enableLeft, + child: SizedBox( + width: enableLeft && !enableRight + ? maxWidth + : (maxWidth / 3).clamp(200, 500), + child: DecoratedBox( + decoration: BoxDecoration( + color: scale + .primaryScale.subtleBackground), + child: ContactsBrowser( + selectedContactRecordKey: _selectedContact + ?.localConversationRecordKey + .toVeilid(), + onContactSelected: _onContactSelected, + onChatStarted: _onChatStarted, + ).paddingLTRB(8, 0, 8, 8)))), + if (enableRight && enableLeft) + Container( + constraints: const BoxConstraints( + minWidth: 1, maxWidth: 1), + color: scale.primaryScale.subtleBorder), + if (enableRight) + if (_selectedContact == null) + const NoContactWidget().expanded() + else + ContactDetailsWidget( + contact: _selectedContact!, + onModifiedState: _onModifiedState) + .paddingLTRB(16, 16, 16, 16) + .expanded(), + ])); }))); } - Future onContactSelected(proto.Contact? contact) async { + void _onModifiedState(bool isModified) { setState(() { - _selectedContact = contact; + _isModified = isModified; }); } - Future onChatStarted(proto.Contact contact) async { + Future _onContactSelected(proto.Contact? contact) async { + if (contact != _selectedContact && _isModified) { + final ok = await showConfirmModal( + context: context, + title: translate('confirmation.discard_changes'), + text: translate('confirmation.are_you_sure_discard')); + if (!ok) { + return false; + } + } + setState(() { + _selectedContact = contact; + _isModified = false; + }); + return true; + } + + Future _onChatStarted(proto.Contact contact) async { final chatListCubit = context.read(); await chatListCubit.getOrCreateChatSingleContact(contact: contact); @@ -163,4 +192,5 @@ class _ContactsDialogState extends State { } proto.Contact? _selectedContact; + bool _isModified = false; } diff --git a/lib/contacts/views/edit_contact_form.dart b/lib/contacts/views/edit_contact_form.dart index 7803ab2..5477c60 100644 --- a/lib/contacts/views/edit_contact_form.dart +++ b/lib/contacts/views/edit_contact_form.dart @@ -1,3 +1,4 @@ +import 'package:async_tools/async_tools.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -6,13 +7,18 @@ import 'package:flutter_translate/flutter_translate.dart'; import '../../proto/proto.dart' as proto; import '../../theme/theme.dart'; +import '../models/contact_spec.dart'; import 'availability_widget.dart'; +const _kDoSubmitEditContact = 'doSubmitEditContact'; + class EditContactForm extends StatefulWidget { const EditContactForm({ - required this.formKey, required this.contact, - this.onSubmit, + required this.onSubmit, + required this.submitText, + required this.submitDisabledText, + this.onModifiedState, super.key, }); @@ -20,19 +26,22 @@ class EditContactForm extends StatefulWidget { State createState() => _EditContactFormState(); final proto.Contact contact; - final Future Function(GlobalKey)? onSubmit; - final GlobalKey formKey; + final String submitText; + final String submitDisabledText; + final Future Function(ContactSpec) onSubmit; + final void Function(bool)? onModifiedState; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties - ..add(ObjectFlagProperty< - Future Function( - GlobalKey p1)?>.has('onSubmit', onSubmit)) + ..add(ObjectFlagProperty Function(ContactSpec p1)>.has( + 'onSubmit', onSubmit)) + ..add(ObjectFlagProperty.has( + 'onModifiedState', onModifiedState)) ..add(DiagnosticsProperty('contact', contact)) - ..add( - DiagnosticsProperty>('formKey', formKey)); + ..add(StringProperty('submitText', submitText)) + ..add(StringProperty('submitDisabledText', submitDisabledText)); } static const String formFieldNickname = 'nickname'; @@ -41,16 +50,46 @@ class EditContactForm extends StatefulWidget { } class _EditContactFormState extends State { + final _formKey = GlobalKey(); + @override void initState() { + _savedValue = ContactSpec.fromProto(widget.contact); + _currentValueNickname = _savedValue.nickname; + super.initState(); } - Widget _availabilityWidget(proto.Availability availability, Color color) => - AvailabilityWidget(availability: availability, color: color); + ContactSpec _makeContactSpec() { + final nickname = _formKey.currentState! + .fields[EditContactForm.formFieldNickname]!.value as String; + final notes = _formKey + .currentState!.fields[EditContactForm.formFieldNotes]!.value as String; + final showAvailability = _formKey.currentState! + .fields[EditContactForm.formFieldShowAvailability]!.value as bool; - @override - Widget build(BuildContext context) { + return ContactSpec( + nickname: nickname, notes: notes, showAvailability: showAvailability); + } + + // Check if everything is the same and update state + void _onChanged() { + final currentValue = _makeContactSpec(); + _isModified = currentValue != _savedValue; + final onModifiedState = widget.onModifiedState; + if (onModifiedState != null) { + onModifiedState(_isModified); + } + } + + Widget _availabilityWidget(proto.Availability availability, Color color) => + AvailabilityWidget( + availability: availability, + color: color, + vertical: false, + ); + + Widget _editContactForm(BuildContext context) { final theme = Theme.of(context); final scale = theme.extension()!; final scaleConfig = theme.extension()!; @@ -60,75 +99,94 @@ class _EditContactFormState extends State { if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) { border = scale.primaryScale.elementBackground; } else { - border = scale.primaryScale.border; + border = scale.primaryScale.subtleBorder; } return FormBuilder( - key: widget.formKey, + key: _formKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + onChanged: _onChanged, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - AvatarWidget( - name: widget.contact.profile.name, - size: 128, - borderColor: border, - foregroundColor: scale.primaryScale.primaryText, - backgroundColor: scale.primaryScale.primary, - scaleConfig: scaleConfig, - textStyle: theme.textTheme.titleLarge!.copyWith(fontSize: 64), - ).paddingLTRB(0, 0, 0, 16), - SelectableText(widget.contact.profile.name, - style: textTheme.headlineMedium) - .noEditDecoratorLabel( - context, - translate('contact_form.form_name'), - scale: scale.secondaryScale, - ) - .paddingSymmetric(vertical: 4), - SelectableText(widget.contact.profile.pronouns, - style: textTheme.headlineSmall) - .noEditDecoratorLabel( - context, - translate('contact_form.form_pronouns'), - scale: scale.secondaryScale, - ) - .paddingSymmetric(vertical: 4), - Row(mainAxisSize: MainAxisSize.min, children: [ - _availabilityWidget(widget.contact.profile.availability, - scale.primaryScale.primaryText), - SelectableText(widget.contact.profile.status, - style: textTheme.bodyMedium) - .paddingSymmetric(horizontal: 8) - ]) - .noEditDecoratorLabel( - context, - translate('contact_form.form_status'), - scale: scale.secondaryScale, - ) - .paddingSymmetric(vertical: 4), - SelectableText(widget.contact.profile.about, - minLines: 1, maxLines: 8, style: textTheme.bodyMedium) - .noEditDecoratorLabel( - context, - translate('contact_form.form_about'), - scale: scale.secondaryScale, - ) - .paddingSymmetric(vertical: 4), - SelectableText( - widget.contact.identityPublicKey.value.toVeilid().toString(), - style: textTheme.labelMedium! - .copyWith(fontFamily: 'Source Code Pro')) - .noEditDecoratorLabel( - context, - translate('contact_form.form_fingerprint'), - scale: scale.secondaryScale, - ) - .paddingSymmetric(vertical: 4), - Divider(color: border).paddingLTRB(8, 0, 8, 8), + styledCard( + context: context, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row(children: [ + const Spacer(), + AvatarWidget( + name: _currentValueNickname.isNotEmpty + ? _currentValueNickname + : widget.contact.profile.name, + size: 128, + borderColor: border, + foregroundColor: scale.primaryScale.primaryText, + backgroundColor: scale.primaryScale.primary, + scaleConfig: scaleConfig, + textStyle: theme.textTheme.titleLarge! + .copyWith(fontSize: 64), + ).paddingLTRB(0, 0, 0, 16), + const Spacer() + ]), + SelectableText(widget.contact.profile.name, + style: textTheme.bodyLarge) + .noEditDecoratorLabel( + context, + translate('contact_form.form_name'), + ) + .paddingSymmetric(vertical: 4), + SelectableText(widget.contact.profile.pronouns, + style: textTheme.bodyLarge) + .noEditDecoratorLabel( + context, + translate('contact_form.form_pronouns'), + ) + .paddingSymmetric(vertical: 4), + Row(mainAxisSize: MainAxisSize.min, children: [ + _availabilityWidget( + widget.contact.profile.availability, + scale.primaryScale.appText), + SelectableText(widget.contact.profile.status, + style: textTheme.bodyMedium) + .paddingSymmetric(horizontal: 8) + ]) + .noEditDecoratorLabel( + context, + translate('contact_form.form_status'), + ) + .paddingSymmetric(vertical: 4), + SelectableText(widget.contact.profile.about, + minLines: 1, + maxLines: 8, + style: textTheme.bodyMedium) + .noEditDecoratorLabel( + context, + translate('contact_form.form_about'), + ) + .paddingSymmetric(vertical: 4), + SelectableText( + widget.contact.identityPublicKey.value + .toVeilid() + .toString(), + style: textTheme.bodyMedium! + .copyWith(fontFamily: 'Source Code Pro')) + .noEditDecoratorLabel( + context, + translate('contact_form.form_fingerprint'), + ) + .paddingSymmetric(vertical: 4), + ]).paddingAll(16)) + .paddingLTRB(0, 0, 0, 16), FormBuilderTextField( - //autofocus: true, name: EditContactForm.formFieldNickname, - initialValue: widget.contact.nickname, + initialValue: _currentValueNickname, + onChanged: (x) { + setState(() { + _currentValueNickname = x ?? ''; + }); + }, decoration: InputDecoration( labelText: translate('contact_form.form_nickname')), maxLength: 64, @@ -136,14 +194,16 @@ class _EditContactFormState extends State { ), FormBuilderCheckbox( name: EditContactForm.formFieldShowAvailability, - initialValue: widget.contact.showAvailability, + initialValue: _savedValue.showAvailability, side: BorderSide(color: scale.primaryScale.border, width: 2), + checkColor: scale.primaryScale.borderText, + activeColor: scale.primaryScale.border, title: Text(translate('contact_form.form_show_availability'), style: textTheme.labelMedium), ), FormBuilderTextField( name: EditContactForm.formFieldNotes, - initialValue: widget.contact.notes, + initialValue: _savedValue.notes, minLines: 1, maxLines: 8, maxLength: 1024, @@ -152,24 +212,38 @@ class _EditContactFormState extends State { textInputAction: TextInputAction.newline, ), ElevatedButton( - onPressed: widget.onSubmit == null - ? null - : () async { - if (widget.formKey.currentState?.saveAndValidate() ?? - false) { - await widget.onSubmit!(widget.formKey); - } - }, + onPressed: _isModified ? _doSubmit : null, child: Row(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), - Text((widget.onSubmit == null) - ? translate('contact_form.save') - : translate('contact_form.save')) - .paddingLTRB(0, 0, 4, 0) + Text(widget.submitText).paddingLTRB(0, 0, 4, 0) ]), - ).paddingSymmetric(vertical: 4).alignAtCenterRight(), + ).paddingSymmetric(vertical: 4).alignAtCenter(), ], ), ); } + + void _doSubmit() { + final onSubmit = widget.onSubmit; + if (_formKey.currentState?.saveAndValidate() ?? false) { + singleFuture((this, _kDoSubmitEditContact), () async { + final updatedContactSpec = _makeContactSpec(); + final saved = await onSubmit(updatedContactSpec); + if (saved) { + setState(() { + _savedValue = updatedContactSpec; + }); + _onChanged(); + } + }); + } + } + + @override + Widget build(BuildContext context) => _editContactForm(context); + + /////////////////////////////////////////////////////////////////////////// + late ContactSpec _savedValue; + late String _currentValueNickname; + bool _isModified = false; } diff --git a/lib/layout/home/drawer_menu/drawer_menu.dart b/lib/layout/home/drawer_menu/drawer_menu.dart index 0821bbb..b56d437 100644 --- a/lib/layout/home/drawer_menu/drawer_menu.dart +++ b/lib/layout/home/drawer_menu/drawer_menu.dart @@ -9,12 +9,13 @@ import 'package:go_router/go_router.dart'; import 'package:veilid_support/veilid_support.dart'; import '../../../account_manager/account_manager.dart'; -import '../../../proto/proto.dart' as proto; import '../../../theme/theme.dart'; import '../../../tools/tools.dart'; import '../../../veilid_processor/veilid_processor.dart'; import 'menu_item_widget.dart'; +const _scaleKind = ScaleKind.secondary; + class DrawerMenu extends StatefulWidget { const DrawerMenu({super.key}); @@ -40,7 +41,7 @@ class _DrawerMenuState extends State { } void _doEditClick(TypedKey superIdentityRecordKey, - proto.Account existingAccount, OwnedDHTRecordPointer accountRecord) { + AccountSpec existingAccount, OwnedDHTRecordPointer accountRecord) { singleFuture(this, () async { await GoRouterHelper(context).push('/edit_account', extra: [superIdentityRecordKey, existingAccount, accountRecord]); @@ -58,45 +59,6 @@ class _DrawerMenuState extends State { borderRadius: BorderRadius.circular(borderRadius))), child: child); - Widget _makeAvatarWidget({ - required String name, - required double size, - required Color borderColor, - required Color foregroundColor, - required Color backgroundColor, - required ScaleConfig scaleConfig, - required TextStyle textStyle, - ImageProvider? imageProvider, - }) { - final abbrev = name.split(' ').map((s) => s.isEmpty ? '' : s[0]).join(); - late final String shortname; - if (abbrev.length >= 3) { - shortname = abbrev[0] + abbrev[1] + abbrev[abbrev.length - 1]; - } else { - shortname = abbrev; - } - - return Container( - height: size, - width: size, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: scaleConfig.preferBorders - ? Border.all( - color: borderColor, - width: 2 * (size ~/ 32 + 1), - strokeAlign: BorderSide.strokeAlignOutside) - : null, - color: Colors.blue, - ), - child: AvatarImage( - //size: 32, - backgroundImage: imageProvider, - backgroundColor: backgroundColor, - foregroundColor: foregroundColor, - child: Text(shortname, style: textStyle))); - } - Widget _makeAccountWidget( {required String name, required bool selected, @@ -173,6 +135,7 @@ class _DrawerMenuState extends State { footerButtonIconColor: border, footerButtonIconHoverColor: hoverBackground, footerButtonIconFocusColor: activeBackground, + minHeight: 48, )); } @@ -184,6 +147,7 @@ class _DrawerMenuState extends State { final theme = Theme.of(context); final scaleScheme = theme.extension()!; final scaleConfig = theme.extension()!; + final scale = scaleScheme.scale(_scaleKind); final loggedInAccounts = []; final loggedOutAccounts = []; @@ -197,9 +161,6 @@ class _DrawerMenuState extends State { final avAccountRecordState = perAccountState?.avAccountRecordState; if (perAccountState != null && avAccountRecordState != null) { // Account is logged in - final scale = scaleConfig.useVisualIndicators - ? theme.extension()!.primaryScale - : theme.extension()!.tertiaryScale; final loggedInAccount = avAccountRecordState.when( data: (value) => _makeAccountWidget( name: value.profile.name, @@ -213,7 +174,7 @@ class _DrawerMenuState extends State { footerCallback: () { _doEditClick( superIdentityRecordKey, - value, + AccountSpec.fromProto(value), perAccountState.accountInfo.userLogin!.accountRecordInfo .accountRecord); }), @@ -311,13 +272,14 @@ class _DrawerMenuState extends State { Widget _getBottomButtons() { final theme = Theme.of(context); - final scale = theme.extension()!; + final scaleScheme = theme.extension()!; final scaleConfig = theme.extension()!; + final scale = scaleScheme.scale(_scaleKind); final settingsButton = _getButton( icon: const Icon(Icons.settings), tooltip: translate('menu.settings_tooltip'), - scale: scale.tertiaryScale, + scale: scale, scaleConfig: scaleConfig, onPressed: () async { await GoRouterHelper(context).push('/settings'); @@ -326,7 +288,7 @@ class _DrawerMenuState extends State { final addButton = _getButton( icon: const Icon(Icons.add), tooltip: translate('menu.add_account_tooltip'), - scale: scale.tertiaryScale, + scale: scale, scaleConfig: scaleConfig, onPressed: () async { await GoRouterHelper(context).push('/new_account'); @@ -340,8 +302,9 @@ class _DrawerMenuState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final scale = theme.extension()!; + final scaleScheme = theme.extension()!; final scaleConfig = theme.extension()!; + final scale = scaleScheme.scale(_scaleKind); //final textTheme = theme.textTheme; final localAccounts = context.watch().state; final perAccountCollectionBlocMapState = @@ -351,8 +314,8 @@ class _DrawerMenuState extends State { begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - scale.tertiaryScale.border, - scale.tertiaryScale.subtleBorder, + scale.border, + scale.subtleBorder, ]); return DecoratedBox( @@ -360,34 +323,35 @@ class _DrawerMenuState extends State { shadows: [ if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) BoxShadow( - color: scale.tertiaryScale.primary.darken(80), + color: scale.primary.darken(60), spreadRadius: 2, ) else if (scaleConfig.useVisualIndicators && scaleConfig.preferBorders) BoxShadow( - color: scale.tertiaryScale.border, + color: scale.border, spreadRadius: 2, ) else BoxShadow( - color: scale.tertiaryScale.primary.darken(40), - blurRadius: 6, + color: scale.appBackground.darken(60).withAlpha(0x3F), + blurRadius: 16, + spreadRadius: 2, offset: const Offset( 0, - 4, + 2, ), ), ], gradient: scaleConfig.useVisualIndicators ? null : gradient, color: scaleConfig.useVisualIndicators ? (scaleConfig.preferBorders - ? scale.tertiaryScale.appBackground - : scale.tertiaryScale.subtleBorder) + ? scale.appBackground + : scale.subtleBorder) : null, shape: RoundedRectangleBorder( side: scaleConfig.preferBorders - ? BorderSide(color: scale.tertiaryScale.primary, width: 2) + ? BorderSide(color: scale.primary, width: 2) : BorderSide.none, borderRadius: BorderRadius.only( topRight: Radius.circular(16 * scaleConfig.borderRadiusScale), @@ -399,31 +363,31 @@ class _DrawerMenuState extends State { child: ColorFiltered( colorFilter: ColorFilter.mode( theme.brightness == Brightness.light - ? scale.tertiaryScale.primary - : scale.tertiaryScale.border, + ? scale.primary + : scale.border, scaleConfig.preferBorders ? BlendMode.modulate : BlendMode.dst), child: Row(children: [ - SvgPicture.asset( - height: 48, - 'assets/images/icon.svg', - colorFilter: scaleConfig.useVisualIndicators - ? grayColorFilter - : null) - .paddingLTRB(0, 0, 16, 0), + // SvgPicture.asset( + // height: 48, + // 'assets/images/icon.svg', + // colorFilter: scaleConfig.useVisualIndicators + // ? grayColorFilter + // : null) + // .paddingLTRB(0, 0, 16, 0), SvgPicture.asset( height: 48, 'assets/images/title.svg', colorFilter: scaleConfig.useVisualIndicators ? grayColorFilter - : null), + : dodgeFilter), ]))), Text(translate('menu.accounts'), style: theme.textTheme.titleMedium!.copyWith( color: scaleConfig.preferBorders - ? scale.tertiaryScale.border - : scale.tertiaryScale.borderText)) + ? scale.border + : scale.borderText)) .paddingLTRB(0, 16, 0, 16), ListView( shrinkWrap: true, @@ -438,16 +402,16 @@ class _DrawerMenuState extends State { Text('${translate('menu.version')} $packageInfoVersion', style: theme.textTheme.labelMedium!.copyWith( color: scaleConfig.preferBorders - ? scale.tertiaryScale.hoverBorder - : scale.tertiaryScale.subtleBackground)), + ? scale.hoverBorder + : scale.subtleBackground)), const Spacer(), SignalStrengthMeterWidget( color: scaleConfig.preferBorders - ? scale.tertiaryScale.hoverBorder - : scale.tertiaryScale.subtleBackground, + ? scale.hoverBorder + : scale.subtleBackground, inactiveColor: scaleConfig.preferBorders - ? scale.tertiaryScale.border - : scale.tertiaryScale.elementBackground, + ? scale.border + : scale.elementBackground, ), ]) ]).paddingAll(16), diff --git a/lib/layout/home/drawer_menu/menu_item_widget.dart b/lib/layout/home/drawer_menu/menu_item_widget.dart index 8529411..a786010 100644 --- a/lib/layout/home/drawer_menu/menu_item_widget.dart +++ b/lib/layout/home/drawer_menu/menu_item_widget.dart @@ -22,39 +22,42 @@ class MenuItemWidget extends StatelessWidget { this.footerButtonIconHoverColor, this.footerButtonIconFocusColor, this.footerCallback, + this.minHeight = 0, super.key, }); @override Widget build(BuildContext context) => TextButton( - onPressed: callback, - style: TextButton.styleFrom(foregroundColor: foregroundColor).copyWith( - backgroundColor: WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.hovered)) { - return backgroundHoverColor; - } - if (states.contains(WidgetState.focused)) { - return backgroundFocusColor; - } - return backgroundColor; - }), - side: WidgetStateBorderSide.resolveWith((states) { - if (states.contains(WidgetState.hovered)) { - return borderColor != null - ? BorderSide(width: 2, color: borderHoverColor!) - : null; - } - if (states.contains(WidgetState.focused)) { - return borderColor != null - ? BorderSide(width: 2, color: borderFocusColor!) - : null; - } + onPressed: callback, + style: TextButton.styleFrom(foregroundColor: foregroundColor).copyWith( + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.hovered)) { + return backgroundHoverColor; + } + if (states.contains(WidgetState.focused)) { + return backgroundFocusColor; + } + return backgroundColor; + }), + side: WidgetStateBorderSide.resolveWith((states) { + if (states.contains(WidgetState.hovered)) { return borderColor != null - ? BorderSide(width: 2, color: borderColor!) + ? BorderSide(width: 2, color: borderHoverColor!) : null; - }), - shape: WidgetStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(borderRadius ?? 0)))), + } + if (states.contains(WidgetState.focused)) { + return borderColor != null + ? BorderSide(width: 2, color: borderFocusColor!) + : null; + } + return borderColor != null + ? BorderSide(width: 2, color: borderColor!) + : null; + }), + shape: WidgetStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadius ?? 0)))), + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: minHeight), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -81,7 +84,7 @@ class MenuItemWidget extends StatelessWidget { onPressed: footerCallback), ], ).paddingAll(2), - ); + )); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -106,7 +109,8 @@ class MenuItemWidget extends StatelessWidget { ..add(ColorProperty('borderColor', borderColor)) ..add(DoubleProperty('borderRadius', borderRadius)) ..add(ColorProperty('borderHoverColor', borderHoverColor)) - ..add(ColorProperty('borderFocusColor', borderFocusColor)); + ..add(ColorProperty('borderFocusColor', borderFocusColor)) + ..add(DoubleProperty('minHeight', minHeight)); } //////////////////////////////////////////////////////////////////////////// @@ -129,4 +133,5 @@ class MenuItemWidget extends StatelessWidget { final Color? footerButtonIconColor; final Color? footerButtonIconHoverColor; final Color? footerButtonIconFocusColor; + final double minHeight; } diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index f226717..3e8e98b 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -96,7 +96,7 @@ class HomeScreenState extends State ), Row(mainAxisSize: MainAxisSize.min, children: [ StatefulBuilder( - builder: (context, setState) => Checkbox.adaptive( + builder: (context, setState) => Checkbox( value: displayBetaWarning, onChanged: (value) { setState(() { @@ -213,7 +213,6 @@ class HomeScreenState extends State style: theme.textTheme.bodySmall!, child: ZoomDrawer( controller: _zoomDrawerController, - //menuBackgroundColor: Colors.transparent, menuScreen: Builder(builder: (context) { final zoomDrawer = ZoomDrawer.of(context); zoomDrawer!.stateNotifier.addListener(() { @@ -228,7 +227,7 @@ class HomeScreenState extends State child: Builder(builder: _buildAccountPageView)), borderRadius: 0, angle: 0, - mainScreenOverlayColor: theme.shadowColor.withAlpha(0x2F), + //mainScreenOverlayColor: theme.shadowColor.withAlpha(0x2F), openCurve: Curves.fastEaseInToSlowEaseOut, // duration: const Duration(milliseconds: 250), // reverseDuration: const Duration(milliseconds: 250), diff --git a/lib/notifications/views/notifications_preferences.dart b/lib/notifications/views/notifications_preferences.dart index 5967522..95d4a1e 100644 --- a/lib/notifications/views/notifications_preferences.dart +++ b/lib/notifications/views/notifications_preferences.dart @@ -130,6 +130,8 @@ Widget buildSettingsPageNotificationPreferences( FormBuilderCheckbox( name: formFieldDisplayBetaWarning, side: BorderSide(color: scale.primaryScale.border, width: 2), + checkColor: scale.primaryScale.borderText, + activeColor: scale.primaryScale.border, title: Text(translate('settings_page.display_beta_warning'), style: textTheme.labelMedium), initialValue: notificationsPreference.displayBetaWarning, @@ -146,6 +148,8 @@ Widget buildSettingsPageNotificationPreferences( FormBuilderCheckbox( name: formFieldEnableBadge, side: BorderSide(color: scale.primaryScale.border, width: 2), + checkColor: scale.primaryScale.borderText, + activeColor: scale.primaryScale.border, title: Text(translate('settings_page.enable_badge'), style: textTheme.labelMedium), initialValue: notificationsPreference.enableBadge, @@ -161,6 +165,8 @@ Widget buildSettingsPageNotificationPreferences( FormBuilderCheckbox( name: formFieldEnableNotifications, side: BorderSide(color: scale.primaryScale.border, width: 2), + checkColor: scale.primaryScale.borderText, + activeColor: scale.primaryScale.border, title: Text(translate('settings_page.enable_notifications'), style: textTheme.labelMedium), initialValue: notificationsPreference.enableNotifications, diff --git a/lib/proto/veilidchat.pb.dart b/lib/proto/veilidchat.pb.dart index 3947baf..245f9f3 100644 --- a/lib/proto/veilidchat.pb.dart +++ b/lib/proto/veilidchat.pb.dart @@ -3024,6 +3024,7 @@ class ContactInvitationRecord extends $pb.GeneratedMessage { $fixnum.Int64? expiration, $core.List<$core.int>? invitation, $core.String? message, + $core.String? recipient, }) { final $result = create(); if (contactRequestInbox != null) { @@ -3047,6 +3048,9 @@ class ContactInvitationRecord extends $pb.GeneratedMessage { if (message != null) { $result.message = message; } + if (recipient != null) { + $result.recipient = recipient; + } return $result; } ContactInvitationRecord._() : super(); @@ -3061,6 +3065,7 @@ class ContactInvitationRecord extends $pb.GeneratedMessage { ..a<$fixnum.Int64>(5, _omitFieldNames ? '' : 'expiration', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO) ..a<$core.List<$core.int>>(6, _omitFieldNames ? '' : 'invitation', $pb.PbFieldType.OY) ..aOS(7, _omitFieldNames ? '' : 'message') + ..aOS(8, _omitFieldNames ? '' : 'recipient') ..hasRequiredFields = false ; @@ -3162,6 +3167,16 @@ class ContactInvitationRecord extends $pb.GeneratedMessage { $core.bool hasMessage() => $_has(6); @$pb.TagNumber(7) void clearMessage() => clearField(7); + + /// The recipient sent along with the invitation + @$pb.TagNumber(8) + $core.String get recipient => $_getSZ(7); + @$pb.TagNumber(8) + set recipient($core.String v) { $_setString(7, v); } + @$pb.TagNumber(8) + $core.bool hasRecipient() => $_has(7); + @$pb.TagNumber(8) + void clearRecipient() => clearField(8); } diff --git a/lib/proto/veilidchat.pbjson.dart b/lib/proto/veilidchat.pbjson.dart index e102d40..81bf741 100644 --- a/lib/proto/veilidchat.pbjson.dart +++ b/lib/proto/veilidchat.pbjson.dart @@ -628,6 +628,7 @@ const ContactInvitationRecord$json = { {'1': 'expiration', '3': 5, '4': 1, '5': 4, '10': 'expiration'}, {'1': 'invitation', '3': 6, '4': 1, '5': 12, '10': 'invitation'}, {'1': 'message', '3': 7, '4': 1, '5': 9, '10': 'message'}, + {'1': 'recipient', '3': 8, '4': 1, '5': 9, '10': 'recipient'}, ], }; @@ -639,5 +640,6 @@ final $typed_data.Uint8List contactInvitationRecordDescriptor = $convert.base64D 'NlY3JldBgDIAEoCzIRLnZlaWxpZC5DcnlwdG9LZXlSDHdyaXRlclNlY3JldBJTCh1sb2NhbF9j' 'b252ZXJzYXRpb25fcmVjb3JkX2tleRgEIAEoCzIQLnZlaWxpZC5UeXBlZEtleVIabG9jYWxDb2' '52ZXJzYXRpb25SZWNvcmRLZXkSHgoKZXhwaXJhdGlvbhgFIAEoBFIKZXhwaXJhdGlvbhIeCgpp' - 'bnZpdGF0aW9uGAYgASgMUgppbnZpdGF0aW9uEhgKB21lc3NhZ2UYByABKAlSB21lc3NhZ2U='); + 'bnZpdGF0aW9uGAYgASgMUgppbnZpdGF0aW9uEhgKB21lc3NhZ2UYByABKAlSB21lc3NhZ2USHA' + 'oJcmVjaXBpZW50GAggASgJUglyZWNpcGllbnQ='); diff --git a/lib/proto/veilidchat.proto b/lib/proto/veilidchat.proto index 0d4ca0a..e669959 100644 --- a/lib/proto/veilidchat.proto +++ b/lib/proto/veilidchat.proto @@ -478,4 +478,6 @@ message ContactInvitationRecord { bytes invitation = 6; // The message sent along with the invitation string message = 7; + // The recipient sent along with the invitation + string recipient = 8; } \ No newline at end of file diff --git a/lib/router/cubits/router_cubit.dart b/lib/router/cubits/router_cubit.dart index 974319a..48bf95f 100644 --- a/lib/router/cubits/router_cubit.dart +++ b/lib/router/cubits/router_cubit.dart @@ -11,7 +11,6 @@ import 'package:veilid_support/veilid_support.dart'; import '../../../account_manager/account_manager.dart'; import '../../layout/layout.dart'; -import '../../proto/proto.dart' as proto; import '../../settings/settings.dart'; import '../../tools/tools.dart'; import '../../veilid_processor/views/developer.dart'; @@ -43,10 +42,8 @@ class RouterCubit extends Cubit { case AccountRepositoryChange.localAccounts: emit(state.copyWith( hasAnyAccount: accountRepository.getLocalAccounts().isNotEmpty)); - break; case AccountRepositoryChange.userLogins: case AccountRepositoryChange.activeLocalAccount: - break; } }); } @@ -72,7 +69,7 @@ class RouterCubit extends Cubit { final extra = state.extra! as List; return EditAccountPage( superIdentityRecordKey: extra[0]! as TypedKey, - existingAccount: extra[1]! as proto.Account, + initialValue: extra[1]! as AccountSpec, accountRecord: extra[2]! as OwnedDHTRecordPointer, ); }, diff --git a/lib/settings/settings_page.dart b/lib/settings/settings_page.dart index 94606aa..05ba514 100644 --- a/lib/settings/settings_page.dart +++ b/lib/settings/settings_page.dart @@ -45,6 +45,7 @@ class SettingsPageState extends State { child: FormBuilder( key: _formKey, child: ListView( + padding: const EdgeInsets.all(8), children: [ buildSettingsPageColorPreferences( context: context, @@ -56,6 +57,6 @@ class SettingsPageState extends State { context: context, onChanged: () => setState(() {})), ].map((x) => x.paddingLTRB(0, 0, 0, 8)).toList(), ), - ).paddingSymmetric(horizontal: 24, vertical: 16), + ).paddingSymmetric(horizontal: 8, vertical: 8), ))); } diff --git a/lib/theme/models/chat_theme.dart b/lib/theme/models/chat_theme.dart index a7039e5..cd0b9ce 100644 --- a/lib/theme/models/chat_theme.dart +++ b/lib/theme/models/chat_theme.dart @@ -14,7 +14,7 @@ ChatTheme makeChatTheme( secondaryColor: scaleConfig.preferBorders ? scale.secondaryScale.calloutText : scale.secondaryScale.calloutBackground, - backgroundColor: scale.grayScale.appBackground, + backgroundColor: scale.grayScale.appBackground.withAlpha(192), messageBorderRadius: scaleConfig.borderRadiusScale * 16, bubbleBorderSide: scaleConfig.preferBorders ? BorderSide( diff --git a/lib/theme/models/contrast_generator.dart b/lib/theme/models/contrast_generator.dart index b71ebea..861b052 100644 --- a/lib/theme/models/contrast_generator.dart +++ b/lib/theme/models/contrast_generator.dart @@ -1,9 +1,6 @@ import 'package:flutter/material.dart'; import 'radix_generator.dart'; -import 'scale_theme/scale_color.dart'; -import 'scale_theme/scale_input_decorator_theme.dart'; -import 'scale_theme/scale_scheme.dart'; import 'scale_theme/scale_theme.dart'; ScaleColor _contrastScaleColor( @@ -29,6 +26,7 @@ ScaleColor _contrastScaleColor( primaryText: front, borderText: back, dialogBorder: front, + dialogBorderText: back, calloutBackground: front, calloutText: back, ); @@ -246,7 +244,7 @@ ThemeData contrastGenerator({ TextTheme? customTextTheme, }) { final textTheme = customTextTheme ?? makeRadixTextTheme(brightness); - final scaleScheme = _contrastScaleScheme( + final scheme = _contrastScaleScheme( brightness: brightness, primaryFront: primaryFront, primaryBack: primaryBack, @@ -259,55 +257,51 @@ ThemeData contrastGenerator({ errorFront: errorFront, errorBack: errorBack, ); - final colorScheme = scaleScheme.toColorScheme( - brightness, - ); - final scaleTheme = ScaleTheme( - textTheme: textTheme, scheme: scaleScheme, config: scaleConfig); - final baseThemeData = ThemeData.from( - colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true); + final scaleTheme = + ScaleTheme(textTheme: textTheme, scheme: scheme, config: scaleConfig); + + final baseThemeData = scaleTheme.toThemeData(brightness); + + final elevatedButtonTheme = ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: scheme.primaryScale.elementBackground, + foregroundColor: scheme.primaryScale.appText, + disabledBackgroundColor: + scheme.grayScale.elementBackground.withAlpha(0x7F), + disabledForegroundColor: scheme.grayScale.appText.withAlpha(0x7F), + shape: RoundedRectangleBorder( + side: BorderSide(color: scheme.primaryScale.border), + borderRadius: + BorderRadius.circular(8 * scaleConfig.borderRadiusScale))) + .copyWith(side: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return BorderSide(color: scheme.grayScale.border.withAlpha(0x7F)); + } else if (states.contains(WidgetState.pressed)) { + return BorderSide( + color: scheme.primaryScale.border, + strokeAlign: BorderSide.strokeAlignOutside); + } else if (states.contains(WidgetState.hovered)) { + return BorderSide(color: scheme.primaryScale.hoverBorder); + } else if (states.contains(WidgetState.focused)) { + return BorderSide(color: scheme.primaryScale.hoverBorder, width: 2); + } + return BorderSide(color: scheme.primaryScale.border); + }))); + final themeData = baseThemeData.copyWith( - appBarTheme: baseThemeData.appBarTheme.copyWith( - backgroundColor: scaleScheme.primaryScale.border, - foregroundColor: scaleScheme.primaryScale.borderText), - bottomSheetTheme: baseThemeData.bottomSheetTheme.copyWith( - elevation: 0, - modalElevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16 * scaleConfig.borderRadiusScale), - topRight: - Radius.circular(16 * scaleConfig.borderRadiusScale)))), - canvasColor: scaleScheme.primaryScale.subtleBackground, - chipTheme: baseThemeData.chipTheme.copyWith( - backgroundColor: scaleScheme.primaryScale.elementBackground, - selectedColor: scaleScheme.primaryScale.activeElementBackground, - surfaceTintColor: scaleScheme.primaryScale.hoverElementBackground, - checkmarkColor: scaleScheme.primaryScale.border, - side: BorderSide(color: scaleScheme.primaryScale.border)), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - backgroundColor: scaleScheme.primaryScale.elementBackground, - foregroundColor: scaleScheme.primaryScale.appText, - disabledBackgroundColor: scaleScheme.grayScale.elementBackground, - disabledForegroundColor: scaleScheme.grayScale.appText, - shape: RoundedRectangleBorder( - side: BorderSide(color: scaleScheme.primaryScale.border), - borderRadius: - BorderRadius.circular(8 * scaleConfig.borderRadiusScale))), - ), + // chipTheme: baseThemeData.chipTheme.copyWith( + // backgroundColor: scaleScheme.primaryScale.elementBackground, + // selectedColor: scaleScheme.primaryScale.activeElementBackground, + // surfaceTintColor: scaleScheme.primaryScale.hoverElementBackground, + // checkmarkColor: scaleScheme.primaryScale.border, + // side: BorderSide(color: scaleScheme.primaryScale.border)), + elevatedButtonTheme: elevatedButtonTheme, textSelectionTheme: TextSelectionThemeData( - cursorColor: scaleScheme.primaryScale.appText, - selectionColor: scaleScheme.primaryScale.appText.withAlpha(0x7F), - selectionHandleColor: scaleScheme.primaryScale.appText), - inputDecorationTheme: - ScaleInputDecoratorTheme(scaleScheme, scaleConfig, textTheme), - extensions: >[ - scaleScheme, - scaleConfig, - scaleTheme - ]); + cursorColor: scheme.primaryScale.appText, + selectionColor: scheme.primaryScale.appText.withAlpha(0x7F), + selectionHandleColor: scheme.primaryScale.appText), + extensions: >[scheme, scaleConfig, scaleTheme]); return themeData; } diff --git a/lib/theme/models/radix_generator.dart b/lib/theme/models/radix_generator.dart index 3dac7bc..a5c5f87 100644 --- a/lib/theme/models/radix_generator.dart +++ b/lib/theme/models/radix_generator.dart @@ -5,9 +5,6 @@ import 'package:flutter/material.dart'; import 'package:radix_colors/radix_colors.dart'; import '../../tools/tools.dart'; -import 'scale_theme/scale_color.dart'; -import 'scale_theme/scale_input_decorator_theme.dart'; -import 'scale_theme/scale_scheme.dart'; import 'scale_theme/scale_theme.dart'; enum RadixThemeColor { @@ -291,6 +288,7 @@ extension ToScaleColor on RadixColor { primaryText: scaleExtra.foregroundText, borderText: step12, dialogBorder: step9, + dialogBorderText: scaleExtra.foregroundText, calloutBackground: step9, calloutText: scaleExtra.foregroundText, ); @@ -609,7 +607,6 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) { final textTheme = makeRadixTextTheme(brightness); final radix = _radixScheme(brightness, themeColor); final scaleScheme = radix.toScale(); - final colorScheme = scaleScheme.toColorScheme(brightness); final scaleConfig = ScaleConfig( useVisualIndicators: false, preferBorders: false, @@ -619,68 +616,7 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) { final scaleTheme = ScaleTheme( textTheme: textTheme, scheme: scaleScheme, config: scaleConfig); - final baseThemeData = ThemeData.from( - colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true); - - final themeData = baseThemeData.copyWith( - scrollbarTheme: baseThemeData.scrollbarTheme.copyWith( - thumbColor: WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.pressed)) { - return scaleScheme.primaryScale.border; - } else if (states.contains(WidgetState.hovered)) { - return scaleScheme.primaryScale.hoverBorder; - } - return scaleScheme.primaryScale.subtleBorder; - }), trackColor: WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.pressed)) { - return scaleScheme.primaryScale.activeElementBackground; - } else if (states.contains(WidgetState.hovered)) { - return scaleScheme.primaryScale.hoverElementBackground; - } - return scaleScheme.primaryScale.elementBackground; - }), trackBorderColor: WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.pressed)) { - return scaleScheme.primaryScale.subtleBorder; - } else if (states.contains(WidgetState.hovered)) { - return scaleScheme.primaryScale.subtleBorder; - } - return scaleScheme.primaryScale.subtleBorder; - })), - appBarTheme: baseThemeData.appBarTheme.copyWith( - backgroundColor: scaleScheme.primaryScale.border, - foregroundColor: scaleScheme.primaryScale.borderText), - bottomSheetTheme: baseThemeData.bottomSheetTheme.copyWith( - elevation: 0, - modalElevation: 0, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16)))), - canvasColor: scaleScheme.primaryScale.subtleBackground, - chipTheme: baseThemeData.chipTheme.copyWith( - backgroundColor: scaleScheme.primaryScale.elementBackground, - selectedColor: scaleScheme.primaryScale.activeElementBackground, - surfaceTintColor: scaleScheme.primaryScale.hoverElementBackground, - checkmarkColor: scaleScheme.primaryScale.primary, - side: BorderSide(color: scaleScheme.primaryScale.border)), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - backgroundColor: scaleScheme.primaryScale.elementBackground, - foregroundColor: scaleScheme.primaryScale.primary, - disabledBackgroundColor: scaleScheme.grayScale.elementBackground, - disabledForegroundColor: scaleScheme.grayScale.primary, - shape: RoundedRectangleBorder( - side: BorderSide(color: scaleScheme.primaryScale.border), - borderRadius: - BorderRadius.circular(8 * scaleConfig.borderRadiusScale))), - ), - inputDecorationTheme: - ScaleInputDecoratorTheme(scaleScheme, scaleConfig, textTheme), - extensions: >[ - scaleScheme, - scaleConfig, - scaleTheme - ]); + final themeData = scaleTheme.toThemeData(brightness); return themeData; } diff --git a/lib/theme/models/scale_theme/scale_color.dart b/lib/theme/models/scale_theme/scale_color.dart index 244f6a3..e50a01b 100644 --- a/lib/theme/models/scale_theme/scale_color.dart +++ b/lib/theme/models/scale_theme/scale_color.dart @@ -17,6 +17,7 @@ class ScaleColor { required this.primaryText, required this.borderText, required this.dialogBorder, + required this.dialogBorderText, required this.calloutBackground, required this.calloutText, }); @@ -36,6 +37,7 @@ class ScaleColor { Color primaryText; Color borderText; Color dialogBorder; + Color dialogBorderText; Color calloutBackground; Color calloutText; @@ -55,6 +57,7 @@ class ScaleColor { Color? foregroundText, Color? borderText, Color? dialogBorder, + Color? dialogBorderText, Color? calloutBackground, Color? calloutText, }) => @@ -76,6 +79,7 @@ class ScaleColor { primaryText: foregroundText ?? this.primaryText, borderText: borderText ?? this.borderText, dialogBorder: dialogBorder ?? this.dialogBorder, + dialogBorderText: dialogBorderText ?? this.dialogBorderText, calloutBackground: calloutBackground ?? this.calloutBackground, calloutText: calloutText ?? this.calloutText); @@ -112,6 +116,9 @@ class ScaleColor { const Color(0x00000000), dialogBorder: Color.lerp(a.dialogBorder, b.dialogBorder, t) ?? const Color(0x00000000), + dialogBorderText: + Color.lerp(a.dialogBorderText, b.dialogBorderText, t) ?? + const Color(0x00000000), calloutBackground: Color.lerp(a.calloutBackground, b.calloutBackground, t) ?? const Color(0x00000000), diff --git a/lib/theme/models/scale_theme/scale_custom_dropdown_theme.dart b/lib/theme/models/scale_theme/scale_custom_dropdown_theme.dart index 94764a5..692ec85 100644 --- a/lib/theme/models/scale_theme/scale_custom_dropdown_theme.dart +++ b/lib/theme/models/scale_theme/scale_custom_dropdown_theme.dart @@ -1,7 +1,6 @@ import 'package:animated_custom_dropdown/custom_dropdown.dart'; import 'package:flutter/material.dart'; -import 'scale_scheme.dart'; import 'scale_theme.dart'; class ScaleCustomDropdownTheme { diff --git a/lib/theme/models/scale_theme/scale_input_decorator_theme.dart b/lib/theme/models/scale_theme/scale_input_decorator_theme.dart index 3af1f15..1fb26a4 100644 --- a/lib/theme/models/scale_theme/scale_input_decorator_theme.dart +++ b/lib/theme/models/scale_theme/scale_input_decorator_theme.dart @@ -1,36 +1,61 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'scale_scheme.dart'; import 'scale_theme.dart'; class ScaleInputDecoratorTheme extends InputDecorationTheme { ScaleInputDecoratorTheme( this._scaleScheme, ScaleConfig scaleConfig, this._textTheme) - : super( - border: OutlineInputBorder( - borderSide: BorderSide(color: _scaleScheme.primaryScale.border), - borderRadius: - BorderRadius.circular(8 * scaleConfig.borderRadiusScale)), - contentPadding: const EdgeInsets.all(8), - labelStyle: TextStyle( - color: _scaleScheme.primaryScale.subtleText.withAlpha(127)), - floatingLabelStyle: - TextStyle(color: _scaleScheme.primaryScale.subtleText), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: _scaleScheme.primaryScale.hoverBorder, width: 2), - borderRadius: - BorderRadius.circular(8 * scaleConfig.borderRadiusScale))); + : hintAlpha = scaleConfig.preferBorders ? 127 : 255, + super( + contentPadding: const EdgeInsets.all(8), + labelStyle: TextStyle(color: _scaleScheme.primaryScale.subtleText), + floatingLabelStyle: + TextStyle(color: _scaleScheme.primaryScale.subtleText), + border: OutlineInputBorder( + borderSide: BorderSide(color: _scaleScheme.primaryScale.border), + borderRadius: + BorderRadius.circular(8 * scaleConfig.borderRadiusScale)), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: _scaleScheme.primaryScale.border), + borderRadius: + BorderRadius.circular(8 * scaleConfig.borderRadiusScale)), + disabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: _scaleScheme.grayScale.border.withAlpha(0x7F)), + borderRadius: + BorderRadius.circular(8 * scaleConfig.borderRadiusScale)), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: _scaleScheme.errorScale.border), + borderRadius: + BorderRadius.circular(8 * scaleConfig.borderRadiusScale)), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: _scaleScheme.primaryScale.hoverBorder, width: 2), + borderRadius: + BorderRadius.circular(8 * scaleConfig.borderRadiusScale)), + hoverColor: + _scaleScheme.primaryScale.hoverElementBackground.withAlpha(0x7F), + filled: true, + focusedErrorBorder: OutlineInputBorder( + borderSide: + BorderSide(color: _scaleScheme.errorScale.border, width: 2), + borderRadius: + BorderRadius.circular(8 * scaleConfig.borderRadiusScale)), + ); final ScaleScheme _scaleScheme; final TextTheme _textTheme; + final int hintAlpha; + final int disabledAlpha = 127; @override TextStyle? get hintStyle => WidgetStateTextStyle.resolveWith((states) { if (states.contains(WidgetState.disabled)) { return TextStyle(color: _scaleScheme.grayScale.border); } - return TextStyle(color: _scaleScheme.primaryScale.border); + return TextStyle( + color: _scaleScheme.primaryScale.border.withAlpha(hintAlpha)); }); @override @@ -46,7 +71,7 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme { WidgetStateBorderSide.resolveWith((states) { if (states.contains(WidgetState.disabled)) { return BorderSide( - color: _scaleScheme.grayScale.border.withAlpha(127)); + color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha)); } if (states.contains(WidgetState.error)) { if (states.contains(WidgetState.hovered)) { @@ -71,7 +96,7 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme { BorderSide? get outlineBorder => WidgetStateBorderSide.resolveWith((states) { if (states.contains(WidgetState.disabled)) { return BorderSide( - color: _scaleScheme.grayScale.border.withAlpha(127)); + color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha)); } if (states.contains(WidgetState.error)) { if (states.contains(WidgetState.hovered)) { @@ -97,7 +122,7 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme { @override Color? get prefixIconColor => WidgetStateColor.resolveWith((states) { if (states.contains(WidgetState.disabled)) { - return _scaleScheme.primaryScale.primary.withAlpha(127); + return _scaleScheme.primaryScale.primary.withAlpha(disabledAlpha); } if (states.contains(WidgetState.error)) { return _scaleScheme.errorScale.primary; @@ -108,7 +133,7 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme { @override Color? get suffixIconColor => WidgetStateColor.resolveWith((states) { if (states.contains(WidgetState.disabled)) { - return _scaleScheme.primaryScale.primary.withAlpha(127); + return _scaleScheme.primaryScale.primary.withAlpha(disabledAlpha); } if (states.contains(WidgetState.error)) { return _scaleScheme.errorScale.primary; @@ -121,7 +146,39 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme { final textStyle = _textTheme.bodyLarge ?? const TextStyle(); if (states.contains(WidgetState.disabled)) { return textStyle.copyWith( - color: _scaleScheme.grayScale.border.withAlpha(127)); + color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha)); + } + if (states.contains(WidgetState.error)) { + if (states.contains(WidgetState.hovered)) { + return textStyle.copyWith( + color: _scaleScheme.errorScale.hoverBorder); + } + if (states.contains(WidgetState.focused)) { + return textStyle.copyWith( + color: _scaleScheme.errorScale.hoverBorder); + } + return textStyle.copyWith( + color: _scaleScheme.errorScale.subtleBorder); + } + if (states.contains(WidgetState.hovered)) { + return textStyle.copyWith( + color: _scaleScheme.primaryScale.hoverBorder); + } + if (states.contains(WidgetState.focused)) { + return textStyle.copyWith( + color: _scaleScheme.primaryScale.hoverBorder); + } + return textStyle.copyWith( + color: _scaleScheme.primaryScale.border.withAlpha(hintAlpha)); + }); + + @override + TextStyle? get floatingLabelStyle => + WidgetStateTextStyle.resolveWith((states) { + final textStyle = _textTheme.bodyLarge ?? const TextStyle(); + if (states.contains(WidgetState.disabled)) { + return textStyle.copyWith( + color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha)); } if (states.contains(WidgetState.error)) { if (states.contains(WidgetState.hovered)) { @@ -146,18 +203,14 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme { return textStyle.copyWith(color: _scaleScheme.primaryScale.border); }); - @override - TextStyle? get floatingLabelStyle => labelStyle; - @override TextStyle? get helperStyle => WidgetStateTextStyle.resolveWith((states) { final textStyle = _textTheme.bodySmall ?? const TextStyle(); if (states.contains(WidgetState.disabled)) { - return textStyle.copyWith( - color: _scaleScheme.grayScale.border.withAlpha(127)); + return textStyle.copyWith(color: _scaleScheme.grayScale.border); } return textStyle.copyWith( - color: _scaleScheme.secondaryScale.border.withAlpha(127)); + color: _scaleScheme.primaryScale.border.withAlpha(hintAlpha)); }); @override @@ -165,6 +218,14 @@ class ScaleInputDecoratorTheme extends InputDecorationTheme { final textStyle = _textTheme.bodySmall ?? const TextStyle(); return textStyle.copyWith(color: _scaleScheme.errorScale.primary); }); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(IntProperty('disabledAlpha', disabledAlpha)) + ..add(IntProperty('hintAlpha', hintAlpha)); + } } extension ScaleInputDecoratorThemeExt on ScaleTheme { diff --git a/lib/theme/models/scale_theme/scale_scheme.dart b/lib/theme/models/scale_theme/scale_scheme.dart index ac266bc..8c4a6b8 100644 --- a/lib/theme/models/scale_theme/scale_scheme.dart +++ b/lib/theme/models/scale_theme/scale_scheme.dart @@ -76,8 +76,8 @@ class ScaleScheme extends ThemeExtension { ColorScheme toColorScheme(Brightness brightness) => ColorScheme( brightness: brightness, - primary: primaryScale.primary, // reviewed - onPrimary: primaryScale.primaryText, // reviewed + primary: primaryScale.primary, + onPrimary: primaryScale.primaryText, // primaryContainer: primaryScale.hoverElementBackground, // onPrimaryContainer: primaryScale.subtleText, secondary: secondaryScale.primary, @@ -92,15 +92,12 @@ class ScaleScheme extends ThemeExtension { onError: errorScale.primaryText, // errorContainer: errorScale.hoverElementBackground, // onErrorContainer: errorScale.subtleText, - background: grayScale.appBackground, // reviewed - onBackground: grayScale.appText, // reviewed - surface: primaryScale.appBackground, // reviewed - onSurface: primaryScale.appText, // reviewed - surfaceVariant: secondaryScale.appBackground, + surface: primaryScale.appBackground, + onSurface: primaryScale.appText, onSurfaceVariant: secondaryScale.appText, outline: primaryScale.border, outlineVariant: secondaryScale.border, - shadow: primaryScale.primary.darken(80), + shadow: primaryScale.appBackground.darken(60), //scrim: primaryScale.background, // inverseSurface: primaryScale.subtleText, // onInverseSurface: primaryScale.subtleBackground, diff --git a/lib/theme/models/scale_theme/scale_theme.dart b/lib/theme/models/scale_theme/scale_theme.dart index d539c86..4bfc438 100644 --- a/lib/theme/models/scale_theme/scale_theme.dart +++ b/lib/theme/models/scale_theme/scale_theme.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import 'scale_input_decorator_theme.dart'; import 'scale_scheme.dart'; export 'scale_color.dart'; @@ -41,4 +42,86 @@ class ScaleTheme extends ThemeExtension { scheme: scheme.lerp(other.scheme, t), config: config.lerp(other.config, t)); } + + ThemeData toThemeData(Brightness brightness) { + final colorScheme = scheme.toColorScheme(brightness); + + final baseThemeData = ThemeData.from( + colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true); + + final elevatedButtonTheme = ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: scheme.primaryScale.elementBackground, + foregroundColor: scheme.primaryScale.appText, + disabledBackgroundColor: + scheme.grayScale.elementBackground.withAlpha(0x7F), + disabledForegroundColor: + scheme.grayScale.primary.withAlpha(0x7F), + shape: RoundedRectangleBorder( + side: BorderSide(color: scheme.primaryScale.border), + borderRadius: + BorderRadius.circular(8 * config.borderRadiusScale))) + .copyWith(side: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return BorderSide(color: scheme.grayScale.border.withAlpha(0x7F)); + } else if (states.contains(WidgetState.pressed)) { + return BorderSide( + color: scheme.primaryScale.border, + strokeAlign: BorderSide.strokeAlignOutside); + } else if (states.contains(WidgetState.hovered)) { + return BorderSide(color: scheme.primaryScale.hoverBorder); + } else if (states.contains(WidgetState.focused)) { + return BorderSide(color: scheme.primaryScale.hoverBorder, width: 2); + } + return BorderSide(color: scheme.primaryScale.border); + }))); + + final themeData = baseThemeData.copyWith( + scrollbarTheme: baseThemeData.scrollbarTheme.copyWith( + thumbColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.pressed)) { + return scheme.primaryScale.border; + } else if (states.contains(WidgetState.hovered)) { + return scheme.primaryScale.hoverBorder; + } + return scheme.primaryScale.subtleBorder; + }), trackColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.pressed)) { + return scheme.primaryScale.activeElementBackground; + } else if (states.contains(WidgetState.hovered)) { + return scheme.primaryScale.hoverElementBackground; + } + return scheme.primaryScale.elementBackground; + }), trackBorderColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.pressed)) { + return scheme.primaryScale.subtleBorder; + } else if (states.contains(WidgetState.hovered)) { + return scheme.primaryScale.subtleBorder; + } + return scheme.primaryScale.subtleBorder; + })), + appBarTheme: baseThemeData.appBarTheme.copyWith( + backgroundColor: scheme.primaryScale.border, + foregroundColor: scheme.primaryScale.borderText), + bottomSheetTheme: baseThemeData.bottomSheetTheme.copyWith( + elevation: 0, + modalElevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16 * config.borderRadiusScale), + topRight: Radius.circular(16 * config.borderRadiusScale)))), + canvasColor: scheme.primaryScale.subtleBackground, + chipTheme: baseThemeData.chipTheme.copyWith( + backgroundColor: scheme.primaryScale.elementBackground, + selectedColor: scheme.primaryScale.activeElementBackground, + surfaceTintColor: scheme.primaryScale.hoverElementBackground, + checkmarkColor: scheme.primaryScale.primary, + side: BorderSide(color: scheme.primaryScale.border)), + elevatedButtonTheme: elevatedButtonTheme, + inputDecorationTheme: + ScaleInputDecoratorTheme(scheme, config, textTheme), + extensions: >[scheme, config, this]); + + return themeData; + } } diff --git a/lib/theme/models/scale_theme/scale_tile_theme.dart b/lib/theme/models/scale_theme/scale_tile_theme.dart index e7339d1..da2c3cd 100644 --- a/lib/theme/models/scale_theme/scale_tile_theme.dart +++ b/lib/theme/models/scale_theme/scale_tile_theme.dart @@ -40,7 +40,10 @@ extension ScaleTileThemeExt on ScaleTheme { final shapeBorder = RoundedRectangleBorder( side: config.useVisualIndicators - ? BorderSide(width: 2, color: borderColor, strokeAlign: 0) + ? BorderSide( + width: 2, + color: borderColor, + ) : BorderSide.none, borderRadius: BorderRadius.circular(8 * config.borderRadiusScale)); diff --git a/lib/theme/models/scale_theme/scale_toast_theme.dart b/lib/theme/models/scale_theme/scale_toast_theme.dart index de310d4..61f119d 100644 --- a/lib/theme/models/scale_theme/scale_toast_theme.dart +++ b/lib/theme/models/scale_theme/scale_toast_theme.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'scale_scheme.dart'; import 'scale_theme.dart'; enum ScaleToastKind { @@ -35,14 +34,6 @@ extension ScaleToastThemeExt on ScaleTheme { ScaleToastTheme toastTheme(ScaleToastKind kind) { final toastScaleColor = scheme.scale(ScaleKind.tertiary); - Icon icon; - switch (kind) { - case ScaleToastKind.info: - icon = const Icon(Icons.info, size: 32); - case ScaleToastKind.error: - icon = const Icon(Icons.dangerous, size: 32); - } - final primaryColor = toastScaleColor.calloutText; final borderColor = toastScaleColor.border; final backgroundColor = config.useVisualIndicators @@ -54,6 +45,13 @@ extension ScaleToastThemeExt on ScaleTheme { final titleColor = config.useVisualIndicators ? toastScaleColor.calloutBackground : toastScaleColor.calloutText; + Icon icon; + switch (kind) { + case ScaleToastKind.info: + icon = Icon(Icons.info, size: 32, color: primaryColor); + case ScaleToastKind.error: + icon = Icon(Icons.dangerous, size: 32, color: primaryColor); + } return ScaleToastTheme( primaryColor: primaryColor, diff --git a/lib/theme/views/avatar_widget.dart b/lib/theme/views/avatar_widget.dart index 7a1f610..42bea11 100644 --- a/lib/theme/views/avatar_widget.dart +++ b/lib/theme/views/avatar_widget.dart @@ -5,7 +5,7 @@ import 'package:flutter/widgets.dart'; import '../theme.dart'; class AvatarWidget extends StatelessWidget { - AvatarWidget({ + const AvatarWidget({ required String name, required double size, required Color borderColor, @@ -38,15 +38,11 @@ class AvatarWidget extends StatelessWidget { height: _size, width: _size, decoration: BoxDecoration( - shape: BoxShape.circle, - border: _scaleConfig.useVisualIndicators - ? Border.all( - color: _borderColor, - width: 1 * (_size ~/ 32 + 1), - strokeAlign: BorderSide.strokeAlignOutside) - : null, - color: _borderColor, - ), + shape: BoxShape.circle, + border: Border.all( + color: _borderColor, + width: 1 * (_size ~/ 32 + 1), + strokeAlign: BorderSide.strokeAlignOutside)), child: AvatarImage( //size: 32, backgroundImage: _imageProvider, @@ -55,14 +51,15 @@ class AvatarWidget extends StatelessWidget { ? _foregroundColor : _backgroundColor, child: Text( - shortname, + shortname.isNotEmpty ? shortname : '?', + softWrap: false, style: _textStyle.copyWith( color: _scaleConfig.useVisualIndicators && !_scaleConfig.preferBorders ? _backgroundColor : _foregroundColor, ), - ))); + ).fit().paddingAll(_size / 16))); } //////////////////////////////////////////////////////////////////////////// diff --git a/lib/theme/views/slider_tile.dart b/lib/theme/views/slider_tile.dart index 9b6957a..d293fa6 100644 --- a/lib/theme/views/slider_tile.dart +++ b/lib/theme/views/slider_tile.dart @@ -125,16 +125,21 @@ class SliderTile extends StatelessWidget { child: ListTile( onTap: onTap, dense: true, - visualDensity: const VisualDensity(vertical: -4), + visualDensity: + const VisualDensity(horizontal: -4, vertical: -4), title: Text( title, overflow: TextOverflow.fade, softWrap: false, ), subtitle: subtitle.isNotEmpty ? Text(subtitle) : null, + minTileHeight: 48, iconColor: scaleTileTheme.textColor, textColor: scaleTileTheme.textColor, - leading: FittedBox(child: leading), - trailing: FittedBox(child: trailing)))))); + leading: + leading != null ? FittedBox(child: leading) : null, + trailing: trailing != null + ? FittedBox(child: trailing) + : null))))); } } diff --git a/lib/theme/views/styled_alert.dart b/lib/theme/views/styled_alert.dart index 39fa6f2..82218e8 100644 --- a/lib/theme/views/styled_alert.dart +++ b/lib/theme/views/styled_alert.dart @@ -94,16 +94,11 @@ Future showErrorModal( {required BuildContext context, required String title, required String text}) async { - // final theme = Theme.of(context); - // final scale = theme.extension()!; - // final scaleConfig = theme.extension()!; - await Alert( context: context, style: _alertStyle(context), useRootNavigator: false, type: AlertType.error, - //style: AlertStyle(), title: title, desc: text, buttons: [ @@ -122,10 +117,6 @@ Future showErrorModal( ), ) ], - - //backgroundColor: Colors.black, - //titleColor: Colors.white, - //textColor: Colors.white, ).show(); } @@ -144,16 +135,11 @@ Future showWarningModal( {required BuildContext context, required String title, required String text}) async { - // final theme = Theme.of(context); - // final scale = theme.extension()!; - // final scaleConfig = theme.extension()!; - await Alert( context: context, style: _alertStyle(context), useRootNavigator: false, type: AlertType.warning, - //style: AlertStyle(), title: title, desc: text, buttons: [ @@ -172,10 +158,6 @@ Future showWarningModal( ), ) ], - - //backgroundColor: Colors.black, - //titleColor: Colors.white, - //textColor: Colors.white, ).show(); } @@ -183,16 +165,11 @@ Future showWarningWidgetModal( {required BuildContext context, required String title, required Widget child}) async { - // final theme = Theme.of(context); - // final scale = theme.extension()!; - // final scaleConfig = theme.extension()!; - await Alert( context: context, style: _alertStyle(context), useRootNavigator: false, type: AlertType.warning, - //style: AlertStyle(), title: title, content: child, buttons: [ @@ -211,10 +188,6 @@ Future showWarningWidgetModal( ), ) ], - - //backgroundColor: Colors.black, - //titleColor: Colors.white, - //textColor: Colors.white, ).show(); } @@ -222,10 +195,6 @@ Future showConfirmModal( {required BuildContext context, required String title, required String text}) async { - // final theme = Theme.of(context); - // final scale = theme.extension()!; - // final scaleConfig = theme.extension()!; - var confirm = false; await Alert( @@ -266,10 +235,6 @@ Future showConfirmModal( ), ) ], - - //backgroundColor: Colors.black, - //titleColor: Colors.white, - //textColor: Colors.white, ).show(); return confirm; diff --git a/lib/theme/views/styled_dialog.dart b/lib/theme/views/styled_dialog.dart index 4e4bd50..75a0f6b 100644 --- a/lib/theme/views/styled_dialog.dart +++ b/lib/theme/views/styled_dialog.dart @@ -21,7 +21,7 @@ class StyledDialog extends StatelessWidget { Radius.circular(16 * scaleConfig.borderRadiusScale)), ), contentPadding: const EdgeInsets.all(4), - backgroundColor: scale.primaryScale.dialogBorder, + backgroundColor: scale.primaryScale.border, title: Text( title, style: textTheme.titleMedium! diff --git a/lib/theme/views/widget_helpers.dart b/lib/theme/views/widget_helpers.dart index 1b9e80f..f66af4b 100644 --- a/lib/theme/views/widget_helpers.dart +++ b/lib/theme/views/widget_helpers.dart @@ -114,14 +114,13 @@ extension LabelExt on Widget { {ScaleColor? scale}) { final theme = Theme.of(context); final scaleScheme = theme.extension()!; - // final scaleConfig = theme.extension()!; scale = scale ?? scaleScheme.primaryScale; - return Wrap(crossAxisAlignment: WrapCrossAlignment.center, children: [ + return Wrap(crossAxisAlignment: WrapCrossAlignment.end, children: [ Text( '$label:', - style: theme.textTheme.titleLarge!.copyWith(color: scale.border), - ).paddingLTRB(0, 0, 8, 8), + style: theme.textTheme.bodyLarge!.copyWith(color: scale.hoverBorder), + ).paddingLTRB(0, 0, 8, 0), this ]); } @@ -431,6 +430,31 @@ Widget styledTitleContainer({ ])); } +Widget styledCard({ + required BuildContext context, + required Widget child, + Color? borderColor, + Color? backgroundColor, + Color? titleColor, +}) { + final theme = Theme.of(context); + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + + return DecoratedBox( + decoration: ShapeDecoration( + color: backgroundColor ?? scale.primaryScale.elementBackground, + shape: RoundedRectangleBorder( + side: (scaleConfig.useVisualIndicators || scaleConfig.preferBorders) + ? BorderSide( + color: borderColor ?? scale.primaryScale.border, width: 2) + : BorderSide.none, + borderRadius: + BorderRadius.circular(12 * scaleConfig.borderRadiusScale), + )), + child: child.paddingAll(4)); +} + Widget styledBottomSheet({ required BuildContext context, required String title, @@ -500,6 +524,12 @@ const grayColorFilter = ColorFilter.matrix([ 0, ]); +const dodgeFilter = + ColorFilter.mode(Color.fromARGB(96, 255, 255, 255), BlendMode.srcIn); + +const overlayFilter = + ColorFilter.mode(Color.fromARGB(127, 255, 255, 255), BlendMode.dstIn); + Container clipBorder({ required bool clipEnabled, required bool borderEnabled, @@ -510,16 +540,17 @@ Container clipBorder({ // ignore: avoid_unnecessary_containers, use_decorated_box Container( decoration: ShapeDecoration( - color: borderColor, shape: RoundedRectangleBorder( - borderRadius: clipEnabled - ? BorderRadius.circular(borderRadius) - : BorderRadius.zero, - )), + side: borderEnabled && clipEnabled + ? BorderSide(color: borderColor, width: 2) + : BorderSide.none, + borderRadius: clipEnabled + ? BorderRadius.circular(borderRadius) + : BorderRadius.zero, + )), child: ClipRRect( - clipBehavior: Clip.hardEdge, - borderRadius: clipEnabled - ? BorderRadius.circular(borderRadius) - : BorderRadius.zero, - child: child) - .paddingAll(clipEnabled && borderEnabled ? 2 : 0)); + clipBehavior: Clip.antiAliasWithSaveLayer, + borderRadius: clipEnabled + ? BorderRadius.circular(borderRadius - 2) + : BorderRadius.zero, + child: child)); diff --git a/lib/veilid_processor/views/developer.dart b/lib/veilid_processor/views/developer.dart index f561f07..1899c34 100644 --- a/lib/veilid_processor/views/developer.dart +++ b/lib/veilid_processor/views/developer.dart @@ -216,7 +216,7 @@ class _DeveloperPageState extends State { onPressed: () async { final confirm = await showConfirmModal( context: context, - title: translate('toast.confirm'), + title: translate('confirmation.confirm'), text: translate('developer.are_you_sure_clear'), ); if (confirm && context.mounted) { @@ -224,7 +224,7 @@ class _DeveloperPageState extends State { } }), SizedBox.fromSize( - size: const Size(120, 48), + size: const Size(140, 48), child: CustomDropdown( items: _logLevelDropdownItems, initialItem: _logLevelDropdownItems diff --git a/pubspec.lock b/pubspec.lock index b98424d..349a58b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -96,6 +96,14 @@ packages: relative: true source: path version: "0.1.7" + auto_size_text: + dependency: "direct main" + description: + name: auto_size_text + sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599" + url: "https://pub.dev" + source: hosted + version: "3.0.0" awesome_extensions: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 1e0a526..94f8952 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: ansicolor: ^2.0.3 archive: ^4.0.4 async_tools: ^0.1.7 + auto_size_text: ^3.0.0 awesome_extensions: ^2.0.21 badges: ^3.1.2 basic_utils: ^5.8.2 @@ -158,14 +159,16 @@ flutter: - assets/i18n/en.json # Launcher icon - assets/launcher/icon.png - # Images - - assets/images/splash.svg + # Vector Images + - assets/images/grid.svg - assets/images/icon.svg + - assets/images/splash.svg - assets/images/title.svg - assets/images/vlogo.svg + # Raster Images - assets/images/ellet.png - - assets/images/toilet.png - assets/images/handshake.png + - assets/images/toilet.png # Printing - assets/js/pdf/3.2.146/pdf.min.js # Sounds diff --git a/build.bat b/update_generated_files.bat similarity index 100% rename from build.bat rename to update_generated_files.bat diff --git a/build.sh b/update_generated_files.sh similarity index 100% rename from build.sh rename to update_generated_files.sh From 6bd60207d826dd5c6235186606ed64cf05887d68 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 17 Mar 2025 20:30:20 -0400 Subject: [PATCH 33/93] wallpapers and ui cleanup --- assets/i18n/en.json | 1 + assets/images/wallpaper/arctic.svg | 13 + assets/images/wallpaper/babydoll.svg | 663 ++ assets/images/wallpaper/eggplant.svg | 495 + assets/images/wallpaper/elite.svg | 6 + assets/images/wallpaper/forest.svg | 6888 ++++++++++++++ assets/images/wallpaper/garden.svg | 8182 +++++++++++++++++ assets/images/wallpaper/gold.svg | 26 + assets/images/wallpaper/grim.svg | 928 ++ assets/images/wallpaper/lapis.svg | 39 + assets/images/wallpaper/lime.svg | 25 + assets/images/wallpaper/scarlet.svg | 349 + assets/images/wallpaper/vapor.svg | 25 + lib/app.dart | 44 +- lib/chat/views/chat_component_widget.dart | 109 +- lib/chat/views/no_conversation_widget.dart | 10 +- lib/init.dart | 13 +- lib/layout/home/drawer_menu/drawer_menu.dart | 26 +- lib/layout/home/home_account_ready.dart | 35 +- lib/layout/home/home_screen.dart | 1 + lib/settings/settings_page.dart | 2 + lib/theme/models/chat_theme.dart | 8 +- lib/theme/models/radix_generator.dart | 28 + .../models/scale_theme/scale_scheme.dart | 21 +- lib/theme/models/theme_preference.dart | 24 +- .../models/theme_preference.freezed.dart | 38 +- lib/theme/models/theme_preference.g.dart | 2 + lib/theme/views/styled_scaffold.dart | 9 +- lib/theme/views/views.dart | 1 + lib/theme/views/wallpaper_preferences.dart | 32 + lib/theme/views/widget_helpers.dart | 40 +- pubspec.yaml | 14 +- 32 files changed, 17947 insertions(+), 150 deletions(-) create mode 100644 assets/images/wallpaper/arctic.svg create mode 100644 assets/images/wallpaper/babydoll.svg create mode 100644 assets/images/wallpaper/eggplant.svg create mode 100644 assets/images/wallpaper/elite.svg create mode 100644 assets/images/wallpaper/forest.svg create mode 100644 assets/images/wallpaper/garden.svg create mode 100644 assets/images/wallpaper/gold.svg create mode 100644 assets/images/wallpaper/grim.svg create mode 100644 assets/images/wallpaper/lapis.svg create mode 100644 assets/images/wallpaper/lime.svg create mode 100644 assets/images/wallpaper/scarlet.svg create mode 100644 assets/images/wallpaper/vapor.svg create mode 100644 lib/theme/views/wallpaper_preferences.dart diff --git a/assets/i18n/en.json b/assets/i18n/en.json index ef4c44c..31316d3 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -285,6 +285,7 @@ "delivery": "Delivery", "enable_badge": "Enable icon 'badge' bubble", "enable_notifications": "Enable notifications", + "enable_wallpaper": "Enable wallpaper", "message_notification_content": "Message notification content", "invitation_accepted": "On invitation accept/reject", "message_received": "On message received", diff --git a/assets/images/wallpaper/arctic.svg b/assets/images/wallpaper/arctic.svg new file mode 100644 index 0000000..ebcaee4 --- /dev/null +++ b/assets/images/wallpaper/arctic.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/images/wallpaper/babydoll.svg b/assets/images/wallpaper/babydoll.svg new file mode 100644 index 0000000..55f28a3 --- /dev/null +++ b/assets/images/wallpaper/babydoll.svg @@ -0,0 +1,663 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/wallpaper/eggplant.svg b/assets/images/wallpaper/eggplant.svg new file mode 100644 index 0000000..48e6ad3 --- /dev/null +++ b/assets/images/wallpaper/eggplant.svg @@ -0,0 +1,495 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/wallpaper/elite.svg b/assets/images/wallpaper/elite.svg new file mode 100644 index 0000000..606d21f --- /dev/null +++ b/assets/images/wallpaper/elite.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/wallpaper/forest.svg b/assets/images/wallpaper/forest.svg new file mode 100644 index 0000000..fb61069 --- /dev/null +++ b/assets/images/wallpaper/forest.svg @@ -0,0 +1,6888 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/wallpaper/garden.svg b/assets/images/wallpaper/garden.svg new file mode 100644 index 0000000..f4e6372 --- /dev/null +++ b/assets/images/wallpaper/garden.svg @@ -0,0 +1,8182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/wallpaper/gold.svg b/assets/images/wallpaper/gold.svg new file mode 100644 index 0000000..16e5ab5 --- /dev/null +++ b/assets/images/wallpaper/gold.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/wallpaper/grim.svg b/assets/images/wallpaper/grim.svg new file mode 100644 index 0000000..7c3968f --- /dev/null +++ b/assets/images/wallpaper/grim.svg @@ -0,0 +1,928 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/wallpaper/lapis.svg b/assets/images/wallpaper/lapis.svg new file mode 100644 index 0000000..c78fa58 --- /dev/null +++ b/assets/images/wallpaper/lapis.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/wallpaper/lime.svg b/assets/images/wallpaper/lime.svg new file mode 100644 index 0000000..d65222e --- /dev/null +++ b/assets/images/wallpaper/lime.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/wallpaper/scarlet.svg b/assets/images/wallpaper/scarlet.svg new file mode 100644 index 0000000..7047ca7 --- /dev/null +++ b/assets/images/wallpaper/scarlet.svg @@ -0,0 +1,349 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/wallpaper/vapor.svg b/assets/images/wallpaper/vapor.svg new file mode 100644 index 0000000..34bfe59 --- /dev/null +++ b/assets/images/wallpaper/vapor.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/app.dart b/lib/app.dart index fade2c8..72d845f 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:provider/provider.dart'; @@ -49,19 +48,24 @@ class VeilidChatApp extends StatelessWidget { final ThemeData initialThemeData; void _reloadTheme(BuildContext context) { - log.info('Reloading theme'); - final theme = - PreferencesRepository.instance.value.themePreference.themeData(); - ThemeSwitcher.of(context).changeTheme(theme: theme); - - // Hack to reload translations - final localizationDelegate = LocalizedApp.of(context).delegate; singleFuture(this, () async { - await LocalizationDelegate.create( - fallbackLocale: localizationDelegate.fallbackLocale.toString(), - supportedLocales: localizationDelegate.supportedLocales - .map((x) => x.toString()) - .toList()); + log.info('Reloading theme'); + + await VeilidChatGlobalInit.loadAssetManifest(); + + final theme = + PreferencesRepository.instance.value.themePreference.themeData(); + if (context.mounted) { + ThemeSwitcher.of(context).changeTheme(theme: theme); + + // Hack to reload translations + final localizationDelegate = LocalizedApp.of(context).delegate; + await LocalizationDelegate.create( + fallbackLocale: localizationDelegate.fallbackLocale.toString(), + supportedLocales: localizationDelegate.supportedLocales + .map((x) => x.toString()) + .toList()); + } }); } @@ -164,17 +168,17 @@ class VeilidChatApp extends StatelessWidget { scale.primaryScale.subtleBackground, ]); + final wallpaper = PreferencesRepository + .instance.value.themePreference + .wallpaper(); + return Stack( fit: StackFit.expand, alignment: Alignment.center, children: [ - DecoratedBox( - decoration: BoxDecoration(gradient: gradient)), - SvgPicture.asset( - 'assets/images/grid.svg', - fit: BoxFit.cover, - colorFilter: overlayFilter, - ), + wallpaper ?? + DecoratedBox( + decoration: BoxDecoration(gradient: gradient)), MaterialApp.router( scrollBehavior: const ScrollBehaviorModified(), debugShowCheckedModeBanner: false, diff --git a/lib/chat/views/chat_component_widget.dart b/lib/chat/views/chat_component_widget.dart index 79d1f1c..8e43299 100644 --- a/lib/chat/views/chat_component_widget.dart +++ b/lib/chat/views/chat_component_widget.dart @@ -82,15 +82,16 @@ class ChatComponentWidget extends StatelessWidget { Widget _buildChatComponent(BuildContext context) { final theme = Theme.of(context); - final scale = theme.extension()!; + final scaleScheme = theme.extension()!; final scaleConfig = theme.extension()!; + final scale = scaleScheme.scale(ScaleKind.primary); final textTheme = theme.textTheme; - final chatTheme = makeChatTheme(scale, scaleConfig, textTheme); + final chatTheme = makeChatTheme(scaleScheme, scaleConfig, textTheme); final errorChatTheme = (ChatThemeEditor(chatTheme) - ..inputTextColor = scale.errorScale.primary + ..inputTextColor = scaleScheme.errorScale.primary ..sendButtonIcon = Image.asset( 'assets/icon-send.png', - color: scale.errorScale.primary, + color: scaleScheme.errorScale.primary, package: 'flutter_chat_ui', )) .commit(); @@ -126,7 +127,7 @@ class ChatComponentWidget extends StatelessWidget { Container( height: 48, decoration: BoxDecoration( - color: scale.primaryScale.subtleBorder, + color: scale.border, ), child: Row(children: [ Align( @@ -136,12 +137,11 @@ class ChatComponentWidget extends StatelessWidget { child: Text(title, textAlign: TextAlign.start, style: textTheme.titleMedium! - .copyWith(color: scale.primaryScale.borderText)), + .copyWith(color: scale.borderText)), )), const Spacer(), IconButton( - icon: - Icon(Icons.close, color: scale.primaryScale.borderText), + icon: Icon(Icons.close, color: scale.borderText), onPressed: _onClose) .paddingLTRB(16, 0, 16, 0) ]), @@ -201,54 +201,51 @@ class ChatComponentWidget extends StatelessWidget { 2048; return Chat( - key: chatComponentState.chatKey, - theme: - messageIsValid ? chatTheme : errorChatTheme, - messages: messageWindow.window.toList(), - scrollToBottomOnSend: isFirstPage, - scrollController: - chatComponentState.scrollController, - inputOptions: InputOptions( - inputClearMode: messageIsValid - ? InputClearMode.always - : InputClearMode.never, - textEditingController: - chatComponentState.textEditingController), - // isLastPage: isLastPage, - // onEndReached: () async { - // await _handlePageBackward( - // chatComponentCubit, messageWindow); - // }, - //onEndReachedThreshold: onEndReachedThreshold, - //onAttachmentPressed: _handleAttachmentPressed, - //onMessageTap: _handleMessageTap, - //onPreviewDataFetched: _handlePreviewDataFetched, - usePreviewData: false, // - onSendPressed: (pt) { - try { - if (!messageIsValid) { - context.read().error( - text: - translate('chat.message_too_long')); - return; - } - _handleSendPressed(chatComponentCubit, pt); - } on FormatException { - context.read().error( - text: translate('chat.message_too_long')); - } - }, - listBottomWidget: messageIsValid - ? null - : Text(translate('chat.message_too_long'), - style: TextStyle( - color: scale.errorScale.primary)) - .toCenter(), - //showUserAvatars: false, - //showUserNames: true, - user: localUser, - emptyState: const EmptyChatWidget()) - .paddingLTRB(0, 2, 0, 0); + key: chatComponentState.chatKey, + theme: messageIsValid ? chatTheme : errorChatTheme, + messages: messageWindow.window.toList(), + scrollToBottomOnSend: isFirstPage, + scrollController: chatComponentState.scrollController, + inputOptions: InputOptions( + inputClearMode: messageIsValid + ? InputClearMode.always + : InputClearMode.never, + textEditingController: + chatComponentState.textEditingController), + // isLastPage: isLastPage, + // onEndReached: () async { + // await _handlePageBackward( + // chatComponentCubit, messageWindow); + // }, + //onEndReachedThreshold: onEndReachedThreshold, + //onAttachmentPressed: _handleAttachmentPressed, + //onMessageTap: _handleMessageTap, + //onPreviewDataFetched: _handlePreviewDataFetched, + usePreviewData: false, // + onSendPressed: (pt) { + try { + if (!messageIsValid) { + context.read().error( + text: translate('chat.message_too_long')); + return; + } + _handleSendPressed(chatComponentCubit, pt); + } on FormatException { + context.read().error( + text: translate('chat.message_too_long')); + } + }, + listBottomWidget: messageIsValid + ? null + : Text(translate('chat.message_too_long'), + style: TextStyle( + color: + scaleScheme.errorScale.primary)) + .toCenter(), + //showUserAvatars: false, + //showUserNames: true, + user: localUser, + emptyState: const EmptyChatWidget()); }))).expanded(), ], ); diff --git a/lib/chat/views/no_conversation_widget.dart b/lib/chat/views/no_conversation_widget.dart index df1b6d3..830e0d6 100644 --- a/lib/chat/views/no_conversation_widget.dart +++ b/lib/chat/views/no_conversation_widget.dart @@ -12,11 +12,13 @@ class NoConversationWidget extends StatelessWidget { BuildContext context, ) { final theme = Theme.of(context); - final scale = theme.extension()!; + final scaleScheme = theme.extension()!; + final scaleConfig = theme.extension()!; + final scale = scaleScheme.scale(ScaleKind.primary); return DecoratedBox( decoration: BoxDecoration( - color: scale.primaryScale.appBackground.withAlpha(192), + color: scale.appBackground.withAlpha(scaleConfig.wallpaperAlpha), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -24,14 +26,14 @@ class NoConversationWidget extends StatelessWidget { children: [ Icon( Icons.diversity_3, - color: scale.primaryScale.appText.withAlpha(127), + color: scale.appText.withAlpha(127), size: 48, ), Text( textAlign: TextAlign.center, translate('chat.start_a_conversation'), style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: scale.primaryScale.appText.withAlpha(127), + color: scale.appText.withAlpha(127), ), ), ], diff --git a/lib/init.dart b/lib/init.dart index d2744c7..8c80bf9 100644 --- a/lib/init.dart +++ b/lib/init.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; import 'package:veilid_support/veilid_support.dart'; import 'account_manager/account_manager.dart'; @@ -8,6 +9,8 @@ import 'app.dart'; import 'tools/tools.dart'; import 'veilid_processor/veilid_processor.dart'; +List rootAssets = []; + class VeilidChatGlobalInit { VeilidChatGlobalInit._(); @@ -28,14 +31,22 @@ class VeilidChatGlobalInit { logger: (message) => log.debug('DHTRecordPool: $message')); } -// Initialize repositories + // Initialize repositories Future _initializeRepositories() async { await AccountRepository.instance.init(); } + // Initialize asset manifest + static Future loadAssetManifest() async { + final assetManifest = await AssetManifest.loadFromAssetBundle(rootBundle); + rootAssets = assetManifest.listAssets(); + } + static Future initialize() async { final veilidChatGlobalInit = VeilidChatGlobalInit._(); + await loadAssetManifest(); + log.info('Initializing Veilid'); await veilidChatGlobalInit._initializeVeilid(); log.info('Initializing Repositories'); diff --git a/lib/layout/home/drawer_menu/drawer_menu.dart b/lib/layout/home/drawer_menu/drawer_menu.dart index b56d437..f88a888 100644 --- a/lib/layout/home/drawer_menu/drawer_menu.dart +++ b/lib/layout/home/drawer_menu/drawer_menu.dart @@ -320,29 +320,7 @@ class _DrawerMenuState extends State { return DecoratedBox( decoration: ShapeDecoration( - shadows: [ - if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) - BoxShadow( - color: scale.primary.darken(60), - spreadRadius: 2, - ) - else if (scaleConfig.useVisualIndicators && - scaleConfig.preferBorders) - BoxShadow( - color: scale.border, - spreadRadius: 2, - ) - else - BoxShadow( - color: scale.appBackground.darken(60).withAlpha(0x3F), - blurRadius: 16, - spreadRadius: 2, - offset: const Offset( - 0, - 2, - ), - ), - ], + shadows: themedShadow(scaleConfig, scale), gradient: scaleConfig.useVisualIndicators ? null : gradient, color: scaleConfig.useVisualIndicators ? (scaleConfig.preferBorders @@ -381,7 +359,7 @@ class _DrawerMenuState extends State { 'assets/images/title.svg', colorFilter: scaleConfig.useVisualIndicators ? grayColorFilter - : dodgeFilter), + : src96StencilFilter), ]))), Text(translate('menu.accounts'), style: theme.textTheme.titleMedium!.copyWith( diff --git a/lib/layout/home/home_account_ready.dart b/lib/layout/home/home_account_ready.dart index 7b67c0f..8710eea 100644 --- a/lib/layout/home/home_account_ready.dart +++ b/lib/layout/home/home_account_ready.dart @@ -146,8 +146,9 @@ class _HomeAccountReadyState extends State { ); final theme = Theme.of(context); - final scale = theme.extension()!; + final scaleScheme = theme.extension()!; final scaleConfig = theme.extension()!; + final scale = scaleScheme.scale(ScaleKind.primary); final activeChat = context.watch().state; final hasActiveChat = activeChat != null; @@ -163,7 +164,9 @@ class _HomeAccountReadyState extends State { visibleLeft = true; visibleRight = true; leftWidth = leftColumnSize; - rightWidth = constraints.maxWidth - leftColumnSize - 2; + rightWidth = constraints.maxWidth - + leftColumnSize - + (scaleConfig.useVisualIndicators ? 2 : 0); } else { if (hasActiveChat) { visibleLeft = false; @@ -180,19 +183,21 @@ class _HomeAccountReadyState extends State { return Row(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Offstage( - offstage: !visibleLeft, - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: leftWidth), - child: buildLeftPane(context))), - Offstage( - offstage: !(visibleLeft && visibleRight), - child: SizedBox( - width: 2, - height: double.infinity, - child: ColoredBox( - color: scaleConfig.preferBorders - ? scale.primaryScale.subtleBorder - : scale.primaryScale.subtleBackground))), + offstage: !visibleLeft, + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: leftWidth), + child: buildLeftPane(context))) + .withThemedShadow(scaleConfig, scale), + if (scaleConfig.useVisualIndicators) + Offstage( + offstage: !(visibleLeft && visibleRight), + child: SizedBox( + width: 2, + height: double.infinity, + child: ColoredBox( + color: scaleConfig.preferBorders + ? scale.subtleBorder + : scale.subtleBackground))), Offstage( offstage: !visibleRight, child: ConstrainedBox( diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index 3e8e98b..0ec1f26 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -229,6 +229,7 @@ class HomeScreenState extends State angle: 0, //mainScreenOverlayColor: theme.shadowColor.withAlpha(0x2F), openCurve: Curves.fastEaseInToSlowEaseOut, + closeCurve: Curves.fastEaseInToSlowEaseOut, // duration: const Duration(milliseconds: 250), // reverseDuration: const Duration(milliseconds: 250), menuScreenTapClose: canClose, diff --git a/lib/settings/settings_page.dart b/lib/settings/settings_page.dart index 05ba514..f164992 100644 --- a/lib/settings/settings_page.dart +++ b/lib/settings/settings_page.dart @@ -53,6 +53,8 @@ class SettingsPageState extends State { .paddingLTRB(0, 8, 0, 0), buildSettingsPageBrightnessPreferences( context: context, onChanged: () => setState(() {})), + buildSettingsPageWallpaperPreferences( + context: context, onChanged: () => setState(() {})), buildSettingsPageNotificationPreferences( context: context, onChanged: () => setState(() {})), ].map((x) => x.paddingLTRB(0, 0, 0, 8)).toList(), diff --git a/lib/theme/models/chat_theme.dart b/lib/theme/models/chat_theme.dart index cd0b9ce..5552979 100644 --- a/lib/theme/models/chat_theme.dart +++ b/lib/theme/models/chat_theme.dart @@ -14,14 +14,15 @@ ChatTheme makeChatTheme( secondaryColor: scaleConfig.preferBorders ? scale.secondaryScale.calloutText : scale.secondaryScale.calloutBackground, - backgroundColor: scale.grayScale.appBackground.withAlpha(192), + backgroundColor: + scale.grayScale.appBackground.withAlpha(scaleConfig.wallpaperAlpha), messageBorderRadius: scaleConfig.borderRadiusScale * 16, bubbleBorderSide: scaleConfig.preferBorders ? BorderSide( color: scale.primaryScale.calloutBackground, width: 2, ) - : null, + : BorderSide(width: 2, color: Colors.black.withAlpha(96)), sendButtonIcon: Image.asset( 'assets/icon-send.png', color: scaleConfig.preferBorders @@ -86,7 +87,8 @@ ChatTheme makeChatTheme( receivedEmojiMessageTextStyle: const TextStyle( color: Colors.white, fontSize: 64, - )); + ), + dateDividerTextStyle: textTheme.labelSmall!); class EditedChatTheme extends ChatTheme { const EditedChatTheme({ diff --git a/lib/theme/models/radix_generator.dart b/lib/theme/models/radix_generator.dart index a5c5f87..3e3b0e6 100644 --- a/lib/theme/models/radix_generator.dart +++ b/lib/theme/models/radix_generator.dart @@ -603,6 +603,33 @@ TextTheme makeRadixTextTheme(Brightness brightness) { return textTheme; } +double wallpaperAlpha(Brightness brightness, RadixThemeColor themeColor) { + switch (themeColor) { + case RadixThemeColor.scarlet: + return 64; + case RadixThemeColor.babydoll: + return 192; + case RadixThemeColor.vapor: + return 192; + case RadixThemeColor.gold: + return 192; + case RadixThemeColor.garden: + return brightness == Brightness.dark ? 192 : 128; + case RadixThemeColor.forest: + return 192; + case RadixThemeColor.arctic: + return brightness == Brightness.dark ? 208 : 180; + case RadixThemeColor.lapis: + return brightness == Brightness.dark ? 128 : 192; + case RadixThemeColor.eggplant: + return brightness == Brightness.dark ? 192 : 192; + case RadixThemeColor.lime: + return brightness == Brightness.dark ? 192 : 128; + case RadixThemeColor.grim: + return brightness == Brightness.dark ? 240 : 224; + } +} + ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) { final textTheme = makeRadixTextTheme(brightness); final radix = _radixScheme(brightness, themeColor); @@ -611,6 +638,7 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) { useVisualIndicators: false, preferBorders: false, borderRadiusScale: 1, + wallpaperAlpha: wallpaperAlpha(brightness, themeColor), ); final scaleTheme = ScaleTheme( diff --git a/lib/theme/models/scale_theme/scale_scheme.dart b/lib/theme/models/scale_theme/scale_scheme.dart index 8c4a6b8..6bdae25 100644 --- a/lib/theme/models/scale_theme/scale_scheme.dart +++ b/lib/theme/models/scale_theme/scale_scheme.dart @@ -111,22 +111,27 @@ class ScaleConfig extends ThemeExtension { required this.useVisualIndicators, required this.preferBorders, required this.borderRadiusScale, - }); + required double wallpaperAlpha, + }) : _wallpaperAlpha = wallpaperAlpha; final bool useVisualIndicators; final bool preferBorders; final double borderRadiusScale; + final double _wallpaperAlpha; + + int get wallpaperAlpha => _wallpaperAlpha.toInt(); @override - ScaleConfig copyWith({ - bool? useVisualIndicators, - bool? preferBorders, - double? borderRadiusScale, - }) => + ScaleConfig copyWith( + {bool? useVisualIndicators, + bool? preferBorders, + double? borderRadiusScale, + double? wallpaperAlpha}) => ScaleConfig( useVisualIndicators: useVisualIndicators ?? this.useVisualIndicators, preferBorders: preferBorders ?? this.preferBorders, borderRadiusScale: borderRadiusScale ?? this.borderRadiusScale, + wallpaperAlpha: wallpaperAlpha ?? this._wallpaperAlpha, ); @override @@ -139,6 +144,8 @@ class ScaleConfig extends ThemeExtension { t < .5 ? useVisualIndicators : other.useVisualIndicators, preferBorders: t < .5 ? preferBorders : other.preferBorders, borderRadiusScale: - lerpDouble(borderRadiusScale, other.borderRadiusScale, t) ?? 1); + lerpDouble(borderRadiusScale, other.borderRadiusScale, t) ?? 1, + wallpaperAlpha: + lerpDouble(_wallpaperAlpha, other._wallpaperAlpha, t) ?? 1); } } diff --git a/lib/theme/models/theme_preference.dart b/lib/theme/models/theme_preference.dart index 9d6f6dc..dc2e082 100644 --- a/lib/theme/models/theme_preference.dart +++ b/lib/theme/models/theme_preference.dart @@ -1,7 +1,10 @@ import 'package:change_case/change_case.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import '../../init.dart'; import '../views/widget_helpers.dart'; import 'contrast_generator.dart'; import 'radix_generator.dart'; @@ -53,6 +56,7 @@ class ThemePreferences with _$ThemePreferences { BrightnessPreference brightnessPreference, @Default(ColorPreference.vapor) ColorPreference colorPreference, @Default(1) double displayScale, + @Default(true) bool enableWallpaper, }) = _ThemePreferences; factory ThemePreferences.fromJson(dynamic json) => @@ -62,6 +66,17 @@ class ThemePreferences with _$ThemePreferences { } extension ThemePreferencesExt on ThemePreferences { + /// Get wallpaper for existing theme + Widget? wallpaper() { + if (enableWallpaper) { + final assetName = 'assets/images/wallpaper/${colorPreference.name}.svg'; + if (rootAssets.contains(assetName)) { + return SvgPicture.asset(assetName, fit: BoxFit.cover); + } + } + return null; + } + /// Get material 'ThemeData' for existing theme ThemeData themeData() { late final Brightness brightness; @@ -87,7 +102,8 @@ extension ThemePreferencesExt on ThemePreferences { scaleConfig: ScaleConfig( useVisualIndicators: true, preferBorders: false, - borderRadiusScale: 1), + borderRadiusScale: 1, + wallpaperAlpha: 255), primaryFront: Colors.black, primaryBack: Colors.white, secondaryFront: Colors.black, @@ -106,7 +122,8 @@ extension ThemePreferencesExt on ThemePreferences { scaleConfig: ScaleConfig( useVisualIndicators: true, preferBorders: true, - borderRadiusScale: 0.2), + borderRadiusScale: 0.2, + wallpaperAlpha: 208), primaryFront: const Color(0xFF000000), primaryBack: const Color(0xFF00FF00), secondaryFront: const Color(0xFF000000), @@ -123,7 +140,8 @@ extension ThemePreferencesExt on ThemePreferences { scaleConfig: ScaleConfig( useVisualIndicators: true, preferBorders: true, - borderRadiusScale: 0.2), + borderRadiusScale: 0.2, + wallpaperAlpha: 192), primaryFront: const Color(0xFF000000), primaryBack: const Color(0xFF00FF00), secondaryFront: const Color(0xFF000000), diff --git a/lib/theme/models/theme_preference.freezed.dart b/lib/theme/models/theme_preference.freezed.dart index 657d021..d96ed38 100644 --- a/lib/theme/models/theme_preference.freezed.dart +++ b/lib/theme/models/theme_preference.freezed.dart @@ -24,6 +24,7 @@ mixin _$ThemePreferences { throw _privateConstructorUsedError; ColorPreference get colorPreference => throw _privateConstructorUsedError; double get displayScale => throw _privateConstructorUsedError; + bool get enableWallpaper => throw _privateConstructorUsedError; /// Serializes this ThemePreferences to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -44,7 +45,8 @@ abstract class $ThemePreferencesCopyWith<$Res> { $Res call( {BrightnessPreference brightnessPreference, ColorPreference colorPreference, - double displayScale}); + double displayScale, + bool enableWallpaper}); } /// @nodoc @@ -65,6 +67,7 @@ class _$ThemePreferencesCopyWithImpl<$Res, $Val extends ThemePreferences> Object? brightnessPreference = null, Object? colorPreference = null, Object? displayScale = null, + Object? enableWallpaper = null, }) { return _then(_value.copyWith( brightnessPreference: null == brightnessPreference @@ -79,6 +82,10 @@ class _$ThemePreferencesCopyWithImpl<$Res, $Val extends ThemePreferences> ? _value.displayScale : displayScale // ignore: cast_nullable_to_non_nullable as double, + enableWallpaper: null == enableWallpaper + ? _value.enableWallpaper + : enableWallpaper // ignore: cast_nullable_to_non_nullable + as bool, ) as $Val); } } @@ -94,7 +101,8 @@ abstract class _$$ThemePreferencesImplCopyWith<$Res> $Res call( {BrightnessPreference brightnessPreference, ColorPreference colorPreference, - double displayScale}); + double displayScale, + bool enableWallpaper}); } /// @nodoc @@ -113,6 +121,7 @@ class __$$ThemePreferencesImplCopyWithImpl<$Res> Object? brightnessPreference = null, Object? colorPreference = null, Object? displayScale = null, + Object? enableWallpaper = null, }) { return _then(_$ThemePreferencesImpl( brightnessPreference: null == brightnessPreference @@ -127,6 +136,10 @@ class __$$ThemePreferencesImplCopyWithImpl<$Res> ? _value.displayScale : displayScale // ignore: cast_nullable_to_non_nullable as double, + enableWallpaper: null == enableWallpaper + ? _value.enableWallpaper + : enableWallpaper // ignore: cast_nullable_to_non_nullable + as bool, )); } } @@ -137,7 +150,8 @@ class _$ThemePreferencesImpl implements _ThemePreferences { const _$ThemePreferencesImpl( {this.brightnessPreference = BrightnessPreference.system, this.colorPreference = ColorPreference.vapor, - this.displayScale = 1}); + this.displayScale = 1, + this.enableWallpaper = true}); factory _$ThemePreferencesImpl.fromJson(Map json) => _$$ThemePreferencesImplFromJson(json); @@ -151,10 +165,13 @@ class _$ThemePreferencesImpl implements _ThemePreferences { @override @JsonKey() final double displayScale; + @override + @JsonKey() + final bool enableWallpaper; @override String toString() { - return 'ThemePreferences(brightnessPreference: $brightnessPreference, colorPreference: $colorPreference, displayScale: $displayScale)'; + return 'ThemePreferences(brightnessPreference: $brightnessPreference, colorPreference: $colorPreference, displayScale: $displayScale, enableWallpaper: $enableWallpaper)'; } @override @@ -167,13 +184,15 @@ class _$ThemePreferencesImpl implements _ThemePreferences { (identical(other.colorPreference, colorPreference) || other.colorPreference == colorPreference) && (identical(other.displayScale, displayScale) || - other.displayScale == displayScale)); + other.displayScale == displayScale) && + (identical(other.enableWallpaper, enableWallpaper) || + other.enableWallpaper == enableWallpaper)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, brightnessPreference, colorPreference, displayScale); + int get hashCode => Object.hash(runtimeType, brightnessPreference, + colorPreference, displayScale, enableWallpaper); /// Create a copy of ThemePreferences /// with the given fields replaced by the non-null parameter values. @@ -196,7 +215,8 @@ abstract class _ThemePreferences implements ThemePreferences { const factory _ThemePreferences( {final BrightnessPreference brightnessPreference, final ColorPreference colorPreference, - final double displayScale}) = _$ThemePreferencesImpl; + final double displayScale, + final bool enableWallpaper}) = _$ThemePreferencesImpl; factory _ThemePreferences.fromJson(Map json) = _$ThemePreferencesImpl.fromJson; @@ -207,6 +227,8 @@ abstract class _ThemePreferences implements ThemePreferences { ColorPreference get colorPreference; @override double get displayScale; + @override + bool get enableWallpaper; /// Create a copy of ThemePreferences /// with the given fields replaced by the non-null parameter values. diff --git a/lib/theme/models/theme_preference.g.dart b/lib/theme/models/theme_preference.g.dart index 4cb2d71..23c3d38 100644 --- a/lib/theme/models/theme_preference.g.dart +++ b/lib/theme/models/theme_preference.g.dart @@ -16,6 +16,7 @@ _$ThemePreferencesImpl _$$ThemePreferencesImplFromJson( ? ColorPreference.vapor : ColorPreference.fromJson(json['color_preference']), displayScale: (json['display_scale'] as num?)?.toDouble() ?? 1, + enableWallpaper: json['enable_wallpaper'] as bool? ?? true, ); Map _$$ThemePreferencesImplToJson( @@ -24,4 +25,5 @@ Map _$$ThemePreferencesImplToJson( 'brightness_preference': instance.brightnessPreference.toJson(), 'color_preference': instance.colorPreference.toJson(), 'display_scale': instance.displayScale, + 'enable_wallpaper': instance.enableWallpaper, }; diff --git a/lib/theme/views/styled_scaffold.dart b/lib/theme/views/styled_scaffold.dart index 9ee3d36..9d02fab 100644 --- a/lib/theme/views/styled_scaffold.dart +++ b/lib/theme/views/styled_scaffold.dart @@ -9,8 +9,9 @@ class StyledScaffold extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final scale = theme.extension()!; + final scaleScheme = theme.extension()!; final scaleConfig = theme.extension()!; + final scale = scaleScheme.scale(ScaleKind.primary); final enableBorder = !isMobileSize(context); @@ -18,13 +19,11 @@ class StyledScaffold extends StatelessWidget { clipEnabled: enableBorder, borderEnabled: scaleConfig.useVisualIndicators, borderRadius: 16 * scaleConfig.borderRadiusScale, - borderColor: scale.primaryScale.border, + borderColor: scale.border, child: Scaffold(appBar: appBar, body: body, key: key)); if (!scaleConfig.useVisualIndicators) { - scaffold = scaffold.withShadow( - offset: const Offset(0, 16), - shadowColor: scale.primaryScale.primary.withAlpha(0x3F).darken(60)); + scaffold = scaffold.withThemedShadow(scaleConfig, scale); } return GestureDetector( diff --git a/lib/theme/views/views.dart b/lib/theme/views/views.dart index b5aa809..88f4a4a 100644 --- a/lib/theme/views/views.dart +++ b/lib/theme/views/views.dart @@ -12,4 +12,5 @@ export 'slider_tile.dart'; export 'styled_alert.dart'; export 'styled_dialog.dart'; export 'styled_scaffold.dart'; +export 'wallpaper_preferences.dart'; export 'widget_helpers.dart'; diff --git a/lib/theme/views/wallpaper_preferences.dart b/lib/theme/views/wallpaper_preferences.dart new file mode 100644 index 0000000..1c2e9de --- /dev/null +++ b/lib/theme/views/wallpaper_preferences.dart @@ -0,0 +1,32 @@ +import 'package:animated_theme_switcher/animated_theme_switcher.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_translate/flutter_translate.dart'; + +import '../../settings/settings.dart'; +import '../models/models.dart'; + +const String formFieldEnableWallpaper = 'enable_wallpaper'; + +Widget buildSettingsPageWallpaperPreferences( + {required BuildContext context, required void Function() onChanged}) { + final preferencesRepository = PreferencesRepository.instance; + final themePreferences = preferencesRepository.value.themePreference; + return ThemeSwitcher.withTheme( + builder: (_, switcher, theme) => FormBuilderCheckbox( + name: formFieldEnableWallpaper, + title: Text(translate('settings_page.enable_wallpaper')), + initialValue: themePreferences.enableWallpaper, + onChanged: (value) async { + if (value != null) { + final newThemePrefs = + themePreferences.copyWith(enableWallpaper: value); + final newPrefs = preferencesRepository.value + .copyWith(themePreference: newThemePrefs); + + await preferencesRepository.set(newPrefs); + switcher.changeTheme(theme: newThemePrefs.themeData()); + onChanged(); + } + })); +} diff --git a/lib/theme/views/widget_helpers.dart b/lib/theme/views/widget_helpers.dart index f66af4b..1b768dd 100644 --- a/lib/theme/views/widget_helpers.dart +++ b/lib/theme/views/widget_helpers.dart @@ -19,6 +19,40 @@ extension BorderExt on Widget { child: this); } +extension ShadowExt on Widget { + Container withThemedShadow(ScaleConfig scaleConfig, ScaleColor scale) => + // ignore: use_decorated_box + Container( + decoration: BoxDecoration( + boxShadow: themedShadow(scaleConfig, scale), + ), + child: this, + ); +} + +List themedShadow(ScaleConfig scaleConfig, ScaleColor scale) => [ + if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) + BoxShadow( + color: scale.primary.darken(60), + spreadRadius: 2, + ) + else if (scaleConfig.useVisualIndicators && scaleConfig.preferBorders) + BoxShadow( + color: scale.border, + spreadRadius: 2, + ) + else + BoxShadow( + color: scale.primary.darken(60).withAlpha(0x7F), + blurRadius: 16, + spreadRadius: 2, + offset: const Offset( + 0, + 2, + ), + ), + ]; + extension SizeToFixExt on Widget { FittedBox fit({BoxFit? fit, Key? key}) => FittedBox( key: key, @@ -524,10 +558,10 @@ const grayColorFilter = ColorFilter.matrix([ 0, ]); -const dodgeFilter = +const src96StencilFilter = ColorFilter.mode(Color.fromARGB(96, 255, 255, 255), BlendMode.srcIn); -const overlayFilter = +const dst127StencilFilter = ColorFilter.mode(Color.fromARGB(127, 255, 255, 255), BlendMode.dstIn); Container clipBorder({ @@ -551,6 +585,6 @@ Container clipBorder({ child: ClipRRect( clipBehavior: Clip.antiAliasWithSaveLayer, borderRadius: clipEnabled - ? BorderRadius.circular(borderRadius - 2) + ? BorderRadius.circular(borderRadius - 4) : BorderRadius.zero, child: child)); diff --git a/pubspec.yaml b/pubspec.yaml index 94f8952..5f924ba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -159,8 +159,20 @@ flutter: - assets/i18n/en.json # Launcher icon - assets/launcher/icon.png + # Theme wallpaper + - assets/images/wallpaper/arctic.svg + - assets/images/wallpaper/babydoll.svg + - assets/images/wallpaper/eggplant.svg + - assets/images/wallpaper/elite.svg + - assets/images/wallpaper/forest.svg + - assets/images/wallpaper/garden.svg + - assets/images/wallpaper/gold.svg + - assets/images/wallpaper/grim.svg + - assets/images/wallpaper/lapis.svg + - assets/images/wallpaper/lime.svg + - assets/images/wallpaper/scarlet.svg + - assets/images/wallpaper/vapor.svg # Vector Images - - assets/images/grid.svg - assets/images/icon.svg - assets/images/splash.svg - assets/images/title.svg From ef1ded4494743d66ebef9e6b645180c38772c450 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 17 Mar 2025 22:00:26 -0400 Subject: [PATCH 34/93] updates and cleanup --- assets/i18n/en.json | 5 +- lib/chat/cubits/chat_component_cubit.dart | 15 +++-- lib/contacts/views/contact_item_widget.dart | 16 ++++- lib/contacts/views/contacts_browser.dart | 14 ++--- lib/contacts/views/contacts_dialog.dart | 2 +- lib/settings/settings_page.dart | 66 ++++++++++++--------- lib/theme/views/brightness_preferences.dart | 35 +++++------ lib/theme/views/color_preferences.dart | 39 ++++++------ lib/theme/views/wallpaper_preferences.dart | 35 +++++------ pubspec.lock | 13 ++-- pubspec.yaml | 10 ++-- 11 files changed, 142 insertions(+), 108 deletions(-) diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 31316d3..61bb802 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -100,7 +100,8 @@ "yes": "Yes", "no": "No", "update": "Update", - "waiting_for_network": "Waiting For Network" + "waiting_for_network": "Waiting For Network", + "chat": "Chat" }, "toast": { "error": "Error", @@ -127,7 +128,7 @@ "contacts": "Contacts", "edit_contact": "Edit Contact", "invitations": "Invitations", - "no_contact_selected": "Double-click a contact to edit it", + "no_contact_selected": "Select a contact to view or edit", "new_chat": "Open Chat", "close_contact": "Close Contact" }, diff --git a/lib/chat/cubits/chat_component_cubit.dart b/lib/chat/cubits/chat_component_cubit.dart index 326a597..9e50e02 100644 --- a/lib/chat/cubits/chat_component_cubit.dart +++ b/lib/chat/cubits/chat_component_cubit.dart @@ -50,8 +50,11 @@ class ChatComponentCubit extends Cubit { messageWindow: const AsyncLoading(), title: '', )) { + // Immediate Init + _init(); + // Async Init - _initWait.add(_init); + _initWait.add(_initAsync); } factory ChatComponentCubit.singleContact( @@ -68,7 +71,7 @@ class ChatComponentCubit extends Cubit { messagesCubit: messagesCubit, ); - Future _init(Completer _cancel) async { + void _init() { // Get local user info and account record cubit _localUserIdentityKey = _accountInfo.identityTypedPublicKey; @@ -77,9 +80,6 @@ class ChatComponentCubit extends Cubit { _accountRecordCubit.stream.listen(_onChangedAccountRecord); _onChangedAccountRecord(_accountRecordCubit.state); - // Subscribe to remote user info - await _updateConversationSubscriptions(); - // Subscribe to messages _messagesSubscription = _messagesCubit.stream.listen(_onChangedMessages); _onChangedMessages(_messagesCubit.state); @@ -90,6 +90,11 @@ class ChatComponentCubit extends Cubit { _onChangedContacts(_contactListCubit.state); } + Future _initAsync(Completer _cancel) async { + // Subscribe to remote user info + await _updateConversationSubscriptions(); + } + @override Future close() async { await _initWait(); diff --git a/lib/contacts/views/contact_item_widget.dart b/lib/contacts/views/contact_item_widget.dart index a0f2fbc..4614f27 100644 --- a/lib/contacts/views/contact_item_widget.dart +++ b/lib/contacts/views/contact_item_widget.dart @@ -68,15 +68,27 @@ class ContactItemWidget extends StatelessWidget { : () => singleFuture((this, _kOnTap), () async { await _onTap(_contact); }), - endActions: [ + startActions: [ if (_onDoubleTap != null) + SliderTileAction( + //icon: Icons.edit, + label: translate('button.chat'), + actionScale: ScaleKind.secondary, + onPressed: (_context) => + singleFuture((this, _kOnTap), () async { + await _onDoubleTap(_contact); + }), + ), + ], + endActions: [ + if (_onTap != null) SliderTileAction( //icon: Icons.edit, label: translate('button.edit'), actionScale: ScaleKind.secondary, onPressed: (_context) => singleFuture((this, _kOnTap), () async { - await _onDoubleTap(_contact); + await _onTap(_contact); }), ), if (_onDelete != null) diff --git a/lib/contacts/views/contacts_browser.dart b/lib/contacts/views/contacts_browser.dart index 7040af5..c5a6b22 100644 --- a/lib/contacts/views/contacts_browser.dart +++ b/lib/contacts/views/contacts_browser.dart @@ -39,14 +39,14 @@ class ContactsBrowserElement { class ContactsBrowser extends StatefulWidget { const ContactsBrowser( {required this.onContactSelected, - required this.onChatStarted, + required this.onStartChat, this.selectedContactRecordKey, super.key}); @override State createState() => _ContactsBrowserState(); final Future Function(proto.Contact? contact) onContactSelected; - final Future Function(proto.Contact contact) onChatStarted; + final Future Function(proto.Contact contact) onStartChat; final TypedKey? selectedContactRecordKey; @override @@ -60,7 +60,7 @@ class ContactsBrowser extends StatefulWidget { 'onContactSelected', onContactSelected)) ..add( ObjectFlagProperty Function(proto.Contact contact)>.has( - 'onChatStarted', onChatStarted)); + 'onStartChat', onStartChat)); } } @@ -238,8 +238,8 @@ class _ContactsBrowserState extends State selected: widget.selectedContactRecordKey == contact.localConversationRecordKey.toVeilid(), disabled: false, - onDoubleTap: _onTapContact, - onTap: _onStartChat, + onDoubleTap: _onStartChat, + onTap: _onSelectContact, onDelete: _onDeleteContact) .paddingLTRB(0, 4, 0, 0); case ContactsBrowserElementKind.invitation: @@ -293,12 +293,12 @@ class _ContactsBrowserState extends State ]); } - Future _onTapContact(proto.Contact contact) async { + Future _onSelectContact(proto.Contact contact) async { await widget.onContactSelected(contact); } Future _onStartChat(proto.Contact contact) async { - await widget.onChatStarted(contact); + await widget.onStartChat(contact); } Future _onDeleteContact(proto.Contact contact) async { diff --git a/lib/contacts/views/contacts_dialog.dart b/lib/contacts/views/contacts_dialog.dart index ec85df3..721043e 100644 --- a/lib/contacts/views/contacts_dialog.dart +++ b/lib/contacts/views/contacts_dialog.dart @@ -135,7 +135,7 @@ class _ContactsDialogState extends State { ?.localConversationRecordKey .toVeilid(), onContactSelected: _onContactSelected, - onChatStarted: _onChatStarted, + onStartChat: _onChatStarted, ).paddingLTRB(8, 0, 8, 8)))), if (enableRight && enableLeft) Container( diff --git a/lib/settings/settings_page.dart b/lib/settings/settings_page.dart index f164992..2a05f08 100644 --- a/lib/settings/settings_page.dart +++ b/lib/settings/settings_page.dart @@ -31,34 +31,42 @@ class SettingsPageState extends State { @override Widget build(BuildContext context) => AsyncBlocBuilder( - builder: (context, state) => StyledScaffold( - appBar: DefaultAppBar( - title: Text(translate('settings_page.titlebar')), - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => GoRouterHelper(context).pop(), - ), - actions: [ - const SignalStrengthMeterWidget().paddingLTRB(16, 0, 16, 0), - ]), - body: ThemeSwitchingArea( - child: FormBuilder( - key: _formKey, - child: ListView( - padding: const EdgeInsets.all(8), - children: [ - buildSettingsPageColorPreferences( + builder: (context, state) => ThemeSwitcher.withTheme( + builder: (_, switcher, theme) => StyledScaffold( + appBar: DefaultAppBar( + title: Text(translate('settings_page.titlebar')), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => GoRouterHelper(context).pop(), + ), + actions: [ + const SignalStrengthMeterWidget() + .paddingLTRB(16, 0, 16, 0), + ]), + body: ThemeSwitchingArea( + child: FormBuilder( + key: _formKey, + child: ListView( + padding: const EdgeInsets.all(8), + children: [ + buildSettingsPageColorPreferences( + context: context, + switcher: switcher, + onChanged: () => setState(() {})) + .paddingLTRB(0, 8, 0, 0), + buildSettingsPageBrightnessPreferences( context: context, - onChanged: () => setState(() {})) - .paddingLTRB(0, 8, 0, 0), - buildSettingsPageBrightnessPreferences( - context: context, onChanged: () => setState(() {})), - buildSettingsPageWallpaperPreferences( - context: context, onChanged: () => setState(() {})), - buildSettingsPageNotificationPreferences( - context: context, onChanged: () => setState(() {})), - ].map((x) => x.paddingLTRB(0, 0, 0, 8)).toList(), - ), - ).paddingSymmetric(horizontal: 8, vertical: 8), - ))); + switcher: switcher, + onChanged: () => setState(() {})), + buildSettingsPageWallpaperPreferences( + context: context, + switcher: switcher, + onChanged: () => setState(() {})), + buildSettingsPageNotificationPreferences( + context: context, + onChanged: () => setState(() {})), + ].map((x) => x.paddingLTRB(0, 0, 0, 8)).toList(), + ), + ).paddingSymmetric(horizontal: 8, vertical: 8), + )))); } diff --git a/lib/theme/views/brightness_preferences.dart b/lib/theme/views/brightness_preferences.dart index 0c39976..7a1bb1d 100644 --- a/lib/theme/views/brightness_preferences.dart +++ b/lib/theme/views/brightness_preferences.dart @@ -22,24 +22,25 @@ List> _getBrightnessDropdownItems() { } Widget buildSettingsPageBrightnessPreferences( - {required BuildContext context, required void Function() onChanged}) { + {required BuildContext context, + required void Function() onChanged, + required ThemeSwitcherState switcher}) { final preferencesRepository = PreferencesRepository.instance; final themePreferences = preferencesRepository.value.themePreference; - return ThemeSwitcher.withTheme( - builder: (_, switcher, theme) => FormBuilderDropdown( - name: formFieldBrightness, - decoration: InputDecoration( - label: Text(translate('settings_page.brightness_mode'))), - items: _getBrightnessDropdownItems(), - initialValue: themePreferences.brightnessPreference, - onChanged: (value) async { - final newThemePrefs = themePreferences.copyWith( - brightnessPreference: value as BrightnessPreference); - final newPrefs = preferencesRepository.value - .copyWith(themePreference: newThemePrefs); + return FormBuilderDropdown( + name: formFieldBrightness, + decoration: InputDecoration( + label: Text(translate('settings_page.brightness_mode'))), + items: _getBrightnessDropdownItems(), + initialValue: themePreferences.brightnessPreference, + onChanged: (value) async { + final newThemePrefs = themePreferences.copyWith( + brightnessPreference: value as BrightnessPreference); + final newPrefs = preferencesRepository.value + .copyWith(themePreference: newThemePrefs); - await preferencesRepository.set(newPrefs); - switcher.changeTheme(theme: newThemePrefs.themeData()); - onChanged(); - })); + await preferencesRepository.set(newPrefs); + switcher.changeTheme(theme: newThemePrefs.themeData()); + onChanged(); + }); } diff --git a/lib/theme/views/color_preferences.dart b/lib/theme/views/color_preferences.dart index ce03c0a..a9a8841 100644 --- a/lib/theme/views/color_preferences.dart +++ b/lib/theme/views/color_preferences.dart @@ -1,4 +1,5 @@ import 'package:animated_theme_switcher/animated_theme_switcher.dart'; +import 'package:async_tools/async_tools.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_translate/flutter_translate.dart'; @@ -7,6 +8,7 @@ import '../../settings/settings.dart'; import '../models/models.dart'; const String formFieldTheme = 'theme'; +const String _kSwitchTheme = 'switchTheme'; List> _getThemeDropdownItems() { const colorPrefs = ColorPreference.values; @@ -32,24 +34,27 @@ List> _getThemeDropdownItems() { } Widget buildSettingsPageColorPreferences( - {required BuildContext context, required void Function() onChanged}) { + {required BuildContext context, + required void Function() onChanged, + required ThemeSwitcherState switcher}) { final preferencesRepository = PreferencesRepository.instance; final themePreferences = preferencesRepository.value.themePreference; - return ThemeSwitcher.withTheme( - builder: (_, switcher, theme) => FormBuilderDropdown( - name: formFieldTheme, - decoration: InputDecoration( - label: Text(translate('settings_page.color_theme'))), - items: _getThemeDropdownItems(), - initialValue: themePreferences.colorPreference, - onChanged: (value) async { - final newThemePrefs = themePreferences.copyWith( - colorPreference: value as ColorPreference); - final newPrefs = preferencesRepository.value - .copyWith(themePreference: newThemePrefs); + return FormBuilderDropdown( + name: formFieldTheme, + decoration: + InputDecoration(label: Text(translate('settings_page.color_theme'))), + items: _getThemeDropdownItems(), + initialValue: themePreferences.colorPreference, + onChanged: (value) { + singleFuture(_kSwitchTheme, () async { + final newThemePrefs = themePreferences.copyWith( + colorPreference: value as ColorPreference); + final newPrefs = preferencesRepository.value + .copyWith(themePreference: newThemePrefs); - await preferencesRepository.set(newPrefs); - switcher.changeTheme(theme: newThemePrefs.themeData()); - onChanged(); - })); + await preferencesRepository.set(newPrefs); + switcher.changeTheme(theme: newThemePrefs.themeData()); + onChanged(); + }); + }); } diff --git a/lib/theme/views/wallpaper_preferences.dart b/lib/theme/views/wallpaper_preferences.dart index 1c2e9de..48f0a6a 100644 --- a/lib/theme/views/wallpaper_preferences.dart +++ b/lib/theme/views/wallpaper_preferences.dart @@ -9,24 +9,25 @@ import '../models/models.dart'; const String formFieldEnableWallpaper = 'enable_wallpaper'; Widget buildSettingsPageWallpaperPreferences( - {required BuildContext context, required void Function() onChanged}) { + {required BuildContext context, + required void Function() onChanged, + required ThemeSwitcherState switcher}) { final preferencesRepository = PreferencesRepository.instance; final themePreferences = preferencesRepository.value.themePreference; - return ThemeSwitcher.withTheme( - builder: (_, switcher, theme) => FormBuilderCheckbox( - name: formFieldEnableWallpaper, - title: Text(translate('settings_page.enable_wallpaper')), - initialValue: themePreferences.enableWallpaper, - onChanged: (value) async { - if (value != null) { - final newThemePrefs = - themePreferences.copyWith(enableWallpaper: value); - final newPrefs = preferencesRepository.value - .copyWith(themePreference: newThemePrefs); + return FormBuilderCheckbox( + name: formFieldEnableWallpaper, + title: Text(translate('settings_page.enable_wallpaper')), + initialValue: themePreferences.enableWallpaper, + onChanged: (value) async { + if (value != null) { + final newThemePrefs = + themePreferences.copyWith(enableWallpaper: value); + final newPrefs = preferencesRepository.value + .copyWith(themePreference: newThemePrefs); - await preferencesRepository.set(newPrefs); - switcher.changeTheme(theme: newThemePrefs.themeData()); - onChanged(); - } - })); + await preferencesRepository.set(newPrefs); + switcher.changeTheme(theme: newThemePrefs.themeData()); + onChanged(); + } + }); } diff --git a/pubspec.lock b/pubspec.lock index 349a58b..93fdce8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -92,10 +92,11 @@ packages: async_tools: dependency: "direct main" description: - path: "../dart_async_tools" - relative: true - source: path - version: "0.1.7" + name: async_tools + sha256: a258558160d6adc18612d0c635ce0d18ceabc022f7933ce78ca4806075d79578 + url: "https://pub.dev" + source: hosted + version: "0.1.8" auto_size_text: dependency: "direct main" description: @@ -156,10 +157,10 @@ packages: dependency: "direct main" description: name: bloc_advanced_tools - sha256: d8a680d8a0469456399fb26bae9f7a1d2a1420b5bdf75e204e0fadab9edb0811 + sha256: "977f3c7e3f9a19aec2f2c734ae99c8f0799c1b78f9fd7e4dce91a2dbf773e11b" url: "https://pub.dev" source: hosted - version: "0.1.8" + version: "0.1.9" blurry_modal_progress_hud: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 5f924ba..8b4a0a7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,13 +15,13 @@ dependencies: animated_theme_switcher: ^2.0.10 ansicolor: ^2.0.3 archive: ^4.0.4 - async_tools: ^0.1.7 + async_tools: ^0.1.8 auto_size_text: ^3.0.0 awesome_extensions: ^2.0.21 badges: ^3.1.2 basic_utils: ^5.8.2 bloc: ^8.1.4 - bloc_advanced_tools: ^0.1.8 + bloc_advanced_tools: ^0.1.9 blurry_modal_progress_hud: ^1.1.1 change_case: ^2.2.0 charcode: ^1.4.0 @@ -112,9 +112,9 @@ dependencies: xterm: ^4.0.0 zxing2: ^0.2.3 -dependency_overrides: - async_tools: - path: ../dart_async_tools +#dependency_overrides: +# async_tools: +# path: ../dart_async_tools # bloc_advanced_tools: # path: ../bloc_advanced_tools # searchable_listview: From debb475bdcdcade379d23b4d5c345696575720af Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 17 Mar 2025 22:05:06 -0400 Subject: [PATCH 35/93] pod --- ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c00efec..edf99c6 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -151,7 +151,7 @@ SPEC CHECKSUMS: camera_avfoundation: 04b44aeb14070126c6529e5ab82cc7c9fca107cf file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_native_splash: 6cad9122ea0fad137d23137dd14b937f3e90b145 + flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318 GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8 From 0d888363ff35be0a5d5feb60a293b4b69e91b008 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 17 Mar 2025 22:51:34 -0400 Subject: [PATCH 36/93] native device orientation work --- lib/app.dart | 182 ++++++++++--------- lib/layout/home/drawer_menu/drawer_menu.dart | 10 +- lib/layout/home/home_screen.dart | 67 ++++--- 3 files changed, 141 insertions(+), 118 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index 72d845f..683a450 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -8,6 +8,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:native_device_orientation/native_device_orientation.dart'; import 'package:provider/provider.dart'; import 'package:veilid_support/veilid_support.dart'; @@ -100,6 +101,94 @@ class VeilidChatApp extends StatelessWidget { onInvoke: (intent) => _attachDetach(context)), }, child: Focus(autofocus: true, child: builder(context))))); + Widget appBuilder( + BuildContext context, LocalizationDelegate localizationDelegate) => + ThemeProvider( + initTheme: initialThemeData, + builder: (context, theme) => LocalizationProvider( + state: LocalizationProvider.of(context).state, + child: MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => + PreferencesCubit(PreferencesRepository.instance), + ), + BlocProvider( + create: (context) => NotificationsCubit( + const NotificationsState(queue: IList.empty()))), + BlocProvider( + create: (context) => + ConnectionStateCubit(ProcessorRepository.instance)), + BlocProvider( + create: (context) => RouterCubit(AccountRepository.instance), + ), + BlocProvider( + create: (context) => + LocalAccountsCubit(AccountRepository.instance), + ), + BlocProvider( + create: (context) => + UserLoginsCubit(AccountRepository.instance), + ), + BlocProvider( + create: (context) => + ActiveLocalAccountCubit(AccountRepository.instance), + ), + BlocProvider( + create: (context) => PerAccountCollectionBlocMapCubit( + accountRepository: AccountRepository.instance, + locator: context.read)), + ], + child: + BackgroundTicker(child: _buildShortcuts(builder: (context) { + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + + final gradient = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: scaleConfig.preferBorders && + theme.brightness == Brightness.light + ? [ + scale.grayScale.hoverElementBackground, + scale.grayScale.subtleBackground, + ] + : [ + scale.primaryScale.hoverElementBackground, + scale.primaryScale.subtleBackground, + ]); + + final wallpaper = PreferencesRepository + .instance.value.themePreference + .wallpaper(); + + return Stack( + fit: StackFit.expand, + alignment: Alignment.center, + children: [ + wallpaper ?? + DecoratedBox( + decoration: BoxDecoration(gradient: gradient)), + MaterialApp.router( + scrollBehavior: const ScrollBehaviorModified(), + debugShowCheckedModeBanner: false, + routerConfig: context.read().router(), + title: translate('app.title'), + theme: theme, + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + FormBuilderLocalizations.delegate, + localizationDelegate + ], + supportedLocales: localizationDelegate.supportedLocales, + locale: localizationDelegate.currentLocale, + ) + ]); + })), + )), + ); + @override Widget build(BuildContext context) => FutureProvider( initialData: null, @@ -112,93 +201,14 @@ class VeilidChatApp extends StatelessWidget { } // Once init is done, we proceed with the app final localizationDelegate = LocalizedApp.of(context).delegate; - return ThemeProvider( - initTheme: initialThemeData, - builder: (context, theme) => LocalizationProvider( - state: LocalizationProvider.of(context).state, - child: MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => - PreferencesCubit(PreferencesRepository.instance), - ), - BlocProvider( - create: (context) => NotificationsCubit( - const NotificationsState(queue: IList.empty()))), - BlocProvider( - create: (context) => - ConnectionStateCubit(ProcessorRepository.instance)), - BlocProvider( - create: (context) => - RouterCubit(AccountRepository.instance), - ), - BlocProvider( - create: (context) => - LocalAccountsCubit(AccountRepository.instance), - ), - BlocProvider( - create: (context) => - UserLoginsCubit(AccountRepository.instance), - ), - BlocProvider( - create: (context) => - ActiveLocalAccountCubit(AccountRepository.instance), - ), - BlocProvider( - create: (context) => PerAccountCollectionBlocMapCubit( - accountRepository: AccountRepository.instance, - locator: context.read)), - ], - child: - BackgroundTicker(child: _buildShortcuts(builder: (context) { - final scale = theme.extension()!; - final scaleConfig = theme.extension()!; - final gradient = LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: scaleConfig.preferBorders && - theme.brightness == Brightness.light - ? [ - scale.grayScale.hoverElementBackground, - scale.grayScale.subtleBackground, - ] - : [ - scale.primaryScale.hoverElementBackground, - scale.primaryScale.subtleBackground, - ]); - - final wallpaper = PreferencesRepository - .instance.value.themePreference - .wallpaper(); - - return Stack( - fit: StackFit.expand, - alignment: Alignment.center, - children: [ - wallpaper ?? - DecoratedBox( - decoration: BoxDecoration(gradient: gradient)), - MaterialApp.router( - scrollBehavior: const ScrollBehaviorModified(), - debugShowCheckedModeBanner: false, - routerConfig: context.read().router(), - title: translate('app.title'), - theme: theme, - localizationsDelegates: [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - FormBuilderLocalizations.delegate, - localizationDelegate - ], - supportedLocales: - localizationDelegate.supportedLocales, - locale: localizationDelegate.currentLocale, - ) - ]); - })), - )), - ); + if (isiOS || isAndroid) { + return NativeDeviceOrientationReader( + //useSensor: false, + builder: (context) => appBuilder(context, localizationDelegate)); + } else { + return appBuilder(context, localizationDelegate); + } }); @override diff --git a/lib/layout/home/drawer_menu/drawer_menu.dart b/lib/layout/home/drawer_menu/drawer_menu.dart index f88a888..6aa817f 100644 --- a/lib/layout/home/drawer_menu/drawer_menu.dart +++ b/lib/layout/home/drawer_menu/drawer_menu.dart @@ -318,7 +318,7 @@ class _DrawerMenuState extends State { scale.subtleBorder, ]); - return DecoratedBox( + Widget menu = DecoratedBox( decoration: ShapeDecoration( shadows: themedShadow(scaleConfig, scale), gradient: scaleConfig.useVisualIndicators ? null : gradient, @@ -393,6 +393,12 @@ class _DrawerMenuState extends State { ), ]) ]).paddingAll(16), - ).paddingLTRB(0, 2, 2, 2); + ); + + if (scaleConfig.preferBorders || scaleConfig.useVisualIndicators) { + menu = menu.paddingLTRB(0, 2, 2, 2); + } + + return menu; } } diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index 0ec1f26..56f4c02 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -13,6 +13,7 @@ import 'package:veilid_support/veilid_support.dart'; import '../../account_manager/account_manager.dart'; import '../../settings/settings.dart'; import '../../theme/theme.dart'; +import '../../tools/native_safe_area.dart'; import 'drawer_menu/drawer_menu.dart'; import 'home_account_invalid.dart'; import 'home_account_locked.dart'; @@ -208,36 +209,42 @@ class HomeScreenState extends State .indexWhere((x) => x.superIdentity.recordKey == activeLocalAccount); final canClose = activeIndex != -1; - return SafeArea( - child: DefaultTextStyle( - style: theme.textTheme.bodySmall!, - child: ZoomDrawer( - controller: _zoomDrawerController, - menuScreen: Builder(builder: (context) { - final zoomDrawer = ZoomDrawer.of(context); - zoomDrawer!.stateNotifier.addListener(() { - if (zoomDrawer.isOpen()) { - FocusManager.instance.primaryFocus?.unfocus(); - } - }); - return const DrawerMenu(); - }), - mainScreen: Provider.value( - value: _zoomDrawerController, - child: Builder(builder: _buildAccountPageView)), - borderRadius: 0, - angle: 0, - //mainScreenOverlayColor: theme.shadowColor.withAlpha(0x2F), - openCurve: Curves.fastEaseInToSlowEaseOut, - closeCurve: Curves.fastEaseInToSlowEaseOut, - // duration: const Duration(milliseconds: 250), - // reverseDuration: const Duration(milliseconds: 250), - menuScreenTapClose: canClose, - mainScreenTapClose: canClose, - disableDragGesture: !canClose, - mainScreenScale: .25, - slideWidth: min(360, MediaQuery.of(context).size.width * 0.9), - ))); + Widget homeWidget = DefaultTextStyle( + style: theme.textTheme.bodySmall!, + child: ZoomDrawer( + controller: _zoomDrawerController, + menuScreen: Builder(builder: (context) { + final zoomDrawer = ZoomDrawer.of(context); + zoomDrawer!.stateNotifier.addListener(() { + if (zoomDrawer.isOpen()) { + FocusManager.instance.primaryFocus?.unfocus(); + } + }); + return const DrawerMenu(); + }), + mainScreen: Provider.value( + value: _zoomDrawerController, + child: Builder(builder: _buildAccountPageView)), + borderRadius: 0, + angle: 0, + //mainScreenOverlayColor: theme.shadowColor.withAlpha(0x2F), + openCurve: Curves.fastEaseInToSlowEaseOut, + closeCurve: Curves.fastEaseInToSlowEaseOut, + // duration: const Duration(milliseconds: 250), + // reverseDuration: const Duration(milliseconds: 250), + menuScreenTapClose: canClose, + mainScreenTapClose: canClose, + disableDragGesture: !canClose, + mainScreenScale: .25, + slideWidth: min(360, MediaQuery.of(context).size.width * 0.9), + )); + + if (isiOS || isAndroid) { + homeWidget = NativeSafeArea( + bottom: false, left: false, right: false, child: homeWidget); + } + + return homeWidget; } //////////////////////////////////////////////////////////////////////////// From 3c95c9d1a36a908835738e53f0ec7044ce49edf0 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 18 Mar 2025 15:34:39 -0400 Subject: [PATCH 37/93] fix safeareas --- ios/Podfile.lock | 6 -- lib/app.dart | 9 +- lib/chat/views/no_conversation_widget.dart | 44 ++++---- lib/layout/home/home_account_ready.dart | 51 +++++----- lib/layout/home/home_screen.dart | 10 +- lib/tools/native_safe_area.dart | 111 --------------------- lib/veilid_processor/views/developer.dart | 5 +- pubspec.lock | 8 -- pubspec.yaml | 1 - 9 files changed, 51 insertions(+), 194 deletions(-) delete mode 100644 lib/tools/native_safe_area.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index edf99c6..2528d2d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -54,8 +54,6 @@ PODS: - nanopb/encode (= 3.30910.0) - nanopb/decode (3.30910.0) - nanopb/encode (3.30910.0) - - native_device_orientation (0.0.1): - - Flutter - package_info_plus (0.4.5): - Flutter - pasteboard (0.0.1): @@ -87,7 +85,6 @@ DEPENDENCIES: - Flutter (from `Flutter`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`) - - native_device_orientation (from `.symlinks/plugins/native_device_orientation/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - pasteboard (from `.symlinks/plugins/pasteboard/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) @@ -124,8 +121,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_native_splash/ios" mobile_scanner: :path: ".symlinks/plugins/mobile_scanner/ios" - native_device_orientation: - :path: ".symlinks/plugins/native_device_orientation/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" pasteboard: @@ -163,7 +158,6 @@ SPEC CHECKSUMS: MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e mobile_scanner: af8f71879eaba2bbcb4d86c6a462c3c0e7f23036 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - native_device_orientation: e3580675687d5034770da198f6839ebf2122ef94 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 diff --git a/lib/app.dart b/lib/app.dart index 683a450..519f2bb 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -8,7 +8,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; -import 'package:native_device_orientation/native_device_orientation.dart'; import 'package:provider/provider.dart'; import 'package:veilid_support/veilid_support.dart'; @@ -202,13 +201,7 @@ class VeilidChatApp extends StatelessWidget { // Once init is done, we proceed with the app final localizationDelegate = LocalizedApp.of(context).delegate; - if (isiOS || isAndroid) { - return NativeDeviceOrientationReader( - //useSensor: false, - builder: (context) => appBuilder(context, localizationDelegate)); - } else { - return appBuilder(context, localizationDelegate); - } + return SafeArea(child: appBuilder(context, localizationDelegate)); }); @override diff --git a/lib/chat/views/no_conversation_widget.dart b/lib/chat/views/no_conversation_widget.dart index 830e0d6..13c4cc6 100644 --- a/lib/chat/views/no_conversation_widget.dart +++ b/lib/chat/views/no_conversation_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; import '../../theme/models/scale_theme/scale_scheme.dart'; +import '../../theme/views/views.dart'; class NoConversationWidget extends StatelessWidget { const NoConversationWidget({super.key}); @@ -17,27 +18,26 @@ class NoConversationWidget extends StatelessWidget { final scale = scaleScheme.scale(ScaleKind.primary); return DecoratedBox( - decoration: BoxDecoration( - color: scale.appBackground.withAlpha(scaleConfig.wallpaperAlpha), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.diversity_3, - color: scale.appText.withAlpha(127), - size: 48, - ), - Text( - textAlign: TextAlign.center, - translate('chat.start_a_conversation'), - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: scale.appText.withAlpha(127), - ), - ), - ], - ), - ); + decoration: BoxDecoration( + color: scale.appBackground.withAlpha(scaleConfig.wallpaperAlpha), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.diversity_3, + color: scale.appText.withAlpha(127), + size: 48, + ), + Text( + textAlign: TextAlign.center, + translate('chat.start_a_conversation'), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: scale.appText.withAlpha(127), + ), + ), + ], + )); } } diff --git a/lib/layout/home/home_account_ready.dart b/lib/layout/home/home_account_ready.dart index 8710eea..9109b78 100644 --- a/lib/layout/home/home_account_ready.dart +++ b/lib/layout/home/home_account_ready.dart @@ -93,33 +93,32 @@ class _HomeAccountReadyState extends State { }); }); - Widget buildUserPanel() => Builder(builder: (context) { - final profile = context.select( - (c) => c.state.asData!.value.profile); - final theme = Theme.of(context); - final scale = theme.extension()!; - final scaleConfig = theme.extension()!; - - return ColoredBox( - color: scaleConfig.preferBorders - ? scale.primaryScale.subtleBackground - : scale.primaryScale.subtleBorder, - child: Column(children: [ - Row(children: [ - buildMenuButton().paddingLTRB(0, 0, 8, 0), - ProfileWidget( - profile: profile, - showPronouns: false, - ).expanded(), - buildContactsButton().paddingLTRB(8, 0, 0, 0), - ]).paddingAll(8), - const ChatListWidget().expanded() - ])); - }); - Widget buildLeftPane(BuildContext context) => Builder( - builder: (context) => - Material(color: Colors.transparent, child: buildUserPanel())); + builder: (context) => Material( + color: Colors.transparent, + child: Builder(builder: (context) { + final profile = context.select( + (c) => c.state.asData!.value.profile); + final theme = Theme.of(context); + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + + return ColoredBox( + color: scaleConfig.preferBorders + ? scale.primaryScale.subtleBackground + : scale.primaryScale.subtleBorder, + child: Column(children: [ + Row(children: [ + buildMenuButton().paddingLTRB(0, 0, 8, 0), + ProfileWidget( + profile: profile, + showPronouns: false, + ).expanded(), + buildContactsButton().paddingLTRB(8, 0, 0, 0), + ]).paddingAll(8), + const ChatListWidget().expanded() + ])); + }))); Widget buildRightPane(BuildContext context) { final activeChatCubit = context.watch(); diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index 56f4c02..63e2f19 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -13,7 +13,6 @@ import 'package:veilid_support/veilid_support.dart'; import '../../account_manager/account_manager.dart'; import '../../settings/settings.dart'; import '../../theme/theme.dart'; -import '../../tools/native_safe_area.dart'; import 'drawer_menu/drawer_menu.dart'; import 'home_account_invalid.dart'; import 'home_account_locked.dart'; @@ -209,7 +208,7 @@ class HomeScreenState extends State .indexWhere((x) => x.superIdentity.recordKey == activeLocalAccount); final canClose = activeIndex != -1; - Widget homeWidget = DefaultTextStyle( + return DefaultTextStyle( style: theme.textTheme.bodySmall!, child: ZoomDrawer( controller: _zoomDrawerController, @@ -238,13 +237,6 @@ class HomeScreenState extends State mainScreenScale: .25, slideWidth: min(360, MediaQuery.of(context).size.width * 0.9), )); - - if (isiOS || isAndroid) { - homeWidget = NativeSafeArea( - bottom: false, left: false, right: false, child: homeWidget); - } - - return homeWidget; } //////////////////////////////////////////////////////////////////////////// diff --git a/lib/tools/native_safe_area.dart b/lib/tools/native_safe_area.dart deleted file mode 100644 index ed3746e..0000000 --- a/lib/tools/native_safe_area.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; -import 'package:native_device_orientation/native_device_orientation.dart'; - -class NativeSafeArea extends StatelessWidget { - const NativeSafeArea({ - required this.child, - this.left = true, - this.top = true, - this.right = true, - this.bottom = true, - this.minimum = EdgeInsets.zero, - this.maintainBottomViewPadding = false, - super.key, - }); - - /// Whether to avoid system intrusions on the left. - final bool left; - - /// Whether to avoid system intrusions at the top of the screen, typically the - /// system status bar. - final bool top; - - /// Whether to avoid system intrusions on the right. - final bool right; - - /// Whether to avoid system intrusions on the bottom side of the screen. - final bool bottom; - - /// This minimum padding to apply. - /// - /// The greater of the minimum insets and the media padding will be applied. - final EdgeInsets minimum; - - /// Specifies whether the [SafeArea] should maintain the bottom - /// [MediaQueryData.viewPadding] instead of the bottom - /// [MediaQueryData.padding], defaults to false. - /// - /// For example, if there is an onscreen keyboard displayed above the - /// SafeArea, the padding can be maintained below the obstruction rather than - /// being consumed. This can be helpful in cases where your layout contains - /// flexible widgets, which could visibly move when opening a software - /// keyboard due to the change in the padding value. Setting this to true will - /// avoid the UI shift. - final bool maintainBottomViewPadding; - - /// The widget below this widget in the tree. - /// - /// The padding on the [MediaQuery] for the [child] will be suitably adjusted - /// to zero out any sides that were avoided by this widget. - /// - /// {@macro flutter.widgets.ProxyWidget.child} - final Widget child; - - @override - Widget build(BuildContext context) { - final nativeOrientation = - NativeDeviceOrientationReader.orientation(context); - - late final bool realLeft; - late final bool realRight; - late final bool realTop; - late final bool realBottom; - - switch (nativeOrientation) { - case NativeDeviceOrientation.unknown: - case NativeDeviceOrientation.portraitUp: - realLeft = left; - realRight = right; - realTop = top; - realBottom = bottom; - case NativeDeviceOrientation.portraitDown: - realLeft = right; - realRight = left; - realTop = bottom; - realBottom = top; - case NativeDeviceOrientation.landscapeRight: - realLeft = bottom; - realRight = top; - realTop = left; - realBottom = right; - case NativeDeviceOrientation.landscapeLeft: - realLeft = top; - realRight = bottom; - realTop = right; - realBottom = left; - } - - return SafeArea( - left: realLeft, - right: realRight, - top: realTop, - bottom: realBottom, - minimum: minimum, - maintainBottomViewPadding: maintainBottomViewPadding, - child: child); - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('left', left)) - ..add(DiagnosticsProperty('top', top)) - ..add(DiagnosticsProperty('right', right)) - ..add(DiagnosticsProperty('bottom', bottom)) - ..add(DiagnosticsProperty('minimum', minimum)) - ..add(DiagnosticsProperty( - 'maintainBottomViewPadding', maintainBottomViewPadding)); - } -} diff --git a/lib/veilid_processor/views/developer.dart b/lib/veilid_processor/views/developer.dart index 1899c34..cb64d3d 100644 --- a/lib/veilid_processor/views/developer.dart +++ b/lib/veilid_processor/views/developer.dart @@ -260,8 +260,7 @@ class _DeveloperPageState extends State { ), body: GestureDetector( onTap: () => FocusScope.of(context).unfocus(), - child: SafeArea( - child: Column(children: [ + child: Column(children: [ Stack(alignment: AlignmentDirectional.center, children: [ Image.asset('assets/images/ellet.png'), TerminalView(globalDebugTerminal, @@ -333,7 +332,7 @@ class _DeveloperPageState extends State { } }, ).paddingAll(4) - ])))); + ]))); } //////////////////////////////////////////////////////////////////////////// diff --git a/pubspec.lock b/pubspec.lock index 93fdce8..529ae6c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -937,14 +937,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.7" - native_device_orientation: - dependency: "direct main" - description: - name: native_device_orientation - sha256: "0c330c068575e4be72cce5968ca479a3f8d5d1e5dfce7d89d5c13a1e943b338c" - url: "https://pub.dev" - source: hosted - version: "2.0.3" nested: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8b4a0a7..5617c1c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,7 +63,6 @@ dependencies: loggy: ^2.0.3 meta: ^1.16.0 mobile_scanner: ^6.0.7 - native_device_orientation: ^2.0.3 package_info_plus: ^8.3.0 pasteboard: ^0.3.0 path: ^1.9.1 From ae841ec42afed4fef448b1041282b4b21840dcf3 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 19 Mar 2025 23:28:09 -0400 Subject: [PATCH 38/93] theming work, revamp contact invitation --- assets/i18n/en.json | 3 +- .../cubits/active_local_account_cubit.dart | 1 - .../per_account_collection_state.dart | 12 +- lib/chat/views/chat_component_widget.dart | 10 +- lib/chat/views/new_chat_bottom_sheet.dart | 31 -- lib/chat/views/no_conversation_widget.dart | 3 +- lib/chat/views/views.dart | 1 - .../views/contact_invitation_item_widget.dart | 2 +- lib/contacts/views/contacts_browser.dart | 324 ++++++++---------- lib/contacts/views/contacts_dialog.dart | 196 ----------- lib/contacts/views/contacts_page.dart | 171 +++++++++ lib/contacts/views/views.dart | 2 +- lib/layout/home/drawer_menu/drawer_menu.dart | 18 +- .../home/drawer_menu/menu_item_widget.dart | 2 + lib/layout/home/home_account_ready.dart | 27 +- lib/layout/home/home_screen.dart | 14 +- lib/router/cubits/router_cubit.dart | 52 ++- lib/theme/models/chat_theme.dart | 8 +- lib/theme/models/contrast_generator.dart | 47 ++- .../models/scale_theme/scale_scheme.dart | 2 +- lib/theme/models/scale_theme/scale_theme.dart | 66 +++- lib/theme/views/pop_control.dart | 3 +- lib/theme/views/styled_scaffold.dart | 5 +- lib/tools/window_control.dart | 1 - pubspec.lock | 4 +- pubspec.yaml | 6 +- 26 files changed, 504 insertions(+), 507 deletions(-) delete mode 100644 lib/chat/views/new_chat_bottom_sheet.dart delete mode 100644 lib/contacts/views/contacts_dialog.dart create mode 100644 lib/contacts/views/contacts_page.dart diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 61bb802..9192851 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -158,9 +158,8 @@ "away": "Away" }, "add_contact_sheet": { - "new_contact": "New Contact", + "add_contact": "Add Contact", "create_invite": "Create\nInvitation", - "receive_invite": "Receive\nInvitation", "scan_invite": "Scan\nInvitation", "paste_invite": "Paste\nInvitation" }, diff --git a/lib/account_manager/cubits/active_local_account_cubit.dart b/lib/account_manager/cubits/active_local_account_cubit.dart index 58a9cb8..8856848 100644 --- a/lib/account_manager/cubits/active_local_account_cubit.dart +++ b/lib/account_manager/cubits/active_local_account_cubit.dart @@ -14,7 +14,6 @@ class ActiveLocalAccountCubit extends Cubit { switch (change) { case AccountRepositoryChange.activeLocalAccount: emit(_accountRepository.getActiveLocalAccount()); - break; // Ignore these case AccountRepositoryChange.localAccounts: case AccountRepositoryChange.userLogins: diff --git a/lib/account_manager/models/per_account_collection_state/per_account_collection_state.dart b/lib/account_manager/models/per_account_collection_state/per_account_collection_state.dart index 7fc8f0d..9e0a6f0 100644 --- a/lib/account_manager/models/per_account_collection_state/per_account_collection_state.dart +++ b/lib/account_manager/models/per_account_collection_state/per_account_collection_state.dart @@ -32,6 +32,7 @@ class PerAccountCollectionState with _$PerAccountCollectionState { } extension PerAccountCollectionStateExt on PerAccountCollectionState { + // Returns if the account is ready and logged in bool get isReady => avAccountRecordState != null && avAccountRecordState!.isData && @@ -45,7 +46,11 @@ extension PerAccountCollectionStateExt on PerAccountCollectionState { activeConversationsBlocMapCubit != null && activeSingleContactChatBlocMapCubit != null; - Widget provide({required Widget child}) => MultiBlocProvider(providers: [ + /// If we have a selected account and it is ready and not locked, + /// this will provide the unlocked account's cubits to the context + Widget provideReady({required Widget child}) { + if (isReady) { + return MultiBlocProvider(providers: [ BlocProvider.value(value: accountInfoCubit!), BlocProvider.value(value: accountRecordCubit!), BlocProvider.value(value: contactInvitationListCubit!), @@ -56,4 +61,9 @@ extension PerAccountCollectionStateExt on PerAccountCollectionState { BlocProvider.value(value: activeConversationsBlocMapCubit!), BlocProvider.value(value: activeSingleContactChatBlocMapCubit!), ], child: child); + } else { + // Otherwise we just provide the child + return child; + } + } } diff --git a/lib/chat/views/chat_component_widget.dart b/lib/chat/views/chat_component_widget.dart index 8e43299..f41ba47 100644 --- a/lib/chat/views/chat_component_widget.dart +++ b/lib/chat/views/chat_component_widget.dart @@ -33,11 +33,6 @@ class ChatComponentWidget extends StatelessWidget { @override Widget build(BuildContext context) { - // final theme = Theme.of(context); - // final scale = theme.extension()!; - // final scaleConfig = theme.extension()!; - // final textTheme = theme.textTheme; - // Get the account info final accountInfo = context.watch().state; @@ -125,7 +120,7 @@ class ChatComponentWidget extends StatelessWidget { return Column( children: [ Container( - height: 48, + height: 40, decoration: BoxDecoration( color: scale.border, ), @@ -141,9 +136,10 @@ class ChatComponentWidget extends StatelessWidget { )), const Spacer(), IconButton( + iconSize: 24, icon: Icon(Icons.close, color: scale.borderText), onPressed: _onClose) - .paddingLTRB(16, 0, 16, 0) + .paddingLTRB(0, 0, 8, 0) ]), ), DecoratedBox( diff --git a/lib/chat/views/new_chat_bottom_sheet.dart b/lib/chat/views/new_chat_bottom_sheet.dart deleted file mode 100644 index 646a3ec..0000000 --- a/lib/chat/views/new_chat_bottom_sheet.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:awesome_extensions/awesome_extensions.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_translate/flutter_translate.dart'; - -import '../../theme/theme.dart'; - -Widget newChatBottomSheetBuilder( - BuildContext sheetContext, BuildContext context) { - //final theme = Theme.of(sheetContext); - //final scale = theme.extension()!; - - return KeyboardListener( - focusNode: FocusNode(), - onKeyEvent: (ke) { - if (ke.logicalKey == LogicalKeyboardKey.escape) { - Navigator.pop(sheetContext); - } - }, - child: styledBottomSheet( - context: context, - title: translate('add_chat_sheet.new_chat'), - child: SizedBox( - height: 160, - child: const Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text( - 'Group and custom chat functionality is not available yet') - ]).paddingAll(16)))); -} diff --git a/lib/chat/views/no_conversation_widget.dart b/lib/chat/views/no_conversation_widget.dart index 13c4cc6..3269bea 100644 --- a/lib/chat/views/no_conversation_widget.dart +++ b/lib/chat/views/no_conversation_widget.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import '../../theme/models/scale_theme/scale_scheme.dart'; -import '../../theme/views/views.dart'; +import '../../theme/theme.dart'; class NoConversationWidget extends StatelessWidget { const NoConversationWidget({super.key}); diff --git a/lib/chat/views/views.dart b/lib/chat/views/views.dart index 7e8adce..9703643 100644 --- a/lib/chat/views/views.dart +++ b/lib/chat/views/views.dart @@ -1,4 +1,3 @@ export 'chat_component_widget.dart'; export 'empty_chat_widget.dart'; -export 'new_chat_bottom_sheet.dart'; export 'no_conversation_widget.dart'; diff --git a/lib/contact_invitation/views/contact_invitation_item_widget.dart b/lib/contact_invitation/views/contact_invitation_item_widget.dart index 779f962..544e5db 100644 --- a/lib/contact_invitation/views/contact_invitation_item_widget.dart +++ b/lib/contact_invitation/views/contact_invitation_item_widget.dart @@ -48,7 +48,7 @@ class ContactInvitationItemWidget extends StatelessWidget { key: ObjectKey(contactInvitationRecord), disabled: tileDisabled, selected: selected, - tileScale: ScaleKind.primary, + tileScale: ScaleKind.secondary, title: title, leading: const Icon(Icons.person_add), onTap: () async { diff --git a/lib/contacts/views/contacts_browser.dart b/lib/contacts/views/contacts_browser.dart index c5a6b22..74cd0b5 100644 --- a/lib/contacts/views/contacts_browser.dart +++ b/lib/contacts/views/contacts_browser.dart @@ -17,19 +17,25 @@ import 'contact_item_widget.dart'; import 'empty_contact_list_widget.dart'; enum ContactsBrowserElementKind { - invitation, contact, + invitation, } class ContactsBrowserElement { - ContactsBrowserElement.invitation(proto.ContactInvitationRecord i) - : kind = ContactsBrowserElementKind.invitation, - contact = null, - invitation = i; ContactsBrowserElement.contact(proto.Contact c) : kind = ContactsBrowserElementKind.contact, invitation = null, contact = c; + ContactsBrowserElement.invitation(proto.ContactInvitationRecord i) + : kind = ContactsBrowserElementKind.invitation, + contact = null, + invitation = i; + + String get sortKey => switch (kind) { + ContactsBrowserElementKind.contact => contact!.displayName, + ContactsBrowserElementKind.invitation => + invitation!.recipient + invitation!.message + }; final ContactsBrowserElementKind kind; final proto.ContactInvitationRecord? invitation; @@ -66,27 +72,25 @@ class ContactsBrowser extends StatefulWidget { class _ContactsBrowserState extends State with SingleTickerProviderStateMixin { - Widget buildInvitationBar(BuildContext context) { + Widget buildInvitationButton(BuildContext context) { final theme = Theme.of(context); - final textTheme = theme.textTheme; - final scale = theme.extension()!; + final scaleScheme = theme.extension()!; final scaleConfig = theme.extension()!; final menuIconColor = scaleConfig.preferBorders - ? scale.primaryScale.hoverBorder - : scale.primaryScale.hoverBorder; + ? scaleScheme.primaryScale.hoverBorder + : scaleScheme.primaryScale.hoverBorder; final menuBackgroundColor = scaleConfig.preferBorders - ? scale.primaryScale.elementBackground - : scale.primaryScale.elementBackground; + ? scaleScheme.primaryScale.activeElementBackground + : scaleScheme.primaryScale.activeElementBackground; - final menuBorderColor = scale.primaryScale.hoverBorder; + final menuBorderColor = scaleScheme.primaryScale.hoverBorder; final menuParams = StarMenuParameters( - shape: MenuShape.grid, - checkItemsScreenBoundaries: true, + shape: MenuShape.linear, centerOffset: const Offset(0, 64), - backgroundParams: - BackgroundParams(backgroundColor: theme.shadowColor.withAlpha(128)), + // backgroundParams: + // BackgroundParams(backgroundColor: theme.shadowColor.withAlpha(128)), boundaryBackground: BoundaryBackground( color: menuBackgroundColor, decoration: ShapeDecoration( @@ -99,89 +103,64 @@ class _ContactsBrowserState extends State borderRadius: BorderRadius.circular( 8 * scaleConfig.borderRadiusScale))))); - final receiveInviteMenuItems = [ - Column(mainAxisSize: MainAxisSize.min, children: [ - IconButton( - onPressed: () async { - _receiveInviteMenuController.closeMenu!(); - await ScanInvitationDialog.show(context); - }, - iconSize: 32, - icon: Icon( - Icons.qr_code_scanner, - size: 32, - color: menuIconColor, - ), - ), - Text(translate('add_contact_sheet.scan_invite'), - maxLines: 2, - textAlign: TextAlign.center, - style: textTheme.labelSmall!.copyWith(color: menuIconColor)) - ]).paddingAll(4), - Column(mainAxisSize: MainAxisSize.min, children: [ - IconButton( - onPressed: () async { - _receiveInviteMenuController.closeMenu!(); - await PasteInvitationDialog.show(context); - }, - iconSize: 32, - icon: Icon( - Icons.paste, - size: 32, - color: menuIconColor, - ), - ), - Text(translate('add_contact_sheet.paste_invite'), - maxLines: 2, - textAlign: TextAlign.center, - style: textTheme.labelSmall!.copyWith(color: menuIconColor)) - ]).paddingAll(4) - ]; - - return Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Column(mainAxisSize: MainAxisSize.min, children: [ - IconButton( - onPressed: () async { - await CreateInvitationDialog.show(context); - }, - iconSize: 32, - icon: const Icon(Icons.contact_page), - color: menuIconColor, - ), - Text(translate('add_contact_sheet.create_invite'), - maxLines: 2, - textAlign: TextAlign.center, - style: textTheme.labelSmall!.copyWith(color: menuIconColor)) - ]), - StarMenu( - items: receiveInviteMenuItems, - onItemTapped: (_index, controller) { - controller.closeMenu!(); - }, - controller: _receiveInviteMenuController, - params: menuParams, - child: Column(mainAxisSize: MainAxisSize.min, children: [ - IconButton( - onPressed: () {}, - iconSize: 32, - icon: ImageIcon( - const AssetImage('assets/images/handshake.png'), - size: 32, - color: menuIconColor, - )), - Text(translate('add_contact_sheet.receive_invite'), + ElevatedButton makeMenuButton( + {required IconData iconData, + required String text, + required void Function()? onPressed}) => + ElevatedButton.icon( + onPressed: onPressed, + icon: Icon( + iconData, + size: 32, + ).paddingSTEB(0, 8, 0, 8), + label: Text( + text, maxLines: 2, textAlign: TextAlign.center, - style: textTheme.labelSmall!.copyWith(color: menuIconColor)) - ]), - ), - ]).paddingAll(16); + ).paddingSTEB(0, 8, 0, 8)); + + final inviteMenuItems = [ + makeMenuButton( + iconData: Icons.paste, + text: translate('add_contact_sheet.paste_invite'), + onPressed: () async { + _invitationMenuController.closeMenu!(); + await PasteInvitationDialog.show(context); + }), + makeMenuButton( + iconData: Icons.qr_code_scanner, + text: translate('add_contact_sheet.scan_invite'), + onPressed: () async { + _invitationMenuController.closeMenu!(); + await ScanInvitationDialog.show(context); + }).paddingLTRB(0, 0, 0, 8), + makeMenuButton( + iconData: Icons.contact_page, + text: translate('add_contact_sheet.create_invite'), + onPressed: () async { + _invitationMenuController.closeMenu!(); + await CreateInvitationDialog.show(context); + }).paddingLTRB(0, 0, 0, 8), + ]; + + return StarMenu( + items: inviteMenuItems, + onItemTapped: (_index, controller) { + controller.closeMenu!(); + }, + controller: _invitationMenuController, + params: menuParams, + child: IconButton( + onPressed: () {}, + iconSize: 24, + icon: Icon(Icons.person_add, color: menuIconColor), + tooltip: translate('add_contact_sheet.add_contact')), + ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); - final textTheme = theme.textTheme; final scale = theme.extension()!; //final scaleConfig = theme.extension()!; @@ -196,100 +175,89 @@ class _ContactsBrowserState extends State final contactList = ciState.state.asData?.value.map((x) => x.value).toIList(); - final expansionListData = - >{}; - if (contactInvitationRecordList.isNotEmpty) { - expansionListData[ContactsBrowserElementKind.invitation] = - contactInvitationRecordList - .toList() - .map(ContactsBrowserElement.invitation) - .toList(); - } + final initialList = []; if (contactList != null) { - expansionListData[ContactsBrowserElementKind.contact] = - contactList.toList().map(ContactsBrowserElement.contact).toList(); + initialList + .addAll(contactList.toList().map(ContactsBrowserElement.contact)); } + if (contactInvitationRecordList.isNotEmpty) { + initialList.addAll(contactInvitationRecordList + .toList() + .map(ContactsBrowserElement.invitation)); + } + + initialList.sort((a, b) => a.sortKey.compareTo(b.sortKey)); return Column(children: [ - buildInvitationBar(context), - SearchableList.expansion( - expansionListData: expansionListData, - expansionTitleBuilder: (k) { - final kind = k as ContactsBrowserElementKind; - late final String title; - switch (kind) { - case ContactsBrowserElementKind.contact: - title = translate('contacts_dialog.contacts'); - case ContactsBrowserElementKind.invitation: - title = translate('contacts_dialog.invitations'); - } - - return Center( - child: Text(title, style: textTheme.titleSmall), - ); - }, - expansionInitiallyExpanded: (k) => true, - expansionListBuilder: (_index, element) { - switch (element.kind) { - case ContactsBrowserElementKind.contact: - final contact = element.contact!; - return ContactItemWidget( - contact: contact, - selected: widget.selectedContactRecordKey == - contact.localConversationRecordKey.toVeilid(), - disabled: false, - onDoubleTap: _onStartChat, - onTap: _onSelectContact, - onDelete: _onDeleteContact) - .paddingLTRB(0, 4, 0, 0); - case ContactsBrowserElementKind.invitation: - final invitation = element.invitation!; - return ContactInvitationItemWidget( - contactInvitationRecord: invitation, disabled: false) - .paddingLTRB(0, 4, 0, 0); - } - }, - filterExpansionData: (value) { - final lowerValue = value.toLowerCase(); - final filteredMap = { - for (final entry in expansionListData.entries) - entry.key: (expansionListData[entry.key] ?? []).where((element) { + SearchableList( + initialList: initialList, + itemBuilder: (element) { switch (element.kind) { case ContactsBrowserElementKind.contact: final contact = element.contact!; - return contact.nickname - .toLowerCase() - .contains(lowerValue) || - contact.profile.name - .toLowerCase() - .contains(lowerValue) || - contact.profile.pronouns - .toLowerCase() - .contains(lowerValue); + return ContactItemWidget( + contact: contact, + selected: widget.selectedContactRecordKey == + contact.localConversationRecordKey.toVeilid(), + disabled: false, + onDoubleTap: _onStartChat, + onTap: _onSelectContact, + onDelete: _onDeleteContact) + .paddingLTRB(0, 4, 0, 0); case ContactsBrowserElementKind.invitation: final invitation = element.invitation!; - return invitation.message - .toLowerCase() - .contains(lowerValue) || - invitation.recipient.toLowerCase().contains(lowerValue); + return ContactInvitationItemWidget( + contactInvitationRecord: invitation, + disabled: false) + .paddingLTRB(0, 4, 0, 0); } - }).toList() - }; - return filteredMap; - }, - hideEmptyExpansionItems: true, - searchFieldHeight: 40, - listViewPadding: const EdgeInsets.all(4), - spaceBetweenSearchAndList: 4, - emptyWidget: contactList == null - ? waitingPage(text: translate('contact_list.loading_contacts')) - : const EmptyContactListWidget(), - defaultSuffixIconColor: scale.primaryScale.border, - closeKeyboardWhenScrolling: true, - searchFieldEnabled: contactList != null, - inputDecoration: - InputDecoration(labelText: translate('contact_list.search')), - ).expanded() + }, + filter: (value) { + final lowerValue = value.toLowerCase(); + + final filtered = []; + for (final element in initialList) { + switch (element.kind) { + case ContactsBrowserElementKind.contact: + final contact = element.contact!; + if (contact.nickname.toLowerCase().contains(lowerValue) || + contact.profile.name + .toLowerCase() + .contains(lowerValue) || + contact.profile.pronouns + .toLowerCase() + .contains(lowerValue)) { + filtered.add(element); + } + case ContactsBrowserElementKind.invitation: + final invitation = element.invitation!; + if (invitation.message + .toLowerCase() + .contains(lowerValue) || + invitation.recipient + .toLowerCase() + .contains(lowerValue)) { + filtered.add(element); + } + } + } + return filtered; + }, + searchFieldHeight: 40, + listViewPadding: const EdgeInsets.fromLTRB(4, 0, 4, 4), + searchFieldPadding: const EdgeInsets.fromLTRB(4, 8, 4, 4), + emptyWidget: contactList == null + ? waitingPage( + text: translate('contact_list.loading_contacts')) + : const EmptyContactListWidget(), + defaultSuffixIconColor: scale.primaryScale.border, + closeKeyboardWhenScrolling: true, + searchFieldEnabled: contactList != null, + inputDecoration: + InputDecoration(labelText: translate('contact_list.search')), + secondaryWidget: + buildInvitationButton(context).paddingLTRB(4, 0, 0, 0)) + .expanded() ]); } @@ -318,5 +286,5 @@ class _ContactsBrowserState extends State } //////////////////////////////////////////////////////////////////////////// - final _receiveInviteMenuController = StarMenuController(); + final _invitationMenuController = StarMenuController(); } diff --git a/lib/contacts/views/contacts_dialog.dart b/lib/contacts/views/contacts_dialog.dart deleted file mode 100644 index 721043e..0000000 --- a/lib/contacts/views/contacts_dialog.dart +++ /dev/null @@ -1,196 +0,0 @@ -import 'package:async_tools/async_tools.dart'; -import 'package:awesome_extensions/awesome_extensions.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_translate/flutter_translate.dart'; -import 'package:provider/provider.dart'; - -import '../../chat/chat.dart'; -import '../../chat_list/chat_list.dart'; -import '../../layout/layout.dart'; -import '../../proto/proto.dart' as proto; -import '../../theme/theme.dart'; -import '../contacts.dart'; - -const _kDoBackArrow = 'doBackArrow'; - -class ContactsDialog extends StatefulWidget { - const ContactsDialog._({required this.modalContext}); - - @override - State createState() => _ContactsDialogState(); - - static Future show(BuildContext modalContext) async { - await showDialog( - context: modalContext, - barrierDismissible: false, - useRootNavigator: false, - builder: (context) => ContactsDialog._(modalContext: modalContext)); - } - - final BuildContext modalContext; - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - .add(DiagnosticsProperty('modalContext', modalContext)); - } -} - -class _ContactsDialogState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final scale = theme.extension()!; - final appBarIconColor = scale.primaryScale.borderText; - - final enableSplit = !isMobileWidth(context); - final enableLeft = enableSplit || _selectedContact == null; - final enableRight = enableSplit || _selectedContact != null; - - return SizedBox( - width: MediaQuery.of(context).size.width, - child: StyledScaffold( - appBar: DefaultAppBar( - title: Text(!enableSplit && enableRight - ? translate('contacts_dialog.edit_contact') - : translate('contacts_dialog.contacts')), - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () { - singleFuture((this, _kDoBackArrow), () async { - final confirmed = await _onContactSelected(null); - if (!enableSplit && enableRight) { - } else { - if (confirmed) { - if (context.mounted) { - Navigator.pop(context); - } - } - } - }); - }, - ), - actions: [ - if (_selectedContact != null) - FittedBox( - fit: BoxFit.scaleDown, - child: - Column(mainAxisSize: MainAxisSize.min, children: [ - IconButton( - icon: const Icon(Icons.chat_bubble), - color: appBarIconColor, - tooltip: translate('contacts_dialog.new_chat'), - onPressed: () async { - await _onChatStarted(_selectedContact!); - }), - Text(translate('contacts_dialog.new_chat'), - style: theme.textTheme.labelSmall! - .copyWith(color: appBarIconColor)), - ])).paddingLTRB(8, 0, 8, 0), - if (enableSplit && _selectedContact != null) - FittedBox( - fit: BoxFit.scaleDown, - child: - Column(mainAxisSize: MainAxisSize.min, children: [ - IconButton( - icon: const Icon(Icons.close), - color: appBarIconColor, - tooltip: - translate('contacts_dialog.close_contact'), - onPressed: () async { - await _onContactSelected(null); - }), - Text(translate('contacts_dialog.close_contact'), - style: theme.textTheme.labelSmall! - .copyWith(color: appBarIconColor)), - ])).paddingLTRB(8, 0, 8, 0), - ]), - body: LayoutBuilder(builder: (context, constraint) { - final maxWidth = constraint.maxWidth; - - return ColoredBox( - color: scale.primaryScale.appBackground, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Offstage( - offstage: !enableLeft, - child: SizedBox( - width: enableLeft && !enableRight - ? maxWidth - : (maxWidth / 3).clamp(200, 500), - child: DecoratedBox( - decoration: BoxDecoration( - color: scale - .primaryScale.subtleBackground), - child: ContactsBrowser( - selectedContactRecordKey: _selectedContact - ?.localConversationRecordKey - .toVeilid(), - onContactSelected: _onContactSelected, - onStartChat: _onChatStarted, - ).paddingLTRB(8, 0, 8, 8)))), - if (enableRight && enableLeft) - Container( - constraints: const BoxConstraints( - minWidth: 1, maxWidth: 1), - color: scale.primaryScale.subtleBorder), - if (enableRight) - if (_selectedContact == null) - const NoContactWidget().expanded() - else - ContactDetailsWidget( - contact: _selectedContact!, - onModifiedState: _onModifiedState) - .paddingLTRB(16, 16, 16, 16) - .expanded(), - ])); - }))); - } - - void _onModifiedState(bool isModified) { - setState(() { - _isModified = isModified; - }); - } - - Future _onContactSelected(proto.Contact? contact) async { - if (contact != _selectedContact && _isModified) { - final ok = await showConfirmModal( - context: context, - title: translate('confirmation.discard_changes'), - text: translate('confirmation.are_you_sure_discard')); - if (!ok) { - return false; - } - } - setState(() { - _selectedContact = contact; - _isModified = false; - }); - return true; - } - - Future _onChatStarted(proto.Contact contact) async { - final chatListCubit = context.read(); - await chatListCubit.getOrCreateChatSingleContact(contact: contact); - - if (mounted) { - context - .read() - .setActiveChat(contact.localConversationRecordKey.toVeilid()); - - Navigator.pop(context); - } - } - - proto.Contact? _selectedContact; - bool _isModified = false; -} diff --git a/lib/contacts/views/contacts_page.dart b/lib/contacts/views/contacts_page.dart new file mode 100644 index 0000000..28217e6 --- /dev/null +++ b/lib/contacts/views/contacts_page.dart @@ -0,0 +1,171 @@ +import 'package:async_tools/async_tools.dart'; +import 'package:awesome_extensions/awesome_extensions.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; +import 'package:provider/provider.dart'; + +import '../../chat/chat.dart'; +import '../../chat_list/chat_list.dart'; +import '../../layout/layout.dart'; +import '../../proto/proto.dart' as proto; +import '../../theme/theme.dart'; +import '../contacts.dart'; + +const _kDoBackArrow = 'doBackArrow'; + +class ContactsPage extends StatefulWidget { + const ContactsPage({super.key}); + + @override + State createState() => _ContactsPageState(); +} + +class _ContactsPageState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final scale = theme.extension()!; + final appBarIconColor = scale.primaryScale.borderText; + + final enableSplit = !isMobileSize(context); + final enableLeft = enableSplit || _selectedContact == null; + final enableRight = enableSplit || _selectedContact != null; + + return StyledScaffold( + appBar: DefaultAppBar( + title: Text(!enableSplit && enableRight + ? translate('contacts_dialog.edit_contact') + : translate('contacts_dialog.contacts')), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + singleFuture((this, _kDoBackArrow), () async { + final confirmed = await _onContactSelected(null); + if (!enableSplit && enableRight) { + } else { + if (confirmed) { + if (context.mounted) { + Navigator.pop(context); + } + } + } + }); + }, + ), + actions: [ + if (_selectedContact != null) + FittedBox( + fit: BoxFit.scaleDown, + child: Column(mainAxisSize: MainAxisSize.min, children: [ + IconButton( + icon: const Icon(Icons.chat_bubble), + color: appBarIconColor, + tooltip: translate('contacts_dialog.new_chat'), + onPressed: () async { + await _onChatStarted(_selectedContact!); + }), + Text(translate('contacts_dialog.new_chat'), + style: theme.textTheme.labelSmall! + .copyWith(color: appBarIconColor)), + ])).paddingLTRB(8, 0, 8, 0), + if (enableSplit && _selectedContact != null) + FittedBox( + fit: BoxFit.scaleDown, + child: Column(mainAxisSize: MainAxisSize.min, children: [ + IconButton( + icon: const Icon(Icons.close), + color: appBarIconColor, + tooltip: translate('contacts_dialog.close_contact'), + onPressed: () async { + await _onContactSelected(null); + }), + Text(translate('contacts_dialog.close_contact'), + style: theme.textTheme.labelSmall! + .copyWith(color: appBarIconColor)), + ])).paddingLTRB(8, 0, 8, 0), + ]), + body: LayoutBuilder(builder: (context, constraint) { + final maxWidth = constraint.maxWidth; + + return ColoredBox( + color: scale.primaryScale.appBackground, + child: + Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Offstage( + offstage: !enableLeft, + child: SizedBox( + width: enableLeft && !enableRight + ? maxWidth + : (maxWidth / 3).clamp(200, 500), + child: DecoratedBox( + decoration: BoxDecoration( + color: scale.primaryScale.subtleBackground), + child: ContactsBrowser( + selectedContactRecordKey: _selectedContact + ?.localConversationRecordKey + .toVeilid(), + onContactSelected: _onContactSelected, + onStartChat: _onChatStarted, + ).paddingLTRB(8, 0, 8, 8)))), + if (enableRight && enableLeft) + Container( + constraints: + const BoxConstraints(minWidth: 1, maxWidth: 1), + color: scale.primaryScale.subtleBorder), + if (enableRight) + if (_selectedContact == null) + const NoContactWidget().expanded() + else + ContactDetailsWidget( + contact: _selectedContact!, + onModifiedState: _onModifiedState) + .paddingLTRB(16, 16, 16, 16) + .expanded(), + ])); + })); + } + + void _onModifiedState(bool isModified) { + setState(() { + _isModified = isModified; + }); + } + + Future _onContactSelected(proto.Contact? contact) async { + if (contact != _selectedContact && _isModified) { + final ok = await showConfirmModal( + context: context, + title: translate('confirmation.discard_changes'), + text: translate('confirmation.are_you_sure_discard')); + if (!ok) { + return false; + } + } + setState(() { + _selectedContact = contact; + _isModified = false; + }); + return true; + } + + Future _onChatStarted(proto.Contact contact) async { + final chatListCubit = context.read(); + await chatListCubit.getOrCreateChatSingleContact(contact: contact); + + if (mounted) { + context + .read() + .setActiveChat(contact.localConversationRecordKey.toVeilid()); + Navigator.pop(context); + } + } + + proto.Contact? _selectedContact; + bool _isModified = false; +} diff --git a/lib/contacts/views/views.dart b/lib/contacts/views/views.dart index 55b96d0..b74aff3 100644 --- a/lib/contacts/views/views.dart +++ b/lib/contacts/views/views.dart @@ -2,7 +2,7 @@ export 'availability_widget.dart'; export 'contact_details_widget.dart'; export 'contact_item_widget.dart'; export 'contacts_browser.dart'; -export 'contacts_dialog.dart'; +export 'contacts_page.dart'; export 'edit_contact_form.dart'; export 'empty_contact_list_widget.dart'; export 'no_contact_widget.dart'; diff --git a/lib/layout/home/drawer_menu/drawer_menu.dart b/lib/layout/home/drawer_menu/drawer_menu.dart index 6aa817f..779609c 100644 --- a/lib/layout/home/drawer_menu/drawer_menu.dart +++ b/lib/layout/home/drawer_menu/drawer_menu.dart @@ -84,8 +84,9 @@ class _DrawerMenuState extends State { hoverBorder = border; activeBorder = border; } else { - background = - selected ? scale.activeElementBackground : scale.elementBackground; + background = selected + ? scale.elementBackground + : scale.elementBackground.withAlpha(128); hoverBackground = scale.hoverElementBackground; activeBackground = scale.activeElementBackground; border = loggedIn ? scale.border : scale.subtleBorder; @@ -132,9 +133,16 @@ class _DrawerMenuState extends State { callback: callback, footerButtonIcon: loggedIn ? Icons.edit_outlined : null, footerCallback: footerCallback, - footerButtonIconColor: border, - footerButtonIconHoverColor: hoverBackground, - footerButtonIconFocusColor: activeBackground, + footerButtonIconColor: + scaleConfig.preferBorders ? scale.border : scale.borderText, + footerButtonIconHoverColor: + (scaleConfig.preferBorders || scaleConfig.useVisualIndicators) + ? null + : hoverBorder, + footerButtonIconFocusColor: + (scaleConfig.preferBorders || scaleConfig.useVisualIndicators) + ? null + : activeBorder, minHeight: 48, )); } diff --git a/lib/layout/home/drawer_menu/menu_item_widget.dart b/lib/layout/home/drawer_menu/menu_item_widget.dart index a786010..1255458 100644 --- a/lib/layout/home/drawer_menu/menu_item_widget.dart +++ b/lib/layout/home/drawer_menu/menu_item_widget.dart @@ -39,6 +39,8 @@ class MenuItemWidget extends StatelessWidget { } return backgroundColor; }), + overlayColor: + WidgetStateProperty.resolveWith((states) => backgroundHoverColor), side: WidgetStateBorderSide.resolveWith((states) { if (states.contains(WidgetState.hovered)) { return borderColor != null diff --git a/lib/layout/home/home_account_ready.dart b/lib/layout/home/home_account_ready.dart index 9109b78..5f90c9f 100644 --- a/lib/layout/home/home_account_ready.dart +++ b/lib/layout/home/home_account_ready.dart @@ -89,7 +89,11 @@ class _HomeAccountReadyState extends State { )), tooltip: translate('menu.contacts_tooltip'), onPressed: () async { - await ContactsDialog.show(context); + await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const ContactsPage(), + ), + ); }); }); @@ -139,10 +143,7 @@ class _HomeAccountReadyState extends State { @override Widget build(BuildContext context) { - final isLarge = responsiveVisibility( - context: context, - phone: false, - ); + final isSmallScreen = isMobileSize(context); final theme = Theme.of(context); final scaleScheme = theme.extension()!; @@ -159,14 +160,7 @@ class _HomeAccountReadyState extends State { late final bool visibleRight; late final double leftWidth; late final double rightWidth; - if (isLarge) { - visibleLeft = true; - visibleRight = true; - leftWidth = leftColumnSize; - rightWidth = constraints.maxWidth - - leftColumnSize - - (scaleConfig.useVisualIndicators ? 2 : 0); - } else { + if (isSmallScreen) { if (hasActiveChat) { visibleLeft = false; visibleRight = true; @@ -178,6 +172,13 @@ class _HomeAccountReadyState extends State { leftWidth = constraints.maxWidth; rightWidth = 400; // whatever } + } else { + visibleLeft = true; + visibleRight = true; + leftWidth = leftColumnSize; + rightWidth = constraints.maxWidth - + leftColumnSize - + (scaleConfig.useVisualIndicators ? 2 : 0); } return Row(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index 63e2f19..a534415 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -136,15 +136,11 @@ class HomeScreenState extends State } // Re-export all ready blocs to the account display subtree - return perAccountCollectionState.provide( - child: Navigator( - onPopPage: (route, result) { - if (!route.didPop(result)) { - return false; - } - return true; - }, - pages: const [MaterialPage(child: HomeAccountReady())])); + final pages = >[ + const MaterialPage(child: HomeAccountReady()) + ]; + return perAccountCollectionState.provideReady( + child: Navigator(onDidRemovePage: pages.remove, pages: pages)); } } diff --git a/lib/router/cubits/router_cubit.dart b/lib/router/cubits/router_cubit.dart index 48bf95f..6684c45 100644 --- a/lib/router/cubits/router_cubit.dart +++ b/lib/router/cubits/router_cubit.dart @@ -65,29 +65,49 @@ class RouterCubit extends Cubit { ), GoRoute( path: '/edit_account', + redirect: (_, state) { + final extra = state.extra; + if (extra == null || + extra is! List || + extra[0] is! TypedKey) { + return '/'; + } + return null; + }, builder: (context, state) { - final extra = state.extra! as List; + final extra = state.extra! as List; return EditAccountPage( - superIdentityRecordKey: extra[0]! as TypedKey, - initialValue: extra[1]! as AccountSpec, - accountRecord: extra[2]! as OwnedDHTRecordPointer, + superIdentityRecordKey: extra[0] as TypedKey, + initialValue: extra[1] as AccountSpec, + accountRecord: extra[2] as OwnedDHTRecordPointer, ); }, ), GoRoute( - path: '/new_account', - builder: (context, state) => const NewAccountPage(), - ), - GoRoute( - path: '/new_account/recovery_key', - builder: (context, state) { - final extra = state.extra! as List; + path: '/new_account', + builder: (context, state) => const NewAccountPage(), + routes: [ + GoRoute( + path: 'recovery_key', + redirect: (_, state) { + final extra = state.extra; + if (extra == null || + extra is! List || + extra[0] is! WritableSuperIdentity || + extra[1] is! String) { + return '/'; + } + return null; + }, + builder: (context, state) { + final extra = state.extra! as List; - return ShowRecoveryKeyPage( - writableSuperIdentity: - extra[0]! as WritableSuperIdentity, - name: extra[1]! as String); - }), + return ShowRecoveryKeyPage( + writableSuperIdentity: + extra[0] as WritableSuperIdentity, + name: extra[1] as String); + }), + ]), GoRoute( path: '/settings', builder: (context, state) => const SettingsPage(), diff --git a/lib/theme/models/chat_theme.dart b/lib/theme/models/chat_theme.dart index 5552979..b650e17 100644 --- a/lib/theme/models/chat_theme.dart +++ b/lib/theme/models/chat_theme.dart @@ -16,7 +16,7 @@ ChatTheme makeChatTheme( : scale.secondaryScale.calloutBackground, backgroundColor: scale.grayScale.appBackground.withAlpha(scaleConfig.wallpaperAlpha), - messageBorderRadius: scaleConfig.borderRadiusScale * 16, + messageBorderRadius: scaleConfig.borderRadiusScale * 12, bubbleBorderSide: scaleConfig.preferBorders ? BorderSide( color: scale.primaryScale.calloutBackground, @@ -37,7 +37,7 @@ ChatTheme makeChatTheme( filled: !scaleConfig.preferBorders, fillColor: scale.primaryScale.subtleBackground, isDense: true, - contentPadding: const EdgeInsets.fromLTRB(8, 12, 8, 12), + contentPadding: const EdgeInsets.fromLTRB(8, 8, 8, 8), disabledBorder: OutlineInputBorder( borderSide: scaleConfig.preferBorders ? BorderSide(color: scale.grayScale.border, width: 2) @@ -65,10 +65,12 @@ ChatTheme makeChatTheme( color: scaleConfig.preferBorders ? scale.primaryScale.elementBackground : scale.primaryScale.border), - inputPadding: const EdgeInsets.all(12), + inputPadding: const EdgeInsets.all(6), inputTextColor: !scaleConfig.preferBorders ? scale.primaryScale.appText : scale.primaryScale.border, + messageInsetsHorizontal: 12, + messageInsetsVertical: 8, attachmentButtonIcon: const Icon(Icons.attach_file), sentMessageBodyTextStyle: textTheme.bodyLarge!.copyWith( color: scaleConfig.preferBorders diff --git a/lib/theme/models/contrast_generator.dart b/lib/theme/models/contrast_generator.dart index 861b052..314e28a 100644 --- a/lib/theme/models/contrast_generator.dart +++ b/lib/theme/models/contrast_generator.dart @@ -263,6 +263,36 @@ ThemeData contrastGenerator({ final baseThemeData = scaleTheme.toThemeData(brightness); + WidgetStateProperty elementBorderWidgetStateProperty() => + WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return BorderSide(color: scheme.grayScale.border.withAlpha(0x7F)); + } else if (states.contains(WidgetState.pressed)) { + return BorderSide( + color: scheme.primaryScale.border, + strokeAlign: BorderSide.strokeAlignOutside); + } else if (states.contains(WidgetState.hovered)) { + return BorderSide(color: scheme.primaryScale.hoverBorder, width: 2); + } else if (states.contains(WidgetState.focused)) { + return BorderSide(color: scheme.primaryScale.hoverBorder, width: 2); + } + return BorderSide(color: scheme.primaryScale.border); + }); + + WidgetStateProperty elementBackgroundWidgetStateProperty() => + WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return scheme.grayScale.elementBackground; + } else if (states.contains(WidgetState.pressed)) { + return scheme.primaryScale.activeElementBackground; + } else if (states.contains(WidgetState.hovered)) { + return scheme.primaryScale.hoverElementBackground; + } else if (states.contains(WidgetState.focused)) { + return scheme.primaryScale.activeElementBackground; + } + return scheme.primaryScale.elementBackground; + }); + final elevatedButtonTheme = ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: scheme.primaryScale.elementBackground, @@ -274,20 +304,9 @@ ThemeData contrastGenerator({ side: BorderSide(color: scheme.primaryScale.border), borderRadius: BorderRadius.circular(8 * scaleConfig.borderRadiusScale))) - .copyWith(side: WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.disabled)) { - return BorderSide(color: scheme.grayScale.border.withAlpha(0x7F)); - } else if (states.contains(WidgetState.pressed)) { - return BorderSide( - color: scheme.primaryScale.border, - strokeAlign: BorderSide.strokeAlignOutside); - } else if (states.contains(WidgetState.hovered)) { - return BorderSide(color: scheme.primaryScale.hoverBorder); - } else if (states.contains(WidgetState.focused)) { - return BorderSide(color: scheme.primaryScale.hoverBorder, width: 2); - } - return BorderSide(color: scheme.primaryScale.border); - }))); + .copyWith( + side: elementBorderWidgetStateProperty(), + backgroundColor: elementBackgroundWidgetStateProperty())); final themeData = baseThemeData.copyWith( // chipTheme: baseThemeData.chipTheme.copyWith( diff --git a/lib/theme/models/scale_theme/scale_scheme.dart b/lib/theme/models/scale_theme/scale_scheme.dart index 6bdae25..dd88b4f 100644 --- a/lib/theme/models/scale_theme/scale_scheme.dart +++ b/lib/theme/models/scale_theme/scale_scheme.dart @@ -97,7 +97,7 @@ class ScaleScheme extends ThemeExtension { onSurfaceVariant: secondaryScale.appText, outline: primaryScale.border, outlineVariant: secondaryScale.border, - shadow: primaryScale.appBackground.darken(60), + shadow: primaryScale.primary.darken(60), //scrim: primaryScale.background, // inverseSurface: primaryScale.subtleText, // onInverseSurface: primaryScale.subtleBackground, diff --git a/lib/theme/models/scale_theme/scale_theme.dart b/lib/theme/models/scale_theme/scale_theme.dart index 4bfc438..e787c0e 100644 --- a/lib/theme/models/scale_theme/scale_theme.dart +++ b/lib/theme/models/scale_theme/scale_theme.dart @@ -43,6 +43,50 @@ class ScaleTheme extends ThemeExtension { config: config.lerp(other.config, t)); } + WidgetStateProperty elementBorderWidgetStateProperty() => + WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return BorderSide( + color: scheme.grayScale.border.withAlpha(0x7F), + strokeAlign: BorderSide.strokeAlignOutside); + } else if (states.contains(WidgetState.pressed)) { + return BorderSide( + color: scheme.primaryScale.border, + ); + } else if (states.contains(WidgetState.hovered)) { + return BorderSide( + color: scheme.primaryScale.hoverBorder, + strokeAlign: BorderSide.strokeAlignOutside); + } else if (states.contains(WidgetState.focused)) { + return BorderSide( + color: scheme.primaryScale.hoverBorder, + width: 2, + strokeAlign: BorderSide.strokeAlignOutside); + } + return BorderSide( + color: scheme.primaryScale.border, + strokeAlign: BorderSide.strokeAlignOutside); + }); + + WidgetStateProperty elementColorWidgetStateProperty() => + WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return scheme.grayScale.primary.withAlpha(0x7F); + } else if (states.contains(WidgetState.pressed)) { + return scheme.primaryScale.borderText; + } else if (states.contains(WidgetState.hovered)) { + return scheme.primaryScale.borderText; + } else if (states.contains(WidgetState.focused)) { + return scheme.primaryScale.borderText; + } + return Color.lerp( + scheme.primaryScale.borderText, scheme.primaryScale.primary, 0.25); + }); + + // WidgetStateProperty elementBackgroundWidgetStateProperty() { + // return null; + // } + ThemeData toThemeData(Brightness brightness) { final colorScheme = scheme.toColorScheme(brightness); @@ -51,8 +95,9 @@ class ScaleTheme extends ThemeExtension { final elevatedButtonTheme = ElevatedButtonThemeData( style: ElevatedButton.styleFrom( + elevation: 0, + textStyle: textTheme.labelSmall, backgroundColor: scheme.primaryScale.elementBackground, - foregroundColor: scheme.primaryScale.appText, disabledBackgroundColor: scheme.grayScale.elementBackground.withAlpha(0x7F), disabledForegroundColor: @@ -61,20 +106,11 @@ class ScaleTheme extends ThemeExtension { side: BorderSide(color: scheme.primaryScale.border), borderRadius: BorderRadius.circular(8 * config.borderRadiusScale))) - .copyWith(side: WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.disabled)) { - return BorderSide(color: scheme.grayScale.border.withAlpha(0x7F)); - } else if (states.contains(WidgetState.pressed)) { - return BorderSide( - color: scheme.primaryScale.border, - strokeAlign: BorderSide.strokeAlignOutside); - } else if (states.contains(WidgetState.hovered)) { - return BorderSide(color: scheme.primaryScale.hoverBorder); - } else if (states.contains(WidgetState.focused)) { - return BorderSide(color: scheme.primaryScale.hoverBorder, width: 2); - } - return BorderSide(color: scheme.primaryScale.border); - }))); + .copyWith( + foregroundColor: elementColorWidgetStateProperty(), + side: elementBorderWidgetStateProperty(), + iconColor: elementColorWidgetStateProperty(), + )); final themeData = baseThemeData.copyWith( scrollbarTheme: baseThemeData.scrollbarTheme.copyWith( diff --git a/lib/theme/views/pop_control.dart b/lib/theme/views/pop_control.dart index 29dc562..deaf785 100644 --- a/lib/theme/views/pop_control.dart +++ b/lib/theme/views/pop_control.dart @@ -29,11 +29,12 @@ class PopControl extends StatelessWidget { return PopScope( canPop: false, - onPopInvoked: (didPop) { + onPopInvokedWithResult: (didPop, _) { if (didPop) { return; } _doDismiss(navigator); + return; }, child: child); } diff --git a/lib/theme/views/styled_scaffold.dart b/lib/theme/views/styled_scaffold.dart index 9d02fab..4fc803f 100644 --- a/lib/theme/views/styled_scaffold.dart +++ b/lib/theme/views/styled_scaffold.dart @@ -1,4 +1,3 @@ -import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/material.dart'; import '../theme.dart'; @@ -13,7 +12,7 @@ class StyledScaffold extends StatelessWidget { final scaleConfig = theme.extension()!; final scale = scaleScheme.scale(ScaleKind.primary); - final enableBorder = !isMobileSize(context); + const enableBorder = false; //!isMobileSize(context); var scaffold = clipBorder( clipEnabled: enableBorder, @@ -28,7 +27,7 @@ class StyledScaffold extends StatelessWidget { return GestureDetector( onTap: () => FocusManager.instance.primaryFocus?.unfocus(), - child: scaffold.paddingAll(enableBorder ? 32 : 0)); + child: scaffold /*.paddingAll(enableBorder ? 32 : 0) */); } //////////////////////////////////////////////////////////////////////////// diff --git a/lib/tools/window_control.dart b/lib/tools/window_control.dart index 2e9a21f..32c05ff 100644 --- a/lib/tools/window_control.dart +++ b/lib/tools/window_control.dart @@ -7,7 +7,6 @@ import 'package:flutter/services.dart'; import 'package:window_manager/window_manager.dart'; import '../theme/views/responsive.dart'; -import 'tools.dart'; export 'package:window_manager/window_manager.dart' show TitleBarStyle; diff --git a/pubspec.lock b/pubspec.lock index 529ae6c..fa60f63 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1310,10 +1310,10 @@ packages: description: path: "." ref: main - resolved-ref: db0f7b6f1baec0250ecba82f3d161bac1cf23d7d + resolved-ref: f367c2f713dcc0c965a4f7af5952d94b2f699998 url: "https://gitlab.com/veilid/Searchable-Listview.git" source: git - version: "2.14.1" + version: "2.16.0" share_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 5617c1c..5206f5d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -111,13 +111,13 @@ dependencies: xterm: ^4.0.0 zxing2: ^0.2.3 -#dependency_overrides: +# dependency_overrides: # async_tools: # path: ../dart_async_tools # bloc_advanced_tools: # path: ../bloc_advanced_tools -# searchable_listview: -# path: ../Searchable-Listview +# searchable_listview: +# path: ../Searchable-Listview # flutter_chat_ui: # path: ../flutter_chat_ui From 23867a1784a549227a75365278280ba19cdc55f0 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 20 Mar 2025 17:31:02 -0400 Subject: [PATCH 39/93] ui cleanup --- assets/images/handshake.png | Bin 43924 -> 0 bytes assets/images/toilet.png | Bin 28325 -> 0 bytes assets/images/toilet.svg | 9 +++ lib/account_manager/views/profile_widget.dart | 6 +- lib/chat_list/views/chat_list_widget.dart | 3 +- .../chat_single_contact_item_widget.dart | 4 +- lib/contacts/views/availability_widget.dart | 57 ++++++++------- lib/contacts/views/contacts_page.dart | 69 ++++++++---------- lib/layout/default_app_bar.dart | 9 ++- lib/layout/home/home_account_ready.dart | 8 +- .../scale_theme/scale_app_bar_theme.dart | 31 ++++++++ lib/theme/models/scale_theme/scale_theme.dart | 7 +- .../models/scale_theme/scale_tile_theme.dart | 1 - lib/theme/views/slider_tile.dart | 4 +- lib/theme/views/wallpaper_preferences.dart | 10 ++- lib/theme/views/widget_helpers.dart | 4 +- pubspec.yaml | 3 +- 17 files changed, 135 insertions(+), 90 deletions(-) delete mode 100644 assets/images/handshake.png delete mode 100644 assets/images/toilet.png create mode 100644 assets/images/toilet.svg create mode 100644 lib/theme/models/scale_theme/scale_app_bar_theme.dart diff --git a/assets/images/handshake.png b/assets/images/handshake.png deleted file mode 100644 index d40fad1c889f016d4a61f59f17350a99b9ef0e31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43924 zcmeEui9gh9`~NJMBs3~YmTaj|C!;W=O@vT(vL!}C#L;9)nK>#WMcHaZD~iF8lx1RM z+8C4!jx3WhGQvnoQT?uubDrn<{t>^|^Yl8eSAFLG-1oJ$-!eD6wbHQvA+~k7`&F@#J5Zl})$(GGo+ACPC$I2!mZSa> zc9vSM`~5$b464svSml4t^jqfRw!i0o#$BIV^6`CN8a7yXz1=4??2@)k`v3p`|0K{_ zaZhfIO?v*pW?mg;HToJR+WUt(_KKXv##DMr$`A;vstse8u3faBR3>A-ya(5_qFTOu zVCnBS+c6`UVALSafleQE)Vr%~_==tO*SJGyBfn>^6L(T>>F+Tdfygb1$_64ry1I^jSKJ5VSHaB2r_!gS1p_$JuMr* z*u+Hh*B`^}I$15eDr*0cwW}UUJoFGAzFPEbJ6Z{I1vP@&F6kiIc7Yyxcd2Lk1C*i% zDA+lhk%k$-d^#W=JtiTC9Z<(_-!MDh;Swk;L+Y@Q%-cgvQyYJa+w;EW_N@hSQgLeU(4D?GdZZ zU(w7v87v&Fn!IIwUz;$P%V4_yR<>o-gpe$@5E}e`6t8TrV8jlJ{OlI2{Ff#{>sk>r zG-QyFHvWm%$NbUl{4A>I@(rZgsvBrWa2i+K>!Z!Qi?rR;ZOlp@jY+#}$-T_qsZGxQ{`4vc zs&q-$hXHwZF|lPy-O`xb8Ctbfygt=g;!lgt`u%p)Bx`OOa@>;7QrYmdpT(DT?!WeYc2o?bZ#t$X=ujr{y@tw1(m~L+fzNuZ^0& zN{Mt)piomVNL6wVW<)ih)l~2a_dqz6&k(niegV$cL_Gx?EsDk*OQx5|-990EevpMp zW3_eQ?_%YX>3WEk^&`*jPuHIDC6!@SQiu5_0}}BY2=1kFx4#AoYdLriU;B(0 zrF_r#60mPk)y~+NF#pM+bv}_R#SiG!WkhOy#2i(rgP#hfT<>p0|&uCwCCB0^O3@CTjxDrdS|qw8FQ zNAJC-JwN&U?n%Qavz+cWMZRa8AlS&dKkrCp=g9%YU>+Zi<)42{(;`f#f~A-;V$}tf z`mEmjdg33VrRhd83ugE@oe_O|JxgtWUV-lEMdlkce|f7~zJJNDE$S+<|#;*W_R5L=6QeXC8RT9}JNF_WcmCjLK5`p3FX;EXan2Dmu{ z#&-|FQh^-oZsIaN(uK0frZ|A6W0Ov7zvfPOubrb1YC~Bnt4p{c8?Wr)lD8*Uy3bfM zybScT!doL#+a_=8RW?zfh3P3?Dk$f9-yzc7HWK`#;uYbqB994d_!`vu~9 zchqy-4c|Bgo(mF=(Gr@z$y7emRj?Hq{N4fb$P0|!^5*&?m0ZSY17f1#^cq1w-}HfZ zU661$bN5~+$%Z44OY;uLZc)09jn5~Z`Q=EmBAs4jQcg?>?+#^R;gPgzyYzgADgU{? z*n^fTE~>XDrS$S{ps%~C?9sczallVagM)XjMNHBuIHvs6M%Al~1V7c=?NZh=4d;jX z>_q|wvg0rZM3)W`VG(6I;(7Y4H|0|OOI{W27#d>C>%Zm|dbVob2kWkSiWo`jwic#C zH9BUOW&9)JUcV(>VQHunthU>iKPlqxx`5?9tlqWGCS8~_>>6Am6~B$}?Vel*j!}b0 zd}aF6C?Bm|WWVsnn&HN;4z){S_FCp1RLy4^M#=bZE)Sz9UL1aeuh;|12}wbeOx_q< z{Scc(cXg;E}wg5*j1@-8{ttgXgO>l97R2^YHWLZp^)2qkGS_rpztyEJr&II zHTRRN>W=i@1tkm554#>aDid!;=q{D(kYs#E2_*b8MEW`Rl3Z}G?FEajITXt`t3xM| zgv$J#LQj));Q24c$EHfepE$A=JO5k3+4&jskSevs4!QH;^VC{oX|w&7*CR#i)w&x zpl}d#1FHyI?)+|PWApf*C$fjL!iPD$`8mLWQvV$Io(mNDd*13HLy2jKJ7} z6|?>%3l@>9-^_c#`-Yh;YrE}cO4BJ)LIPt4Z_2+e+!}S`1<%?I$IxNaY!F8Zs`#Fr zL@?>OAiA9&)_+U#g#|amf-Q-?qEUWKanVx_1V8}t^aDlTgKlhu7D9F zOr%@%e%_#f?RPxzL`>EYwk7`fSBA(H< zzlIz?B+Ho9`B!i-pa_k%Is|o$ip)_DcW!MwMkHfC9tHkqwnbe6}^HUOdaBo759uXa@>^aiVFy<~|3s>hd9hS1rIa`@9VI zkVXZ;6h6|4a>?ceFBt2GaRVn~M4$ejLN>@hWJ=mmpIdPw`8(HY5d7rg6_KbF^^T3G zL<+%+Mq9GoLw0sIGrQlI?|IF7vqgVF>w|p2V$O$R3lxtRvYUCR0UW!Qm?BICe~Z&7 z#_mnepID`Gbo=6CzT~(vjM_51t{ZNZJE?4OJSOH4MX@#S8ckMn=sW@-{)ryRpe(QQ zFa9T$nwflBI~L~>{MwuN=CiGn--2HyM~3Y;_}I}a@ZxkMiSc&LmTx*`+OjQ3NU=XN zoaObZ;B$zoV87rU2&~(o7!lYMl6sY|{riun1d9YT_{VQsFVeRofo7`-;zrMg8c%@O za}vjOU64J7TTRFPT?B_2_&eJ@{RbC$!9^Yj{i9PXMP6{kpDEH8onav+vb6?@R8~X1 zH{tD7jbRWpTA7vi2oKZfehZPT#0k7ryH&kKuZr{8)~mu5itj^rSNzD(qfYq zJ@VOlBvz*=Z6Ran4a0uQkB5nSL~GuA6|#Fvh$%!}+MFfV6G?pk$op*&=+48} zrpdGqk@12%thmp1lF?`7zkAFAOLFyeS=jvAf34*B2O)nervhFUmwJ)D3`sI8jv-$3 z7O_b9qdahtQ-+Tu0R}4`Le5ev`R}=}CRDN0JEI|JJ6Q(_LpW}nt(=FPUXCh_LaUNW zNYIMV5eMfOXS!n>ux+@u6ne{B$1>Y8+2a3N=QG%Dw)f(rBR5{A|7ct7k=}`B)T|Zn z@ZaPgo%19_=Gpuy3&N&5>nk0w4j6|EbeU&%OwWIhDD#cfqG$6b=ursxL_Wq)yg%p3 zp_y!p85X&@WD-4?N@sfgVCQK5yW8p}a#vU-{KX_* zaO1rM3wo(XHkzcx>mmn|^F=NtRl7-yty4Gb9b(&;18NV^rw7&%(vb^ArQO*t2+$?puO|1cVeGUkB zoA{96GT*cC<6=QS=XVNyjM?-h3p~j`R_CJd3MWx(XbAD%6vz=!d~U=HEN0_9TvYBF z+{LzEphp!aez_Q$7(xq8>^QP9e?2RH(RuAsDp>xCm%KW(pM;uIUI9lzumV3-BwBL{ z2_z=S!iIu(_{cZ}#Roy8cUePFmseSG6yRKsF-^&uJIvo%%d6U1B~Z?JsRy2OXd~`x z3jKx~?sC3oFbKq;ndeIVhGIP%OuX`Ag}8@q0uV#V^gpXR`TOazw8J8sn#IIQqPmbSqq*mcz!khC27*8?{5fD(;!keKzMPR-%$DJ@;02m1)&R5fngPY#8M#kl|eCdkTB%3@XlzKm`*_O5`>(rHomMhatq&NX6{3Zf1^C-&e4m z%%EZ-#p>8(yu(M^%z$+dA?s=+8oqNuBBm-S zd5ix)A?Hp79C3XFr0BIHgXQd;t;YgD|8Cy6Psd_)46Y7mY1=159x*~QzH17$7e{@u z#Qa{0S1*%uIutQ2TIc@=;%`X-Y!H;xEfDe}IfZ~mWUgT5thl2{ghOnSypK_1As*!T z6jy%PH`yhf2M$+;y{>>yLR1Uck%8p;#!zd+z zE~gmqe#vx(2V_DTNM0GDNyx$uZ-Y>Gd-%GJebEM_^^e3s%U*$?zB?y1%iSr9(Bd*) zd=Q0hvs&e---Y3$%X{9!FM8iYyu7%Mkg&-79x>^cCe7W7 zYt2mD_m|^p7n!nx*j@pY$Iph}Nb&F6(V((ig&flnqJ2I-P`JB-owKDIGvbzX^-N>0 z;4%`S;i$d0K8rj+(W(C!iqo7TlCn%^dPS((v!O@2Zl;z&f;k3f_Nk2>{*cxCp;gaj z!w1-i!G6-jqTI92C6!N*sNV6CISH8&@rM)AOMPNQ4j~27=H5v{<-4~xKR+4?QSaO%T()~wwu3L_7Q1#?gm$)j&h;_cPctXqu%zLv z-Y98g;~9v_#GG`+ecaqFRq(E6>$-n@=THiOnGTf-PVu2w9ocr+cPog40Ht(#b~~gY zqcc2j)oK+FCef}P$)*Q?JZL=jZ}31|C5i*wg>oCVoE-u%zJsoX0q_$x8w;@5~6a-l}af9K&})dU;3{;vt<>_!|+MW$r5lAV4o81Z(n zBw?nbMkd0Q?BOW*S0R4)@4hdBU$s{3f$E7Ycl57ezL5{s{1%{xJaRL$zqs;VC76r~ z-ufR1diZ~ZoV~J?~(ru+pmnglyU~*XKAiv*9Qn!O|Vz1!?3C-pi4AUpy6D6*|mA}rk=)Xam#Ok7T zF~CC2IREfYf8d0?TY<64=+4(r;Dodwah&TexaR)$O{V(*+i7*&0bf~LCJsA?bu5^e zp~#9=1=F0%*`DdWBslZ#7rZyLIhT>E%N#%}b#gHwmhpGt002x{ht-&yv`ujYyAGpL1mX&T`#}r2E#Q0ot2~fs7(Cj_+?eTZhI$U!=EU*)P)oev zVGXB)jg0Sp4Yp|3qx#U=F(ZLOJHx1pq8;hAFAmbCH;fQ-hOo#(A zXM)5-I>LiqKz`^?j-43?=m@T2Fy<9owO&jX;Q5|jC*;Im%I2doUG6Z$8sN<~d^D+3 zX@vcM;un?(@k?GM5q#ox)o0Oq&Z;jgHl{~C-GgFfWfmeR zh+R$aZaqRY7hXT2;C};zkMPr{SI!d4cT%qJibh#T#Gew6u(S>6~{C3QRS>yr5^fGC;eXKB#KhF&}~GYB}C! znjZS|#*H>Il4T0RM&C$*KLaM)N+Ev^an6~>X1hPC(u(+#GytGG|2x8w$V_L6Py`{A zp)$izoq?SAXYa~R>?OnwLydfh97}NnCk_%iaMEay;d$k)v<$@F*lE8k)K zx^HtvVIvahpBfpnY9aLOnC|`i9)LB^3}Ja)hDUn%733X#y429(-8?#TCKZko;@LJzz^`|F|UjcZDs|cxP=)pE#oh$CL(KRG-7^&a% zv>vM~WnwRYtQUVeuXjtiT|r=@%3y;jg^8d{erUu}ZDCIJ%gzo6NCn#c2v2iyjb?4XGI^)hI>m-W*cctE@+( zvt#t~7wKP=5I{xO99qskD=9uBLS-z)E1~8vEQzuWsdxT9v9TPI@dJOK4XlZs)4hOb zuFD4r9~bZ5o1=Q_hVrHSia_BrP69T%q|NDLCcUSW$kh%KW^== z*;_A?EHRT(YJ&iqOc2tvB#06)@V2OJ5tH=UJy70MAyHv7 zxD&^YwwhVFyjLd1jY22W=_y)6k{mTo5kI!GmO#UK`PyiW0`Cxw)H}?trE>Ar#54JE zh-;(qk%p<&3CGL5UfbZOcKaI(hB*aKBaKc;&&#xqXVoN3%K~8axzfz6d!6b2?H-_~ zv}WFO;Ky8ATo>8oT;h34nk`K{8Sz= zQRxh`*KG77$SJihzF`i|^Wn~Ud*&DSY}PD;{!Gz11gvY|BMIW8Ih*?CLV^)$yN>w6 zXb%_4J1Ku01myJyQq$YwOOO)6R^GOTl`FDgoF|KS!M+71b=@#Bksupz*-*a)^ z67#~b3=g{?;VEh&Rd?t{>9i<;+K^121O`$Yyb4*O&?v+GE%2QNxx1(B&$Vqcvg&9? zviUMadB~jnlL(@pgHuCQ^vXVW$wmLb`5@ZY3pjJ_sPeVuye=r z*}Z3kRX~-WJb_VjSKZ|#+hC`U)L$&@fHT+%gQKN6^aPe^=J`;KF^*Dh2B~*e%*lys zk^1sQzNc?dkX;M2l8HoC<3a|z_ZBh5?c)b}PbDbpD{w0v%Y%eCTEe}y1D}*Q_4vr0 zz*`0hyEshh2sV^o_}b7x(svpDIh;=F-_uzc4f9j^o)4Vl&(qaGXPq0}7wM2|eP!cs z5iKjmsmlnJ(P`?yGg)z4Xa?l)z@Nv+YksQ4$VDW;u{1$Z{aFiVQort_y2EJ>k?{H! z0OsxJcJQH331qpt8*Zxk&?mKu`uKC0vpePemzNBV8Fjamqs;h%kVA+0n>mkxSUsYI zzP5*J@B63+#s>bG1%Mb<^&H~6mw)n%k~;=x$^8bTZVT@uX49?j(E&|ECO$J*!B5UD zpM(H}Hs}I&Zg!t)!fj878Ra7Mrf4l2z$km^f_d#mUsW}3g$ zdL(+}O(d%8x4P?_ke0)7*hL|^`6L*tz&UCZHldBUnZknb-q+b z(Ht{t1MPNAhf=vWEAWxh_KYughJSv8zAy9(bp2HYsV(y}%6hY8)?LrvY=!C*;`cfn zow8r{RwN75?8frb6vdgphO>mN51~}5YJyXfxQ04i_ME8VX&&_?YCDgIrG1MX4EUir zBqjKkDBnuu#jg|JFbcYD6b8}cL_WKgfK-{M7TPT19!Oh#^!Jhpi^r(hcwn0INQa?9IO3h+rJ4cg1u+>+_x2fg6kYUtc_ z#z}YF<=#t-O%nBzC7w{DMc~UFfufnA9xi8#G5{0&V>zuicP7tx=W@NeNSq4B_jiq@ zYm1@`2}q;RIYzW|{NqrspbU7E;u)xLLz0lHah33mwEIUKM@DlzI#}!BI}Wul;XTxm>4oiJ5W)*OUACoC zY&jES!VM=?D=#-vy~OPL`YE?M01gyI4<~tK{G#KCl;hx>kvLbRPna+f4C5oiDNAR% z1+Ts|pdFzXGcelQ`1L90C$sWMVc!z{^MEBZ$+bwJHXT0n?bfy>CR+%D*BKTCdrMn* z)~H}0$-YuX{pX|*5`OeyHZTJ}7xvDhJh@ehX0I~YMEFf4D7oUCz6A+?Fut!6?Del4 zG7;U_SSlM%4Ndf_kom85)CAdZc#9yP*Y_AEL$nB!*BOHnzn0xCb7`vDIn(<6 zKdwW3LV0re6FN7SG05Z1xg@=wZ|0c=3J+4FXu7T=2aZu9!(P4AE4?6@-MI{jx*BE6 zQnimPPI_ZF{T8NtC~I1;@cNWYR?G(bm%>3dbxZNxfwJ3%)Cj7TJmNbgn0R2I)MVyO z2n`tw`4YMOzo7#cDEvl!%8>&M6$-^&dmD7mid5BDS^25J4?{iK4$kyE6*HiQ*M%vM zPcL|-E7tO`ipviUAb9V3^FkmdKRNZ}Z^WQ=sVSEPz`t*dYUS)mXz zq2f>_eroynVMiwVwk&SPrWk@L(0$C~Gph2ApG23CGXO3pu^()qjHt4ySB)1-6N%g)3kdGbjwQUh??VQ#9+2OgA%ZO5r@2C3$+6 zGf7oCR|RkE_&qASlABWYQI`a#c8ZYdf?u0s~1XYgAfsn`Zy{ z;jGSCd&{H9c22@}ssbXOizbg;PknSCGIxck`mDU*Jw9>|m6Wbu;YC^8nj_M{PhA_*SElJ?lc>X4$L&Tex!a)S z71GVFFUmB!V;F@MlyX{Vz;f(sp5ZiLVdh4U*|e|uCYQJ}CY+M3W{NYBKv068s#Q}9 z^tz9s(u%<6Yp#;Q#Ge-0MI9194_mSy!|_f)V$7j8UaND*9Fv;xPzm-rEzee)b#k9r zZJO*VIxeV7OjEbyUVIw+K*1rPXvdYctck@cGA3K@MD{N_I_6T@xgEUcSvJEA>zBXX zFH;R?ZmfCQ9jXxh3euAmoB*k_ZkFC2xz>9A$pwl?ZAK@3F5cv)d!)8r1>ar*fCVXo zt+vMS<_F*O)q34Aad`V{5nCHcvVjUxI$gokU?pq3c5CswWX?-YG9cJ6ZG#pCJA>*= zq_A?mVSWV%8r7Yv>MXfr7zwF@Fo}%aIp;jNGkHv4fu7%Osb2(ivt0?}>voO}90xN} z`=LN#*Xg?dUw{zu*w$-cFT}`EX(;Ek_5po^RU*?J2(d%E8XY;FJ4xx z=AQ*&TofW8<0E5$d+-dPv?&O#a3qa_#wMvLxb41{(`Q=sG>6(VesL3Z)B0EREboaC zSFA6`N1RYnOAJpgkOmeaeDI>tBh~_~lq#8>pM~_R(l7}J^Md617R`rQ#@|P3*owHZ zc8+UenjURC+8Apr`>Sr)Q{E#~9QnNQc}IgEBifp~`Z>?LO~LLkQeLjNkF7dIQKYn4 zYYt^0yS~o>dXVv5gR@ClQD~2!DibYzubk15DN)$WJ>1tHu0PE0VVz7~F^IkZ-AmZJoW@f+U^ z#w0Y0%IMW{J?b7v%zU@Ye-hY^YZzUmKOFd`?J6SLrm)yXsQf~cnlI>U}N^S-$ztppH^HJ3AC6H`b} zo^tAeo!&3^fSrCk6dWXcyouAs*C2}WRAI7(u9ICjwuSM10FkcBiG;=39;dot44@7Y z_2`eq;t8;1QkveGbeQv$N7Z}f-NDs9aF`_vOuv zBS3$VGT?pLKh|Ws0}Etdq9h>RDjaK7_0;v=6}4s$witv`XEGbE)s0gVFuy;)?^|KQFOe5Ik+#j z`mACKIZynZ6P$AA-rIq6#$?fg9W9M9ir7O@wC~)5FuE)CSw`EGUS&Rp4$ZkCiR{k9 zj2boZYC$TfD*|3Lu-%$_5R|S*_=c6kxj`?e!e=kR<}-|~y>I4~LRpu)wDSS!W`zF^XBh@2G7%<8Z{_|*8kh2FjCMh&%ds0-uBa& zbMEPZ;|GtOr$-~^0kpiz5_1-4YWy*N3T6-CRgS1lMQyh(drO#`YqlX>HDqK0Htybl zK)PSh&7J9%fljCEqc$ys#-0;gEqrE(FT2!xCcd&o4X? zO4+L$prI?oQq>-s!q|42qua zOl^tzBSb7=_{jYf`hg&5nvgkNQ15#iqs`jq`kA~wMH9sy@GRoO3xTYeY0m($So`E~ z<1`!s$hE>a1wGBO+cm4Aohgb2-M65;PlcY>8|WC3hxE(ye?^{(IPD2XG5dos^7Y~pk}5$jP6uIE8qSi$h^UPWeam-OaiIC0)k$)>8D?RLtc20 zD5NNamfOzW7pc^q)iPk^uc$@!K>gnF01@APcM8g$XJ&TaA%-B<^w#Y>uA0d>Rw}po zJlzCi@&G!!LpVO#>N4Y>e#hu{gYsz0c^Hk%pMa44BWO~D%2}riZvHsTSGR)dj_!6% zTKcNwcv}{)Pt8QCCm#{rOFf%X7%qv^By>wm_~q?h)?HwuFJBkhEBq6L2P63<#3U-I zIWRm>m}&x%=uepM2}NYrv&!QR@l4@uJx5(OgQNht_sy4wy!q#%rCKp&5uTJqkr1mUvfoBXl+2yl*^V6SQ!KLL$}cO;ytbWc|WN zLBfN@yY=BcO0G#CEAoo5C3~U0V}ic1_1-8me^bo=@p?Vlh!t0< zII(0OX@Ehx7%ZIJOnEg)HIN$qvCjhn9n9A*nD;)Ue5XNoxP@8&CrHErg@cX*@;gg! zn!M?~`=$`Y=>>+<7QkoJg4wk$0r{zSmCD_8@*#c#yg>Gnodr??+KNjpBzU~#``g*K ziKrc<38Zn>k~p4Df>Gg4`A~PGiMqb=o&7n8}lW!XLsh{z8@6K<$KFMyWEk4kD%OUIB zX;1G3j!dvTJA7;leu}mbSY3~}6&b8n%$LZ;N-QKG+ji$j&uD{@ok*}BQAPCi~Ce6}vY%HLunFoC=S5UhiRd)|} z>dov+LezG_>RHP}_KvI;CeK^qD6S9N6URuqcx2+m0J@q`zBWRuTmoh--w0TnFm3Ab zxwu?)d_(BB-)B4JL5z-p!l2o_g9d(AY)c%`?VD0>Pt@Rhbdav$eRIb5)BY3Pe~t9X zat>0z?Xh$i(p8zi{?$sb#{aeUX$P`toI7+`e{d!e)3(w2ug+uBHsb2#frpcrH-gJ9 z;*m>nkpD%=Jo6P$f6KM%+v$&fg(wVFw8wP>8vjX=LyNUwf@*V@S1`$9(d?qu)uCjs zIA>V4pQ?+Ud$ZgR&HAY=BRr!QSli-g1iuFaC{~0%>eG`Xtq^+a81Y{1Dk@u~>8T@0c8| z?v7@U02{rpJo@aP6#BsyUN8!HF%7bi@(>!wkI56y#-GnWZhxF349%9e;{JAlU-Q|I z%XMJ1=3D-ExpJc=cKYP+4j1V@%-wxnz&(2Jos0I-ZcTH@c27)Ze#4}})f2RFM|}Th zlJ5Qdx1Uo~incr4`M&#r^0PU@^hLl#EAYnHO%fI`;G0)rN1AX~9qQ5?QV@I*sic#- zBsmt)!7`lQ$oPIpu%z~Y@6KuN>jtT|7M?q)&~Z&xHgE|u&@7akhafF%HX}^)VaY8q z`9EQLdp+1sj1Psn!sHe(S&T0S{h?VD1mn9ec_Zi%D9c0vFDuo=8iGu?Dnz_sqz9IM z8U|!!W`3n+dcdxpDA;9G93zDvR1Gja4a1uqywT2He^PiRBdKFP_VnE$ldzsuCKiN2 zLX1;UCjeTfhFlNT9Z@gA5K8a5<2(?qxJBd@JL4kI7f3@RTG1$@K{p6SmJj9IB9F(b zSJx2F2-9`4kJ}O4{bjFv&mG%8AE;#2;9yshV<8QQOv7ac?bwIMSKGwoNH*dt|> z@zFECLEKT}tN7^b_n&};v!w{hiHsW42y^l5(7MWxvYg<@DFyXVto;~SDtIPZniJ|1 zM|{^pb96^Ygnbn1Fl5XhOoHQT9xspTO!WcUsC)zkQ~JHIM`Ut;+cNwWl(jqTajb** zE~SFoabyB+Ub!SyuYj<8qZTTKWfA)22PoO5;v<5qJnse~9hs0Wy5X(WsqGO^G>M7X z3pV-P)S_g!Xmtzo^vN|5F{CnARm+^M$Yb?#Fl?|Z?^VGyk4;amd3aTCgT66<9m$Pg z!9`nO2CE9&noS+%KiRfu){C?eCJ*d0!18zFD!e;P1xa(r4Z8R{I$UC>Z6A%uGQMYu zmUb8+dHd^5!8urTxt;#Z>#clr_|w%MPSKve)Y>(PfN-?&t zc?Q@^WTJfSyVFL-+X`xcua>6LO|UUxa2&TQa&-+Wj|Gfoca_OS=qY-1%)?&Sj`lNZ zc2X9Ryz>5ZEAaa3 zp3%49%Xy1J_8%Ax>S@^CwsYeZGs3q4=dX9o)kE_rhf3dFzarOXIzst+vcY-5?+F6!BZp{czF4!94MXQR1ILCx*HfwuO zF-O5h7M^)~L1AnVAyH}{_WbmLTM3a@+;CTiH-ffWU!ENgNpAv+kwH$h^PEms6!qH* z>jRG{Mw;}QTM=a^M=1}eGWuC6C^H@#d`v>wD*Sjll>LR3(GSa(z? zx6*!8*wcpqK_y?q?Hxyia%?1B{?9CclYGqEo3-me0@H}W7Lz`4?{hn}YkGV#B09@G zj%M<_F)H49yG15S51Gs|@W6#r7p2gzt;+OJ2U^^rlG|Z&%0#oRRIXyNywn6Zrs$6r zJqklI+Y6Xnr{qJ)l$8s{^`NZZ;iE%|ayRxl0O5p@y?+n z_)v1e%RejXte`VI|A5F1QSnmUAzBBom(S%mg@V0q*mAg{?z!|9n~f*B15T-%+#zy{ z1BF=HNtcmJ#207BMJa<|unP;CrY#GxHFvmowiX>@W!bl1HE>HRXpVB}xfEab3Pg#8 zr?g^n_d5?o?lRz}Xu{QqrOb3j3$=+EwO93h1+>Hp{4O2~f$tu}!e?au4 zjkyh?YR$CmbmWTir(*2pqN(`TT1A(=Kb5T?g-|;3KJv){A6fIl(P928nJG%Hk=(|k z?l(^T{n<9`{t0lvO@>hh{?7i({CE5ReAkT%mnf4wjq{fG23JNcMtCm zJQX`T8(=vfzpI_UZi3s9c|1th4W)}M-L7UT*^PBfqo=vz;DWuk{Oo3E=qnaIg`w=m z(X`fcp=SAR;E{?Ngl@DPI@Leo;`cb$15_49JuqT;Zd1yi`_2O6kiSevkt5#wZo8l? z-?e5j+AL|o$v}89*g>+$ZLAtjGVBTBYwNA@X3VoV zNt#22y(Sr8((6Z^zz-5{$YkU!31ha<;1bSBxH}RWWBJnwY3Ao=1MwC9Z3I6B+;hh+ zF!Rtq*w=q0b2f7lQ{MBshnHB~O0c$^uP5@VJrNUwz?9yYK&XGcYF07v?D%%3-2J6M zw8M;ddU&)}xxM8)-1lD4%!WXrp}>^$dWmAiqu1)xXbcNxk6e>h=|Ui}fj|;Qh2p8K z&1oGiK?1Iat!6gqTav;Tdt;8mj6#S;i{5Ceg&N)wT1^I-W}EX(1BEosBif@k1MroB z!vY;n5F>UdBHCSdiIRibvzy%XwZj*9pSYS|-leh=sY=*9^8mI~1Ma#aepC3t*e%E5 zDsV?Y2}duts>riJu$%R5gtIX)beH_ta|e(*D=aa`-hJvl=;MvY>`+C)RUoXq!H zL!H2WWHR2OS=j3~(Y{K35^UL+J(k`+s-)$N*gwSTU^>~JjW^k~CBzr^&OMiEmn2R# z-f8Y%^a%zU;j;h+Fq0>$;>8mqHviOP`Ip-bA7*@aWYqXlt{&I}MZ{ht73OsSaY&_C zYieGX{e+7zBev7SF$35>Jx$+E+UJl{g3^W8$SsWe%L~jw$QHqHN2ay*vkmjXnx-eh zR!$VN(un(QrT6*-8_J~vkb<2%it;=NK|)`!IcHiGVs(5NDl^^#cef5b(;+w>7N680 zpElpRuC%w5qlTZ_4w8O)pW`$cC=_yZsY&ovimQIuv=q7wjMFtkf3V|kL~YR+-xmUp zaOc4tKk1EzKOR|eC(l=2fN68t__JR#=}uc<>TL$TBhr3PPU9lL4CG4y*f#W6`0Bv^ zZZzV?a4RDVamY71it~{Teds0UOLo)jLzh)xOk`$vSk4DWW+$&yQ=L~VKL~EF18&Z* zJJ&e%W2@g0O;7)#?dLWvYC})><`uki!@wk!@qgBv*69i}j8YZ6=4h?@#H&>=?>cki zYn)(=bA{TD^}9$vs5_qvG+!%c&~4`zaC7uNpaS4tT+aoD$e986*3H$=|tFA&KGJ&><)xkm;}RW9k-60 z$NHKM&<5B5_fmVnPBQ?jMV51%?gB@^xk7`o`aw@JehO;NJIWJNrWuWD>SU+XG`cgC zc&E>*9G!8qSTz^bF$@KbCETGh}dUSn@d3SWYJ6yZSL zRR*gVsujA2w2Cx*#sJz2nY?%#%A%@{YtT+r9J(i$>3$M50xh?fv&RII$bP_W^NeyI ze%BY@gGM5xWb!Y_-mxt_Em}fRn?}`(ggxA!&_H=|I5DSuHT4vduGe&!n9>T$i0Bq( z`Sed8*9tl%Z)uN>#^}Ki!@mD~EEl@Xe)`8z`hdU{2M~(nexeN}M z%j>(*nDUp6-}r#LLx9cnh<3KydO|d|{=D%ATbW2bh^OArPu~pJC6?z(tE|F%s#+== z%ji?!xYF^ffrQRZt>mT)fk2NoWCwl-|BFRptTTs>LX9B@2P!Lq$BkiF8OxG{(gin@e zM8pB_8b=}3-zReK!Hk7Yw#T+Bj4{-2Ib;C5GvN0@Reuc#5|WUZdTW&&km7;zR8RcT zKdT#PyXg}^X0xCHv5C-qSUf%a8p&66Q1ZuvCoIm!rd^=laKT+J1oU2d1tu9bm9@Q& zV!h-ogN99rM}X=x|B5=J^^-4n@O_ITaftBI=3qIUXeceOrt$jn8cTbi8qHk6LF1?1 zgGQkxQzSF6*Up!Byf0am!S#|>NzU-d$*rO$wD6K#aIZfgLp{H-awTn=6fTL6?g})C zrKpvMMJ}gw|LOmmUR^?@8`&#*gus`8mU;ILXshXF3WHAxG8nNe_?8T)JGF62qs5jS zuYBs{-RDwUBp<1+KK|ME@nKrQLo;Q%XlX4L_{P%@QNQ3DC@i_#Q!TtXxXJd72`}XN z0=|Fx+143uMfy%(Z1B<}Ji=|1%!Vn_T;COV7Ick@dEU+bx?LGaGsZ$rMH#V|_m>$s zbnwFEI^xGT|7qkC126W$x#9BY%ylSbDwW_vDS?RjO-l$Si+3L}H=LH_+y!S@8?;Su zNwk!Jh#OE5fK5BuW5<6WI7zYea)w7oPCx3?GQ5&I?)A`;-`;V$iBAr81fJ5@RF1sH z1dBj!?SXHF&BVZWSDx7DlUutaEv_=gV1(*PkT9In4XB}Tc#2UIN?8QvhFq0|%NV`Q z#?s@Tb9nK4peG2RIH!<+vL47V zoaTViDtUcd3ELONEB8DkrfENvv>=xLr2Qje@)q-5_uP<=n=q-eFt`^8CgC=2#_`3& zr-39GH4-rv5gW>FB2{dnzpfIj$4CCmzrKd>4buh}N;05}SxN&HU7G_Htw6T-RhI+o zy9DF=A}Qk~^>AIjs7MVPZ$Z#?+888k zQRM8;m{sJPnVD%`_fowrZy04Qh!N~%kGhHe3M^ke_Ly^5Yp8e2FD%0h?TG!Q(i3!l z3g*ZWzN1qazOEr26RG6fb4MNd@r}X?ew%;%Ly|9!NK#fkF#TcX8B_A()9Ui#uRBf0#nw2wI`; zUbWnPPVxC=?HM@wgT2>B+^(54_sau^%|yaT^|G-)q#*U3*MiG~kbSN*bPca^X8(Ye zWd5iZS_SSq8npF7sV3P21Vf>aXSp5cM7FeDs%d-uI|w&GYLqsG~C{{K<+kOhu^`xFcMkN$A71fmRREDPo zS&J4+iXPSbJ?i&<-uFM_oO7T1zV7S3mhZKk^VTcX5oVe0;%loMWpEo^d-z_@xHwX$ zUjKy$`_9+xN&vqnEIRvO*7Og-&uljLT**mr34#$b`TpWSR54x#aStbhxN13LvhC4L z3+Kmus^PVV(FPm)lw2*Ux;j@2|4Nof)^wI2npuI4E)#T2Fdlf+r!g1a!0lVIEe!1| zg$f=Kqiz3a3GK|aZoLgtV|omOWj1yxFv>(JP2{yb=j+aY)b(_Dotxu`^sfWH56=9( z5M>K4ge7U%t|~0i)_(Y9_X*M~v+VOPwOXdN@RvrH849*(H0Us&Ng^L&V^DIz0#rb8 zoB-K;{`%Jjs>;9ucIw;pZ5cUtB$MjTytt`VAyttuI-=M9@@7Y;dGB}c_}H5oEYx!6GZ)FO?ynv4^ArS+a{NC#Ec_3k(BjP#-!V5ZgaGA zXWpO*Zf_&Q>*dpR|#aey4PCMZdH z)G7+qQQP)56sN>WXT9ut)<+7x&aci}Wed^I@!m>Qc6lV%pFky`XOLD#&S!Fx?5?Tt zdVHgjo~!clbE4D+I_t)Lf^J)xf41({2t`AIGLbH56raWFyEz#ZhDgq#Uy8K$N9eI9 zXDmkQ)}?y#X^C(TwZ84-4-!?fNZwAeGXG{!f8GG5{wb^Ef-$Q~opR+?d5x5E<-Q{) zJ=sL*P9Go2+n-Tl7JI7BhBj&%HAUbWan8a=E`HJgbZ}6R-OdV^08N4&$7V#WffaZ! zY{E4A>mWB%c-$(j;FU$O5ITzYLiI`V51o#y$OM|txYGKxeNUV0{NR_Nv$aU$hUm6t zv&Hoe>m$r=F_-Jh`Mj=oRah<%=qFG!A=Q1PYXtwHVCLQH(z|tjCv)bJe@e$23$9x| zvqCmAKPUe&dI;X%GLEfS06=tAA@(8+C($Ime1sjV))LjZK)z%IBji^5r2O?Cf zcc2dBWe;T*Oz1c_QJoiD&CK*SR5*j|EW|5T^5)OZ^JzJqvy-%50rjti_)ecqSB3W_P+d556&v#z+%4LgH=P9De??!J6&s<*n8%4lHF zCsyuq9f(-{1=5%6NhgSz5nH)zGXgP3V|uWpZQ^{pnM?|Hc)t|1<8*>%*vA+r>W7$1oZS)GvJr+_88nZL#ES4}_f{IB!&-))I0h zOLKzgM9%4Y$xohn1qU$vohu4wEdYg|%7ci?iw0jj0MtCRD0wJVHn*QL^RdhNw)q+0mrre+Ct#R0 zjM5hlmZUZWe4bQKf8DNwnQSYrvQ~tf^XlYXdX_&^V(ip8Pav@zFVEgpyq)umCO~lB`33Ku-P``8@I81hwqkhA zWPsdgSDMcGhKP_pfz+!y)HGl!ZKnYu|~S^rhy(C7}NZKX6%3Mr;jmm zQQPCV3qOF>dFPwny(A`M-A{?UtZ0E}Tap$~4t%XPcKJ9n1?mcYqr|Eh?0FVo~XQg=W(Kh4SfXpFUPut3D~b!IH8m zuy#OSs!NvI9r5rsz4hbH*Z7FodvbNfU-G8XuxQp{dqn1ViILQOB143%9@Co0-{)dS zo@eC*H#Yt->pQejr{fF2h{tsW@=lD4TQ79p67EBBTHh=zNt&X%uJ;&y$#Tk~ z6*cHbwi|6P6ZN%^NKHhWxBCPw7@OWwRiPYq`3hAbEELe=CL9Zo62xx|vPkZ5q8+iI zf(>a9m9Ec1G#3)liTCjv93XLth=N)B08Z%n)|lSRlRfT*5?^p45+Qp|QeaHj)g!4C z>)7{@?2Di5hQ7=SiRzs`}u`xgLOD{9aMGls5QR->l&Q%C72J%#&A)T?0O*bg%RBX1jVL%yE)# z?oK{Zh=}XX|BuUkCzY94nr2shN8d*aQQcuqz68; z8dz1^FDg}#M&)*a?p8^Kqg+(V)j6W&tj4-Ld)^Cm|5pid##vte<&qVSaa=njg7lc= zP6VOyGICJzJbj%*(IM^~asUQ+dxfHZnxZLNQ#nM|Z)jwNuhdpp^d@l%td}vbf;=Z}eP@=QhzX&f9?csbTV=E`QeY)oc zG&SCGU~T)iCU3Q_#mUl+HE30%@@`Csbfc9#>yofVCH41*E=S%#gBN{-JJcZg*F+_z z>Xq6_y;dC!jmrv@9oHFX2E@?9eeVC34kW3`X!G>JI;G5W+ay6R|zLy&`<@=(^ObUyRqjsk)r4U}8v+3c!P zt9tqm`FJM>jj1%4BQx&yC&x`JFl^yI)ca|u1B>Z`i2LuUY!U1|o(jq<)-;w)>3{iF zva>qb(@n4V!HE13r&dF4I}ZZVMpspXiG?Y%2oq05O&%&breEY% z%-7bE)}ZVvAb@JgR_X^DO@BqrNK-NrYWpRAttB`C$)$al!*I;fs9D!n!f`N&-|eMx zt6?e)1w6Nf63OPtnYG7NIzpw&E{(R>sNURIpR|ZLe{mkoTh+%YSzb5azy6c>B zMy2^jn1jtl$S~-Pjj$gK-fc^x9z#%Y9~@nMlKKn1j*CL+WaO#U)_Us&#%~+&6>e?v zo@m-yU>6_jFuogydEmKch`e^kyE;kep!$AuzVQhbEm|4HPyG9V-Ae7W`wy#g#!J72 zCCeobqquwhQEu))tmjT!!lfO&H6{LfcV=RHIl-y2jF3aX{ikkAni6K5Q#nI0Z@`4@ z#a3kpv)?RFdeTNZK{`QcBj1+aWv;zd%Qgaqdb5QW2g^A0kgBax=ng?3@}aDN-87=I zLao1&HaM?o_V;_L#m}+<>`Y4Z4t$!GL&oM04Hk1oQjfaM&{$lRb9Zf%h46E7KW9DV z#EP1A)Z^+%)Rz9K+;Vk-QUU1tioAN%A@sa|OB?Klpg(H?8;kymy5k$kXd20OMN{1Ym4&OJ#K3Z)u@N*FN$90n3p@XNHkOke2MY zf1Co-L;r`D1L7hzbIgVV;=@-Bf4i^cH~M@IPOc=p%{536ejbod$d^^an|tmjmwOQh zNW2KS=z;O!18~Dbe!^Ki6NyxzGhZJ;hpGT!e;}Ww8)-70Xf&`$%9&Rpk)%xXLfkZi zi_E+eZv%I@){AlyHab`hUR*eNea0Sn7RoQ^m-t($s4OUD-4_+YFno5eqH6bJhG3h1 zPUV6DQhSp8l}l|ocgse;vB{B1X2a#LlU#osO*ty3({XX*k=OCG=xsrJk+;%&s1BJa z*9f%!VlrA)LMZj`q#^Hn0$ql0a-ojmOmsW46DA4oC#RZa7pXP%2=}EP&1opt7@ZPj zH&DQ>*d*W31lR8*#hLMDDXJX1KOM~bUoJqd1X_AyzRz#3)?}Lk4c_Zqy*uhVLy!pf zl%gezmV~k`+YY+lu^lLSyRUBCc!1n4;h$fRgn_B72_%|>7EY*%CFEt?%$2?YAr9;R zR%nElwdLo0Bmbntt34gtusi>4XII_0+CWl!wf~#5K$8sMJXF5j^zn{_BH&eK-+^Dn z3IZkclijBX+5Xqx3TII5=t!AIRk)ye=d%|%QsQT%!}w+EIJ|jN2|13-<&##)c|IDyOREt9))vAINMOe)xdtGnx=X?83_;=@ z3knqkdghM{i(%7_)L&L;gl(mX9v-5)9j0?izFw&}U*U{?mA^!F!9hX3dLl7V=5|AV zbW4!k8EdYdzIpjZlaT+85hp4j^h-o}P9-{}sU4jup}IEpouOROkw<98bVmZNuhyF% z)o)a+lq=Yy22T3OX}Pz6Vuv>s1JhOysE?iBrc+P(kZ5Q^S1H6>*mf7&`oPX0SBVke8(;_1^e*Xg<^YudZ zhMoFJKbbtA2wN%D6&5K7(6pnI0UEA^UP2y9E~yIL+N|7^{U&Ls(Aq8|@`?fWdJ-Kh z#sDo_lNlZ)U3lWCXh=5G&Z^kW0)Ajs1n5dHWR4$BXym7--2pwmbIAO|0^c{!BD8N zIt7W7!3c**Bc);QCn*%??N`w=&zJB+5+eOlPS&dN?`UGAJQTL;<7*iemJuLq^NOD% zt6^n?8$<;K{mci<0SzPjy}zysURSYq(AJ!snH!?KAFcqt%k(*n_*}lHO(|&cZa;U_ zKTalLzreyQHWpFJdZhVwy&#$i*H|HiMLEV!u*YE&+S41MdH=XM(N7z0-FsExZk>+D zA+~3UG5!PI9qk;av@p48Qck7uKs2L7rLUsS23>iq0K&-rtBCr!{ht`-<owl9Tr-g>O^JC!d!6;G0)aa0C#wXaDs&)E+LA$f=~8 z|B@P={mD9mUeZI9dp2+vzFGQEBHzU}ReCs6#q9#rHHYMxQ<-*U5tQh3*FxORzt)^SUYZr1fs z$KVT#Xz7~((~f->CI}_cJK?LjDN=9e<)j|!R}bpBnKNcB_3(qGR`a*aAT&NhDiBN4 zVaKNArd6n&+xOn^@KGSki%$vU3%t=tzUbU&AGqv zYbtp$=}7Ijt0!}z5x-rwe^)A!2mV<+*bjYo@M~>Y&!ccds{@Csz}jLJxV0VBz(uP-(fM6o_=PI|7Ak z5Xt;_L`Vs1MIQ+%OBuVgr6Jv$uAy=LqSu}QKk(Iyo`(G~K(l=EG=hoDg>`G^cWE zi$qKCgN>Hs1CChyQ$~r;W7W5{0At#Ng`Q2`UmQ`Q{rR*OZ6;8cH9>YWMyCzLwjV^p zKbN*0vI3)h%*!8>{gZ7GsklXPemH0Ya)e4gDO8E26t>2~u~tKU><&d~wRLRMimt$i zuYT&8?~(N*`zgF8`AvRJ@v|6D92q!aOkJSyb0h_26e{;ic93t7vs`Ve@$HDq-T`H~ z%B%xO(rUj${`PeKb9J3_%C+u)?5X0xYHkHOcjcH38w%}_o`xl4dYa5BF)ma%SL+LF zrB|VL=&}f%5}qSsV*kgC^4Zu>_xcQ4yjHh~&@=n|a&HB6Yi&wWVU-0SwJvO%OR7`R zUr0?Kmwam2Yy?c5cYT$X`UfSsB5i!Cf&CewpA#F+t|`|xAU(AVVC2~ls(2a!>+oL8 zHRMPzN^U|J)W2;aU!Ua|_jir8mNTQ+`}riI-8j0Cum4olH%qR_8V>gv>a`k={0|<0 zKZz7QY_TCLATERInOyh!RFA9WmL!1cw_DC4g|`_wfw1!n3?=1CN{SM%HU%3lbsyuLIxn-iowA z5N}2FhMf&8wrf7s(~`LM{JPFgRB;hg-B=;0PK6oP@Wbm(H)cbGB7(e;=ugb%E@rbi z*R7cN@?Ok^i&U!OS3%1OK-)xNhGz`kG7{1(#LBo&; ze^L$ExgSq%-yhXU;IquRNR-7RIm)abfyh_(4FFk<2!NA*3EGDwR}?Zlnf0rp#XlmB z087Wm46tE~jl-K~5-%d+OZRF&h3XKo7W%rF4Yxkw4BlJ-Ys(B+-zz^K42LeKSlY7} z6$1YqfyoyBZtRg$n7Imbcv8_v0>Gh%%ta>gLJW99nRT_sLw(#t`wHUUPZTEjV}5j& zmya&`oZdt$9ksi7-OYLHq_c@ee@u_^N`ZeNh~f`AtlCH*soOzmWX#yI5pKi+Pfp(v z5ONr+M{cM&FvSa@Ym=!Tj_iRmK#$Ahd!1BmQ*uEPZqTC3_PXLhR$$y+U7|?^*ZV6G z7vY^xLndu6ZuHT?6s`)N`C+5!`F|^zq=L-E{yy?WclLl zZ;-HQluZ?H^T#PU+~dYrcWb^jnkob;DE{V!(&*kym0Hlu_>#hz2g6op=c4(#5NW)& z0>T9Wf_?zl?iLImXK)Nf1+cp>$9MuOo&YWB%#Cnlj)Rw0J0JryAkV(I-3?f!styWG zXZHwpOz_3wx;!tn9IE)sZnVhp>C9HhWI1k3Yue`eW5-88X2kj8bx&m2PYl&YMQCR( zz`yx^@`U$;$xx$yO%T&(zUqyza1itJIM%8O+~XOFsq{YeE+YNP#d1Rd!-W*>+Gw&{ zm8e}^)sGF~$IKv5)^+&u2*JfFz%tYp$rTw8m~)9&?=F@O6)yl(G6eHRh{e71M5`b#v+9+<~GG03NXb*;&cGjx`Taw8{*+q6jb} z6-4bpDMO43Ycg7LOVy+PqW=9$w<7b)vDX_7bvpdPb?*~|J?KJ#sI@+-Cv=dgJb6>k zX=Hnr!B2^GJIV!sf5<*a3*TW`d`nDEZg!|iLl&!!`3dp~%8n8^Z@lx1SoLfI3Yyz1 z>1mG*OSS~3=o9r12J%O8-uP)0h_;e{-ZVmrGO%cZ^VE>sy*f8=3%=0h#_!7Z^BkvpC zP|hh$Ie~!Hv3!zZ*qQ{3FMi0F0rU@+I}D4@gf>WRj{`>T4Mc6!f<3RD4`aMNygivV zh<<}|$8*#vX!lAGc4k8Os>7R;n_Tos>n|Z-Voivd5&%H+q_#4Sta+;_M1!+S6OC-r z2Rv-dj38g95_K;Lug<86G+C1~b6!dOI?(($EXMkpGBP1QE9T06Se>`o>GmWWbL}L6 zAg1_elGLq|1%DHyqaU4oc?{0Z-&~f0%?xA*rCS%8FDfNzclgR5nW{w{zfGWblSZA} z-$odfq_`mk39?=miT{(_*cN_4c3Puz1fHOtSY4+>7uAUdh`$9;$8Fyj^hHXc%l}+$ zAOFV9JxRiZ(O@jl!vZg~*frcp$r-a0`d$VmVjY(|upp<>a-Vj;>Gf~DZ@_IY*VAag zd}pABRKic5Xm5s>_JTbNw{12%i;zRHu#kXLtYa6wb)gL&=8l?6Z5Azlc15SMnev(R z^$_zCA!_EUXu%q>pFa zLH?Jq#BGUOMZc-Eh?fs7cy+rZql`cK)I4>z|winMU|V-q1kqZhNa)0 z(U$O03*c*QrmWjZMgz|6SGg5eC(<^>!tyPj>PQsG!HG;b*iM;z(vtg0^ z)^%SRX5ixcVSHZhp`{9uY3Z8$P_}FZ@ns}up%j5-@)9434C8oI&xZgFCNWB{<7;?2 zQL_bc(NpoL9ln_nTq#kwF=bI{D+?lX``Vz>aQ>e1b52q6;jLk6_hQ)SXc?DD^^~4` zaU*0r(;8wN`Hjueb_ySmT#kEEk>x=Y$pj@gttK$vf$hu41k!pf`N!pnjU!4mA)viOXo{{ zy+oYR7xr4}Yv*~hM-Blr_@LKrN*g?Ygu+90ugRkfBK`P()50ysfZK2IB7ZwdeCFbx z_{}{M9Cs4%PJ)?dk2B%czt>RysfiFeC=5pxd=eSGNqb~(Pz)9^Sk|$xi4w52TL%gHnOU6*my~52}DO)von>uMrqMc~@uu%?N$5I!-hH1~mYE zABQb%%tk5gu+kx&)**Q(2yOZIWc)dRy+xAf&`Ts8_HFK2fK>5^zu#uM<99aSo-RP5 zk9>rSH(xlAuy1qEn&i*OoMQ6Uijdn%m1DfQE2tT=I{P4EeLFf&Z41t++!&O^yL-Fv zY2h$&W?LrsCuFOe!xlH#G?L@fTa2`~UQ*D0gm10&Y#0pR1~b7sT)gqFXx)FiA*4%vJbYFheVwl6#U1nCZ6mIt-!o3}k_ z-+uRgR3G6`8-SmIH=kVGR=1@UEN{weKTR9Nr(OIrbEHj1BS%a}V*_ws$ZO=Q?L|6~ zCCmstvPOXBxiGWXH+8$V2PH|5QU+MRmDU4%IOh1S*uSW;Xc#r{JDd2%lRAd0xISGmodmDuL zjVbE=bXWu*|e|BZ=0 zc%-vmEeFD8CAz9o>(N>ap0@NT6`nS#U;Z=Q-8RC}Oui+th;&P0r^202CPxTm?6Hx^ zj6?cRDQ2$y(eeHyM*B8)A>m<{Y#yL=c?si$W%o-gDSOx^%{x;A@imk|<9i7E2=d14mj3e;DCi><5|+njp1Ra!25Z&+{WnSBNAIIc1Ht$uv4b$`I8kN>sq zZ2M+=CpIbHeVx~wcYR3*r+z`MTJLMcGOg(AKK%yF`YqKGBu-}Z&?G5s_CM9de3rK7 z`qOQR!Ub1mwk0c(*i%2d$EfR}HD^uD9O35z32X6jYUs z9P4cIY0K++S-gWp5=35^d{3Aw479>mL9Z{)+fw!YB-e{XvQ;i&N8#O5O7;3r_$s#C z{W$UBwi?51GC6%iLd))$fHh7DyZdR2lQ2;rubkGzrf*AMUic42zSjn~E<(@0AqTl2ea zbWE#D<$r8Gc`K%>-&(bI#uP#JWLja4W2VQ?vJvjb7e4OHmi_UTvVjl1+~N-VmSF;pei+3pcm9mrGEuMF%rB9U@4!uD~LS#>8Y zOT~Sc)*SKjtv?t~C7Hx^Ogn}Ztt=j4GLwo%jwDyxy@ z!Mnz=S&FPL5T0Tgj+~``dp9fK%^WOTn2|6%$ts0Ijn0c&ZkO4$wIJhuaVq02ZDQRz z%~HwA70YBC2L=D#_RsEeE68ZRu66a4Pyc&w4aYlv6H{AKMY+)}siLvDpDyP6e12EA zocXEQzUt+WJ*|X9y6~=w9(>SCxII_6H{ylQ%)Bns;vL)C@6i?K6jTXDj%@}VorhZg zY>}x#aP&}LRHvrUVPP`$)tmyG1)}?Wzt@RR_W#()Tl95G$&K--8bcE2(!$)@eIHZR zKa=$8WEoazKKw8TsSoX? zD;gDyJND21Z*^F80Poj{t`}lo66sN!UZt}^w{meRX?Ha7OvaERb1SdwAwRb*zTUdp zHeFG*?Vm8vJ#69y?Ipa|0acW*_QJip=M-od6pV-R`soQlDxd8ScPM+?#3$|Zc${CR z6{}Px{!h2kzF(6>>MHQwrP}Xau0{DGnzoFUTpSkazB10I%H4Y#|ZCT zUUyY9Jbo4oW|g(vv>xrDH-}bJN$d-uJjX1L(=QTbOccm+3(kuZ=ZK#2UR#KYua3H< z^xqB|O`~JZFX{6P3#v9hy|X^HOx#Ofcxyy~#L4--OT|kn{}rB9AKLH$xmO@^mXuwe@532W^46G=TqiSYmu+BfojR7DCyARZ%;e+&mfUb?MUM7{Pr`i zC^=+`fE@^-LqvSv z`Ec7azrHcAyhxNI3FUX&h~79E>}ke?^vH7W0*Xh{`Qxs<$Z%q%DGK_P!+%E>QJ%Li z+D6tH{Yc1EqpF3XUrHsMIAsCnN|Vpb(_Kpm5nR`puUEO;pnD&9sx$V0(mT)Mk$Psm zs3XsWL|V{$iK*r3wD1{TqexL{Eb!?c;o+QQ`VyOd{A5>jLB_-DT9mqU4t3i_Y#u`^ z;#UcA6zU#Pnf1m6RTU#j>?FTiK0L1#RTNUoc}3kZvBzU|FiVd}=hG6)&|)vQ zpNzC~{jMgZ>tOe?%`yhdL0*mbeeOc1&y3AVGcZnOBr3>g&RGFX6TQ5ny#fN4`zXCBrlL z#wb~Rb=Z&ZKK+J#8$&`#=vB=Tp$w5NNl_7amWiJ<`^@BbJ!O7sj8`BDHO7N^pXU|4 zzL+uex%eTeXfLSY^TNlOw{!Dg>pibBe+iO_dWn0?FOSDNv3)FEeE}!4u&mWAUD16W zp~VEUjy#*WqT(7uwgcX62ug3@J=CpqoY5{{MYkD7B7t{r0n9Ox?`8<4RaxvHj;dl1RUokpF49z#n-Gh z$2HK`i-}Q+AS4W!KfMSM{*ZK?&c1sn0xcqRL=pVg#v-GR#91Wa)Ny~_Oh&=DegACI znpy0-F5&*N)*+d$QL~_IgKf4dL z)f#fTt&*u>H=dL&#m$|a%p|CY6CTD}Y^BcVQ&ftM^Rc<~u?G4bewV6~LH_HH`Vvvv zP!kT2BH7i@MxMO&a|?Ez8=1~I4MiYzWio|S_wpitd@FCgalv>P2&1DUVAtjn_ZPeC zCdBa8LkId2|ECGq2Z(d%{e6py{*|Ps>RzON5be@5`A<1|r4Hk?Y-Wy2)s8In)kkNoFSLh%&_kPZp(7KcsP}`m+Pbq-}3k^6t0|@#ymm z3N*GZx)@m|F4P@+LvOp?)8p3%`Dp;mWymJ$+X}5Q__r35bIo@T-e|_t2+Ha4OLI0KyvAI5^p zSsdzwg8HzA`O_wyJrWF!v7psly-cjG4^CGvZ;hFvtrA5cr~eyE?5$2KAGV#}HULg(7o~S zH$j0oF^8_`HZ=NWhkul%Tj1R~SytVI%9!^ntE=1Fq42~BkNU`})Xo<^k@;PF^eY4U zjl$@GcMsvd6k;{{pPy>8^7vg%aJw+zVW0juLJj)+MENBLDIqJHAcX}LiGm(dp= z?{ehrTHU$OXki-FT!(EspV;pkBl*mLjIJDJfUi?!#Yc_p*GBTFGjqC9`F>{yb1&hv z1UU!;S#Drljm{2wEx__92TN1T{RCL*1Q4SGS`IE!tnLEPxUubL}b!ygARhq3na#Ze;?%p^0>I`xM4 zsNy%VBB!5)4i(nzHcS7kJ=$N8QC_BXe+WnLWY-2}(tJ@SZp`FTC6oozQ^NMaJgYVr zmFOjGH|RDmekg#$U3RTPZ}bh+WPH7hpZ@-(00!fKWnsm6gF0+5Xi3~a2cp>(?PFTwf$nF|+^Beg~oH)j4E&HBKx7=!zO2tb_ z7!0S2Gb4T-&(S)%>8MZtWWEjLFR>R2`cq2J#9~^}c-{2Zi>_A89la;EZ}BHi)Hl~5 z4!wMfP3oIUHyKT3!Mha5E}_saw`(;aek=bTnZDhV&?q1IHgiPfdX;|9mww3PvlItq@O^Rg3cp7v^V6Y3&{oa0Z zRn8Eb&RFLKVsc7?E%%7CKx$wPHF`Xz;y!kNM$_bL@thx@q8}f8kMpl5oGBBY3m#e0 zYsc@}>1@ytW_x}|nK-PcWsRrT)>|^$yXY$O_1LAWVQ(~64)b>T8g$R}ReZm?xQt#I z!uyr8{QAwMyIhwoc3^CIG*cz~5IAE=Z@{UpR+x4-w4U*O`Y$oJW)b{jYbW>2vRU>l zjhud13EW)|HoOnf9cg=B8ftP=wC;cw^V1A3i5cl!>SP&KQ64eYLolSYvw?f0?WR5m zhSOcIu2yKp_tQ^=+>#MLj$77ZLQXH6g5i$rW{AqOJX#wQAFXZjDLLKM%B**aNvEg2 zG~Ro7N-2AvBtuMifYx8@={t-Hs&0)Oy98I-Abrb79<$yh#_9Re#w;#XU7KC1La>|l zS4-w6Z>OrD~-UF&%}Yo{$`W$^(c zP*e;%A&(j_!E|C7bT{KJfDVW0icdRgG8o7wigutbhs=D=W??pYN* zYhsOYNxVqIg7Jj@*{7~gyYlIDS1<4N`9Y28d6uQ@1Cordt^^@QL=yzNJ#x(T-^A=6 zTbWvU#NlMSVJK|oZbA^fd-0I}`clTwQM~Y;IIZwM(BR6ecm+b}2`Chmojnt)BM>Qr z?tEXxfA{b6u?8+yl|h%up*Ju z3(~-<7wacnarkoH7Xbtk4(n2;TgWY`mSisVKRUZ~DsgPxsu!onk9-o_i#PWtf4#=H zF%UJWHITRWTF?Hlk5hYPf=aPmuVDIE9^Epcn%bgFll%G(o`TCOaoJKF>vvBRnDrVK z0pKQ|upia*$V2f|TWejBIV|ilEehP4wPGL5DvjzKLrIUA zm}<&lTzMW^?9LpzI(kUzFDpDA1_ND-vZhy3MV~kaZih8WK4o=GzTBf}T#hH{)OHgpod|sU_>d0=JSo znl!IjO`aD(-WeUr>#cNq=Cxxg?Djt^g#;yjsqh|q|Vw$D0{cVQ%j6bbiHTZzBZ!5o};xr*i*77 zd160T9Q5*9In+Vl0k)ma6G)P_IpJf}3|jhZ8J6bI23gX(jCg8e3U~Q8$)ftvKi9W7*PIKZcsl;?d6#|M z<>h$(f7i3|Ay;F~P+Lxct(_fXV)8uSbT1M=#)%gU#46NQ^6Nd>A~!70O61IceSrX( z2)E26!Y%NhY2-p3Z52|Z?BJv`irOk~lmyl~fn<`hegOH)@|tsU6wg!#28n&H#*kx| z2D60IWI&T6WlzWLk=iPJJ$9T+4TYq)tco&yAGdasEGtjBl*D;}+e^7D7aqrggxj~k zqn{2ccVR1)aCtIV;DKJCsR2d2gc#i(ws zX$Qth?5i#&XQ&rIt~xO4 z4}2=NrUS#%O{ks5rIO6)0c3%jwu+%H1PrbR!6RiWn+fur2CLNTbMN+OC%ZF>49XS2^M-`NljpzB*P7^Fy|8p;gZI!y)DD~K>XfH z{65aD1(?EEPF%-1cgewT85RRzYAt=9d(0C+pB#Q-B<~gcq~^p&OYw~TB3@TOK}M{P z;)d@EWm-!X1G{jG>1EhCP)N_WL7Cv5ZSAk=k>R#`?W~e(Ps1`M@w*`jKl?e$*Wh-{W}AK0xfw8Tv6`SiFLQ;1 zu$M~}%7gHj?74;HbP*PlP5ZrAoSWM7;Pg7qZ(``#3j<^DxYVDyGiicsI!%s4928=$ zF_H{D=Nbxo)@SFKAcO9x{@K!yQ{cWp9XllP-w!B%Akpc{&@y{Y}>a$Jd@$}?Bb5X)&?Le~N z&(qUWIJHrDTKtFp_A~*M3l;j{Hw~=d{7)$*v7kB$evxMi1GivhhJU8M0dB8*4W*s* zd+Mh1$?4vpc#HI(+uenM3o!au1WJkBz6Sr?5RW_m+1+rBP+QW1OBpdsdAEWGH)xTL z>>xzPZl#j!Y5Ia}U40Do;4RqO>UI3XWNgcQ<&tboAUl|n)#JCs9G6xFW&v)&Zp^}y zKk%dxu46NTJ`&N=@3R6-!zd~aHI(d2gj{&HFTVBzKvSIa?94y^paO%0ToE!fj)3BT z4O_I?ccUE`oU@vL{sG&9`U>kx+4g68?V!ttB{h`k|C_|#2pLusa3`UX0&$XYKtC)I z?)3+^`7hD{G3>ZrQBx=?jHNY{chCMze6PO@O8|yDW0(r<+6bCu0r=J;p0WD#jDQZn zb0y|_{eN@aiCgS&3;%ypVIKuhs85q)d(=PekV{r1>;He zipDW6wGrBs5C$-7nWCHY^<7A*puiUA!em_ZI_ZbKj zK_M0mao$>BnXE$Rjwb0pDkHvQS`o$w1N#+0MG)^`nG(P??lbr^2Pp#K=28VyeseF| zRg%Fnr^&T1`OQIh)v!6_GO?jvLIThCMk}T9cq~5`!Q#f`a(bmdurb&Ye?!`mVpjn! z;w=gutd%3(+UZ5?^}rGO4y6)yHRA1)WAc>P20$qr<(j27*n>nSc**%cFOiaEHF7Ws zqd$|{Kv9{Gg=;|35QLaNi@g+jZOIf|PU3ns0suY?_^r!9c1&|gS7w=Pa0?{wo8Hsaq#IhSEBZ`N*n*pj zGWcF5t0^ijxm@bDtrhWckpFD3sSBp1sZ-m+e}06Oy+PGy(9jG}wW`?g&zycyya%6#<$w&c>qu{`WzlGx+a~Qpp)} zipn-91HGWHgM9?=cVW2M7!J}&d3Jy%WLh=@qv^7j_Bt@mm=1aW{cQhS@U=71iXD}29 zrg1F3ObIK|Age?XG!7jR7TFf2kqPMjr$_Z=m5|FGe1MhXL zI*H}7y)Yt9j5#RgTT_@uDQ2X7KvxQ7k^cjVDV4Hs;~t1MW8DA( z4qh?^Fz(8#KI|IAF-0Y6Dt_Bn^?^in&_r~afYZrknb-_ohRQUt@0b>4K$f$Dr+R-% ziM2i$Ry%ckn=(06hAVF3dYNM+O^j3^!xEmx zoDw>kT+&!eQW5tIL0~mlTLp-m1=idMUEQLL8K}~pfBLtdM3S4#bXdAp4)zgY8*W>Q zvpGcl}4sl+OUOLf6gKOhjVfgO69<`s?z_Z-o4zKvi+g=5q)O%MsfVDrI0(H@xB zY^)HFsQ~;Mo*Q3_8M7%hhV4R0279vuqh8Jt8|x-3JQoPrBX zD74s=(lVeq=UdibgVH9T3)mFN}AUaRiqeTdYUi^bZK%o;Z(qjgK^IS z$J7>qoiU|%Q0$m+;NBWTuumRl^ht)bq}+j_=TNoxFAN>hF|AP-Lt{pg(4doPa(c`v z%1zMrjdJOhl~sHB*e(wc5=MrJBrP3W!h#9P#|FGX#>oaaNigGf&I()#Y{Ep6EH@og zlEO%ED(53^cOO%F%ViZEkEbGXbazFXmv9x9yHZ;vY*xVCNnDnIhUXCWoSwz4os{M! zSf;H~lo?NjAx25j3oNBT<^G@Q&Gsj%BMRfQE8q*D+-}(LQscTH+E&w5SfW@Ow2L4X zo7$?^7s&0U72?GRiiF14B3io(qE;JOh}x~St!)?NMlnsT$=YtKji4lL2_-RNvN5RaYn zaAqBLraJ9J$h8=sS_Now#ZrW$0A+#wtX7B9XF)OK;O2|f=f}9R@%-mokHKU=`FhMw z=Nnc52l&<)YK$LWWQ?oo<5KbY4X4_1E^5~4g1y*|~+Y_A2vMZD(nU0Te%K_My z0*Yb&CY`E_?u@s@4yGeSB6cWED@gJ3Ze{IUeNXZC4f}m+9Q4vsW{Ec4xuHc~#6$?( zc?F@9>H400ZbvHSkkrKBYO_2+4m0H1e;T}flFKtAA9>vbQMcclUmkv;K9_pqE5dE* zqcezKKvE}1pCKrO6ZpI38PmU%t@j*yHzmQ$U}CRz=!;>%LoZKP#ng1&%?4ySQt%9e z0;$9VvkixpKtaIIR^_3q(W|B+wTAFyTcncSwGTW&c<=gLieMaIPGhy7JVChY`h7N& zlsiK*uJ!H&vkxRV^VP5}iHAf60pzwBl%pmqO>Q=PaJ*JEO)+E~u?)%!2`X!~*5e6I zQCPhcQ(cU*W|R?bMhE{Ql!u_T04?WA;lhx$A}>5ZykyPSO}wZ5NGq#6!7|phx4r5JmRF#$qA*;9 zCvEAv2`Levs{ymM02JbfPP)&)#p6hu>sK!>I)Nr^tRSEGy{-8Ov4Xhw;_awu2rW4w zh3+V(JoEKWqn~=rQI+*zkz&-+r|C+k$f)Rz2-!?YQgDhAtDTK$LdX@T7{K9Blx!}$ zFpRI6<|JN`QABg!-b$k@acjaU>@Zxw&%CKQ&9d4{ijhRMFx5^EE!3IAm0tA@zRzI} z)SQc$bTLiegGocAf!Fy;w)Kh#HRw$L=F*Ovt3t^?|16OXW-F5 zXn``DkJdufp5QN-1VXdabW~%csCDSYm?ROJCYqfX*%_-COXw~LO&sX2l0pQ&8zKyZ zW=*nVv2L@aVF2exvI9=btw2%l*%QG5fa~7YP3P=W_S(AQilpg4GI{O9? z2BC?V%V(u8gMM!pY(yv^Rn2xuRfbbqUU5as+I<3sTZQEL8BcT09t!PYR!o)pFkQ#sX1SQ8WTpS6# z*wOXmoszop_OY8=c3L&X+}?istC0tfIG(!nskCrMa{nBKSKK&ox^(Lx*;S|APJw|l*j3srwAr)1}auF}uO-hWM& zX1)1kDjt%L_ThizaPt&0hifJu$G1P0swu776)$a(-*=^WFp;PFtAA*+F@F7@#7`C; z*i<%As5ZMN3xD4nF$+ocXyRnySWez#^~3un3ws7$nJBC%^G!6~+qZhsqO3~WgvDR{ ud4A%vtGKK04WoVI4*mu3zx)mLJ)Ii#U;q2k(I1**2Uh2=^>nP%8vX+%j8xYE diff --git a/assets/images/toilet.png b/assets/images/toilet.png deleted file mode 100644 index 6c0efa2a51a8399cf5af75529780da71f17a58d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28325 zcmeFZX;@R|);}CTz^Nd$B38j#Hc(}DVg_g2B2cRvq96n!QbhroB7`AMMFl~3Dxgdb zRdj<8w2CAQaR7`cXel!R)F=`lNGL>P`mdeXbDr<-r}w(fIoElf%j|pK_qx}-ers*c z{OsoZ&Ft@IQz(>g(5;((p-^UU$p6h$gJ1fV-K&THr{?eCyooYR{`X3G9-Bh>fr4&Y zzw2mLzc8Bf!ECq35l$3u;Qkk2%Zr+8sRmdNe!!_mFKMBz;33E_3k<&9m z#1Q#hX^oNaw%Z7=&cG;aIq@tMn3*>nb&;C!4Q1eBy*5O^HGYx$bdz@x)7zF|X!tTTPXZ`H!v_S9kqH?tJchO63WZum*sc)J*%Q0)Cz(Bz#iK~H7hc&ik>ya`(qMPy#H&$IF6~kHin_X>TGA(#|L1R|`UnN;k>0X)cbr=Q2j#B1PKw z9{waJ)f$WZ74O<6JuflkuOHuzrz2z6EvqxRH5Nv4B7P@qe-sR}JMx)xI0K!zA{pDB zkvwEBq-P|uR4Eh_xQ6}W=9J*(a@Cd{zV6b$kjiw?H}vF#of}ulH#qq+=!7{M)(ra> zexc!cM$vUT?~H}?kIP75HRKZ)M7vSFmU7i!hi@PW=S3(zIn^q}FI~>=e+cnEi2KJ* zrR{IZkc2FeRgKP|MDK}Y9(niMSmScEZhIINF1F&L$ee8t>`Rka_M4CgXLw+HZ3TAh ztQLFKjP^d<{$TRZsAO4x@2WuYCdomiXOuqOFmVH_D z%@FJwG>X{h3#N7Gu@9MZ+n-F&gj0KWHTt1-Zeb_jLjKvgqLO=BcPsvO`0d2I*~=&t zi|gu}%ak?h-k|X-1mlFbhS;XE+rmgr?yknGC~)wg(2I3Znu4V2&?j7F^B%ax&7e^B zhYz0iOQy{VIkFzFF&t~O+!C3(JCM}1 zSA|631(Ax-jxzcNYdzXMIV{}RbkX=E++}WIs{^HbL&tJ~SH4&zS=P6WRNehQ(3I}+ zV(^k(jS0PacT?o{s^;0NN8v0rOB2cR#mh58WW_Fu-_ynLyQ-$Ulpt(xGM{nfIG1bQ@P^AZbI zl4?Zh?)F|OaLE@d2{(FNB(F{h{iNX{?I&XP!JBd}Q4XbE5DBo6lagimTS&qwEk1ck z)ONX{Q(EHlJR4W#Z)3Kq#cRPil&I>B_=FpFPO{8JelVp!OWh!PkL>h~`JgB8ylG>; zSRm9-+pgFvI<NCyC`>*tfi>8E`?*+E}$j}V&)H>SzW zpVG27(J*>F7RQjCzd^DD-spOb`s~I$#d>#VkldiGOC8+$m23SZFDWd?W|y!nm~NB9 zzWs_7MRrK^FS65@NUNk!CiQ%<=~Cv?0>x^Zi6~6SVLna%`cH3rfeRsiC|Pz-OMc*o zBmTJ4tragnczv!SU(cw`W>5ITZloSlD8pm#39hT`^zN_!v_S;Jq^d1jrJ(mi#0{jf z-N9c?P71$FvKs^ae(fsfPDcAGLi|*sm903t6jXH#yZ=Bp@bGjM$!u*=8XA9>xoM$- zd5fk3vAy@1o7BJlGi94JS)!$@c=`Q9Iws6xJ`GmLA?^JEEOLYFv``^rliDFP=Whpp zD+Lc~!>0)@22Xz^m4{+dzg7A>u_x(4I8{bo>VfDK>e%UUymv{q=rmFjSKA}abh^t8 z&JazZQyk_qx5YvgSVo8=(crFf`Z70NdRZ}ZM(xj-vysh_;qYJZzL0pg=UYDck4-HX ztf|rQ1q)zjk#}Y-SX2Eu3}=MI`%Hy6`C`sU=?;7+-Vrt&9$T|Tqiq3hA2WRKu6%8q z&9VCiZnWZowOu>7M{v8~_~35`_nQk!(ZW^LpSQ}+XTNpY0(4Nr>B$cC!Awt@@O;Jvdft9RDulxzQ4NFI7)S#7@W-L57fp4Do9rrd6|fX;R9|4z>Jhg<2%A<33LYpWl+uI+qgt1qyM z@f=#(ah(W$<=}55*8+Z8vSpnfy~9Yb2DdjBsJ>*kSm5>#m`~m0I>Ay|S^fE=&bvow z@Oe>ykhv9~pkcy?%%@Ru(XmdftUfxZ^DY>5bCF6U4ne{DRJ$I_?S*yNvifsA(#00} zp%oqWf!V^_L?I}bom{H?2aD+57S%^%@F0V+d+_2xWUN=B)sD(9_CTofF1K2T;U3n7 z_Lcm#yRKC`u@(l`%|_{U%`tCjzs;f^Nx-N=ZezV{ReXH zxy6?eDPp~1h1$_-4p*ETd3Y#x=bJp9tl&&761`4#dH{SM$b|HQ^k%u=*3kHD_0e9^g6|!5Ud284~+36DoikE_uhb|%vSLq9Qsr#5vMI%k_xc+R3 zUD#*DwkMdiXL-(;ZkNMbnw;C~@-3^2oS(L#&W2+bBwFel<=1UHyrsZ7Vvy>mDb@?M zP~34gJvq;CY)qnSERMk`G{r{Xt%m?*~{ms%vYlZq6C*|aA4@`!bF>V;`DN9=rUL;Zay87m7Qr(ws zmtK;X8jBJM@h6Fvm*S=k-_s}P_&8E}1+HZ6C>Y>6A5u8!?T3?xxJYVWXYmGY#<`#6 z$EJ0AvdgYApPqf_f|(MtOb?R2j+0ZZ!^9_k#p@$Ob}JbnrGB#0cN9vt83O;0f+t)? z!D+iF&6e46?WHDLR?lHi3<3+fHH0b;6gNuyK&q32|C``FSWRSVi?7^Qaee|B8H`m) zbmuUB#T&o^%oTfF3q+zBveT!$UaZ-m_yufA71euPPJdHT!sJgWbanIh^fE`i2q&E` zxw|fQQm|E^m%Yzjc3PFUS;m@_r%{xoHPuH$aHrnRVw%EBnti^3bb)UEtoWzfV){fS zVh+!LHu7w=6zZFumGivdxKX4g_0~rAM4m)zp+Z}=Lz0g~;j4HIlfPeb)x8x)OI_eT zpvU>Sc~CEOhk^>M%wAx`b2fP;5UTvU+eyo6E8M<_S);tSoG^Rs@bX|5Be-=o>`*Mu@+O{ zX3d(%ces0Gtgn0Ie{2-0Xn#11MN(y_!>c0-~xu5kLs z2a=Bj<1*0t^Y6I-+4(Mmp$R|y_bAwCTGtc1&V1VWl^sl98B5jXM}Yy)x&NGkp1Y|L zn$(QtU5^x^#|+^TzYo$BZs|Fc4wix*5XtHJ@|gLJW1|CYJ#d&7qPSvX+yf^6_>$KD z^TSQ@hyS~x5Pq;eMy4>Yx_x=-|Jkde6ZVpa3(BvdK0uiwY=3Wk?EbX(GRRITw1d}~ zdd&))BjZBf0pw< zFY!N*_`ksM{~<`y8g<*q`2SIlW2MMk#3_)){LmAr*|J!XP(~^@O({b2_hb^VULKcO zYUvVKCVyq!3CLMYo{b4rMitP3M>gyJ^uK99ETjQrzsOkSik#{qh9w%5Zjhy@j(Pv* zM=YueNd#cxd_Cho#|C&pn)q)9b$5_Vr+k63uT(xS>;h6M)>d1W{5Khz&l&Cdy;*&} zg6_jW_xMp6>uWN1aTn(47DgUylgH#&DDo$Q8z5ty8l(Pa6&9HUY2)yj|72$Q;@{y1 zMV{H8M%XSB2!wXp)%p@hW+=Rm8m}E5i1p#-RUrucz&R^<K zzBH*MQ-DL-1w_hL=`E;FqW1r%mT(#EMvS6TsTOj9(DHUQ7NhaEnKj4bOa-k%{ZTpa zOEGDr<4d|6{2z7NS6de`pH3-irmEWn8@K<1S@YQR?H8INO`fw8(-*8_FX=1~c;f>K zkf2Cv|FX9b90r0qdhoIvuz@1+t-3`RBhmh{lm(v_(v#=>w`Mx|GXF&Vwy<6(0=pOd zxA1y<6_r5Yr3yIv8lnEza_puj)|`}oqW%3#dU8FKV~Wx(12~f{szS}0$^$l+l`;7q z3PS!|+f^PDj0Z77c57Wcr=vi|DHiu~UBXf&TE?O~Pp}fUY>Xh5ZVp>s_NcWsVsNrtc%fmrJ+^}R^qAt2 z2NvnS_H83t?&8sQkEgz5u3T`x?!*Aa^T6}rT7FBnN~`}<*zeiZIKVDjE?`82>^@Sz zh`tMu14S9hD1+bwiudXpQmF&z9Ampu;odBKf=)i#&=Cx=Qs? zNsh?BJn7-gr6`~@{~~8Nf<>M|N>J%WRFWoIdFMpSSzo{)%3*i8OQ5-8&_e*o>u;p1 za_lLFG3sIQEhndH+zEUt}1bX;gXuzi!7UHjNKX-XYYF!sWCBd5P~c zmBe`KLn$K`pO1T+3C^H}+_BOvc+%k1wy2i;YMmm0W*TCJ2IIRh=l7WjEqe_XC(P87 zndZXsnBk>PQNuO*_>%ki8-a&3hd2j;fS#-wq1k%Cjdp?H`r~~)nKfIBE}RRE-0+Ts zSh^oO9MFA>y`9n2cAKb0YyyVE?;Wyl>hUrlo!V~4%OWoH*djGszj348c}Ung3SUwe;yi7GH0+6s6Alq*_9{dlUS37M5 z<#>lfXCaA`Vj~xk=A~qh<5_Un%D}6{K@u-p>K2xYqz~>T>)QU~4b7%L0Fau()|sLl zR4+{w^D-Qsu!O_SXs}iFEy*&TD}N8`WrIhaLG;f!&(P)?LkkvgYAY^_M3&Wwyu^sm zQ=$G?8_0MiCCO|xd$FohCdhe=@osel#fBz zP2g@cb_uCjTMye(K1czU%4MWx95X)Y3h+Ov?}X3`p0Ik$tOTyrI@4_q`_$ZM19u7T z4m=Mj4Um!0KW!WIP@lYQw9@^{fwp&7y_^Iks3#L#!x%#cyJi#|-h~0?>5LXO zeSPx0`uae~fZobgT^k;LLx|54Uv3~FXOEJv^;b63=$QBGS=xorbpIf@yKtwiVI?1y z!z2D(d*J33iI&R`G~JT~HwsTTER9lJe1}iA$gJW>==8%4M_Gi}5$)6)A|=;y`5wE& zrK~MMxk>33k!TdX9(Re5-%R_x3M#P&EK+vf;K-@aIb^fT-Cf!(F(r?B8*3uCQ?0YK zPw(#PM8MR}61KL2Bpj5Jyg0`?*G(Gtw(*!2XLKXB*HI9Zd^|#zLQ%{Du)T}^UF8}! zZdpCfOEkI2iqs;+&rzRr(f10bSB=HucFC5V4G#MRWyd0yLEi&OxG3UX&VvIewZpwg z-Z{%op~JpjjR8Fi@RiF2m(aov1qZX-4N3vNE{87GvH4Wy4y^gvmNM=J1uu>`UOZ=2 zGekGRP@WCpj1B%|ovI2+oI@SmjNzYr(Lw0?ny_eYiT2@OVsoEK`X`G!+oT1Em3GmGds5Gt`Qn=}NwCJ|Fgaav zsMQA5gH{E!kVk`WxYMvkEi`AoT!lYJT@;O>!9V!CJly!C?mFAMM6p@6ra|;4op(jt z#L(9sqd@vzyTx6$M~f8-yt>X^Dne2h(X-keew)098*$cFMt-u}^ zj89fBk<-`Is*2{Mix$@CCa?$lqsI-J6;mW z@CoR6?G(~m;*y-;K!$>l{Ez3*oC~5G^yFb)&8xs6C1XLaA`v<~dbh9sU` zD}pW0vCHnrw+#K+Y^(G)q~2YMLtc<6v$%94m;a>|TIp~DC8jgPyg9%X7;z}mW-8|s zg}CU%?)a0?DP%=THBp}|Q7TM84C*~uX<{>FZ(rM~2`~0_mmRXNp85+hhkpLuXw5l) zJ|h`Agye1wgO&-ob;(#L-h4RT2YP<~Fc=bi+bBb#C6~qip*KiKUG>KPB1kFAqBV{8 z-&X2dFTV=SHBMj9Wn^P4XlG~HRDZ7NL<_i8dzTdu&>XAW-5muxQ(ZCrBIrD4j*s>Nu*UOR8xwb& zrbFKEV-s+h!Prr}$Z%`}UIfj*!Ay{l$!=`#3c-#{GV^c{+-G~Q5}d=m?}zASc3M|k zgN^jj4=D95LgTM7Yc9tG;-357EjE~n0{<}#(kIg;sI1ls2G|`2g4g9qO}`k}97!H3 zmMkksteR0k*sd1Xpk~eG0q3&VBG5@IXrjn;fZ5Zx2pBgCMkTqTKas>yvpvQztzl1| z=Z7lwjiNry<#gBBp9J&S6BYR^VDK-8a$dx2!l%V|Pus_f!BnYzxhvOrhKUk495+&pQGNdv` z^aTb!QY|NzftXF2pod6LRyQg|@sfza-F{Gy?*zU-`B51rT^zuWAB{4@@xEkBz4v|~ z8rnq&6&FO>sNN+}-^*~1M$ITqnuE!Yw5s0x?4MD{qSr#!7MTiiX_*CuR<({h8n$h1 zj_aH0dyKr-53&O;RSqW3`*GH})5`uEjn8gf`u%#<85i#^yl~g~q;fxJ5jI+$=D%^) z?>AV#?rvcv{Jd$#=FOYGo2%8kab1kX%$dqc8-5TBPjjsPy3yK~RbGGZgLm_q-<}jX z=^FJEMAkDZ`a*^GdFgjeIW(8CjZv~**2U;R-24tP7*!A12WQTxJ6&iiVBhre`0x(z z@V7la(*8v$pI3BVRM((uEWO#E&5aR9z(@}N%r0zjyR;G2vk~518Gq)ye#wZ62kvEH z6KU&j;KLS$!$?iTGSI*IqrS^yY53Bt;N2H^+toSoE;<~Eg)ZmYkgZ{P#FzBmc_8}( z*vK3-em|FP;8?^nchY^tHTC0v$?9b-I2weT6GXFXB5ZXiiTE;vOf$K3)xzIJ_ipM_ zOAO{xa~*U!p^)DPV3AF7(zL&HK5Ut^=!mjqMYfB9{E+jaJq@~ZWaZdMe=j*t1gXwK z!%_zB=~iNrrIe(9zY%C}$Wzgl3}GXu6-zv@!L4Db{pF7y7ncenD18qGRMHr~rV4}b zju2Z2AhcP8_!JudE0_K#xI>ud3WnG2NE61|(jMsnZ?aN=VCs3uW$GfHZpi*!RNbIe z(ELD?YL%%$y#*BzJC6))X@3xA+oZav+2!Efx9YPrXyp&0_+m*uGWK_GifT*{R4QlK73Eu{-Isdd{BbVg_tB$uKgSjcxA5^rR_h+gX^tq~= zyi!HukI0D?5aRo2d@E`KfNiZY-|Mgf#pUjO)(QL_^O*E`5q$1O%?5Dsl`W-2hn`#>^J-M zc47{?@FM5Ogx^JbfkHa}?bbuXtdTRY=dikB&tI@XH)$S-ME>wSt5N8=J*+LF=6G2J zF%=*u_|#O4EzPgcMwf4U_&w{QNDQyP^Is3byY9ztdr{-6&T&&Ki2R({3uLg?5QL{; z^Z@Lj!?FIh-If+N5y5|K&#;Auw5q9STiW`T)!w^SXL$ z$ak`BbvZv2-63RdWbRYYQVUlTt!PjxXn3GO^~}(qo=rGKVt`N|J`73JP_yI z4I701*|`cfGjfNk0XgL%@W64u{0i^gvf=pQJQZr*VO>rgsL*-iue1#ARqq+McYe&e zAM1HpL551xqcO*NIT?+4ax&^yY<^VwDM#Orzo()v zswAlc1ea2m=MHU3D?-l4F_nbSbeFIbaur*!il&>TvV6%Ik_I3s15AdzWWNUp=PeKc z+ZTib*FMxK!i!E0EK?)p#i~j zJzlb%Ve%|5qQtif9$Uw%6}%+H>19J37*32|H61DEg)4XbPsDenu-IbSmK5nQ>%HdW z{)$pwkX)pjD{KW9Z|ZATGon+TuGOTv(GGw34XR3oQg*G@<*ciy4M*lu`=kR3Dqjh6 z^<=(>#gph%obB<*whu~u7fAz%6vmY+?)~}dAsOSt;eW&g_gDFfX&qmC*eD&a$_BD9 zb!igjUKaJ`*=c{=3<}c>=LQ;;(ro>YN#1=2H6Q=T*crsg-Pz>)iJz8Sx}ysLdIFh- zJFLq?#_r1xF%V1Ik+C!|w3W0Y`@jpd^=M8Pmoga%RS}Su^6>~63UOs%y0Gj{*@kBq zT1TgU`i5E~@e{^1DjdL=1S!c%q27`iM#C$8_Xkv(TtFE8wy2)MBxeZYV({Ed36+!X z(Rk{sKQaf!e#nfHF}9Ohf9RfMss%pnuYbC&dp_Hq#tjIVOI=cWXDMg2bgm?HuZ@7s9Z%xJjnLA@aG04^+w@Ez&;%VxB-b} zmV!Z}=kRmk zkS*GcMRtZla>TjL9alpgotP&Y(+zD7Kls&XFT`1Fq^Wma)RVu|3V8cM*7-G!!L%?8 zD4V;4Md8gx(J-_P{|74%F@qhbcO@0uaN~jL`Whn`r*rR`?kaH$%PzGYos7hUkwA9w zpGtjyAVn$7By>poYcgMKbyL4ZHz~&IAz`*%nviD)Y@#KW8w-|hcjkC$ub~YvBk(!$ zxJGk!C?q;rKTr$MRz z!DdbB_=GcAi8}W{UewV$GphZqdI1r$A5X}Tz|X9UU&eYV)Ct!8!5(>(p`FUvi`8eO zu&}vQY~*jmJy)@8thkr56~C-uZ6b_ILh4^`+hPN+>x}+Ee0knGbF0Vd(Or$hZ2MsO z)jYb7BTn|ZN=rGHf!0(cdXRKG6q649d~reV%+D_M?&V4rzkF?&u>)gLHWM6lrx~CasAL6dmJtm6Wtol~eYc=Jn9TL$_dY7W z_k&knxc#s2MlIpHY=R|mT+XcAb~Ignc+0ohBIA6q`^}BrcMGOwknf#BD{KT_c_hk5 zbD6-0&faJ_GoK1n_~If#YwPU_aTHxl%CG>*f2KSTiptG=soCq5yT>%`m`CSJ70 zrx>wge0!Vcj!OH%Ys=|Vl6}fQVe-;V&H`H}fx|tSiyX&tiLW|Z_3c2%tOCuC54nh)bsp*9o!e-D5 zjt<9h6IO1+G%EJ_`skx`{&UT=;uUHIsXt>O&pJ;>)w{_qDq=|kZof<* zlnc8h8laLQ0;0goteI*B-$ck@p&+|X=zZHv7BSQ0m{Q-2K1rs**nNZ{w~g+TOn2OOn{*h9oQjxh0_wGn;W-+A`;(nq zIM#kS8Ra`Y3p0*=Q>qfQ4vU2Hgp3TcMc-kH=DDosmN z$iz%y&73~Gq+3Ua^I>8<0^Xq*y@14@CYVbNm-ppcbk$6(HPi~0VUa#K!<7UbLpNkO z=u%wv^Dn<&vw@WEr^n!rSE@7>Sjm&}P*G>LXhDM(XP;WZ?vN}p=yX3Tze1Q@PGK3W zqP>iHB0tDkGS;BPiGVeb$if=_1?Hm&ib=LoP@24t-PbcidkK_VCUQc#r2&ycMl z%RO3pge(vlGlzXf;De7qTjeG`YfU}t{-t{Ik}Ta}A$ri(|LPPJElrPEYX%$1qD>FT zwFYU=XLms5{s`(i#X~a39^Orgg|iX*6$|q8wTX9d;VT2ua#4(I-XZl5ee`4 z?7>2^QdsvbXfK(rWQf-m?H3NzgC#6+$SX=oZSV{|Jy+=o0cpICld$-QJcH8LBsU6n zGkl!{lQ-?OCG~kKG}pP*_BRVix=7Jk@2SiX{oZ?8)y~{3G;w19zfA*JV)DY2jH|Re z+;I(5Vks;RP1uV;AAZ#S2SS zK%Ng7fGX&FbT0=vuie5Lph4w|v16;{bfJud4Y==R3H8{*U5!C``sC|gR|v2^Qc&SM z^7_SjCJ3^TU2qWozy`7 z_YtxKXuQ5zu+v=g!WEuR`C1mV!IZi@qt_kF&`phDYFzXy&th3;lzCXY1TWsV3 zQj{y9X4tus6nq=WFc=#^nwPlkX4h<2=U-mLd62Y|npt|7k!wpHG3OtBbgDMu=~);e zCyn(VOB$^Et7wN87R?Km1j}Uu#ZDEHkOAB?H6DI|B&>uD0%3!qZPL2l)2DzA z-$=5Sc>wxh#>_hz>>#|mA@w#fd{dt`0FoeqnXnHLHILy#$h~rcYA3_+qMQ`!ZD+6= zGhk?L5Af_2Fb?kfKr9(I8td#$x%j1>h!I50=vM}&+0boi$0|TRaLx`!0*3oS^;PWb zs?IOG)k|^oW+eFI;h>ry)vytQOb%PLg>f>(eOuV6-n5G`;kf%9Vi96siQPDOe2@MKBo%VQy&xn& zOkN6a^NJwNqfnsY3CHbC&(v2t_b~`@7u-8thx34~6cp}xx)z{-7QbT>_ymUwYQt;W$kSouE1qB#S;GmZlDl;coF zu3X=YeqnS$x$^Gu==}GOz#NjmR{Ac1t^6|!b(8~YZYL#$k|_a_y8Z2X$(bSe*bD5W z|3~2BN}6tFQ4Z;0$jZ!M>^kCPDZERMKWIyd!d%ruhq&V_&`xdlU)Dyt;qK7+y19|8 z&LU(+frAzKkQw%8z!!cGK*=DAw*7*Q)FI{>66%#8qcJ%iXgymP*V8Xd4=S@An(B$5 z^n)vp8i!i-6c_#l4(6t$!kaOD6tCPCcBeN-_n&%nioED0r6AtqKm>n7TqI-s-ZvEf ziU?VaQS`P^iEmidv*e|PL=N!%gUeLZc%~eiy8`K6DG3;amT||SG;ku5i>MYL?f?+i z2U>S%TCX4he8R1e4D!D8Mqyr*>h|IagH$VIq69b5j-bz zXnWfK6mJvXmb}vKodgBw*02qffKvJXl>~o<7R~ ztWK7S)4 zkOg|^5$^aXI^~Ji=LElbQl>A+!y3JAZe%$?d;duTN#*Z|uM??mOF{1%gsG^sK&R-z z>w29c8jO+LrwAu|TkYIld;m`Z;00V6>6HW-juj&^_b_Rw#=^k`XfVKqnHoxt&t?^< z-XhWi2F-V@Wh9Z6KU|)7hv#&Iw=_+Jp%wbo`G-g2s*PJ5)e7Dpg?jyXTrDX-9vofb z*W&+>jQ>9Zx|enW7-v`+#*KefSzuQb@?$6DbSpXB7k- z(QGQ_Ws~+tmTh+=Q%i-Df05rrJ6igfLir&HDLX!}pT8$9&#?4YTmMHDao}B8@5Pc@ zhAcf&w$vzkFYdcty6DO7C6$RDjZOe&rq>ytoYNqz-zIH^$O37Mdm&LmBI=wa!pQ?@ z#WH4Ifn%g~;*$|60xjoZke^G>j3(*C8(`sHOM$!|=1fAuCaGXBob&~?_{64U&!F%f z`1tolv_)C`JsFY;AKVjCjuybjIfPl3h7yG~PI{r(AiT&m?BbgxDNRrEA-B+Hov@^t z{>+#T$GzNOeeGcgA#krC z&Ha(G_uTPd;baEVgGBF3_T;^tL?wsIjt3l34m!pi-_pv|%%#g(;}6WB&}4Eauwhgk zDeVl^SXEI;#2>I#Zb?=O`sSfKsE=Y6;3{|A0Cfx}vy&@CMHGYu*7auLQnpC1mDesd z8!~)u1{Tc7Cp`=6<>w@3X~&z0SV^l8A}Dy8j}bCR6+G|B&UO!i+Qh221%%Xg=AWoG zrv8u+ZQe`=JCOW=bdh$ze*@@#lx&hWo{QUC2^dIeTd4N1t)RrCu`h2Uiw<{H?UVEv zmV(M02SfXUK{W&_R#V6`PZw#R70U%yhBlE8PMiMIO?xeo6&SUQg)wR=J?yQcS91S`3{kg3xevt z8wu4+?2b`4uQ&{^t=BGo-cqoZqhdTq7+hGS&7DVS-(xlWQWf&eC0A~mfTPMCB zt0TTRHE8kQ09fwL@O5B1&54DWcWS(q_)<*T6fT*{LZ(dx4)5HIQ(lI6G)~^sXFZ|w z_Z*W5_&!N?(4PtG#|M#8N$BjpurA`u9T4Y7@QUAJ zksf3T$76*NbMl8zBEDYO zLbpVUGdQ3Hw+RLk-4BRs>;Xns9J#pwfKenh2@aS2gLM5`c3k*oM$_{MnJI1$9VG*w z%c2k1$P_g4ST}kBN@zzbqODJjz*qc2LcAAL=nN>{b2;P^2+M=BM0O-Vr!B7WFMIoG zG8~`whb>{v>_}N0{&_*llc5yEiXg1OB~nZ_GrEi+*ZflP$S)=y=Xo@?gALBFclycr zZ|E99V}GfY@b0&j@O^S7*dq0~Wa@Xsd~4YKUTu%}r&<>m0j57Aa+S;EFL=J2Gv|8<6lQX;lxC;HX&N|y`q=6g|Jdv%)h82#_M1GXRf1-B2IqTs zH|cw9MAAEt#?;|>Km%Ry1W@e{Oy0O5xzg)H&0C&o;yWuge9YSk(d*E{^k0g+8PJay zK^=p+0h{Z#hSd<7AB3TJzHiZc$qI=K zbusmloe=<6pqW>g&?KhTZwuoQF^?ij%y(BTiJU`F8b!R~SC=h1`wh1<=`k7yyCZ@40GY+D^3s-Cr<> zgfDti7JLC8Q%xG}{C>9vC6nh~ct<&*XRE7h0!gsU`c&jHXrwta*#qP0+q^wiWVsZflY zg}E6|&wOtt=t9PB5?`7@4;&ziT|b`3fn2p_TQ)9|@y7oqtxx|Sv_a|6jX5~{`4Zlu zBGwZqnD)AFmEygpFMfG}Xp*gD>kZA&uj$Lm7L8&fY%KCMPzmXh?)~Z1=k?ne1uZJP zaUW8zoS`nJJlJ9sy$c6-RoY<5kAXY4zhjp{)={PRTG%Y#PPxMQJgF7wM9ey@oN22@ zwVM9a&zamw1>gM9+f*>!K7COSIwYWUVt>Xq z=|=L*#1{uLg(JriMgcLH4|xckC4^@0k@3o{pRa?d$>s~=YDjr){AaL1DJyhvn>4PK z*C$?@2Mg)^A4%*x!A@B04}H0llF{%63V%;ofGumj*!`Z2+g-G)Q5CKcyplGgZgx9EKy}uCrKovF;fQ_{E>d;CSakc{D+Z8w{nC;wik8Mx(jJuBx zbjAHc^lGHMXlGB*aIamBxvn)7M9svpBek0S=k=&TNj#qv^RlBu5tKy9VuM zp{sd(bsf%UcMR^Bnk{Kj(j1i7kwIxm-a@Ejezk?+mW~3V;tt{cm*yc|744g=*W zTmSck=S8X9adsXQHYrb_Y+7KhW6!^{Si zJpvyAn->uEw~0Wv8j5$?WeqYbyT~rHXI5s_ypGw937O=rs1Y(_ws!*e#5wr#{{$Q2 z#7M+k{(75ZRnE7GaQ1{Kq9*0@2g%d}lKphicTHjTa`&EsjqH23fU{c7#7q!E#4qswHZh#+1Os_?QB)&<&z%j%paPf<)#P9J zG<#V(LHJw{U2V{{6a9wfz$^zuLduf!OwRr?2j=z0K&%k}ns?_s`Imtm0c}fn)@_5P zcABo#h_qfZZc@B2OQzc7BGp=G5FLdB*e)bu>fwM{FygQ-)?(36A2u>T>UjE1UQxOT z19fJ&jhY1{?83&T*KpX-j-)cCBXQn#Mt=q@1lxUo_E&# zK`}Z7Dzp-oKo^_?4a8!Ll3Wc6bv96QgYZcc8G#zL80`yr%I$?X#AzR08k_?yYzwPF zi7`}LfL{m0vj-jOC*-Q(vMmoUvDqC5ZIx-WK-eXdd52{#(oWE$(PDJ! z8n2QJ&@Bhd!n(4U9Et{?Rdw|iagROm52*e)zi57EIQe`xAUKQQ@s*n@)Kp1DER3?1 zzKp0%;aLcvIa&h7!4ftIi?)YN!qi3dUTiObw6u26+pD+1qr!JQeh6Xeot zFy#G?_jyi`z|h(kajbV?BRJx;TDX1%{0WBfN1#vqIsgYHqX4P3!)VKnqg31}H{}OZ zsv?}s7Yagnhk->8D zg8?M0v7OyvEEv!8`s}_mU1ZK1KSti|;3Cf29z7HJnrB!e%rwSuPiTgZA(fp%&EYwx zD15(s0(vSXF#KRiQ}Uke>us|GJ|6IOb^f$N?|0FVFpivtpL%t<`V?OkO$}HQ?o+J+J$Lg6UaT~ z^3MV^r$Oi#s!XYQ;s79R489n`(jk2nZGA%QidHxZR2xjauPrvPxy~D01?)9R1DMc9 zf5t-{HIwRHjjQB|Z@Ty_8lTTw+A8#fe1@W3vsse43fMN6L9;wKefLw1(U`bb+pHm+ z-Eoibk#DmRz*j*6Qu;ii_ITEAtRx#Gy4Hq0PMUbj+KSk_YX~t)jv}5=8L#W#`zih}QoxOn zab>}Hl;d%ij@A`0DdJ=%D8K1t&6!RYIgZONDN$m7m&6C)eeN*86qGOKlC(XYW%A(p z)XXsW8z2CV@g>Cc~l2phbcyjJ{ zoVafTh+OPU6>YEEgwG`r{HeMSSi#)H=-LkLW!Ib}Y-)5`H5lg$QWjS|Ob(k-o!JZj5=A&t>R z9O_BJ)|^?n{qyBF-)Vd>L@P*?y#;!)FH4{4(tH8>Hsp@~Mh?xax#{(AJB(!yc##s#gGBWLX9luXC8U5CL>(o|BA=7>vnl=Fyi@-bos zwg35&eS`OQf!%HoQ%>64KAkvm4ApZIJ~rSU#Lda-;lXdf!r@tHApk%YFIiP@F>;Jx znE<-@4>vI~HOLiUPNWKEKp zQVU?xX_;UzUUras3K8VF$sf-GQLE2sIiP0ZafbkOB^dRw5^gAjmMP^>A`ugAzpvwW zCB^e10=@`>ZvppP2QBlhZSPNB(8GOiO2IGV}D%zazxztb?cP_zW zQ2@TZJox#*DM;eO*;z1>Y!QD(vBP)R$U; zkHcSD3*0}rytx+AL2)EmB7-?MLZTKvdrr%8Xm0~z5Q)Q|P#o_~Qw^J5KndL)>(c`H z5G&iCe_VwiO5kG}GWV3*XiT6%bWg<7$M7|3|92|dhpO3yFQc!`rg#$s_a|oMmS>NI z*LY6gE4w#Y(jM)%?`?ZA&Yl=1&zz|jbNIt-6pDJK&}%xJZWfn^XF0-E0Jnh#Aj~;H zpY?;iw`$aHN^>+M@Gae8h_v>oDn;Wt>%L917sc)gOI5vRR`ZA0dGO;O1OTRx)6aP~ zyqvQNxD}ccN;+rCp~r!VFTG26MQh-sAK|2K`!{Vbyx3*_;1#LgsWg4C-=`XN91gj{ zb9V`2lXKSOpd!;vINda0ybX_-qLZf~ zKuVb@r65OXj@d>=!a+O;LK$qW#G=0sfBM1xkg7*LOEvwa`tg_1VY4Yq*rcSaAX!VU z&*zI_x4UEOI5se;5NaQr*771SN`?U+Q*hWW(@u5mumpl2_$h4|@cdURcdVp6y75s$ zCdFL3-_&gq1tC{}dl`PUfVxIS+nWoY)V>7~f9B7kEGb&W_O=El6qZvwUIHz3>c?K% zNHr--(#g@6-6Z$JjX4KFl_MNy9wNEFL@jsu+VFD<1>6Bja3egOzu(6b>me;9AKLL* ziA7`<*!?YZ?hH!bK)d(h^la$7IC_#B6r=(9tpn`8frO*Kz^&8n;bq~V31tox`Ljz# zR5HV%BtpLT)wzC@>?AJ^A~l2l;Hp7)X+0Tih@mI6&{bGFh#US+ zWHvdRTyVBQm^nO8n`!{}E^~JW1oQN%p_AWHtN}!VoS%gOyQ;#KK75Gf&!A{vFheEC zht_d*50!QiG;YYgw2kcjTF-W*ANmj@jCorTf-cS+R{~z%luw;Xe+B=bQ#IpiWnKMmk^FqY7?N=k$-9{YJ;@ zmt>Zr3}5(mfSfSvegfG1C5BP-5UKa`LC;IQ$w>@~Mv~H;(MXv94JQ58G<*g^5@Pp= zRJl-X>NhIdv9Da&S%Ab^IBr#@7(M^$1DolTd_VqiP3g&l;VDuTiqR%AXDa~S4UH*Y zJ8!g0K?+{4msz@n;R`+OZVMpgg#FdF7kz{{3y>39o(>co%S<`5)l9bjC0t65WPt=H zJps-9FkJItE|tGRheNCXs&UG*DOtaMb#g~*DO6P$<{r`YR7~||K7Hd$EGPQCmdQgcVAd^I^ z(x`=6WvCF07zKj_36&5LbH5$!d-q>>{w8PId+oK>Uh7-m+Tn7ow)VH0xm^v~t(^f4 zYq%>iNwU4z`M*9clVqF$q>IN`vBz4{CX{dDM9#;w|MGFj{)V5|?xLJv^I{u^sy9T- zH6KyF$oLxD$4a}bB~q`Rh8w1GAjvZEHj4`mV+xQ|TFuEuo0*CL|tq zU<`G3@%vQq=|vX*KK6Kb|1-|r|HG!fWUvo{XP7~_8-!F4eSusBsl{EE4FOOwUnBXaz6d%=erIdWn(Xnlo_!!{@qegVvKa{tyo342F?J@$!Hw)I$xFsi6~F%9cI$({ z=cKulOZO2^1T+9e=m_Fy?dBOzmWgg%(kQ<+jKoHFv^0B1yfy){cgDm5k;#c8mbEgapKE7tStbT^S^Im;@UDC%b>e>D7 z?`nLuFI#QAV8!-ZGb;YEMHK5}{gdCC@PY3t#%x?-yXLFh)gBYuyM8pCnq+%r!uFXf z`#znt?uz}CdHk=A*gkq>cHqFo6tlyQM=O4szBks4e!_{>g+-Z+T$D<)TjBL4VadI+*0Nw8zw$EP}yCpFRa#r{-j7RC9^D<}cRkbI!D89{D zri;$HTBLb<9CBz6`;Gb=)+VY_m#J<2y4j~T(2 z7F-Ic7*xo+ZyMVQ@tvEvrJiq0rLW_*I#i`rhc8v1%=_!2@)#k%wV!2C)R6R8bi433 zh|dw?lU2bmG%~vXdpPwr7UqAA4BV2uFl+GnrE^R>v9cj)oTTFEXVyH#EIYdH3gh|- zFR;zNb#8mix8t4m8KrZ3)Jb$zKWf{-BvO`GabF*&T~I1Mg43bNQ!$T+wJA3JgzeL2 z+Bz?%<}n$<+oeq#T~o@Z;+znXL)@$~1R6N+->Ic59Z;b1EK{8d>*L#9f&zTfDw876 z;-xiV&&qyn&a&$^`vjC5TeolSm&Ic^lpk zSpeGPoNO`)cI*6{n(z<8>dJ&1V7)KN*6I$MN#)9JgiX|a)y{zj*fFfQ2MAc3`StFPomsnulDr7B)MBzM}&4r6l8 zFgZfPbaU|wHKOin@*t786s|D9#W1g%13i5ySaU3T^+*$&lq7Eq@3k`My5<_1=@P>P zX?zi!Sw4czC1{+BXm=p65d2jaIc$;+*Nm!lh|9X5EUe69o_SIh0JKRa_D z%R7C{DBBtGlw*izLDOn48$9x{LH5pLFVu}>t33g@Qi?LbokGB!37fcb(5ECbAjr69 zgpjmVOfYat{4aDXhFzHF@?xkiAZ#Fw6T6h&t}GWHfT#CeK#!F~!W(XMhNTydQ9EH+}*{biuNieZ(yo7OdBAFQ;s zjDjtoQ)P3h$|hfxN^X&1ION()nRAk}q(|jqaj4^&tUJo#dn7L0jNJi@zOXyV{c6ybYW7 zj^}qrUV*62mE*AdQ4u=A6g|D_qOIL4JiM%%YTTW0m<{^tq?;k6vNf51#^0Q~dn1l$ z5=II1Aj?O%n^CTb(B0kIAaU(HS>Rf&ZCuHDrX7|rbOy}wb%Od?9#Qw7zLK9%PU6gF z7y;^sRNU@@EMce^_4XUljo%0VgPXtC#0dZaw^t43{tA?f9&kFvN-?dkVp1cnno_PN zL_fOG=tVdA#;SMTSK&|y7EbbyH!}>aT>b7Fy7iXc%CXKv)>}Hb=#V~J`I*%QN|IdD zbUk^!c-6LutBLExWh)C?`m(^xf?S90*!5l5EC9HKQt&H%o^R}0v4x`!n7^-PK%VBj z(@1(hu2J3XSyKRTc8_?(f8~Ji9WWYn1@cRv44Vb|B?Lx;5qigZi|3SJU+~6jgUqH^ zxymjQ9}?}ed0VaVDHIb_+-4A37t7(H4=b~ZSkw?gSdY|DI23Ba^_*gr9QTiGmF}Aw zwHk#IaDrUNX=dA9U^fMSH^1F;mk37H@AVTsfrrMe0Rd8x{?lP|& z+>CH+I9c!N5%LBb*7A}|j3xP+Ok|R_oVzJcSxUNU+6qy?rEC}A4WV9c*xBGK-!RqS z;3$4TdTD-+dF%_6irl%(z!VbwjDdM*wV55jASlY_jRiM>Nnsohi9Q3Dk=SkuRw53KH&Ph#!JEGJ# zj|Dc{ktv@2eu4sx>SxNr-z!Zh^2#E|v$)MCIqziTUB&U?;(kB{uE5;)E$&f2 zD2p4{Yv?Z8@XVU~8j+tU;Mb3w+V(Tm=u7fJ6a~I>am?j`aiFPBe)$!5bhp^b7m5L{ z)*4M6mMQ>)8hI$!&z6;uenHoYNHj)q6VPuaoyeB(^*9#gCVW2C&@y;5P>R$0O-z3v zjM6?%V?IWz;OT&-wqR`*uq6s=eRGmHxrvOl;Z8FrA4H<)i3CAf-#k-X8(qypdHl(K z&g=_}KNyy_CYT)SA-dhV=rq#pCc0JaCes;sKcd1LfRHZKH2G?7)BdDIjUS`-(fr9& zZ*d=^_2@rk`VM%I1g3UFG1_g}qH@5|QUhzfw+K z=DJ)y+CxXJj;$x{Lm5}H9E9-CbEg_dPlOhJm!P#IKYCL~nr2+ZvpE{reZ`AUM1^-m zCPhmGX#wI^N4GfgG5cp9bMk|^l_dcpYQRymJp69t()nb@n2^3i-xcl%c2V4bet>85ziIDVWT|N4v# zT%5I$BTgo*L!tn67wzw|*=Q$l2^ORJ=H*ddt3MuzzEexf%IbE3`!v>R$~tZ!TewVY zN!p)m89$9B_w^{Z`N3RjjnbO;B_!#m>CZ%s019EM&gA5y=`(+V5pNzAna54}FVLJ& zXoJZ1;x}=k7Hl?pSzyVFJ`k_9poKAnDi7n#Di#Oxxb>h>McZOkkGxQ`w{@SoF(-%2 z&3|*gGKo zfFqY!mrxV3EP0Y?^3kKT{tHjGa03W8ka+OD)*ixdWsyq&O z*?KVO_!r@S#>Z>DOXEK4-3R3Y38avN-?dr6O&EzbmoiYVDSy;me07lX`9jE8!mo7H zRnc?)%$+GJgkx^-C2eGgt_o=j>qaArC-xB6ej&36Y%a(8B?D#6IIq4IAx7A6nGW&H zWl_-&H=R=UuWyb*Vda$$U%!{2trNd}4Tg{37S!7+GC8RWW$DE#6?SMfkgpA4+Ljwp zvPK*8`badC@qb^-0W+~+L$f|V!$_gFmhcZ&kMw5NPLpM3<{G}hBv13Oh`@&WrY+i> z3WDeTCPB-}1j|d{AXG+AK>2q#Uc3fm8CeOpx$o!2)XTj_=01WzF?(CYFK>p>GC2#i ztx8Ky>I~W4V%2SoWC$9ZoFPAa&1(jZU|Y0M_D9(YxOb1^%7vWGLEL(`sVwPr)RF~E z4OnPd;zf&PF6^qNoq!0td+2^&4MUeO%#K4zlq)zze0zDyMsdTI?)Vl^#!|%dR~CXw z^=3a^e_UDma?t~o;4p49LGGL%ueD<94j~{ACW{+T)m;D>Gtym_lo@os2}JGBHI(Ze4CtKNbAEw! z8BX)LKo@n-kMgR0tk!pW$QJtxr%}-Y&wS+&jhQ=mcsH|4<3=9qZ7xK@uiYMp-SGd< cKgG1SA=BjQG_{>y0U0%Ch2Qc^%lP~L4cHrxmjD0& diff --git a/assets/images/toilet.svg b/assets/images/toilet.svg new file mode 100644 index 0000000..0984274 --- /dev/null +++ b/assets/images/toilet.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lib/account_manager/views/profile_widget.dart b/lib/account_manager/views/profile_widget.dart index 672b13a..c026856 100644 --- a/lib/account_manager/views/profile_widget.dart +++ b/lib/account_manager/views/profile_widget.dart @@ -43,7 +43,7 @@ class ProfileWidget extends StatelessWidget { : scale.primaryScale.borderText, width: 2), borderRadius: BorderRadius.all( - Radius.circular(12 * scaleConfig.borderRadiusScale))), + Radius.circular(8 * scaleConfig.borderRadiusScale))), ), child: Row(children: [ const Spacer(), @@ -54,7 +54,7 @@ class ProfileWidget extends StatelessWidget { ? scale.primaryScale.border : scale.primaryScale.borderText), textAlign: TextAlign.left, - ).paddingAll(12), + ).paddingAll(8), if (_profile.pronouns.isNotEmpty && _showPronouns) Text('(${_profile.pronouns})', textAlign: TextAlign.right, @@ -62,7 +62,7 @@ class ProfileWidget extends StatelessWidget { color: scaleConfig.preferBorders ? scale.primaryScale.border : scale.primaryScale.primary)) - .paddingAll(12), + .paddingAll(8), const Spacer() ]), ); diff --git a/lib/chat_list/views/chat_list_widget.dart b/lib/chat_list/views/chat_list_widget.dart index f9a6ad3..73bd7e6 100644 --- a/lib/chat_list/views/chat_list_widget.dart +++ b/lib/chat_list/views/chat_list_widget.dart @@ -83,7 +83,8 @@ class ChatListWidget extends StatelessWidget { }, filter: (value) => _itemFilter(contactMap, chatList, value), - spaceBetweenSearchAndList: 4, + searchFieldPadding: + const EdgeInsets.fromLTRB(0, 0, 0, 4), inputDecoration: InputDecoration( labelText: translate('chat_list.search'), ), diff --git a/lib/chat_list/views/chat_single_contact_item_widget.dart b/lib/chat_list/views/chat_single_contact_item_widget.dart index 3bdf645..5191fdd 100644 --- a/lib/chat_list/views/chat_single_contact_item_widget.dart +++ b/lib/chat_list/views/chat_single_contact_item_widget.dart @@ -50,7 +50,7 @@ class ChatSingleContactItemWidget extends StatelessWidget { final avatar = AvatarWidget( name: name, - size: 34, + size: 32, borderColor: scaleTheme.config.useVisualIndicators ? scaleTheme.scheme.primaryScale.primaryText : scaleTheme.scheme.primaryScale.subtleBorder, @@ -75,7 +75,7 @@ class ChatSingleContactItemWidget extends StatelessWidget { trailing: AvailabilityWidget( availability: availability, color: scaleTileTheme.textColor, - ), + ).fit(fit: BoxFit.scaleDown), onTap: () { singleFuture(activeChatCubit, () async { activeChatCubit.setActiveChat(_localConversationRecordKey); diff --git a/lib/contacts/views/availability_widget.dart b/lib/contacts/views/availability_widget.dart index a79f774..75ef0a8 100644 --- a/lib/contacts/views/availability_widget.dart +++ b/lib/contacts/views/availability_widget.dart @@ -1,35 +1,39 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_translate/flutter_translate.dart'; import '../../proto/proto.dart' as proto; +import '../../theme/theme.dart'; class AvailabilityWidget extends StatelessWidget { const AvailabilityWidget( {required this.availability, required this.color, this.vertical = true, - this.iconSize = 24, + this.size = 32, super.key}); static Widget availabilityIcon(proto.Availability availability, Color color, {double size = 24}) { - late final Widget iconData; + late final Widget icon; switch (availability) { case proto.Availability.AVAILABILITY_AWAY: - iconData = ImageIcon(const AssetImage('assets/images/toilet.png'), - size: size, color: color); + icon = SvgPicture.asset('assets/images/toilet.svg', + width: size, + height: size, + colorFilter: ColorFilter.mode(color, BlendMode.srcATop)); case proto.Availability.AVAILABILITY_BUSY: - iconData = Icon(Icons.event_busy, size: size); + icon = Icon(Icons.event_busy, size: size); case proto.Availability.AVAILABILITY_FREE: - iconData = Icon(Icons.event_available, size: size); + icon = Icon(Icons.event_available, size: size); case proto.Availability.AVAILABILITY_OFFLINE: - iconData = Icon(Icons.cloud_off, size: size); + icon = Icon(Icons.cloud_off, size: size); case proto.Availability.AVAILABILITY_UNSPECIFIED: - iconData = Icon(Icons.question_mark, size: size); + icon = Icon(Icons.question_mark, size: size); } - return iconData; + return icon; } static String availabilityName(proto.Availability availability) { @@ -53,26 +57,25 @@ class AvailabilityWidget extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); final textTheme = theme.textTheme; - // final scale = theme.extension()!; - // final scaleConfig = theme.extension()!; final name = availabilityName(availability); - final icon = availabilityIcon(availability, color, size: iconSize); + final icon = availabilityIcon(availability, color, size: size * 2 / 3); return vertical - ? Column( - mainAxisSize: MainAxisSize.min, - //mainAxisAlignment: MainAxisAlignment.center, - children: [ - icon, - Text(name, style: textTheme.labelSmall!.copyWith(color: color)) - .paddingLTRB(0, 0, 0, 0) - ]) - : Row(mainAxisSize: MainAxisSize.min, children: [ - icon, - Text(name, style: textTheme.labelLarge!.copyWith(color: color)) - .paddingLTRB(8, 0, 0, 0) - ]); + ? ConstrainedBox( + constraints: BoxConstraints.tightFor(width: size), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + icon, + Text(name, style: textTheme.labelSmall!.copyWith(color: color)) + .fit(fit: BoxFit.scaleDown) + ])) + : ConstrainedBox( + constraints: BoxConstraints.tightFor(height: size), + child: Row(mainAxisSize: MainAxisSize.min, children: [ + icon, + Text(name, style: textTheme.labelLarge!.copyWith(color: color)) + .paddingLTRB(size / 4, 0, 0, 0) + ])); } //////////////////////////////////////////////////////////////////////////// @@ -80,7 +83,7 @@ class AvailabilityWidget extends StatelessWidget { final proto.Availability availability; final Color color; final bool vertical; - final double iconSize; + final double size; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -89,7 +92,7 @@ class AvailabilityWidget extends StatelessWidget { ..add( DiagnosticsProperty('availability', availability)) ..add(DiagnosticsProperty('vertical', vertical)) - ..add(DoubleProperty('iconSize', iconSize)) + ..add(DoubleProperty('size', size)) ..add(ColorProperty('color', color)); } } diff --git a/lib/contacts/views/contacts_page.dart b/lib/contacts/views/contacts_page.dart index 28217e6..0f1731d 100644 --- a/lib/contacts/views/contacts_page.dart +++ b/lib/contacts/views/contacts_page.dart @@ -1,6 +1,5 @@ import 'package:async_tools/async_tools.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:provider/provider.dart'; @@ -30,8 +29,10 @@ class _ContactsPageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final scale = theme.extension()!; - final appBarIconColor = scale.primaryScale.borderText; + final scaleTheme = theme.extension()!; + final appBarTheme = scaleTheme.appBarTheme(); + final scaleScheme = theme.extension()!; + final scale = scaleScheme.scale(ScaleKind.primary); final enableSplit = !isMobileSize(context); final enableLeft = enableSplit || _selectedContact == null; @@ -39,9 +40,11 @@ class _ContactsPageState extends State { return StyledScaffold( appBar: DefaultAppBar( - title: Text(!enableSplit && enableRight - ? translate('contacts_dialog.edit_contact') - : translate('contacts_dialog.contacts')), + title: Text( + !enableSplit && enableRight + ? translate('contacts_dialog.edit_contact') + : translate('contacts_dialog.contacts'), + ), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { @@ -60,41 +63,29 @@ class _ContactsPageState extends State { ), actions: [ if (_selectedContact != null) - FittedBox( - fit: BoxFit.scaleDown, - child: Column(mainAxisSize: MainAxisSize.min, children: [ - IconButton( - icon: const Icon(Icons.chat_bubble), - color: appBarIconColor, - tooltip: translate('contacts_dialog.new_chat'), - onPressed: () async { - await _onChatStarted(_selectedContact!); - }), - Text(translate('contacts_dialog.new_chat'), - style: theme.textTheme.labelSmall! - .copyWith(color: appBarIconColor)), - ])).paddingLTRB(8, 0, 8, 0), + IconButton( + icon: const Icon(Icons.chat_bubble), + iconSize: 24, + color: appBarTheme.iconColor, + tooltip: translate('contacts_dialog.new_chat'), + onPressed: () async { + await _onChatStarted(_selectedContact!); + }).paddingLTRB(8, 0, 8, 0), if (enableSplit && _selectedContact != null) - FittedBox( - fit: BoxFit.scaleDown, - child: Column(mainAxisSize: MainAxisSize.min, children: [ - IconButton( - icon: const Icon(Icons.close), - color: appBarIconColor, - tooltip: translate('contacts_dialog.close_contact'), - onPressed: () async { - await _onContactSelected(null); - }), - Text(translate('contacts_dialog.close_contact'), - style: theme.textTheme.labelSmall! - .copyWith(color: appBarIconColor)), - ])).paddingLTRB(8, 0, 8, 0), + IconButton( + icon: const Icon(Icons.close), + iconSize: 24, + color: appBarTheme.iconColor, + tooltip: translate('contacts_dialog.close_contact'), + onPressed: () async { + await _onContactSelected(null); + }).paddingLTRB(8, 0, 8, 0), ]), body: LayoutBuilder(builder: (context, constraint) { final maxWidth = constraint.maxWidth; return ColoredBox( - color: scale.primaryScale.appBackground, + color: scale.appBackground, child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ Offstage( @@ -104,20 +95,20 @@ class _ContactsPageState extends State { ? maxWidth : (maxWidth / 3).clamp(200, 500), child: DecoratedBox( - decoration: BoxDecoration( - color: scale.primaryScale.subtleBackground), + decoration: + BoxDecoration(color: scale.subtleBackground), child: ContactsBrowser( selectedContactRecordKey: _selectedContact ?.localConversationRecordKey .toVeilid(), onContactSelected: _onContactSelected, onStartChat: _onChatStarted, - ).paddingLTRB(8, 0, 8, 8)))), + ).paddingLTRB(4, 0, 4, 8)))), if (enableRight && enableLeft) Container( constraints: const BoxConstraints(minWidth: 1, maxWidth: 1), - color: scale.primaryScale.subtleBorder), + color: scale.subtleBorder), if (enableRight) if (_selectedContact == null) const NoContactWidget().expanded() diff --git a/lib/layout/default_app_bar.dart b/lib/layout/default_app_bar.dart index fbf2360..b9c0b41 100644 --- a/lib/layout/default_app_bar.dart +++ b/lib/layout/default_app_bar.dart @@ -4,9 +4,12 @@ import 'package:flutter_svg/flutter_svg.dart'; class DefaultAppBar extends AppBar { DefaultAppBar( - {required super.title, super.key, Widget? leading, super.actions}) + {super.title, + super.flexibleSpace, + super.key, + Widget? leading, + super.actions}) : super( - titleSpacing: 0, leading: leading ?? Container( margin: const EdgeInsets.all(4), @@ -14,6 +17,6 @@ class DefaultAppBar extends AppBar { color: Colors.black.withAlpha(32), shape: BoxShape.circle), child: - SvgPicture.asset('assets/images/vlogo.svg', height: 32) + SvgPicture.asset('assets/images/vlogo.svg', height: 24) .paddingAll(4))); } diff --git a/lib/layout/home/home_account_ready.dart b/lib/layout/home/home_account_ready.dart index 5f90c9f..3674a08 100644 --- a/lib/layout/home/home_account_ready.dart +++ b/lib/layout/home/home_account_ready.dart @@ -33,7 +33,7 @@ class _HomeAccountReadyState extends State { color: scaleConfig.preferBorders ? scale.primaryScale.border : scale.primaryScale.borderText, - constraints: const BoxConstraints.expand(height: 48, width: 48), + constraints: const BoxConstraints.expand(height: 40, width: 40), style: ButtonStyle( backgroundColor: WidgetStateProperty.all( scaleConfig.preferBorders @@ -50,7 +50,7 @@ class _HomeAccountReadyState extends State { : scale.primaryScale.borderText, width: 2), borderRadius: BorderRadius.all( - Radius.circular(12 * scaleConfig.borderRadiusScale))), + Radius.circular(8 * scaleConfig.borderRadiusScale))), )), tooltip: translate('menu.accounts_menu_tooltip'), onPressed: () async { @@ -68,7 +68,7 @@ class _HomeAccountReadyState extends State { color: scaleConfig.preferBorders ? scale.primaryScale.border : scale.primaryScale.borderText, - constraints: const BoxConstraints.expand(height: 48, width: 48), + constraints: const BoxConstraints.expand(height: 40, width: 40), style: ButtonStyle( backgroundColor: WidgetStateProperty.all( scaleConfig.preferBorders @@ -85,7 +85,7 @@ class _HomeAccountReadyState extends State { : scale.primaryScale.borderText, width: 2), borderRadius: BorderRadius.all( - Radius.circular(12 * scaleConfig.borderRadiusScale))), + Radius.circular(8 * scaleConfig.borderRadiusScale))), )), tooltip: translate('menu.contacts_tooltip'), onPressed: () async { diff --git a/lib/theme/models/scale_theme/scale_app_bar_theme.dart b/lib/theme/models/scale_theme/scale_app_bar_theme.dart new file mode 100644 index 0000000..ea8e83e --- /dev/null +++ b/lib/theme/models/scale_theme/scale_app_bar_theme.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +import 'scale_theme.dart'; + +class ScaleAppBarTheme { + ScaleAppBarTheme({ + required this.textStyle, + required this.iconColor, + required this.backgroundColor, + }); + + final TextStyle textStyle; + final Color iconColor; + final Color backgroundColor; +} + +extension ScaleAppBarThemeExt on ScaleTheme { + ScaleAppBarTheme appBarTheme({ScaleKind scaleKind = ScaleKind.primary}) { + final scale = scheme.scale(scaleKind); + + final textStyle = textTheme.titleLarge!.copyWith(color: scale.borderText); + final iconColor = scale.borderText; + final backgroundColor = scale.border; + + return ScaleAppBarTheme( + textStyle: textStyle, + iconColor: iconColor, + backgroundColor: backgroundColor, + ); + } +} diff --git a/lib/theme/models/scale_theme/scale_theme.dart b/lib/theme/models/scale_theme/scale_theme.dart index e787c0e..43ca641 100644 --- a/lib/theme/models/scale_theme/scale_theme.dart +++ b/lib/theme/models/scale_theme/scale_theme.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'scale_input_decorator_theme.dart'; import 'scale_scheme.dart'; +export 'scale_app_bar_theme.dart'; export 'scale_color.dart'; export 'scale_input_decorator_theme.dart'; export 'scale_scheme.dart'; @@ -137,8 +138,10 @@ class ScaleTheme extends ThemeExtension { return scheme.primaryScale.subtleBorder; })), appBarTheme: baseThemeData.appBarTheme.copyWith( - backgroundColor: scheme.primaryScale.border, - foregroundColor: scheme.primaryScale.borderText), + backgroundColor: scheme.primaryScale.border, + foregroundColor: scheme.primaryScale.borderText, + toolbarHeight: 40, + ), bottomSheetTheme: baseThemeData.bottomSheetTheme.copyWith( elevation: 0, modalElevation: 0, diff --git a/lib/theme/models/scale_theme/scale_tile_theme.dart b/lib/theme/models/scale_theme/scale_tile_theme.dart index da2c3cd..d549157 100644 --- a/lib/theme/models/scale_theme/scale_tile_theme.dart +++ b/lib/theme/models/scale_theme/scale_tile_theme.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'scale_scheme.dart'; import 'scale_theme.dart'; class ScaleTileTheme { diff --git a/lib/theme/views/slider_tile.dart b/lib/theme/views/slider_tile.dart index d293fa6..8e5f178 100644 --- a/lib/theme/views/slider_tile.dart +++ b/lib/theme/views/slider_tile.dart @@ -125,15 +125,13 @@ class SliderTile extends StatelessWidget { child: ListTile( onTap: onTap, dense: true, - visualDensity: - const VisualDensity(horizontal: -4, vertical: -4), title: Text( title, overflow: TextOverflow.fade, softWrap: false, ), subtitle: subtitle.isNotEmpty ? Text(subtitle) : null, - minTileHeight: 48, + minTileHeight: 52, iconColor: scaleTileTheme.textColor, textColor: scaleTileTheme.textColor, leading: diff --git a/lib/theme/views/wallpaper_preferences.dart b/lib/theme/views/wallpaper_preferences.dart index 48f0a6a..e90022c 100644 --- a/lib/theme/views/wallpaper_preferences.dart +++ b/lib/theme/views/wallpaper_preferences.dart @@ -14,10 +14,18 @@ Widget buildSettingsPageWallpaperPreferences( required ThemeSwitcherState switcher}) { final preferencesRepository = PreferencesRepository.instance; final themePreferences = preferencesRepository.value.themePreference; + final theme = Theme.of(context); + final scale = theme.extension()!; + final textTheme = theme.textTheme; + return FormBuilderCheckbox( name: formFieldEnableWallpaper, - title: Text(translate('settings_page.enable_wallpaper')), + title: Text(translate('settings_page.enable_wallpaper'), + style: textTheme.labelMedium), initialValue: themePreferences.enableWallpaper, + side: BorderSide(color: scale.primaryScale.border, width: 2), + checkColor: scale.primaryScale.borderText, + activeColor: scale.primaryScale.border, onChanged: (value) async { if (value != null) { final newThemePrefs = diff --git a/lib/theme/views/widget_helpers.dart b/lib/theme/views/widget_helpers.dart index 1b768dd..910074e 100644 --- a/lib/theme/views/widget_helpers.dart +++ b/lib/theme/views/widget_helpers.dart @@ -442,7 +442,7 @@ Widget styledTitleContainer({ color: borderColor ?? scale.primaryScale.border, shape: RoundedRectangleBorder( borderRadius: - BorderRadius.circular(12 * scaleConfig.borderRadiusScale), + BorderRadius.circular(8 * scaleConfig.borderRadiusScale), )), child: Column(children: [ Text( @@ -456,7 +456,7 @@ Widget styledTitleContainer({ backgroundColor ?? scale.primaryScale.subtleBackground, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( - 12 * scaleConfig.borderRadiusScale), + 8 * scaleConfig.borderRadiusScale), )), child: child) .paddingAll(4) diff --git a/pubspec.yaml b/pubspec.yaml index 5206f5d..a37a8ab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -176,10 +176,9 @@ flutter: - assets/images/splash.svg - assets/images/title.svg - assets/images/vlogo.svg + - assets/images/toilet.svg # Raster Images - assets/images/ellet.png - - assets/images/handshake.png - - assets/images/toilet.png # Printing - assets/js/pdf/3.2.146/pdf.min.js # Sounds From 2141dbff214b06da8aea303121b0395fa9c06be6 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Fri, 21 Mar 2025 11:33:58 -0400 Subject: [PATCH 40/93] deadlock cleanup --- .../cubits/account_info_cubit.dart | 1 - .../cubits/local_accounts_cubit.dart | 1 - .../cubits/per_account_collection_cubit.dart | 55 ++++++----- .../cubits/user_logins_cubit.dart | 1 - .../views/edit_account_page.dart | 24 +++-- .../views/edit_profile_form.dart | 3 - lib/app.dart | 4 +- lib/chat/views/chat_component_widget.dart | 2 +- lib/chat_list/cubits/chat_list_cubit.dart | 2 - .../views/create_invitation_dialog.dart | 1 + .../views/scan_invitation_dialog.dart | 4 +- lib/contacts/cubits/contact_list_cubit.dart | 6 +- lib/contacts/views/edit_contact_form.dart | 3 - .../views/empty_contact_list_widget.dart | 5 +- .../active_conversations_bloc_map_cubit.dart | 3 +- lib/layout/home/drawer_menu/drawer_menu.dart | 19 ++-- lib/layout/home/home_screen.dart | 3 +- .../views/notifications_preferences.dart | 9 -- lib/theme/models/radix_generator.dart | 2 +- lib/theme/models/scale_theme/scale_color.dart | 12 +-- .../scale_custom_dropdown_theme.dart | 2 +- .../models/scale_theme/scale_scheme.dart | 16 ++-- lib/theme/models/scale_theme/scale_theme.dart | 25 ++++- lib/theme/models/theme_preference.dart | 7 +- lib/theme/views/scanner_error_widget.dart | 4 - lib/theme/views/styled_alert.dart | 9 +- lib/theme/views/wallpaper_preferences.dart | 4 - .../repository/processor_repository.dart | 7 -- .../example/test/widget_test.dart | 5 +- .../lib/dht_support/src/dht_log/dht_log.dart | 38 ++++---- .../src/dht_log/dht_log_spine.dart | 18 ++-- .../src/dht_record/dht_record.dart | 44 ++++----- .../src/dht_record/dht_record_pool.dart | 95 ++++++++++--------- .../src/dht_short_array/dht_short_array.dart | 40 ++++---- .../dht_short_array_cubit.dart | 1 - .../dht_short_array/dht_short_array_head.dart | 9 +- .../dht_short_array_write.dart | 4 +- .../src/interfaces/dht_closeable.dart | 6 +- pubspec.lock | 7 +- pubspec.yaml | 6 +- 40 files changed, 254 insertions(+), 253 deletions(-) diff --git a/lib/account_manager/cubits/account_info_cubit.dart b/lib/account_manager/cubits/account_info_cubit.dart index d9d93fc..a5eab11 100644 --- a/lib/account_manager/cubits/account_info_cubit.dart +++ b/lib/account_manager/cubits/account_info_cubit.dart @@ -23,7 +23,6 @@ class AccountInfoCubit extends Cubit { if (acctInfo != null) { emit(acctInfo); } - break; } }); } diff --git a/lib/account_manager/cubits/local_accounts_cubit.dart b/lib/account_manager/cubits/local_accounts_cubit.dart index 704d8c5..3781297 100644 --- a/lib/account_manager/cubits/local_accounts_cubit.dart +++ b/lib/account_manager/cubits/local_accounts_cubit.dart @@ -20,7 +20,6 @@ class LocalAccountsCubit extends Cubit switch (change) { case AccountRepositoryChange.localAccounts: emit(_accountRepository.getLocalAccounts()); - break; // Ignore these case AccountRepositoryChange.userLogins: case AccountRepositoryChange.activeLocalAccount: diff --git a/lib/account_manager/cubits/per_account_collection_cubit.dart b/lib/account_manager/cubits/per_account_collection_cubit.dart index 089443a..fc2d447 100644 --- a/lib/account_manager/cubits/per_account_collection_cubit.dart +++ b/lib/account_manager/cubits/per_account_collection_cubit.dart @@ -15,6 +15,9 @@ import '../../notifications/notifications.dart'; import '../../proto/proto.dart' as proto; import '../account_manager.dart'; +const _kAccountRecordSubscriptionListenKey = + 'accountRecordSubscriptionListenKey'; + class PerAccountCollectionCubit extends Cubit { PerAccountCollectionCubit({ required Locator locator, @@ -32,6 +35,7 @@ class PerAccountCollectionCubit extends Cubit { await _processor.close(); await accountInfoCubit.close(); await _accountRecordSubscription?.cancel(); + await serialFutureClose((this, _kAccountRecordSubscriptionListenKey)); await accountRecordCubit?.close(); await activeSingleContactChatBlocMapCubitUpdater.close(); @@ -83,7 +87,7 @@ class PerAccountCollectionCubit extends Cubit { accountRecordCubit = null; // Update state to 'loading' - nextState = _updateAccountRecordState(nextState, null); + nextState = await _updateAccountRecordState(nextState, null); emit(nextState); } else { ///////////////// Logged in /////////////////// @@ -95,20 +99,22 @@ class PerAccountCollectionCubit extends Cubit { // Update state to value nextState = - _updateAccountRecordState(nextState, accountRecordCubit!.state); + await _updateAccountRecordState(nextState, accountRecordCubit!.state); emit(nextState); // Subscribe AccountRecordCubit _accountRecordSubscription ??= accountRecordCubit!.stream.listen((avAccountRecordState) { - emit(_updateAccountRecordState(state, avAccountRecordState)); + serialFuture((this, _kAccountRecordSubscriptionListenKey), () async { + emit(await _updateAccountRecordState(state, avAccountRecordState)); + }); }); } } - PerAccountCollectionState _updateAccountRecordState( + Future _updateAccountRecordState( PerAccountCollectionState prevState, - AsyncValue? avAccountRecordState) { + AsyncValue? avAccountRecordState) async { // Get next state final nextState = prevState.copyWith(avAccountRecordState: avAccountRecordState); @@ -121,8 +127,8 @@ class PerAccountCollectionCubit extends Cubit { .avAccountRecordState?.asData?.value.contactInvitationRecords .toVeilid(); - final contactInvitationListCubit = contactInvitationListCubitUpdater.update( - accountInfo.userLogin == null || + final contactInvitationListCubit = await contactInvitationListCubitUpdater + .update(accountInfo.userLogin == null || contactInvitationListRecordPointer == null ? null : (accountInfo, contactInvitationListRecordPointer)); @@ -131,34 +137,35 @@ class PerAccountCollectionCubit extends Cubit { final contactListRecordPointer = nextState.avAccountRecordState?.asData?.value.contactList.toVeilid(); - final contactListCubit = contactListCubitUpdater.update( + final contactListCubit = await contactListCubitUpdater.update( accountInfo.userLogin == null || contactListRecordPointer == null ? null : (accountInfo, contactListRecordPointer)); // WaitingInvitationsBlocMapCubit - final waitingInvitationsBlocMapCubit = waitingInvitationsBlocMapCubitUpdater - .update(accountInfo.userLogin == null || - contactInvitationListCubit == null || - contactListCubit == null - ? null - : ( - accountInfo, - accountRecordCubit!, - contactInvitationListCubit, - contactListCubit, - _locator(), - )); + final waitingInvitationsBlocMapCubit = + await waitingInvitationsBlocMapCubitUpdater.update( + accountInfo.userLogin == null || + contactInvitationListCubit == null || + contactListCubit == null + ? null + : ( + accountInfo, + accountRecordCubit!, + contactInvitationListCubit, + contactListCubit, + _locator(), + )); // ActiveChatCubit - final activeChatCubit = activeChatCubitUpdater + final activeChatCubit = await activeChatCubitUpdater .update((accountInfo.userLogin == null) ? null : true); // ChatListCubit final chatListRecordPointer = nextState.avAccountRecordState?.asData?.value.chatList.toVeilid(); - final chatListCubit = chatListCubitUpdater.update( + final chatListCubit = await chatListCubitUpdater.update( accountInfo.userLogin == null || chatListRecordPointer == null || activeChatCubit == null @@ -167,7 +174,7 @@ class PerAccountCollectionCubit extends Cubit { // ActiveConversationsBlocMapCubit final activeConversationsBlocMapCubit = - activeConversationsBlocMapCubitUpdater.update( + await activeConversationsBlocMapCubitUpdater.update( accountRecordCubit == null || chatListCubit == null || contactListCubit == null @@ -181,7 +188,7 @@ class PerAccountCollectionCubit extends Cubit { // ActiveSingleContactChatBlocMapCubit final activeSingleContactChatBlocMapCubit = - activeSingleContactChatBlocMapCubitUpdater.update( + await activeSingleContactChatBlocMapCubitUpdater.update( accountInfo.userLogin == null || activeConversationsBlocMapCubit == null ? null diff --git a/lib/account_manager/cubits/user_logins_cubit.dart b/lib/account_manager/cubits/user_logins_cubit.dart index 734ced3..5623a34 100644 --- a/lib/account_manager/cubits/user_logins_cubit.dart +++ b/lib/account_manager/cubits/user_logins_cubit.dart @@ -17,7 +17,6 @@ class UserLoginsCubit extends Cubit { switch (change) { case AccountRepositoryChange.userLogins: emit(_accountRepository.getUserLogins()); - break; // Ignore these case AccountRepositoryChange.localAccounts: case AccountRepositoryChange.activeLocalAccount: diff --git a/lib/account_manager/views/edit_account_page.dart b/lib/account_manager/views/edit_account_page.dart index 81b67eb..bd18967 100644 --- a/lib/account_manager/views/edit_account_page.dart +++ b/lib/account_manager/views/edit_account_page.dart @@ -61,6 +61,13 @@ class _EditAccountPageState extends WindowSetupState { ); Future _onRemoveAccount() async { + // dismiss the keyboard by unfocusing the textfield + FocusScope.of(context).unfocus(); + await asyncSleep(const Duration(milliseconds: 250)); + if (!mounted) { + return; + } + final confirmed = await StyledDialog.show( context: context, title: translate('edit_account_page.remove_account_confirm'), @@ -87,10 +94,7 @@ class _EditAccountPageState extends WindowSetupState { ])) ]).paddingAll(24) ])); - if (confirmed != null && confirmed && mounted) { - // dismiss the keyboard by unfocusing the textfield - FocusScope.of(context).unfocus(); - + if (confirmed != null && confirmed) { try { setState(() { _isInAsyncCall = true; @@ -125,6 +129,13 @@ class _EditAccountPageState extends WindowSetupState { } Future _onDestroyAccount() async { + // dismiss the keyboard by unfocusing the textfield + FocusScope.of(context).unfocus(); + await asyncSleep(const Duration(milliseconds: 250)); + if (!mounted) { + return; + } + final confirmed = await StyledDialog.show( context: context, title: translate('edit_account_page.destroy_account_confirm'), @@ -154,10 +165,7 @@ class _EditAccountPageState extends WindowSetupState { ])) ]).paddingAll(24) ])); - if (confirmed != null && confirmed && mounted) { - // dismiss the keyboard by unfocusing the textfield - FocusScope.of(context).unfocus(); - + if (confirmed != null && confirmed) { try { setState(() { _isInAsyncCall = true; diff --git a/lib/account_manager/views/edit_profile_form.dart b/lib/account_manager/views/edit_profile_form.dart index d6bb504..80bd2b3 100644 --- a/lib/account_manager/views/edit_profile_form.dart +++ b/lib/account_manager/views/edit_profile_form.dart @@ -282,9 +282,6 @@ class _EditProfileFormState extends State { FormBuilderCheckbox( name: EditProfileForm.formFieldAutoAway, initialValue: _savedValue.autoAway, - side: BorderSide(color: scale.primaryScale.border, width: 2), - checkColor: scale.primaryScale.borderText, - activeColor: scale.primaryScale.border, title: Text(translate('account.form_auto_away'), style: textTheme.labelMedium), onChanged: (v) { diff --git a/lib/app.dart b/lib/app.dart index 519f2bb..f8241da 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -47,7 +47,7 @@ class VeilidChatApp extends StatelessWidget { final ThemeData initialThemeData; - void _reloadTheme(BuildContext context) { + void reloadTheme(BuildContext context) { singleFuture(this, () async { log.info('Reloading theme'); @@ -95,7 +95,7 @@ class VeilidChatApp extends StatelessWidget { }, child: Actions(actions: >{ ReloadThemeIntent: CallbackAction( - onInvoke: (intent) => _reloadTheme(context)), + onInvoke: (intent) => reloadTheme(context)), AttachDetachIntent: CallbackAction( onInvoke: (intent) => _attachDetach(context)), }, child: Focus(autofocus: true, child: builder(context))))); diff --git a/lib/chat/views/chat_component_widget.dart b/lib/chat/views/chat_component_widget.dart index f41ba47..1646772 100644 --- a/lib/chat/views/chat_component_widget.dart +++ b/lib/chat/views/chat_component_widget.dart @@ -120,7 +120,7 @@ class ChatComponentWidget extends StatelessWidget { return Column( children: [ Container( - height: 40, + height: 48, decoration: BoxDecoration( color: scale.border, ), diff --git a/lib/chat_list/cubits/chat_list_cubit.dart b/lib/chat_list/cubits/chat_list_cubit.dart index 6bb88c1..3ae3db1 100644 --- a/lib/chat_list/cubits/chat_list_cubit.dart +++ b/lib/chat_list/cubits/chat_list_cubit.dart @@ -86,14 +86,12 @@ class ChatListCubit extends DHTShortArrayCubit // Nothing to do here return; } - break; case proto.Chat_Kind.group: if (c.group.localConversationRecordKey == contact.localConversationRecordKey) { throw StateError('direct conversation record key should' ' not be used for group chats!'); } - break; case proto.Chat_Kind.notSet: throw StateError('unknown chat kind'); } diff --git a/lib/contact_invitation/views/create_invitation_dialog.dart b/lib/contact_invitation/views/create_invitation_dialog.dart index d835de8..581e8d6 100644 --- a/lib/contact_invitation/views/create_invitation_dialog.dart +++ b/lib/contact_invitation/views/create_invitation_dialog.dart @@ -191,6 +191,7 @@ class _CreateInvitationDialogState extends State { mainAxisSize: MainAxisSize.min, children: [ TextField( + autofocus: true, controller: _recipientTextController, onChanged: (value) { setState(() {}); diff --git a/lib/contact_invitation/views/scan_invitation_dialog.dart b/lib/contact_invitation/views/scan_invitation_dialog.dart index 645640e..fa8bba9 100644 --- a/lib/contact_invitation/views/scan_invitation_dialog.dart +++ b/lib/contact_invitation/views/scan_invitation_dialog.dart @@ -86,7 +86,7 @@ class ScannerOverlay extends CustomPainter { final cutoutPath = Path()..addRect(scanWindow); final backgroundPaint = Paint() - ..color = Colors.black.withOpacity(0.5) + ..color = Colors.black.withAlpha(127) ..style = PaintingStyle.fill ..blendMode = BlendMode.dstOut; @@ -188,7 +188,7 @@ class ScanInvitationDialogState extends State { child: Container( alignment: Alignment.bottomCenter, height: 100, - color: Colors.black.withOpacity(0.4), + color: Colors.black.withAlpha(127), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ diff --git a/lib/contacts/cubits/contact_list_cubit.dart b/lib/contacts/cubits/contact_list_cubit.dart index df70cc0..a0591ad 100644 --- a/lib/contacts/cubits/contact_list_cubit.dart +++ b/lib/contacts/cubits/contact_list_cubit.dart @@ -149,10 +149,14 @@ class ContactListCubit extends DHTShortArrayCubit { // Mark the conversation records for deletion await DHTRecordPool.instance .deleteRecord(deletedItem.localConversationRecordKey.toVeilid()); + } on Exception catch (e) { + log.debug('error deleting local conversation record: $e', e); + } + try { await DHTRecordPool.instance .deleteRecord(deletedItem.remoteConversationRecordKey.toVeilid()); } on Exception catch (e) { - log.debug('error deleting conversation records: $e', e); + log.debug('error deleting remote conversation record: $e', e); } } } diff --git a/lib/contacts/views/edit_contact_form.dart b/lib/contacts/views/edit_contact_form.dart index 5477c60..514f019 100644 --- a/lib/contacts/views/edit_contact_form.dart +++ b/lib/contacts/views/edit_contact_form.dart @@ -195,9 +195,6 @@ class _EditContactFormState extends State { FormBuilderCheckbox( name: EditContactForm.formFieldShowAvailability, initialValue: _savedValue.showAvailability, - side: BorderSide(color: scale.primaryScale.border, width: 2), - checkColor: scale.primaryScale.borderText, - activeColor: scale.primaryScale.border, title: Text(translate('contact_form.form_show_availability'), style: textTheme.labelMedium), ), diff --git a/lib/contacts/views/empty_contact_list_widget.dart b/lib/contacts/views/empty_contact_list_widget.dart index 2563a1d..e6912fd 100644 --- a/lib/contacts/views/empty_contact_list_widget.dart +++ b/lib/contacts/views/empty_contact_list_widget.dart @@ -15,8 +15,7 @@ class EmptyContactListWidget extends StatelessWidget { final textTheme = theme.textTheme; final scale = theme.extension()!; - return Expanded( - child: Column( + return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.center, @@ -35,6 +34,6 @@ class EmptyContactListWidget extends StatelessWidget { ), ), ], - )); + ); } } diff --git a/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart b/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart index fbf0e80..08a249f 100644 --- a/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart +++ b/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart @@ -50,7 +50,7 @@ typedef ActiveConversationsBlocMapState // We currently only build the cubits for the chats that are active, not // archived chats or contacts that are not actively in a chat. // -// TODO: Polling contacts for new inactive chats is yet to be done +// TODO(crioux): Polling contacts for new inactive chats is yet to be done // class ActiveConversationsBlocMapCubit extends BlocMapCubit, ActiveConversationCubit> @@ -166,7 +166,6 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit { // ? grayColorFilter // : null) // .paddingLTRB(0, 0, 16, 0), - SvgPicture.asset( - height: 48, - 'assets/images/title.svg', - colorFilter: scaleConfig.useVisualIndicators - ? grayColorFilter - : src96StencilFilter), + GestureDetector( + onLongPress: () async { + context + .findAncestorWidgetOfExactType()! + .reloadTheme(context); + }, + child: SvgPicture.asset( + height: 48, + 'assets/images/title.svg', + colorFilter: scaleConfig.useVisualIndicators + ? grayColorFilter + : src96StencilFilter)), ]))), Text(translate('menu.accounts'), style: theme.textTheme.titleMedium!.copyWith( diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index a534415..3d1b14f 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -66,10 +66,11 @@ class HomeScreenState extends State final scale = theme.extension()!; final scaleConfig = theme.extension()!; - await showWarningWidgetModal( + await showAlertWidgetModal( context: context, title: translate('splash.beta_title'), child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + const Icon(Icons.warning, size: 64), RichText( textAlign: TextAlign.center, text: TextSpan( diff --git a/lib/notifications/views/notifications_preferences.dart b/lib/notifications/views/notifications_preferences.dart index 95d4a1e..82f3555 100644 --- a/lib/notifications/views/notifications_preferences.dart +++ b/lib/notifications/views/notifications_preferences.dart @@ -129,9 +129,6 @@ Widget buildSettingsPageNotificationPreferences( // Display Beta Warning FormBuilderCheckbox( name: formFieldDisplayBetaWarning, - side: BorderSide(color: scale.primaryScale.border, width: 2), - checkColor: scale.primaryScale.borderText, - activeColor: scale.primaryScale.border, title: Text(translate('settings_page.display_beta_warning'), style: textTheme.labelMedium), initialValue: notificationsPreference.displayBetaWarning, @@ -147,9 +144,6 @@ Widget buildSettingsPageNotificationPreferences( // Enable Badge FormBuilderCheckbox( name: formFieldEnableBadge, - side: BorderSide(color: scale.primaryScale.border, width: 2), - checkColor: scale.primaryScale.borderText, - activeColor: scale.primaryScale.border, title: Text(translate('settings_page.enable_badge'), style: textTheme.labelMedium), initialValue: notificationsPreference.enableBadge, @@ -164,9 +158,6 @@ Widget buildSettingsPageNotificationPreferences( // Enable Notifications FormBuilderCheckbox( name: formFieldEnableNotifications, - side: BorderSide(color: scale.primaryScale.border, width: 2), - checkColor: scale.primaryScale.borderText, - activeColor: scale.primaryScale.border, title: Text(translate('settings_page.enable_notifications'), style: textTheme.labelMedium), initialValue: notificationsPreference.enableNotifications, diff --git a/lib/theme/models/radix_generator.dart b/lib/theme/models/radix_generator.dart index 3e3b0e6..ce05769 100644 --- a/lib/theme/models/radix_generator.dart +++ b/lib/theme/models/radix_generator.dart @@ -638,7 +638,7 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) { useVisualIndicators: false, preferBorders: false, borderRadiusScale: 1, - wallpaperAlpha: wallpaperAlpha(brightness, themeColor), + wallpaperOpacity: wallpaperAlpha(brightness, themeColor), ); final scaleTheme = ScaleTheme( diff --git a/lib/theme/models/scale_theme/scale_color.dart b/lib/theme/models/scale_theme/scale_color.dart index e50a01b..d79d878 100644 --- a/lib/theme/models/scale_theme/scale_color.dart +++ b/lib/theme/models/scale_theme/scale_color.dart @@ -50,11 +50,11 @@ class ScaleColor { Color? subtleBorder, Color? border, Color? hoverBorder, - Color? background, - Color? hoverBackground, + Color? primary, + Color? hoverPrimary, Color? subtleText, Color? appText, - Color? foregroundText, + Color? primaryText, Color? borderText, Color? dialogBorder, Color? dialogBorderText, @@ -72,11 +72,11 @@ class ScaleColor { subtleBorder: subtleBorder ?? this.subtleBorder, border: border ?? this.border, hoverBorder: hoverBorder ?? this.hoverBorder, - primary: background ?? this.primary, - hoverPrimary: hoverBackground ?? this.hoverPrimary, + primary: primary ?? this.primary, + hoverPrimary: hoverPrimary ?? this.hoverPrimary, subtleText: subtleText ?? this.subtleText, appText: appText ?? this.appText, - primaryText: foregroundText ?? this.primaryText, + primaryText: primaryText ?? this.primaryText, borderText: borderText ?? this.borderText, dialogBorder: dialogBorder ?? this.dialogBorder, dialogBorderText: dialogBorderText ?? this.dialogBorderText, diff --git a/lib/theme/models/scale_theme/scale_custom_dropdown_theme.dart b/lib/theme/models/scale_theme/scale_custom_dropdown_theme.dart index 692ec85..2c5eb1c 100644 --- a/lib/theme/models/scale_theme/scale_custom_dropdown_theme.dart +++ b/lib/theme/models/scale_theme/scale_custom_dropdown_theme.dart @@ -68,7 +68,7 @@ extension ScaleCustomDropdownThemeExt on ScaleTheme { listItemDecoration: null, ); - final disabledDecoration = CustomDropdownDisabledDecoration( + const disabledDecoration = CustomDropdownDisabledDecoration( fillColor: null, shadow: null, suffixIcon: null, diff --git a/lib/theme/models/scale_theme/scale_scheme.dart b/lib/theme/models/scale_theme/scale_scheme.dart index dd88b4f..8363476 100644 --- a/lib/theme/models/scale_theme/scale_scheme.dart +++ b/lib/theme/models/scale_theme/scale_scheme.dart @@ -111,27 +111,27 @@ class ScaleConfig extends ThemeExtension { required this.useVisualIndicators, required this.preferBorders, required this.borderRadiusScale, - required double wallpaperAlpha, - }) : _wallpaperAlpha = wallpaperAlpha; + required this.wallpaperOpacity, + }); final bool useVisualIndicators; final bool preferBorders; final double borderRadiusScale; - final double _wallpaperAlpha; + final double wallpaperOpacity; - int get wallpaperAlpha => _wallpaperAlpha.toInt(); + int get wallpaperAlpha => wallpaperOpacity.toInt(); @override ScaleConfig copyWith( {bool? useVisualIndicators, bool? preferBorders, double? borderRadiusScale, - double? wallpaperAlpha}) => + double? wallpaperOpacity}) => ScaleConfig( useVisualIndicators: useVisualIndicators ?? this.useVisualIndicators, preferBorders: preferBorders ?? this.preferBorders, borderRadiusScale: borderRadiusScale ?? this.borderRadiusScale, - wallpaperAlpha: wallpaperAlpha ?? this._wallpaperAlpha, + wallpaperOpacity: wallpaperOpacity ?? this.wallpaperOpacity, ); @override @@ -145,7 +145,7 @@ class ScaleConfig extends ThemeExtension { preferBorders: t < .5 ? preferBorders : other.preferBorders, borderRadiusScale: lerpDouble(borderRadiusScale, other.borderRadiusScale, t) ?? 1, - wallpaperAlpha: - lerpDouble(_wallpaperAlpha, other._wallpaperAlpha, t) ?? 1); + wallpaperOpacity: + lerpDouble(wallpaperOpacity, other.wallpaperOpacity, t) ?? 1); } } diff --git a/lib/theme/models/scale_theme/scale_theme.dart b/lib/theme/models/scale_theme/scale_theme.dart index 43ca641..ef430d2 100644 --- a/lib/theme/models/scale_theme/scale_theme.dart +++ b/lib/theme/models/scale_theme/scale_theme.dart @@ -84,6 +84,24 @@ class ScaleTheme extends ThemeExtension { scheme.primaryScale.borderText, scheme.primaryScale.primary, 0.25); }); + WidgetStateProperty checkboxFillColorWidgetStateProperty() => + WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + if (states.contains(WidgetState.disabled)) { + return scheme.grayScale.primary.withAlpha(0x7F); + } else if (states.contains(WidgetState.pressed)) { + return scheme.primaryScale.hoverBorder; + } else if (states.contains(WidgetState.hovered)) { + return scheme.primaryScale.hoverBorder; + } else if (states.contains(WidgetState.focused)) { + return scheme.primaryScale.border; + } + return scheme.primaryScale.border; + } else { + return Colors.transparent; + } + }); + // WidgetStateProperty elementBackgroundWidgetStateProperty() { // return null; // } @@ -140,7 +158,7 @@ class ScaleTheme extends ThemeExtension { appBarTheme: baseThemeData.appBarTheme.copyWith( backgroundColor: scheme.primaryScale.border, foregroundColor: scheme.primaryScale.borderText, - toolbarHeight: 40, + toolbarHeight: 48, ), bottomSheetTheme: baseThemeData.bottomSheetTheme.copyWith( elevation: 0, @@ -150,6 +168,11 @@ class ScaleTheme extends ThemeExtension { topLeft: Radius.circular(16 * config.borderRadiusScale), topRight: Radius.circular(16 * config.borderRadiusScale)))), canvasColor: scheme.primaryScale.subtleBackground, + checkboxTheme: baseThemeData.checkboxTheme.copyWith( + side: BorderSide(color: scheme.primaryScale.border, width: 2), + checkColor: elementColorWidgetStateProperty(), + fillColor: checkboxFillColorWidgetStateProperty(), + ), chipTheme: baseThemeData.chipTheme.copyWith( backgroundColor: scheme.primaryScale.elementBackground, selectedColor: scheme.primaryScale.activeElementBackground, diff --git a/lib/theme/models/theme_preference.dart b/lib/theme/models/theme_preference.dart index dc2e082..4be6b4e 100644 --- a/lib/theme/models/theme_preference.dart +++ b/lib/theme/models/theme_preference.dart @@ -1,6 +1,5 @@ import 'package:change_case/change_case.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -103,7 +102,7 @@ extension ThemePreferencesExt on ThemePreferences { useVisualIndicators: true, preferBorders: false, borderRadiusScale: 1, - wallpaperAlpha: 255), + wallpaperOpacity: 255), primaryFront: Colors.black, primaryBack: Colors.white, secondaryFront: Colors.black, @@ -123,7 +122,7 @@ extension ThemePreferencesExt on ThemePreferences { useVisualIndicators: true, preferBorders: true, borderRadiusScale: 0.2, - wallpaperAlpha: 208), + wallpaperOpacity: 208), primaryFront: const Color(0xFF000000), primaryBack: const Color(0xFF00FF00), secondaryFront: const Color(0xFF000000), @@ -141,7 +140,7 @@ extension ThemePreferencesExt on ThemePreferences { useVisualIndicators: true, preferBorders: true, borderRadiusScale: 0.2, - wallpaperAlpha: 192), + wallpaperOpacity: 192), primaryFront: const Color(0xFF000000), primaryBack: const Color(0xFF00FF00), secondaryFront: const Color(0xFF000000), diff --git a/lib/theme/views/scanner_error_widget.dart b/lib/theme/views/scanner_error_widget.dart index 0926128..d5463f4 100644 --- a/lib/theme/views/scanner_error_widget.dart +++ b/lib/theme/views/scanner_error_widget.dart @@ -14,16 +14,12 @@ class ScannerErrorWidget extends StatelessWidget { switch (error.errorCode) { case MobileScannerErrorCode.controllerUninitialized: errorMessage = 'Controller not ready.'; - break; case MobileScannerErrorCode.permissionDenied: errorMessage = 'Permission denied'; - break; case MobileScannerErrorCode.unsupported: errorMessage = 'Scanning is unsupported on this device'; - break; default: errorMessage = 'Generic Error'; - break; } return ColoredBox( diff --git a/lib/theme/views/styled_alert.dart b/lib/theme/views/styled_alert.dart index 82218e8..8602c22 100644 --- a/lib/theme/views/styled_alert.dart +++ b/lib/theme/views/styled_alert.dart @@ -11,6 +11,7 @@ AlertStyle _alertStyle(BuildContext context) { return AlertStyle( animationType: AnimationType.grow, + isCloseButton: false, //animationDuration: const Duration(milliseconds: 200), alertBorder: RoundedRectangleBorder( side: !scaleConfig.useVisualIndicators @@ -131,7 +132,7 @@ Future showErrorStacktraceModal( ); } -Future showWarningModal( +Future showAlertModal( {required BuildContext context, required String title, required String text}) async { @@ -139,7 +140,7 @@ Future showWarningModal( context: context, style: _alertStyle(context), useRootNavigator: false, - type: AlertType.warning, + type: AlertType.none, title: title, desc: text, buttons: [ @@ -161,7 +162,7 @@ Future showWarningModal( ).show(); } -Future showWarningWidgetModal( +Future showAlertWidgetModal( {required BuildContext context, required String title, required Widget child}) async { @@ -169,7 +170,7 @@ Future showWarningWidgetModal( context: context, style: _alertStyle(context), useRootNavigator: false, - type: AlertType.warning, + type: AlertType.none, title: title, content: child, buttons: [ diff --git a/lib/theme/views/wallpaper_preferences.dart b/lib/theme/views/wallpaper_preferences.dart index e90022c..f9ae94c 100644 --- a/lib/theme/views/wallpaper_preferences.dart +++ b/lib/theme/views/wallpaper_preferences.dart @@ -15,7 +15,6 @@ Widget buildSettingsPageWallpaperPreferences( final preferencesRepository = PreferencesRepository.instance; final themePreferences = preferencesRepository.value.themePreference; final theme = Theme.of(context); - final scale = theme.extension()!; final textTheme = theme.textTheme; return FormBuilderCheckbox( @@ -23,9 +22,6 @@ Widget buildSettingsPageWallpaperPreferences( title: Text(translate('settings_page.enable_wallpaper'), style: textTheme.labelMedium), initialValue: themePreferences.enableWallpaper, - side: BorderSide(color: scale.primaryScale.border, width: 2), - checkColor: scale.primaryScale.borderText, - activeColor: scale.primaryScale.border, onChanged: (value) async { if (value != null) { final newThemePrefs = diff --git a/lib/veilid_processor/repository/processor_repository.dart b/lib/veilid_processor/repository/processor_repository.dart index 3622219..7ff2482 100644 --- a/lib/veilid_processor/repository/processor_repository.dart +++ b/lib/veilid_processor/repository/processor_repository.dart @@ -44,13 +44,6 @@ class ProcessorRepository { log.info('Veilid version: $veilidVersion'); - // HACK: In case of hot restart shut down first - try { - await Veilid.instance.shutdownVeilidCore(); - } on Exception { - // Do nothing on failure here - } - final updateStream = await Veilid.instance .startupVeilidCore(await getVeilidConfig(kIsWeb, VeilidChatApp.name)); _updateSubscription = updateStream.listen((update) { diff --git a/packages/veilid_support/example/test/widget_test.dart b/packages/veilid_support/example/test/widget_test.dart index 092d222..d5cfa06 100644 --- a/packages/veilid_support/example/test/widget_test.dart +++ b/packages/veilid_support/example/test/widget_test.dart @@ -5,13 +5,12 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. +import 'package:example/main.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:example/main.dart'; - void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { + testWidgets('Counter increments smoke test', (tester) async { // Build our app and trigger a frame. await tester.pumpWidget(const MyApp()); diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart index bba3306..8f88ce1 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart @@ -171,32 +171,31 @@ class DHTLog implements DHTDeleteable { /// Add a reference to this log @override - Future ref() async => _mutex.protect(() async { - _openCount++; - }); + void ref() { + _openCount++; + } /// Free all resources for the DHTLog @override - Future close() async => _mutex.protect(() async { - if (_openCount == 0) { - throw StateError('already closed'); - } - _openCount--; - if (_openCount != 0) { - return false; - } - await _watchController?.close(); - _watchController = null; - await _spine.close(); - return true; - }); + Future close() async { + if (_openCount == 0) { + throw StateError('already closed'); + } + _openCount--; + if (_openCount != 0) { + return false; + } + // + await _watchController?.close(); + _watchController = null; + await _spine.close(); + return true; + } /// Free all resources for the DHTLog and delete it from the DHT /// Will wait until the short array is closed to delete it @override - Future delete() async { - await _spine.delete(); - } + Future delete() => _spine.delete(); //////////////////////////////////////////////////////////////////////////// // Public API @@ -306,7 +305,6 @@ class DHTLog implements DHTDeleteable { // Openable int _openCount; - final _mutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); // Watch mutex to ensure we keep the representation valid final Mutex _listenMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart index 1ea48be..8eff1b6 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart @@ -24,13 +24,11 @@ class _DHTLogPosition extends DHTCloseable { /// Add a reference to this log @override - Future ref() async { - await shortArray.ref(); - } + void ref() => shortArray.ref(); /// Free all resources for the DHTLogPosition @override - Future close() async => _dhtLogSpine._segmentClosed(_segmentNumber); + Future close() => _dhtLogSpine._segmentClosed(_segmentNumber); } class _DHTLogSegmentLookup extends Equatable { @@ -124,12 +122,8 @@ class _DHTLogSpine { }); } - Future delete() async { - await _spineMutex.protect(() async { - // Will deep delete all segment records as they are children - await _spineRecord.delete(); - }); - } + // Will deep delete all segment records as they are children + Future delete() async => _spineMutex.protect(_spineRecord.delete); Future operate(Future Function(_DHTLogSpine) closure) async => // ignore: prefer_expression_function_bodies @@ -431,7 +425,7 @@ class _DHTLogSpine { late DHTShortArray shortArray; if (openedSegment != null) { // If so, return a ref - await openedSegment.ref(); + openedSegment.ref(); shortArray = openedSegment; } else { // Otherwise open a segment @@ -453,7 +447,7 @@ class _DHTLogSpine { // LRU cache the segment number if (!_openCache.remove(segmentNumber)) { // If this is new to the cache ref it when it goes in - await shortArray.ref(); + shortArray.ref(); } _openCache.add(segmentNumber); if (_openCache.length > _openCacheSize) { diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart index bddb4e7..0a51ba1 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart @@ -64,34 +64,35 @@ class DHTRecord implements DHTDeleteable { /// Add a reference to this DHTRecord @override - Future ref() async => _mutex.protect(() async { - _openCount++; - }); + void ref() { + _openCount++; + } /// Free all resources for the DHTRecord @override - Future close() async => _mutex.protect(() async { - if (_openCount == 0) { - throw StateError('already closed'); - } - _openCount--; - if (_openCount != 0) { - return false; - } + Future close() async { + if (_openCount == 0) { + throw StateError('already closed'); + } + _openCount--; + if (_openCount != 0) { + return false; + } - await serialFutureClose((this, _sfListen)); - await _watchController?.close(); - _watchController = null; - await DHTRecordPool.instance._recordClosed(this); - return true; - }); + await _watchController?.close(); + _watchController = null; + await serialFutureClose((this, _sfListen)); + + await DHTRecordPool.instance._recordClosed(this); + + return true; + } /// Free all resources for the DHTRecord and delete it from the DHT - /// Will wait until the record is closed to delete it + /// Returns true if the deletion was processed immediately + /// Returns false if the deletion was marked for later @override - Future delete() async => _mutex.protect(() async { - await DHTRecordPool.instance.deleteRecord(key); - }); + Future delete() async => DHTRecordPool.instance.deleteRecord(key); //////////////////////////////////////////////////////////////////////////// // Public API @@ -562,7 +563,6 @@ class DHTRecord implements DHTDeleteable { final KeyPair? _writer; final VeilidCrypto _crypto; final String debugName; - final _mutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); int _openCount; StreamController? _watchController; _WatchState? _watchState; diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart index 3f55687..15c955d 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart @@ -479,25 +479,29 @@ class DHTRecordPool with TableDBBackedJson { // Called when a DHTRecord is closed // Cleans up the opened record housekeeping and processes any late deletions Future _recordClosed(DHTRecord record) async { - await _recordTagLock.protect(record.key, - closure: () => _mutex.protect(() async { - final key = record.key; + final key = record.key; + await _recordTagLock.protect(key, closure: () async { + await _mutex.protect(() async { + log('closeDHTRecord: debugName=${record.debugName} key=$key'); - log('closeDHTRecord: debugName=${record.debugName} key=$key'); + final openedRecordInfo = _opened[key]; + if (openedRecordInfo == null || + !openedRecordInfo.records.remove(record)) { + throw StateError('record already closed'); + } + if (openedRecordInfo.records.isNotEmpty) { + return; + } + _opened.remove(key); + await _routingContext.closeDHTRecord(key); + await _checkForLateDeletesInner(key); + }); - final openedRecordInfo = _opened[key]; - if (openedRecordInfo == null || - !openedRecordInfo.records.remove(record)) { - throw StateError('record already closed'); - } - if (openedRecordInfo.records.isEmpty) { - await _watchStateProcessors.remove(key); - await _routingContext.closeDHTRecord(key); - _opened.remove(key); - - await _checkForLateDeletesInner(key); - } - })); + // This happens after the mutex is released + // because the record has already been removed from _opened + // which means that updates to the state processor won't happen + await _watchStateProcessors.remove(key); + }); } // Check to see if this key can finally be deleted @@ -929,40 +933,37 @@ class DHTRecordPool with TableDBBackedJson { } /// Ticker to check watch state change requests - Future tick() async { - final now = veilid.now(); + Future tick() async => _mutex.protect(() async { + // See if any opened records need watch state changes + final now = veilid.now(); + for (final kv in _opened.entries) { + final openedRecordKey = kv.key; + final openedRecordInfo = kv.value; - await _mutex.protect(() async { - // See if any opened records need watch state changes - for (final kv in _opened.entries) { - final openedRecordKey = kv.key; - final openedRecordInfo = kv.value; + var wantsWatchStateUpdate = + openedRecordInfo.shared.needsWatchStateUpdate; - var wantsWatchStateUpdate = - openedRecordInfo.shared.needsWatchStateUpdate; + // Check if we have reached renewal time for the watch + if (openedRecordInfo.shared.unionWatchState != null && + openedRecordInfo.shared.unionWatchState!.renewalTime != null && + now.value > + openedRecordInfo.shared.unionWatchState!.renewalTime!.value) { + wantsWatchStateUpdate = true; + } - // Check if we have reached renewal time for the watch - if (openedRecordInfo.shared.unionWatchState != null && - openedRecordInfo.shared.unionWatchState!.renewalTime != null && - now.value > - openedRecordInfo.shared.unionWatchState!.renewalTime!.value) { - wantsWatchStateUpdate = true; + if (wantsWatchStateUpdate) { + // Update union watch state + final unionWatchState = + _collectUnionWatchState(openedRecordInfo.records); + + _watchStateProcessors.updateState( + openedRecordKey, + unionWatchState, + (newState) => + _watchStateChange(openedRecordKey, unionWatchState)); + } } - - if (wantsWatchStateUpdate) { - // Update union watch state - final unionWatchState = - _collectUnionWatchState(openedRecordInfo.records); - - _watchStateProcessors.updateState( - openedRecordKey, - unionWatchState, - (newState) => - _watchStateChange(openedRecordKey, unionWatchState)); - } - } - }); - } + }); ////////////////////////////////////////////////////////////// // AsyncTableDBBacked diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart index c0ec901..8101a7a 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart @@ -148,33 +148,32 @@ class DHTShortArray implements DHTDeleteable { /// Add a reference to this shortarray @override - Future ref() async => _mutex.protect(() async { - _openCount++; - }); + void ref() { + _openCount++; + } /// Free all resources for the DHTShortArray @override - Future close() async => _mutex.protect(() async { - if (_openCount == 0) { - throw StateError('already closed'); - } - _openCount--; - if (_openCount != 0) { - return false; - } + Future close() async { + if (_openCount == 0) { + throw StateError('already closed'); + } + _openCount--; + if (_openCount != 0) { + return false; + } - await _watchController?.close(); - _watchController = null; - await _head.close(); - return true; - }); + await _watchController?.close(); + _watchController = null; + await _head.close(); + return true; + } /// Free all resources for the DHTShortArray and delete it from the DHT - /// Will wait until the short array is closed to delete it + /// Returns true if the deletion was processed immediately + /// Returns false if the deletion was marked for later @override - Future delete() async { - await _head.delete(); - } + Future delete() async => _head.delete(); //////////////////////////////////////////////////////////////////////////// // Public API @@ -289,7 +288,6 @@ class DHTShortArray implements DHTDeleteable { // Openable int _openCount; - final _mutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); // Watch mutex to ensure we keep the representation valid final Mutex _listenMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart index 8bdda7c..ab56c77 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart @@ -8,7 +8,6 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:meta/meta.dart'; import '../../../veilid_support.dart'; -import '../interfaces/refreshable_cubit.dart'; @immutable class DHTShortArrayElementState extends Equatable { diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart index 4a2c79a..0aaed19 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart @@ -65,12 +65,9 @@ class _DHTShortArrayHead { }); } - Future delete() async { - await _headMutex.protect(() async { - // Will deep delete all linked records as they are children - await _headRecord.delete(); - }); - } + /// Returns true if the deletion was processed immediately + /// Returns false if the deletion was marked for later + Future delete() => _headMutex.protect(_headRecord.delete); Future operate(Future Function(_DHTShortArrayHead) closure) async => // ignore: prefer_expression_function_bodies diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart index c52a7b2..f3e1ac3 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart @@ -40,7 +40,7 @@ class _DHTShortArrayWrite extends _DHTShortArrayRead } } if (!success) { - throw DHTExceptionOutdated(); + throw const DHTExceptionOutdated(); } } @@ -97,7 +97,7 @@ class _DHTShortArrayWrite extends _DHTShortArrayRead } } if (!success) { - throw DHTExceptionOutdated(); + throw const DHTExceptionOutdated(); } } diff --git a/packages/veilid_support/lib/dht_support/src/interfaces/dht_closeable.dart b/packages/veilid_support/lib/dht_support/src/interfaces/dht_closeable.dart index c913340..0fb10ab 100644 --- a/packages/veilid_support/lib/dht_support/src/interfaces/dht_closeable.dart +++ b/packages/veilid_support/lib/dht_support/src/interfaces/dht_closeable.dart @@ -4,7 +4,7 @@ import 'package:meta/meta.dart'; abstract class DHTCloseable { // Public interface - Future ref(); + void ref(); Future close(); // Internal implementation @@ -15,7 +15,9 @@ abstract class DHTCloseable { } abstract class DHTDeleteable extends DHTCloseable { - Future delete(); + /// Returns true if the deletion was processed immediately + /// Returns false if the deletion was marked for later + Future delete(); } extension DHTCloseableExt on DHTCloseable { diff --git a/pubspec.lock b/pubspec.lock index fa60f63..b4df49f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -156,10 +156,9 @@ packages: bloc_advanced_tools: dependency: "direct main" description: - name: bloc_advanced_tools - sha256: "977f3c7e3f9a19aec2f2c734ae99c8f0799c1b78f9fd7e4dce91a2dbf773e11b" - url: "https://pub.dev" - source: hosted + path: "../bloc_advanced_tools" + relative: true + source: path version: "0.1.9" blurry_modal_progress_hud: dependency: "direct main" diff --git a/pubspec.yaml b/pubspec.yaml index a37a8ab..b909a16 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -111,11 +111,11 @@ dependencies: xterm: ^4.0.0 zxing2: ^0.2.3 -# dependency_overrides: +dependency_overrides: # async_tools: # path: ../dart_async_tools -# bloc_advanced_tools: -# path: ../bloc_advanced_tools + bloc_advanced_tools: + path: ../bloc_advanced_tools # searchable_listview: # path: ../Searchable-Listview # flutter_chat_ui: From 739df7c427de01cf7131278597ae9d3bf7248dda Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Fri, 21 Mar 2025 14:50:17 -0400 Subject: [PATCH 41/93] dependency upgrades --- packages/veilid_support/example/.gitignore | 2 + .../example/integration_test/app_test.dart | 90 +++++++++---------- .../veilid_support/example/macos/Podfile.lock | 6 +- .../xcshareddata/xcschemes/Runner.xcscheme | 1 + .../example/macos/Runner/AppDelegate.swift | 4 + packages/veilid_support/example/pubspec.lock | 81 +++++++---------- packages/veilid_support/example/pubspec.yaml | 6 +- packages/veilid_support/pubspec.lock | 69 +++++++------- packages/veilid_support/pubspec.yaml | 46 +++++----- pubspec.lock | 83 ++++++++--------- pubspec.yaml | 37 ++++---- 11 files changed, 206 insertions(+), 219 deletions(-) diff --git a/packages/veilid_support/example/.gitignore b/packages/veilid_support/example/.gitignore index 29a3a50..79c113f 100644 --- a/packages/veilid_support/example/.gitignore +++ b/packages/veilid_support/example/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/packages/veilid_support/example/integration_test/app_test.dart b/packages/veilid_support/example/integration_test/app_test.dart index 6912fd3..5dc7acd 100644 --- a/packages/veilid_support/example/integration_test/app_test.dart +++ b/packages/veilid_support/example/integration_test/app_test.dart @@ -36,6 +36,51 @@ void main() { setUpAll(veilidFixture.attach); tearDownAll(veilidFixture.detach); + group('DHT Support Tests', () { + setUpAll(updateProcessorFixture.setUp); + setUpAll(tickerFixture.setUp); + tearDownAll(tickerFixture.tearDown); + tearDownAll(updateProcessorFixture.tearDown); + + test('create pool', testDHTRecordPoolCreate); + + group('DHTRecordPool Tests', () { + setUpAll(dhtRecordPoolFixture.setUp); + tearDownAll(dhtRecordPoolFixture.tearDown); + + test('create/delete record', testDHTRecordCreateDelete); + test('record scopes', testDHTRecordScopes); + test('create/delete deep record', testDHTRecordDeepCreateDelete); + }); + + group('DHTShortArray Tests', () { + setUpAll(dhtRecordPoolFixture.setUp); + tearDownAll(dhtRecordPoolFixture.tearDown); + + for (final stride in [256, 16 /*64, 32, 16, 8, 4, 2, 1 */]) { + test('create shortarray stride=$stride', + makeTestDHTShortArrayCreateDelete(stride: stride)); + test('add shortarray stride=$stride', + makeTestDHTShortArrayAdd(stride: stride)); + } + }); + + group('DHTLog Tests', () { + setUpAll(dhtRecordPoolFixture.setUp); + tearDownAll(dhtRecordPoolFixture.tearDown); + + for (final stride in [256, 16 /*64, 32, 16, 8, 4, 2, 1 */]) { + test('create log stride=$stride', + makeTestDHTLogCreateDelete(stride: stride)); + test( + timeout: const Timeout(Duration(seconds: 480)), + 'add/truncate log stride=$stride', + makeTestDHTLogAddTruncate(stride: stride), + ); + } + }); + }); + group('TableDB Tests', () { group('TableDBArray Tests', () { // test('create/delete TableDBArray', testTableDBArrayCreateDelete); @@ -146,51 +191,6 @@ void main() { }); }); }); - - group('DHT Support Tests', () { - setUpAll(updateProcessorFixture.setUp); - setUpAll(tickerFixture.setUp); - tearDownAll(tickerFixture.tearDown); - tearDownAll(updateProcessorFixture.tearDown); - - test('create pool', testDHTRecordPoolCreate); - - group('DHTRecordPool Tests', () { - setUpAll(dhtRecordPoolFixture.setUp); - tearDownAll(dhtRecordPoolFixture.tearDown); - - test('create/delete record', testDHTRecordCreateDelete); - test('record scopes', testDHTRecordScopes); - test('create/delete deep record', testDHTRecordDeepCreateDelete); - }); - - group('DHTShortArray Tests', () { - setUpAll(dhtRecordPoolFixture.setUp); - tearDownAll(dhtRecordPoolFixture.tearDown); - - for (final stride in [256, 16 /*64, 32, 16, 8, 4, 2, 1 */]) { - test('create shortarray stride=$stride', - makeTestDHTShortArrayCreateDelete(stride: stride)); - test('add shortarray stride=$stride', - makeTestDHTShortArrayAdd(stride: stride)); - } - }); - - group('DHTLog Tests', () { - setUpAll(dhtRecordPoolFixture.setUp); - tearDownAll(dhtRecordPoolFixture.tearDown); - - for (final stride in [256, 16 /*64, 32, 16, 8, 4, 2, 1 */]) { - test('create log stride=$stride', - makeTestDHTLogCreateDelete(stride: stride)); - test( - timeout: const Timeout(Duration(seconds: 480)), - 'add/truncate log stride=$stride', - makeTestDHTLogAddTruncate(stride: stride), - ); - } - }); - }); }); }); } diff --git a/packages/veilid_support/example/macos/Podfile.lock b/packages/veilid_support/example/macos/Podfile.lock index 6a58494..a2618bd 100644 --- a/packages/veilid_support/example/macos/Podfile.lock +++ b/packages/veilid_support/example/macos/Podfile.lock @@ -21,9 +21,9 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - veilid: a54f57b7bcf0e4e072fe99272d76ca126b2026d0 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + veilid: 319e2e78836d7b3d08203596d0b4a0e244b68d29 PODFILE CHECKSUM: 16208599a12443d53889ba2270a4985981cfb204 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/packages/veilid_support/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/veilid_support/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 15368ec..ac78810 100644 --- a/packages/veilid_support/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/veilid_support/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -59,6 +59,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/packages/veilid_support/example/macos/Runner/AppDelegate.swift b/packages/veilid_support/example/macos/Runner/AppDelegate.swift index 8e02df2..b3c1761 100644 --- a/packages/veilid_support/example/macos/Runner/AppDelegate.swift +++ b/packages/veilid_support/example/macos/Runner/AppDelegate.swift @@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/veilid_support/example/pubspec.lock b/packages/veilid_support/example/pubspec.lock index 3844db3..6ef291b 100644 --- a/packages/veilid_support/example/pubspec.lock +++ b/packages/veilid_support/example/pubspec.lock @@ -5,31 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 url: "https://pub.dev" source: hosted - version: "76.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" + version: "80.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "7.3.0" args: dependency: transitive description: name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.0" async: dependency: transitive description: @@ -42,26 +37,26 @@ packages: dependency: "direct dev" description: name: async_tools - sha256: bbded696bfcb1437d0ca510ac047f261f9c7494fea2c488dd32ba2800e7f49e8 + sha256: afd5426e76631172f8ce6a6359b264b092fa9d2a52cd2528100115be9525e067 url: "https://pub.dev" source: hosted - version: "0.1.7" + version: "0.1.9" bloc: dependency: transitive description: name: bloc - sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + sha256: "52c10575f4445c61dd9e0cafcc6356fdd827c4c64dd7945ef3c4105f6b6ac189" url: "https://pub.dev" source: hosted - version: "8.1.4" + version: "9.0.0" bloc_advanced_tools: dependency: transitive description: name: bloc_advanced_tools - sha256: d8a680d8a0469456399fb26bae9f7a1d2a1420b5bdf75e204e0fadab9edb0811 + sha256: "7c7f294b425552c2d4831b01ad0d3e1f33f2bdf9acfb7b639caa072781d228cf" url: "https://pub.dev" source: hosted - version: "0.1.8" + version: "0.1.10" boolean_selector: dependency: transitive description: @@ -162,18 +157,18 @@ packages: dependency: transitive description: name: fast_immutable_collections - sha256: c3c73f4f989d3302066e4ec94e6ec73b5dc872592d02194f49f1352d64126b8c + sha256: "95a69b9380483dff49ae2c12c9eb92e2b4e1aeff481a33c2a20883471771598a" url: "https://pub.dev" source: hosted - version: "10.2.4" + version: "11.0.3" ffi: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" file: dependency: transitive description: @@ -214,10 +209,10 @@ packages: dependency: transitive description: name: freezed_annotation - sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "3.0.0" frontend_server_client: dependency: transitive description: @@ -280,10 +275,10 @@ packages: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" json_annotation: dependency: transitive description: @@ -320,10 +315,10 @@ packages: dependency: "direct dev" description: name: lint_hard - sha256: "638d2cce6d3d5499826be71311d18cded797a51351eaa1aee7a35a2f0f9bc46e" + sha256: ffe7058cb49e021d244d67e650a63380445b56643c2849c6929e938246b99058 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "6.0.0" logging: dependency: transitive description: @@ -340,14 +335,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -392,10 +379,10 @@ packages: dependency: transitive description: name: package_config - sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" path: dependency: transitive description: @@ -416,10 +403,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" url: "https://pub.dev" source: hosted - version: "2.2.15" + version: "2.2.16" path_provider_foundation: dependency: transitive description: @@ -496,10 +483,10 @@ packages: dependency: transitive description: name: pub_semver - sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" shelf: dependency: transitive description: @@ -528,10 +515,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" sky_engine: dependency: transitive description: flutter @@ -698,10 +685,10 @@ packages: dependency: transitive description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web_socket: dependency: transitive description: @@ -751,5 +738,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0-0 <4.0.0" - flutter: ">=3.24.0" + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/packages/veilid_support/example/pubspec.yaml b/packages/veilid_support/example/pubspec.yaml index c043a1b..b8333e6 100644 --- a/packages/veilid_support/example/pubspec.yaml +++ b/packages/veilid_support/example/pubspec.yaml @@ -14,11 +14,11 @@ dependencies: path: ../ dev_dependencies: - async_tools: ^0.1.6 + async_tools: ^0.1.9 integration_test: sdk: flutter - lint_hard: ^5.0.0 - test: ^1.25.2 + lint_hard: ^6.0.0 + test: ^1.25.15 veilid_test: path: ../../../../veilid/veilid-flutter/packages/veilid_test diff --git a/packages/veilid_support/pubspec.lock b/packages/veilid_support/pubspec.lock index 447a27c..e3dfcdd 100644 --- a/packages/veilid_support/pubspec.lock +++ b/packages/veilid_support/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.0" async: dependency: transitive description: @@ -36,26 +36,27 @@ packages: async_tools: dependency: "direct main" description: - path: "../../../dart_async_tools" - relative: true - source: path - version: "0.1.7" + name: async_tools + sha256: afd5426e76631172f8ce6a6359b264b092fa9d2a52cd2528100115be9525e067 + url: "https://pub.dev" + source: hosted + version: "0.1.9" bloc: dependency: "direct main" description: name: bloc - sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + sha256: "52c10575f4445c61dd9e0cafcc6356fdd827c4c64dd7945ef3c4105f6b6ac189" url: "https://pub.dev" source: hosted - version: "8.1.4" + version: "9.0.0" bloc_advanced_tools: dependency: "direct main" description: name: bloc_advanced_tools - sha256: d8a680d8a0469456399fb26bae9f7a1d2a1420b5bdf75e204e0fadab9edb0811 + sha256: "7c7f294b425552c2d4831b01ad0d3e1f33f2bdf9acfb7b639caa072781d228cf" url: "https://pub.dev" source: hosted - version: "0.1.8" + version: "0.1.10" boolean_selector: dependency: transitive description: @@ -124,10 +125,10 @@ packages: dependency: transitive description: name: built_value - sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" + sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 url: "https://pub.dev" source: hosted - version: "8.9.3" + version: "8.9.5" change_case: dependency: transitive description: @@ -220,18 +221,18 @@ packages: dependency: "direct main" description: name: fast_immutable_collections - sha256: c3c73f4f989d3302066e4ec94e6ec73b5dc872592d02194f49f1352d64126b8c + sha256: "95a69b9380483dff49ae2c12c9eb92e2b4e1aeff481a33c2a20883471771598a" url: "https://pub.dev" source: hosted - version: "10.2.4" + version: "11.0.3" ffi: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" file: dependency: transitive description: @@ -262,18 +263,18 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" + sha256: "7ed2ddaa47524976d5f2aa91432a79da36a76969edd84170777ac5bea82d797c" url: "https://pub.dev" source: hosted - version: "2.5.8" + version: "3.0.4" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "3.0.0" frontend_server_client: dependency: transitive description: @@ -342,10 +343,10 @@ packages: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" json_annotation: dependency: "direct main" description: @@ -366,10 +367,10 @@ packages: dependency: "direct dev" description: name: lint_hard - sha256: "638d2cce6d3d5499826be71311d18cded797a51351eaa1aee7a35a2f0f9bc46e" + sha256: ffe7058cb49e021d244d67e650a63380445b56643c2849c6929e938246b99058 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "6.0.0" logging: dependency: transitive description: @@ -430,10 +431,10 @@ packages: dependency: transitive description: name: package_config - sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" path: dependency: "direct main" description: @@ -454,10 +455,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" url: "https://pub.dev" source: hosted - version: "2.2.15" + version: "2.2.16" path_provider_foundation: dependency: transitive description: @@ -526,10 +527,10 @@ packages: dependency: transitive description: name: pub_semver - sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" pubspec_parse: dependency: transitive description: @@ -746,10 +747,10 @@ packages: dependency: transitive description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web_socket: dependency: transitive description: @@ -791,5 +792,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0-0 <4.0.0" - flutter: ">=3.24.0" + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/packages/veilid_support/pubspec.yaml b/packages/veilid_support/pubspec.yaml index bcd965d..5aff89e 100644 --- a/packages/veilid_support/pubspec.yaml +++ b/packages/veilid_support/pubspec.yaml @@ -1,40 +1,40 @@ name: veilid_support description: Veilid Support Library -publish_to: 'none' +publish_to: "none" version: 1.0.2+0 environment: - sdk: '>=3.2.0 <4.0.0' + sdk: ">=3.2.0 <4.0.0" dependencies: - async_tools: ^0.1.7 - bloc: ^8.1.4 - bloc_advanced_tools: ^0.1.8 - charcode: ^1.3.1 - collection: ^1.18.0 - equatable: ^2.0.5 - fast_immutable_collections: ^10.2.3 - freezed_annotation: ^2.4.1 + async_tools: ^0.1.9 + bloc: ^9.0.0 + bloc_advanced_tools: ^0.1.10 + charcode: ^1.4.0 + collection: ^1.19.1 + equatable: ^2.0.7 + fast_immutable_collections: ^11.0.3 + freezed_annotation: ^3.0.0 json_annotation: ^4.9.0 loggy: ^2.0.3 - meta: ^1.12.0 + meta: ^1.16.0 - path: ^1.9.0 - path_provider: ^2.1.3 + path: ^1.9.1 + path_provider: ^2.1.5 protobuf: ^3.1.0 veilid: # veilid: ^0.0.1 path: ../../../veilid/veilid-flutter -dependency_overrides: - async_tools: - path: ../../../dart_async_tools -# bloc_advanced_tools: -# path: ../../../bloc_advanced_tools +# dependency_overrides: +# async_tools: +# path: ../../../dart_async_tools +# bloc_advanced_tools: +# path: ../../../bloc_advanced_tools dev_dependencies: - build_runner: ^2.4.10 - freezed: ^2.5.2 - json_serializable: ^6.8.0 - lint_hard: ^5.0.0 - test: ^1.25.2 + build_runner: ^2.4.15 + freezed: ^3.0.4 + json_serializable: ^6.9.4 + lint_hard: ^6.0.0 + test: ^1.25.15 diff --git a/pubspec.lock b/pubspec.lock index b4df49f..ea4a216 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -93,10 +93,10 @@ packages: dependency: "direct main" description: name: async_tools - sha256: a258558160d6adc18612d0c635ce0d18ceabc022f7933ce78ca4806075d79578 + sha256: afd5426e76631172f8ce6a6359b264b092fa9d2a52cd2528100115be9525e067 url: "https://pub.dev" source: hosted - version: "0.1.8" + version: "0.1.9" auto_size_text: dependency: "direct main" description: @@ -149,17 +149,18 @@ packages: dependency: "direct main" description: name: bloc - sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + sha256: "52c10575f4445c61dd9e0cafcc6356fdd827c4c64dd7945ef3c4105f6b6ac189" url: "https://pub.dev" source: hosted - version: "8.1.4" + version: "9.0.0" bloc_advanced_tools: dependency: "direct main" description: - path: "../bloc_advanced_tools" - relative: true - source: path - version: "0.1.9" + name: bloc_advanced_tools + sha256: "7c7f294b425552c2d4831b01ad0d3e1f33f2bdf9acfb7b639caa072781d228cf" + url: "https://pub.dev" + source: hosted + version: "0.1.10" blurry_modal_progress_hud: dependency: "direct main" description: @@ -284,10 +285,10 @@ packages: dependency: transitive description: name: camera_avfoundation - sha256: "3057ada0b30402e3a9b6dffec365c9736a36edbf04abaecc67c4309eadc86b49" + sha256: ba48b65a3a97004276ede882e6b838d9667642ff462c95a8bb57ca8a82b6bd25 url: "https://pub.dev" source: hosted - version: "0.9.18+9" + version: "0.9.18+11" camera_platform_interface: dependency: transitive description: @@ -476,10 +477,10 @@ packages: dependency: "direct main" description: name: fast_immutable_collections - sha256: c3c73f4f989d3302066e4ec94e6ec73b5dc872592d02194f49f1352d64126b8c + sha256: "95a69b9380483dff49ae2c12c9eb92e2b4e1aeff481a33c2a20883471771598a" url: "https://pub.dev" source: hosted - version: "10.2.4" + version: "11.0.3" ffi: dependency: transitive description: @@ -529,10 +530,10 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a + sha256: "1046d719fbdf230330d3443187cc33cc11963d15c9089f6cc56faa42a4c5f0cc" url: "https://pub.dev" source: hosted - version: "8.1.6" + version: "9.1.0" flutter_cache_manager: dependency: transitive description: @@ -562,18 +563,18 @@ packages: dependency: "direct main" description: name: flutter_form_builder - sha256: "375da52998c72f80dec9187bd93afa7ab202b89d5d066699368ff96d39fd4876" + sha256: aa3901466c70b69ae6c7f3d03fcbccaec5fde179d3fded0b10203144b546ad28 url: "https://pub.dev" source: hosted - version: "9.7.0" + version: "10.0.1" flutter_hooks: dependency: "direct main" description: name: flutter_hooks - sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 + sha256: b772e710d16d7a20c0740c4f855095026b31c7eb5ba3ab67d2bd52021cd9461d url: "https://pub.dev" source: hosted - version: "0.20.5" + version: "0.21.2" flutter_link_previewer: dependency: transitive description: @@ -692,18 +693,18 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" + sha256: "7ed2ddaa47524976d5f2aa91432a79da36a76969edd84170777ac5bea82d797c" url: "https://pub.dev" source: hosted - version: "2.5.8" + version: "3.0.4" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "3.0.0" frontend_server_client: dependency: transitive description: @@ -752,14 +753,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" - hive: - dependency: transitive - description: - name: hive - sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" - url: "https://pub.dev" - source: hosted - version: "2.2.3" html: dependency: transitive description: @@ -792,14 +785,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" - hydrated_bloc: - dependency: "direct main" - description: - name: hydrated_bloc - sha256: af35b357739fe41728df10bec03aad422cdc725a1e702e03af9d2a41ea05160c - url: "https://pub.dev" - source: hosted - version: "9.1.5" icons_launcher: dependency: "direct dev" description: @@ -876,10 +861,10 @@ packages: dependency: "direct dev" description: name: lint_hard - sha256: "638d2cce6d3d5499826be71311d18cded797a51351eaa1aee7a35a2f0f9bc46e" + sha256: ffe7058cb49e021d244d67e650a63380445b56643c2849c6929e938246b99058 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "6.0.0" logging: dependency: transitive description: @@ -1192,14 +1177,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + qr_code_dart_decoder: + dependency: transitive + description: + name: qr_code_dart_decoder + sha256: "6da7eda27726d504bed3c30eabf78ddca3eb9265e1c8dc49b30ef5974b9c267f" + url: "https://pub.dev" + source: hosted + version: "0.0.5" qr_code_dart_scan: dependency: "direct main" description: name: qr_code_dart_scan - sha256: a21340c4a2ca14e2e114915940fcad166f15c1a065fed8b4fede4a4aba5bc4ff + sha256: bc4fc6f400b4350c6946d123c7871e156459703a61f8fa57d7144df9bbb46610 url: "https://pub.dev" source: hosted - version: "0.9.11" + version: "0.10.0" qr_flutter: dependency: "direct main" description: @@ -1418,10 +1411,10 @@ packages: dependency: "direct main" description: name: sliver_expandable - sha256: ae20eb848bd0ba9dd704732ad654438ac5a5bea2b023fa3cf80a086166d96d97 + sha256: "046d8912ebd072bf9d8e8161e50a4669c520f691fce8bfcbae4ada6982b18ba3" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" sliver_fill_remaining_box_adapter: dependency: "direct main" description: @@ -1895,4 +1888,4 @@ packages: version: "1.1.2" sdks: dart: ">=3.7.0 <4.0.0" - flutter: ">=3.27.0" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index b909a16..379d2f2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,13 +15,13 @@ dependencies: animated_theme_switcher: ^2.0.10 ansicolor: ^2.0.3 archive: ^4.0.4 - async_tools: ^0.1.8 + async_tools: ^0.1.9 auto_size_text: ^3.0.0 awesome_extensions: ^2.0.21 badges: ^3.1.2 basic_utils: ^5.8.2 - bloc: ^8.1.4 - bloc_advanced_tools: ^0.1.9 + bloc: ^9.0.0 + bloc_advanced_tools: ^0.1.10 blurry_modal_progress_hud: ^1.1.1 change_case: ^2.2.0 charcode: ^1.4.0 @@ -30,20 +30,20 @@ dependencies: cupertino_icons: ^1.0.8 equatable: ^2.0.7 expansion_tile_group: ^2.2.0 - fast_immutable_collections: ^10.2.4 + fast_immutable_collections: ^11.0.3 file_saver: ^0.2.14 fixnum: ^1.1.1 flutter: sdk: flutter flutter_animate: ^4.5.2 - flutter_bloc: ^8.1.6 + flutter_bloc: ^9.1.0 flutter_chat_types: ^3.6.2 flutter_chat_ui: git: url: https://gitlab.com/veilid/flutter-chat-ui.git ref: main - flutter_form_builder: ^9.7.0 - flutter_hooks: ^0.20.5 + flutter_form_builder: ^10.0.1 + flutter_hooks: ^0.21.2 flutter_localizations: sdk: flutter flutter_native_splash: ^2.4.5 @@ -54,9 +54,8 @@ dependencies: flutter_translate: ^4.1.0 flutter_zoom_drawer: ^3.2.0 form_builder_validators: ^11.1.2 - freezed_annotation: ^2.4.4 + freezed_annotation: ^3.0.0 go_router: ^14.8.1 - hydrated_bloc: ^9.1.5 image: ^4.5.3 intl: ^0.19.0 json_annotation: ^4.9.0 @@ -73,7 +72,7 @@ dependencies: printing: ^5.14.2 protobuf: ^3.1.0 provider: ^6.1.2 - qr_code_dart_scan: ^0.9.11 + qr_code_dart_scan: ^0.10.0 qr_flutter: ^4.1.0 radix_colors: ^1.0.4 reorderable_grid: ^1.0.10 @@ -87,7 +86,7 @@ dependencies: share_plus: ^10.1.4 shared_preferences: ^2.5.2 signal_strength_indicator: ^0.4.1 - sliver_expandable: ^1.1.1 + sliver_expandable: ^1.1.2 sliver_fill_remaining_box_adapter: ^1.0.0 sliver_tools: ^0.2.12 sorted_list: @@ -111,22 +110,22 @@ dependencies: xterm: ^4.0.0 zxing2: ^0.2.3 -dependency_overrides: +# dependency_overrides: # async_tools: # path: ../dart_async_tools - bloc_advanced_tools: - path: ../bloc_advanced_tools -# searchable_listview: -# path: ../Searchable-Listview +# bloc_advanced_tools: +# path: ../bloc_advanced_tools +# searchable_listview: +# path: ../Searchable-Listview # flutter_chat_ui: -# path: ../flutter_chat_ui +# path: ../flutter_chat_ui dev_dependencies: build_runner: ^2.4.15 - freezed: ^2.5.8 + freezed: ^3.0.4 icons_launcher: ^3.0.1 json_serializable: ^6.9.4 - lint_hard: ^5.0.0 + lint_hard: ^6.0.0 flutter_native_splash: color: "#8588D0" From d6b1c2090609545297adf3fb1bec18f7e09b0444 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 22 Mar 2025 21:43:37 -0400 Subject: [PATCH 42/93] debugging work --- lib/account_manager/models/account_info.dart | 9 +- .../models/local_account/local_account.dart | 2 +- .../local_account/local_account.freezed.dart | 488 ++++++----- .../models/local_account/local_account.g.dart | 6 +- .../per_account_collection_state.dart | 22 +- .../per_account_collection_state.freezed.dart | 665 +++++++-------- .../models/user_login/user_login.dart | 2 +- .../models/user_login/user_login.freezed.dart | 398 +++++---- .../models/user_login/user_login.g.dart | 5 +- lib/chat/cubits/chat_component_cubit.dart | 10 +- lib/chat/models/chat_component_state.dart | 2 +- .../models/chat_component_state.freezed.dart | 361 ++++----- lib/chat/models/message_state.dart | 2 +- lib/chat/models/message_state.freezed.dart | 257 +++--- lib/chat/models/message_state.g.dart | 6 +- lib/chat/models/window_state.dart | 2 +- lib/chat/models/window_state.freezed.dart | 264 +++--- lib/chat_list/cubits/chat_list_cubit.dart | 2 +- lib/chat_list/views/chat_list_widget.dart | 4 +- .../cubits/contact_invitation_list_cubit.dart | 2 +- .../waiting_invitations_bloc_map_cubit.dart | 2 +- .../models/notifications_preference.dart | 2 +- .../notifications_preference.freezed.dart | 565 +++++++------ .../models/notifications_preference.g.dart | 8 +- .../models/notifications_state.dart | 4 +- .../models/notifications_state.freezed.dart | 452 ++++++----- lib/proto/proto.dart | 292 +++++++ lib/proto/veilidchat.pb.dart | 20 +- lib/proto/veilidchat.pbjson.dart | 35 +- lib/proto/veilidchat.proto | 1 + lib/router/cubits/router_cubit.dart | 2 +- lib/router/cubits/router_cubit.freezed.dart | 241 +++--- lib/router/cubits/router_cubit.g.dart | 5 +- lib/settings/models/preferences.dart | 4 +- lib/settings/models/preferences.freezed.dart | 761 +++++++++--------- lib/settings/models/preferences.g.dart | 12 +- lib/theme/models/theme_preference.dart | 2 +- .../models/theme_preference.freezed.dart | 356 ++++---- lib/theme/models/theme_preference.g.dart | 8 +- lib/tools/loggy.dart | 6 + lib/tools/state_logger.dart | 10 +- .../models/processor_connection_state.dart | 2 +- .../processor_connection_state.freezed.dart | 317 ++++---- .../lib/dht_support/proto/proto.dart | 42 + .../src/dht_log/dht_log_cubit.dart | 8 + .../src/dht_log/dht_log_spine.dart | 5 +- .../src/dht_record/dht_record.dart | 4 +- .../src/dht_record/dht_record_pool.dart | 45 +- .../dht_record/dht_record_pool.freezed.dart | 623 +++++++------- .../src/dht_record/dht_record_pool.g.dart | 16 +- .../src/dht_record/extensions.dart | 57 ++ .../dht_short_array_cubit.dart | 23 +- .../dht_short_array/dht_short_array_head.dart | 10 +- .../dht_short_array_write.dart | 4 +- .../identity_support/account_record_info.dart | 2 +- .../account_record_info.freezed.dart | 277 +++---- .../account_record_info.g.dart | 8 +- .../lib/identity_support/identity.dart | 2 +- .../identity_support/identity.freezed.dart | 231 +++--- .../lib/identity_support/identity.g.dart | 6 +- .../identity_support/identity_instance.dart | 2 +- .../identity_instance.freezed.dart | 284 +++---- .../identity_support/identity_instance.g.dart | 8 +- .../lib/identity_support/super_identity.dart | 2 +- .../super_identity.freezed.dart | 330 ++++---- .../identity_support/super_identity.g.dart | 6 +- packages/veilid_support/lib/proto/proto.dart | 24 + .../veilid_support/lib/src/dynamic_debug.dart | 130 +++ .../veilid_support/lib/veilid_support.dart | 3 +- packages/veilid_support/pubspec.lock | 2 +- packages/veilid_support/pubspec.yaml | 1 + 71 files changed, 4155 insertions(+), 3616 deletions(-) create mode 100644 packages/veilid_support/lib/dht_support/src/dht_record/extensions.dart create mode 100644 packages/veilid_support/lib/src/dynamic_debug.dart diff --git a/lib/account_manager/models/account_info.dart b/lib/account_manager/models/account_info.dart index 12ed5e1..8f57add 100644 --- a/lib/account_manager/models/account_info.dart +++ b/lib/account_manager/models/account_info.dart @@ -13,7 +13,7 @@ enum AccountInfoStatus { } @immutable -class AccountInfo extends Equatable { +class AccountInfo extends Equatable implements ToDebugMap { const AccountInfo({ required this.status, required this.localAccount, @@ -30,6 +30,13 @@ class AccountInfo extends Equatable { localAccount, userLogin, ]; + + @override + Map toDebugMap() => { + 'status': status, + 'localAccount': localAccount, + 'userLogin': userLogin, + }; } extension AccountInfoExt on AccountInfo { diff --git a/lib/account_manager/models/local_account/local_account.dart b/lib/account_manager/models/local_account/local_account.dart index 76070ae..1ec6d22 100644 --- a/lib/account_manager/models/local_account/local_account.dart +++ b/lib/account_manager/models/local_account/local_account.dart @@ -16,7 +16,7 @@ part 'local_account.freezed.dart'; // This is the root of the account information tree for VeilidChat // @freezed -class LocalAccount with _$LocalAccount { +sealed class LocalAccount with _$LocalAccount { const factory LocalAccount({ // The super identity key record for the account, // containing the publicKey in the currentIdentity diff --git a/lib/account_manager/models/local_account/local_account.freezed.dart b/lib/account_manager/models/local_account/local_account.freezed.dart index effc69a..e2c3c55 100644 --- a/lib/account_manager/models/local_account/local_account.freezed.dart +++ b/lib/account_manager/models/local_account/local_account.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,238 +10,43 @@ part of 'local_account.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -LocalAccount _$LocalAccountFromJson(Map json) { - return _LocalAccount.fromJson(json); -} - /// @nodoc mixin _$LocalAccount { // The super identity key record for the account, // containing the publicKey in the currentIdentity - SuperIdentity get superIdentity => - throw _privateConstructorUsedError; // The encrypted currentIdentity secret that goes with + SuperIdentity + get superIdentity; // The encrypted currentIdentity secret that goes with // the identityPublicKey with appended salt @Uint8ListJsonConverter() - Uint8List get identitySecretBytes => - throw _privateConstructorUsedError; // The kind of encryption input used on the account - EncryptionKeyType get encryptionKeyType => - throw _privateConstructorUsedError; // If account is not hidden, password can be retrieved via - bool get biometricsEnabled => - throw _privateConstructorUsedError; // Keep account hidden unless account password is entered + Uint8List + get identitySecretBytes; // The kind of encryption input used on the account + EncryptionKeyType + get encryptionKeyType; // If account is not hidden, password can be retrieved via + bool + get biometricsEnabled; // Keep account hidden unless account password is entered // (tries all hidden accounts with auth method (no biometrics)) - bool get hiddenAccount => - throw _privateConstructorUsedError; // Display name for account until it is unlocked - String get name => throw _privateConstructorUsedError; - - /// Serializes this LocalAccount to a JSON map. - Map toJson() => throw _privateConstructorUsedError; + bool get hiddenAccount; // Display name for account until it is unlocked + String get name; /// Create a copy of LocalAccount /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $LocalAccountCopyWith get copyWith => - throw _privateConstructorUsedError; -} + _$LocalAccountCopyWithImpl( + this as LocalAccount, _$identity); -/// @nodoc -abstract class $LocalAccountCopyWith<$Res> { - factory $LocalAccountCopyWith( - LocalAccount value, $Res Function(LocalAccount) then) = - _$LocalAccountCopyWithImpl<$Res, LocalAccount>; - @useResult - $Res call( - {SuperIdentity superIdentity, - @Uint8ListJsonConverter() Uint8List identitySecretBytes, - EncryptionKeyType encryptionKeyType, - bool biometricsEnabled, - bool hiddenAccount, - String name}); - - $SuperIdentityCopyWith<$Res> get superIdentity; -} - -/// @nodoc -class _$LocalAccountCopyWithImpl<$Res, $Val extends LocalAccount> - implements $LocalAccountCopyWith<$Res> { - _$LocalAccountCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of LocalAccount - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? superIdentity = null, - Object? identitySecretBytes = null, - Object? encryptionKeyType = null, - Object? biometricsEnabled = null, - Object? hiddenAccount = null, - Object? name = null, - }) { - return _then(_value.copyWith( - superIdentity: null == superIdentity - ? _value.superIdentity - : superIdentity // ignore: cast_nullable_to_non_nullable - as SuperIdentity, - identitySecretBytes: null == identitySecretBytes - ? _value.identitySecretBytes - : identitySecretBytes // ignore: cast_nullable_to_non_nullable - as Uint8List, - encryptionKeyType: null == encryptionKeyType - ? _value.encryptionKeyType - : encryptionKeyType // ignore: cast_nullable_to_non_nullable - as EncryptionKeyType, - biometricsEnabled: null == biometricsEnabled - ? _value.biometricsEnabled - : biometricsEnabled // ignore: cast_nullable_to_non_nullable - as bool, - hiddenAccount: null == hiddenAccount - ? _value.hiddenAccount - : hiddenAccount // ignore: cast_nullable_to_non_nullable - as bool, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - ) as $Val); - } - - /// Create a copy of LocalAccount - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $SuperIdentityCopyWith<$Res> get superIdentity { - return $SuperIdentityCopyWith<$Res>(_value.superIdentity, (value) { - return _then(_value.copyWith(superIdentity: value) as $Val); - }); - } -} - -/// @nodoc -abstract class _$$LocalAccountImplCopyWith<$Res> - implements $LocalAccountCopyWith<$Res> { - factory _$$LocalAccountImplCopyWith( - _$LocalAccountImpl value, $Res Function(_$LocalAccountImpl) then) = - __$$LocalAccountImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {SuperIdentity superIdentity, - @Uint8ListJsonConverter() Uint8List identitySecretBytes, - EncryptionKeyType encryptionKeyType, - bool biometricsEnabled, - bool hiddenAccount, - String name}); - - @override - $SuperIdentityCopyWith<$Res> get superIdentity; -} - -/// @nodoc -class __$$LocalAccountImplCopyWithImpl<$Res> - extends _$LocalAccountCopyWithImpl<$Res, _$LocalAccountImpl> - implements _$$LocalAccountImplCopyWith<$Res> { - __$$LocalAccountImplCopyWithImpl( - _$LocalAccountImpl _value, $Res Function(_$LocalAccountImpl) _then) - : super(_value, _then); - - /// Create a copy of LocalAccount - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? superIdentity = null, - Object? identitySecretBytes = null, - Object? encryptionKeyType = null, - Object? biometricsEnabled = null, - Object? hiddenAccount = null, - Object? name = null, - }) { - return _then(_$LocalAccountImpl( - superIdentity: null == superIdentity - ? _value.superIdentity - : superIdentity // ignore: cast_nullable_to_non_nullable - as SuperIdentity, - identitySecretBytes: null == identitySecretBytes - ? _value.identitySecretBytes - : identitySecretBytes // ignore: cast_nullable_to_non_nullable - as Uint8List, - encryptionKeyType: null == encryptionKeyType - ? _value.encryptionKeyType - : encryptionKeyType // ignore: cast_nullable_to_non_nullable - as EncryptionKeyType, - biometricsEnabled: null == biometricsEnabled - ? _value.biometricsEnabled - : biometricsEnabled // ignore: cast_nullable_to_non_nullable - as bool, - hiddenAccount: null == hiddenAccount - ? _value.hiddenAccount - : hiddenAccount // ignore: cast_nullable_to_non_nullable - as bool, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$LocalAccountImpl implements _LocalAccount { - const _$LocalAccountImpl( - {required this.superIdentity, - @Uint8ListJsonConverter() required this.identitySecretBytes, - required this.encryptionKeyType, - required this.biometricsEnabled, - required this.hiddenAccount, - required this.name}); - - factory _$LocalAccountImpl.fromJson(Map json) => - _$$LocalAccountImplFromJson(json); - -// The super identity key record for the account, -// containing the publicKey in the currentIdentity - @override - final SuperIdentity superIdentity; -// The encrypted currentIdentity secret that goes with -// the identityPublicKey with appended salt - @override - @Uint8ListJsonConverter() - final Uint8List identitySecretBytes; -// The kind of encryption input used on the account - @override - final EncryptionKeyType encryptionKeyType; -// If account is not hidden, password can be retrieved via - @override - final bool biometricsEnabled; -// Keep account hidden unless account password is entered -// (tries all hidden accounts with auth method (no biometrics)) - @override - final bool hiddenAccount; -// Display name for account until it is unlocked - @override - final String name; - - @override - String toString() { - return 'LocalAccount(superIdentity: $superIdentity, identitySecretBytes: $identitySecretBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount, name: $name)'; - } + /// Serializes this LocalAccount to a JSON map. + Map toJson(); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$LocalAccountImpl && + other is LocalAccount && (identical(other.superIdentity, superIdentity) || other.superIdentity == superIdentity) && const DeepCollectionEquality() @@ -265,60 +71,250 @@ class _$LocalAccountImpl implements _LocalAccount { hiddenAccount, name); - /// Create a copy of LocalAccount - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$LocalAccountImplCopyWith<_$LocalAccountImpl> get copyWith => - __$$LocalAccountImplCopyWithImpl<_$LocalAccountImpl>(this, _$identity); - - @override - Map toJson() { - return _$$LocalAccountImplToJson( - this, - ); + String toString() { + return 'LocalAccount(superIdentity: $superIdentity, identitySecretBytes: $identitySecretBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount, name: $name)'; } } -abstract class _LocalAccount implements LocalAccount { - const factory _LocalAccount( - {required final SuperIdentity superIdentity, - @Uint8ListJsonConverter() required final Uint8List identitySecretBytes, - required final EncryptionKeyType encryptionKeyType, - required final bool biometricsEnabled, - required final bool hiddenAccount, - required final String name}) = _$LocalAccountImpl; +/// @nodoc +abstract mixin class $LocalAccountCopyWith<$Res> { + factory $LocalAccountCopyWith( + LocalAccount value, $Res Function(LocalAccount) _then) = + _$LocalAccountCopyWithImpl; + @useResult + $Res call( + {SuperIdentity superIdentity, + @Uint8ListJsonConverter() Uint8List identitySecretBytes, + EncryptionKeyType encryptionKeyType, + bool biometricsEnabled, + bool hiddenAccount, + String name}); - factory _LocalAccount.fromJson(Map json) = - _$LocalAccountImpl.fromJson; + $SuperIdentityCopyWith<$Res> get superIdentity; +} + +/// @nodoc +class _$LocalAccountCopyWithImpl<$Res> implements $LocalAccountCopyWith<$Res> { + _$LocalAccountCopyWithImpl(this._self, this._then); + + final LocalAccount _self; + final $Res Function(LocalAccount) _then; + + /// Create a copy of LocalAccount + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? superIdentity = null, + Object? identitySecretBytes = null, + Object? encryptionKeyType = null, + Object? biometricsEnabled = null, + Object? hiddenAccount = null, + Object? name = null, + }) { + return _then(_self.copyWith( + superIdentity: null == superIdentity + ? _self.superIdentity + : superIdentity // ignore: cast_nullable_to_non_nullable + as SuperIdentity, + identitySecretBytes: null == identitySecretBytes + ? _self.identitySecretBytes + : identitySecretBytes // ignore: cast_nullable_to_non_nullable + as Uint8List, + encryptionKeyType: null == encryptionKeyType + ? _self.encryptionKeyType + : encryptionKeyType // ignore: cast_nullable_to_non_nullable + as EncryptionKeyType, + biometricsEnabled: null == biometricsEnabled + ? _self.biometricsEnabled + : biometricsEnabled // ignore: cast_nullable_to_non_nullable + as bool, + hiddenAccount: null == hiddenAccount + ? _self.hiddenAccount + : hiddenAccount // ignore: cast_nullable_to_non_nullable + as bool, + name: null == name + ? _self.name + : name // ignore: cast_nullable_to_non_nullable + as String, + )); + } + + /// Create a copy of LocalAccount + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SuperIdentityCopyWith<$Res> get superIdentity { + return $SuperIdentityCopyWith<$Res>(_self.superIdentity, (value) { + return _then(_self.copyWith(superIdentity: value)); + }); + } +} + +/// @nodoc +@JsonSerializable() +class _LocalAccount implements LocalAccount { + const _LocalAccount( + {required this.superIdentity, + @Uint8ListJsonConverter() required this.identitySecretBytes, + required this.encryptionKeyType, + required this.biometricsEnabled, + required this.hiddenAccount, + required this.name}); + factory _LocalAccount.fromJson(Map json) => + _$LocalAccountFromJson(json); // The super identity key record for the account, // containing the publicKey in the currentIdentity @override - SuperIdentity - get superIdentity; // The encrypted currentIdentity secret that goes with + final SuperIdentity superIdentity; +// The encrypted currentIdentity secret that goes with // the identityPublicKey with appended salt @override @Uint8ListJsonConverter() - Uint8List - get identitySecretBytes; // The kind of encryption input used on the account + final Uint8List identitySecretBytes; +// The kind of encryption input used on the account @override - EncryptionKeyType - get encryptionKeyType; // If account is not hidden, password can be retrieved via + final EncryptionKeyType encryptionKeyType; +// If account is not hidden, password can be retrieved via @override - bool - get biometricsEnabled; // Keep account hidden unless account password is entered + final bool biometricsEnabled; +// Keep account hidden unless account password is entered // (tries all hidden accounts with auth method (no biometrics)) @override - bool get hiddenAccount; // Display name for account until it is unlocked + final bool hiddenAccount; +// Display name for account until it is unlocked @override - String get name; + final String name; /// Create a copy of LocalAccount /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$LocalAccountImplCopyWith<_$LocalAccountImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + _$LocalAccountCopyWith<_LocalAccount> get copyWith => + __$LocalAccountCopyWithImpl<_LocalAccount>(this, _$identity); + + @override + Map toJson() { + return _$LocalAccountToJson( + this, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _LocalAccount && + (identical(other.superIdentity, superIdentity) || + other.superIdentity == superIdentity) && + const DeepCollectionEquality() + .equals(other.identitySecretBytes, identitySecretBytes) && + (identical(other.encryptionKeyType, encryptionKeyType) || + other.encryptionKeyType == encryptionKeyType) && + (identical(other.biometricsEnabled, biometricsEnabled) || + other.biometricsEnabled == biometricsEnabled) && + (identical(other.hiddenAccount, hiddenAccount) || + other.hiddenAccount == hiddenAccount) && + (identical(other.name, name) || other.name == name)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + superIdentity, + const DeepCollectionEquality().hash(identitySecretBytes), + encryptionKeyType, + biometricsEnabled, + hiddenAccount, + name); + + @override + String toString() { + return 'LocalAccount(superIdentity: $superIdentity, identitySecretBytes: $identitySecretBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount, name: $name)'; + } } + +/// @nodoc +abstract mixin class _$LocalAccountCopyWith<$Res> + implements $LocalAccountCopyWith<$Res> { + factory _$LocalAccountCopyWith( + _LocalAccount value, $Res Function(_LocalAccount) _then) = + __$LocalAccountCopyWithImpl; + @override + @useResult + $Res call( + {SuperIdentity superIdentity, + @Uint8ListJsonConverter() Uint8List identitySecretBytes, + EncryptionKeyType encryptionKeyType, + bool biometricsEnabled, + bool hiddenAccount, + String name}); + + @override + $SuperIdentityCopyWith<$Res> get superIdentity; +} + +/// @nodoc +class __$LocalAccountCopyWithImpl<$Res> + implements _$LocalAccountCopyWith<$Res> { + __$LocalAccountCopyWithImpl(this._self, this._then); + + final _LocalAccount _self; + final $Res Function(_LocalAccount) _then; + + /// Create a copy of LocalAccount + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? superIdentity = null, + Object? identitySecretBytes = null, + Object? encryptionKeyType = null, + Object? biometricsEnabled = null, + Object? hiddenAccount = null, + Object? name = null, + }) { + return _then(_LocalAccount( + superIdentity: null == superIdentity + ? _self.superIdentity + : superIdentity // ignore: cast_nullable_to_non_nullable + as SuperIdentity, + identitySecretBytes: null == identitySecretBytes + ? _self.identitySecretBytes + : identitySecretBytes // ignore: cast_nullable_to_non_nullable + as Uint8List, + encryptionKeyType: null == encryptionKeyType + ? _self.encryptionKeyType + : encryptionKeyType // ignore: cast_nullable_to_non_nullable + as EncryptionKeyType, + biometricsEnabled: null == biometricsEnabled + ? _self.biometricsEnabled + : biometricsEnabled // ignore: cast_nullable_to_non_nullable + as bool, + hiddenAccount: null == hiddenAccount + ? _self.hiddenAccount + : hiddenAccount // ignore: cast_nullable_to_non_nullable + as bool, + name: null == name + ? _self.name + : name // ignore: cast_nullable_to_non_nullable + as String, + )); + } + + /// Create a copy of LocalAccount + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SuperIdentityCopyWith<$Res> get superIdentity { + return $SuperIdentityCopyWith<$Res>(_self.superIdentity, (value) { + return _then(_self.copyWith(superIdentity: value)); + }); + } +} + +// dart format on diff --git a/lib/account_manager/models/local_account/local_account.g.dart b/lib/account_manager/models/local_account/local_account.g.dart index b60c226..40d55e5 100644 --- a/lib/account_manager/models/local_account/local_account.g.dart +++ b/lib/account_manager/models/local_account/local_account.g.dart @@ -6,8 +6,8 @@ part of 'local_account.dart'; // JsonSerializableGenerator // ************************************************************************** -_$LocalAccountImpl _$$LocalAccountImplFromJson(Map json) => - _$LocalAccountImpl( +_LocalAccount _$LocalAccountFromJson(Map json) => + _LocalAccount( superIdentity: SuperIdentity.fromJson(json['super_identity']), identitySecretBytes: const Uint8ListJsonConverter() .fromJson(json['identity_secret_bytes']), @@ -18,7 +18,7 @@ _$LocalAccountImpl _$$LocalAccountImplFromJson(Map json) => name: json['name'] as String, ); -Map _$$LocalAccountImplToJson(_$LocalAccountImpl instance) => +Map _$LocalAccountToJson(_LocalAccount instance) => { 'super_identity': instance.superIdentity.toJson(), 'identity_secret_bytes': diff --git a/lib/account_manager/models/per_account_collection_state/per_account_collection_state.dart b/lib/account_manager/models/per_account_collection_state/per_account_collection_state.dart index 9e0a6f0..24a394c 100644 --- a/lib/account_manager/models/per_account_collection_state/per_account_collection_state.dart +++ b/lib/account_manager/models/per_account_collection_state/per_account_collection_state.dart @@ -2,6 +2,7 @@ import 'package:async_tools/async_tools.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:veilid_support/veilid_support.dart'; import '../../../chat/chat.dart'; import '../../../chat_list/chat_list.dart'; @@ -14,7 +15,9 @@ import '../../account_manager.dart'; part 'per_account_collection_state.freezed.dart'; @freezed -class PerAccountCollectionState with _$PerAccountCollectionState { +sealed class PerAccountCollectionState + with _$PerAccountCollectionState + implements ToDebugMap { const factory PerAccountCollectionState({ required AccountInfo accountInfo, required AsyncValue? avAccountRecordState, @@ -29,6 +32,23 @@ class PerAccountCollectionState with _$PerAccountCollectionState { required ActiveSingleContactChatBlocMapCubit? activeSingleContactChatBlocMapCubit, }) = _PerAccountCollectionState; + const PerAccountCollectionState._(); + + @override + Map toDebugMap() => { + 'accountInfo': accountInfo, + 'avAccountRecordState': avAccountRecordState, + 'accountInfoCubit': accountInfoCubit, + 'accountRecordCubit': accountRecordCubit, + 'contactInvitationListCubit': contactInvitationListCubit, + 'contactListCubit': contactListCubit, + 'waitingInvitationsBlocMapCubit': waitingInvitationsBlocMapCubit, + 'activeChatCubit': activeChatCubit, + 'chatListCubit': chatListCubit, + 'activeConversationsBlocMapCubit': activeConversationsBlocMapCubit, + 'activeSingleContactChatBlocMapCubit': + activeSingleContactChatBlocMapCubit, + }; } extension PerAccountCollectionStateExt on PerAccountCollectionState { diff --git a/lib/account_manager/models/per_account_collection_state/per_account_collection_state.freezed.dart b/lib/account_manager/models/per_account_collection_state/per_account_collection_state.freezed.dart index 1aa0c7e..4c2e219 100644 --- a/lib/account_manager/models/per_account_collection_state/per_account_collection_state.freezed.dart +++ b/lib/account_manager/models/per_account_collection_state/per_account_collection_state.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,311 +10,36 @@ part of 'per_account_collection_state.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - /// @nodoc mixin _$PerAccountCollectionState { - AccountInfo get accountInfo => throw _privateConstructorUsedError; - AsyncValue? get avAccountRecordState => - throw _privateConstructorUsedError; - AccountInfoCubit? get accountInfoCubit => throw _privateConstructorUsedError; - AccountRecordCubit? get accountRecordCubit => - throw _privateConstructorUsedError; - ContactInvitationListCubit? get contactInvitationListCubit => - throw _privateConstructorUsedError; - ContactListCubit? get contactListCubit => throw _privateConstructorUsedError; - WaitingInvitationsBlocMapCubit? get waitingInvitationsBlocMapCubit => - throw _privateConstructorUsedError; - ActiveChatCubit? get activeChatCubit => throw _privateConstructorUsedError; - ChatListCubit? get chatListCubit => throw _privateConstructorUsedError; - ActiveConversationsBlocMapCubit? get activeConversationsBlocMapCubit => - throw _privateConstructorUsedError; - ActiveSingleContactChatBlocMapCubit? - get activeSingleContactChatBlocMapCubit => - throw _privateConstructorUsedError; + AccountInfo get accountInfo; + AsyncValue? get avAccountRecordState; + AccountInfoCubit? get accountInfoCubit; + AccountRecordCubit? get accountRecordCubit; + ContactInvitationListCubit? get contactInvitationListCubit; + ContactListCubit? get contactListCubit; + WaitingInvitationsBlocMapCubit? get waitingInvitationsBlocMapCubit; + ActiveChatCubit? get activeChatCubit; + ChatListCubit? get chatListCubit; + ActiveConversationsBlocMapCubit? get activeConversationsBlocMapCubit; + ActiveSingleContactChatBlocMapCubit? get activeSingleContactChatBlocMapCubit; /// Create a copy of PerAccountCollectionState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $PerAccountCollectionStateCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $PerAccountCollectionStateCopyWith<$Res> { - factory $PerAccountCollectionStateCopyWith(PerAccountCollectionState value, - $Res Function(PerAccountCollectionState) then) = - _$PerAccountCollectionStateCopyWithImpl<$Res, PerAccountCollectionState>; - @useResult - $Res call( - {AccountInfo accountInfo, - AsyncValue? avAccountRecordState, - AccountInfoCubit? accountInfoCubit, - AccountRecordCubit? accountRecordCubit, - ContactInvitationListCubit? contactInvitationListCubit, - ContactListCubit? contactListCubit, - WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit, - ActiveChatCubit? activeChatCubit, - ChatListCubit? chatListCubit, - ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit, - ActiveSingleContactChatBlocMapCubit? - activeSingleContactChatBlocMapCubit}); - - $AsyncValueCopyWith? get avAccountRecordState; -} - -/// @nodoc -class _$PerAccountCollectionStateCopyWithImpl<$Res, - $Val extends PerAccountCollectionState> - implements $PerAccountCollectionStateCopyWith<$Res> { - _$PerAccountCollectionStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of PerAccountCollectionState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? accountInfo = null, - Object? avAccountRecordState = freezed, - Object? accountInfoCubit = freezed, - Object? accountRecordCubit = freezed, - Object? contactInvitationListCubit = freezed, - Object? contactListCubit = freezed, - Object? waitingInvitationsBlocMapCubit = freezed, - Object? activeChatCubit = freezed, - Object? chatListCubit = freezed, - Object? activeConversationsBlocMapCubit = freezed, - Object? activeSingleContactChatBlocMapCubit = freezed, - }) { - return _then(_value.copyWith( - accountInfo: null == accountInfo - ? _value.accountInfo - : accountInfo // ignore: cast_nullable_to_non_nullable - as AccountInfo, - avAccountRecordState: freezed == avAccountRecordState - ? _value.avAccountRecordState - : avAccountRecordState // ignore: cast_nullable_to_non_nullable - as AsyncValue?, - accountInfoCubit: freezed == accountInfoCubit - ? _value.accountInfoCubit - : accountInfoCubit // ignore: cast_nullable_to_non_nullable - as AccountInfoCubit?, - accountRecordCubit: freezed == accountRecordCubit - ? _value.accountRecordCubit - : accountRecordCubit // ignore: cast_nullable_to_non_nullable - as AccountRecordCubit?, - contactInvitationListCubit: freezed == contactInvitationListCubit - ? _value.contactInvitationListCubit - : contactInvitationListCubit // ignore: cast_nullable_to_non_nullable - as ContactInvitationListCubit?, - contactListCubit: freezed == contactListCubit - ? _value.contactListCubit - : contactListCubit // ignore: cast_nullable_to_non_nullable - as ContactListCubit?, - waitingInvitationsBlocMapCubit: freezed == waitingInvitationsBlocMapCubit - ? _value.waitingInvitationsBlocMapCubit - : waitingInvitationsBlocMapCubit // ignore: cast_nullable_to_non_nullable - as WaitingInvitationsBlocMapCubit?, - activeChatCubit: freezed == activeChatCubit - ? _value.activeChatCubit - : activeChatCubit // ignore: cast_nullable_to_non_nullable - as ActiveChatCubit?, - chatListCubit: freezed == chatListCubit - ? _value.chatListCubit - : chatListCubit // ignore: cast_nullable_to_non_nullable - as ChatListCubit?, - activeConversationsBlocMapCubit: freezed == - activeConversationsBlocMapCubit - ? _value.activeConversationsBlocMapCubit - : activeConversationsBlocMapCubit // ignore: cast_nullable_to_non_nullable - as ActiveConversationsBlocMapCubit?, - activeSingleContactChatBlocMapCubit: freezed == - activeSingleContactChatBlocMapCubit - ? _value.activeSingleContactChatBlocMapCubit - : activeSingleContactChatBlocMapCubit // ignore: cast_nullable_to_non_nullable - as ActiveSingleContactChatBlocMapCubit?, - ) as $Val); - } - - /// Create a copy of PerAccountCollectionState - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $AsyncValueCopyWith? get avAccountRecordState { - if (_value.avAccountRecordState == null) { - return null; - } - - return $AsyncValueCopyWith(_value.avAccountRecordState!, - (value) { - return _then(_value.copyWith(avAccountRecordState: value) as $Val); - }); - } -} - -/// @nodoc -abstract class _$$PerAccountCollectionStateImplCopyWith<$Res> - implements $PerAccountCollectionStateCopyWith<$Res> { - factory _$$PerAccountCollectionStateImplCopyWith( - _$PerAccountCollectionStateImpl value, - $Res Function(_$PerAccountCollectionStateImpl) then) = - __$$PerAccountCollectionStateImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {AccountInfo accountInfo, - AsyncValue? avAccountRecordState, - AccountInfoCubit? accountInfoCubit, - AccountRecordCubit? accountRecordCubit, - ContactInvitationListCubit? contactInvitationListCubit, - ContactListCubit? contactListCubit, - WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit, - ActiveChatCubit? activeChatCubit, - ChatListCubit? chatListCubit, - ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit, - ActiveSingleContactChatBlocMapCubit? - activeSingleContactChatBlocMapCubit}); - - @override - $AsyncValueCopyWith? get avAccountRecordState; -} - -/// @nodoc -class __$$PerAccountCollectionStateImplCopyWithImpl<$Res> - extends _$PerAccountCollectionStateCopyWithImpl<$Res, - _$PerAccountCollectionStateImpl> - implements _$$PerAccountCollectionStateImplCopyWith<$Res> { - __$$PerAccountCollectionStateImplCopyWithImpl( - _$PerAccountCollectionStateImpl _value, - $Res Function(_$PerAccountCollectionStateImpl) _then) - : super(_value, _then); - - /// Create a copy of PerAccountCollectionState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? accountInfo = null, - Object? avAccountRecordState = freezed, - Object? accountInfoCubit = freezed, - Object? accountRecordCubit = freezed, - Object? contactInvitationListCubit = freezed, - Object? contactListCubit = freezed, - Object? waitingInvitationsBlocMapCubit = freezed, - Object? activeChatCubit = freezed, - Object? chatListCubit = freezed, - Object? activeConversationsBlocMapCubit = freezed, - Object? activeSingleContactChatBlocMapCubit = freezed, - }) { - return _then(_$PerAccountCollectionStateImpl( - accountInfo: null == accountInfo - ? _value.accountInfo - : accountInfo // ignore: cast_nullable_to_non_nullable - as AccountInfo, - avAccountRecordState: freezed == avAccountRecordState - ? _value.avAccountRecordState - : avAccountRecordState // ignore: cast_nullable_to_non_nullable - as AsyncValue?, - accountInfoCubit: freezed == accountInfoCubit - ? _value.accountInfoCubit - : accountInfoCubit // ignore: cast_nullable_to_non_nullable - as AccountInfoCubit?, - accountRecordCubit: freezed == accountRecordCubit - ? _value.accountRecordCubit - : accountRecordCubit // ignore: cast_nullable_to_non_nullable - as AccountRecordCubit?, - contactInvitationListCubit: freezed == contactInvitationListCubit - ? _value.contactInvitationListCubit - : contactInvitationListCubit // ignore: cast_nullable_to_non_nullable - as ContactInvitationListCubit?, - contactListCubit: freezed == contactListCubit - ? _value.contactListCubit - : contactListCubit // ignore: cast_nullable_to_non_nullable - as ContactListCubit?, - waitingInvitationsBlocMapCubit: freezed == waitingInvitationsBlocMapCubit - ? _value.waitingInvitationsBlocMapCubit - : waitingInvitationsBlocMapCubit // ignore: cast_nullable_to_non_nullable - as WaitingInvitationsBlocMapCubit?, - activeChatCubit: freezed == activeChatCubit - ? _value.activeChatCubit - : activeChatCubit // ignore: cast_nullable_to_non_nullable - as ActiveChatCubit?, - chatListCubit: freezed == chatListCubit - ? _value.chatListCubit - : chatListCubit // ignore: cast_nullable_to_non_nullable - as ChatListCubit?, - activeConversationsBlocMapCubit: freezed == - activeConversationsBlocMapCubit - ? _value.activeConversationsBlocMapCubit - : activeConversationsBlocMapCubit // ignore: cast_nullable_to_non_nullable - as ActiveConversationsBlocMapCubit?, - activeSingleContactChatBlocMapCubit: freezed == - activeSingleContactChatBlocMapCubit - ? _value.activeSingleContactChatBlocMapCubit - : activeSingleContactChatBlocMapCubit // ignore: cast_nullable_to_non_nullable - as ActiveSingleContactChatBlocMapCubit?, - )); - } -} - -/// @nodoc - -class _$PerAccountCollectionStateImpl implements _PerAccountCollectionState { - const _$PerAccountCollectionStateImpl( - {required this.accountInfo, - required this.avAccountRecordState, - required this.accountInfoCubit, - required this.accountRecordCubit, - required this.contactInvitationListCubit, - required this.contactListCubit, - required this.waitingInvitationsBlocMapCubit, - required this.activeChatCubit, - required this.chatListCubit, - required this.activeConversationsBlocMapCubit, - required this.activeSingleContactChatBlocMapCubit}); - - @override - final AccountInfo accountInfo; - @override - final AsyncValue? avAccountRecordState; - @override - final AccountInfoCubit? accountInfoCubit; - @override - final AccountRecordCubit? accountRecordCubit; - @override - final ContactInvitationListCubit? contactInvitationListCubit; - @override - final ContactListCubit? contactListCubit; - @override - final WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit; - @override - final ActiveChatCubit? activeChatCubit; - @override - final ChatListCubit? chatListCubit; - @override - final ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit; - @override - final ActiveSingleContactChatBlocMapCubit? - activeSingleContactChatBlocMapCubit; - - @override - String toString() { - return 'PerAccountCollectionState(accountInfo: $accountInfo, avAccountRecordState: $avAccountRecordState, accountInfoCubit: $accountInfoCubit, accountRecordCubit: $accountRecordCubit, contactInvitationListCubit: $contactInvitationListCubit, contactListCubit: $contactListCubit, waitingInvitationsBlocMapCubit: $waitingInvitationsBlocMapCubit, activeChatCubit: $activeChatCubit, chatListCubit: $chatListCubit, activeConversationsBlocMapCubit: $activeConversationsBlocMapCubit, activeSingleContactChatBlocMapCubit: $activeSingleContactChatBlocMapCubit)'; - } + _$PerAccountCollectionStateCopyWithImpl( + this as PerAccountCollectionState, _$identity); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$PerAccountCollectionStateImpl && + other is PerAccountCollectionState && (identical(other.accountInfo, accountInfo) || other.accountInfo == accountInfo) && (identical(other.avAccountRecordState, avAccountRecordState) || @@ -361,61 +87,350 @@ class _$PerAccountCollectionStateImpl implements _PerAccountCollectionState { activeConversationsBlocMapCubit, activeSingleContactChatBlocMapCubit); + @override + String toString() { + return 'PerAccountCollectionState(accountInfo: $accountInfo, avAccountRecordState: $avAccountRecordState, accountInfoCubit: $accountInfoCubit, accountRecordCubit: $accountRecordCubit, contactInvitationListCubit: $contactInvitationListCubit, contactListCubit: $contactListCubit, waitingInvitationsBlocMapCubit: $waitingInvitationsBlocMapCubit, activeChatCubit: $activeChatCubit, chatListCubit: $chatListCubit, activeConversationsBlocMapCubit: $activeConversationsBlocMapCubit, activeSingleContactChatBlocMapCubit: $activeSingleContactChatBlocMapCubit)'; + } +} + +/// @nodoc +abstract mixin class $PerAccountCollectionStateCopyWith<$Res> { + factory $PerAccountCollectionStateCopyWith(PerAccountCollectionState value, + $Res Function(PerAccountCollectionState) _then) = + _$PerAccountCollectionStateCopyWithImpl; + @useResult + $Res call( + {AccountInfo accountInfo, + AsyncValue? avAccountRecordState, + AccountInfoCubit? accountInfoCubit, + AccountRecordCubit? accountRecordCubit, + ContactInvitationListCubit? contactInvitationListCubit, + ContactListCubit? contactListCubit, + WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit, + ActiveChatCubit? activeChatCubit, + ChatListCubit? chatListCubit, + ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit, + ActiveSingleContactChatBlocMapCubit? + activeSingleContactChatBlocMapCubit}); + + $AsyncValueCopyWith? get avAccountRecordState; +} + +/// @nodoc +class _$PerAccountCollectionStateCopyWithImpl<$Res> + implements $PerAccountCollectionStateCopyWith<$Res> { + _$PerAccountCollectionStateCopyWithImpl(this._self, this._then); + + final PerAccountCollectionState _self; + final $Res Function(PerAccountCollectionState) _then; + + /// Create a copy of PerAccountCollectionState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accountInfo = null, + Object? avAccountRecordState = freezed, + Object? accountInfoCubit = freezed, + Object? accountRecordCubit = freezed, + Object? contactInvitationListCubit = freezed, + Object? contactListCubit = freezed, + Object? waitingInvitationsBlocMapCubit = freezed, + Object? activeChatCubit = freezed, + Object? chatListCubit = freezed, + Object? activeConversationsBlocMapCubit = freezed, + Object? activeSingleContactChatBlocMapCubit = freezed, + }) { + return _then(_self.copyWith( + accountInfo: null == accountInfo + ? _self.accountInfo + : accountInfo // ignore: cast_nullable_to_non_nullable + as AccountInfo, + avAccountRecordState: freezed == avAccountRecordState + ? _self.avAccountRecordState! + : avAccountRecordState // ignore: cast_nullable_to_non_nullable + as AsyncValue?, + accountInfoCubit: freezed == accountInfoCubit + ? _self.accountInfoCubit + : accountInfoCubit // ignore: cast_nullable_to_non_nullable + as AccountInfoCubit?, + accountRecordCubit: freezed == accountRecordCubit + ? _self.accountRecordCubit + : accountRecordCubit // ignore: cast_nullable_to_non_nullable + as AccountRecordCubit?, + contactInvitationListCubit: freezed == contactInvitationListCubit + ? _self.contactInvitationListCubit + : contactInvitationListCubit // ignore: cast_nullable_to_non_nullable + as ContactInvitationListCubit?, + contactListCubit: freezed == contactListCubit + ? _self.contactListCubit + : contactListCubit // ignore: cast_nullable_to_non_nullable + as ContactListCubit?, + waitingInvitationsBlocMapCubit: freezed == waitingInvitationsBlocMapCubit + ? _self.waitingInvitationsBlocMapCubit + : waitingInvitationsBlocMapCubit // ignore: cast_nullable_to_non_nullable + as WaitingInvitationsBlocMapCubit?, + activeChatCubit: freezed == activeChatCubit + ? _self.activeChatCubit + : activeChatCubit // ignore: cast_nullable_to_non_nullable + as ActiveChatCubit?, + chatListCubit: freezed == chatListCubit + ? _self.chatListCubit + : chatListCubit // ignore: cast_nullable_to_non_nullable + as ChatListCubit?, + activeConversationsBlocMapCubit: freezed == + activeConversationsBlocMapCubit + ? _self.activeConversationsBlocMapCubit + : activeConversationsBlocMapCubit // ignore: cast_nullable_to_non_nullable + as ActiveConversationsBlocMapCubit?, + activeSingleContactChatBlocMapCubit: freezed == + activeSingleContactChatBlocMapCubit + ? _self.activeSingleContactChatBlocMapCubit + : activeSingleContactChatBlocMapCubit // ignore: cast_nullable_to_non_nullable + as ActiveSingleContactChatBlocMapCubit?, + )); + } + /// Create a copy of PerAccountCollectionState /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$PerAccountCollectionStateImplCopyWith<_$PerAccountCollectionStateImpl> - get copyWith => __$$PerAccountCollectionStateImplCopyWithImpl< - _$PerAccountCollectionStateImpl>(this, _$identity); + $AsyncValueCopyWith? get avAccountRecordState { + if (_self.avAccountRecordState == null) { + return null; + } + + return $AsyncValueCopyWith(_self.avAccountRecordState!, + (value) { + return _then(_self.copyWith(avAccountRecordState: value)); + }); + } } -abstract class _PerAccountCollectionState implements PerAccountCollectionState { - const factory _PerAccountCollectionState( - {required final AccountInfo accountInfo, - required final AsyncValue? avAccountRecordState, - required final AccountInfoCubit? accountInfoCubit, - required final AccountRecordCubit? accountRecordCubit, - required final ContactInvitationListCubit? contactInvitationListCubit, - required final ContactListCubit? contactListCubit, - required final WaitingInvitationsBlocMapCubit? - waitingInvitationsBlocMapCubit, - required final ActiveChatCubit? activeChatCubit, - required final ChatListCubit? chatListCubit, - required final ActiveConversationsBlocMapCubit? - activeConversationsBlocMapCubit, - required final ActiveSingleContactChatBlocMapCubit? - activeSingleContactChatBlocMapCubit}) = - _$PerAccountCollectionStateImpl; +/// @nodoc + +class _PerAccountCollectionState extends PerAccountCollectionState { + const _PerAccountCollectionState( + {required this.accountInfo, + required this.avAccountRecordState, + required this.accountInfoCubit, + required this.accountRecordCubit, + required this.contactInvitationListCubit, + required this.contactListCubit, + required this.waitingInvitationsBlocMapCubit, + required this.activeChatCubit, + required this.chatListCubit, + required this.activeConversationsBlocMapCubit, + required this.activeSingleContactChatBlocMapCubit}) + : super._(); @override - AccountInfo get accountInfo; + final AccountInfo accountInfo; @override - AsyncValue? get avAccountRecordState; + final AsyncValue? avAccountRecordState; @override - AccountInfoCubit? get accountInfoCubit; + final AccountInfoCubit? accountInfoCubit; @override - AccountRecordCubit? get accountRecordCubit; + final AccountRecordCubit? accountRecordCubit; @override - ContactInvitationListCubit? get contactInvitationListCubit; + final ContactInvitationListCubit? contactInvitationListCubit; @override - ContactListCubit? get contactListCubit; + final ContactListCubit? contactListCubit; @override - WaitingInvitationsBlocMapCubit? get waitingInvitationsBlocMapCubit; + final WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit; @override - ActiveChatCubit? get activeChatCubit; + final ActiveChatCubit? activeChatCubit; @override - ChatListCubit? get chatListCubit; + final ChatListCubit? chatListCubit; @override - ActiveConversationsBlocMapCubit? get activeConversationsBlocMapCubit; + final ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit; @override - ActiveSingleContactChatBlocMapCubit? get activeSingleContactChatBlocMapCubit; + final ActiveSingleContactChatBlocMapCubit? + activeSingleContactChatBlocMapCubit; /// Create a copy of PerAccountCollectionState /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$PerAccountCollectionStateImplCopyWith<_$PerAccountCollectionStateImpl> - get copyWith => throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + _$PerAccountCollectionStateCopyWith<_PerAccountCollectionState> + get copyWith => + __$PerAccountCollectionStateCopyWithImpl<_PerAccountCollectionState>( + this, _$identity); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _PerAccountCollectionState && + (identical(other.accountInfo, accountInfo) || + other.accountInfo == accountInfo) && + (identical(other.avAccountRecordState, avAccountRecordState) || + other.avAccountRecordState == avAccountRecordState) && + (identical(other.accountInfoCubit, accountInfoCubit) || + other.accountInfoCubit == accountInfoCubit) && + (identical(other.accountRecordCubit, accountRecordCubit) || + other.accountRecordCubit == accountRecordCubit) && + (identical(other.contactInvitationListCubit, + contactInvitationListCubit) || + other.contactInvitationListCubit == + contactInvitationListCubit) && + (identical(other.contactListCubit, contactListCubit) || + other.contactListCubit == contactListCubit) && + (identical(other.waitingInvitationsBlocMapCubit, + waitingInvitationsBlocMapCubit) || + other.waitingInvitationsBlocMapCubit == + waitingInvitationsBlocMapCubit) && + (identical(other.activeChatCubit, activeChatCubit) || + other.activeChatCubit == activeChatCubit) && + (identical(other.chatListCubit, chatListCubit) || + other.chatListCubit == chatListCubit) && + (identical(other.activeConversationsBlocMapCubit, + activeConversationsBlocMapCubit) || + other.activeConversationsBlocMapCubit == + activeConversationsBlocMapCubit) && + (identical(other.activeSingleContactChatBlocMapCubit, + activeSingleContactChatBlocMapCubit) || + other.activeSingleContactChatBlocMapCubit == + activeSingleContactChatBlocMapCubit)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + accountInfo, + avAccountRecordState, + accountInfoCubit, + accountRecordCubit, + contactInvitationListCubit, + contactListCubit, + waitingInvitationsBlocMapCubit, + activeChatCubit, + chatListCubit, + activeConversationsBlocMapCubit, + activeSingleContactChatBlocMapCubit); + + @override + String toString() { + return 'PerAccountCollectionState(accountInfo: $accountInfo, avAccountRecordState: $avAccountRecordState, accountInfoCubit: $accountInfoCubit, accountRecordCubit: $accountRecordCubit, contactInvitationListCubit: $contactInvitationListCubit, contactListCubit: $contactListCubit, waitingInvitationsBlocMapCubit: $waitingInvitationsBlocMapCubit, activeChatCubit: $activeChatCubit, chatListCubit: $chatListCubit, activeConversationsBlocMapCubit: $activeConversationsBlocMapCubit, activeSingleContactChatBlocMapCubit: $activeSingleContactChatBlocMapCubit)'; + } } + +/// @nodoc +abstract mixin class _$PerAccountCollectionStateCopyWith<$Res> + implements $PerAccountCollectionStateCopyWith<$Res> { + factory _$PerAccountCollectionStateCopyWith(_PerAccountCollectionState value, + $Res Function(_PerAccountCollectionState) _then) = + __$PerAccountCollectionStateCopyWithImpl; + @override + @useResult + $Res call( + {AccountInfo accountInfo, + AsyncValue? avAccountRecordState, + AccountInfoCubit? accountInfoCubit, + AccountRecordCubit? accountRecordCubit, + ContactInvitationListCubit? contactInvitationListCubit, + ContactListCubit? contactListCubit, + WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit, + ActiveChatCubit? activeChatCubit, + ChatListCubit? chatListCubit, + ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit, + ActiveSingleContactChatBlocMapCubit? + activeSingleContactChatBlocMapCubit}); + + @override + $AsyncValueCopyWith? get avAccountRecordState; +} + +/// @nodoc +class __$PerAccountCollectionStateCopyWithImpl<$Res> + implements _$PerAccountCollectionStateCopyWith<$Res> { + __$PerAccountCollectionStateCopyWithImpl(this._self, this._then); + + final _PerAccountCollectionState _self; + final $Res Function(_PerAccountCollectionState) _then; + + /// Create a copy of PerAccountCollectionState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? accountInfo = null, + Object? avAccountRecordState = freezed, + Object? accountInfoCubit = freezed, + Object? accountRecordCubit = freezed, + Object? contactInvitationListCubit = freezed, + Object? contactListCubit = freezed, + Object? waitingInvitationsBlocMapCubit = freezed, + Object? activeChatCubit = freezed, + Object? chatListCubit = freezed, + Object? activeConversationsBlocMapCubit = freezed, + Object? activeSingleContactChatBlocMapCubit = freezed, + }) { + return _then(_PerAccountCollectionState( + accountInfo: null == accountInfo + ? _self.accountInfo + : accountInfo // ignore: cast_nullable_to_non_nullable + as AccountInfo, + avAccountRecordState: freezed == avAccountRecordState + ? _self.avAccountRecordState + : avAccountRecordState // ignore: cast_nullable_to_non_nullable + as AsyncValue?, + accountInfoCubit: freezed == accountInfoCubit + ? _self.accountInfoCubit + : accountInfoCubit // ignore: cast_nullable_to_non_nullable + as AccountInfoCubit?, + accountRecordCubit: freezed == accountRecordCubit + ? _self.accountRecordCubit + : accountRecordCubit // ignore: cast_nullable_to_non_nullable + as AccountRecordCubit?, + contactInvitationListCubit: freezed == contactInvitationListCubit + ? _self.contactInvitationListCubit + : contactInvitationListCubit // ignore: cast_nullable_to_non_nullable + as ContactInvitationListCubit?, + contactListCubit: freezed == contactListCubit + ? _self.contactListCubit + : contactListCubit // ignore: cast_nullable_to_non_nullable + as ContactListCubit?, + waitingInvitationsBlocMapCubit: freezed == waitingInvitationsBlocMapCubit + ? _self.waitingInvitationsBlocMapCubit + : waitingInvitationsBlocMapCubit // ignore: cast_nullable_to_non_nullable + as WaitingInvitationsBlocMapCubit?, + activeChatCubit: freezed == activeChatCubit + ? _self.activeChatCubit + : activeChatCubit // ignore: cast_nullable_to_non_nullable + as ActiveChatCubit?, + chatListCubit: freezed == chatListCubit + ? _self.chatListCubit + : chatListCubit // ignore: cast_nullable_to_non_nullable + as ChatListCubit?, + activeConversationsBlocMapCubit: freezed == + activeConversationsBlocMapCubit + ? _self.activeConversationsBlocMapCubit + : activeConversationsBlocMapCubit // ignore: cast_nullable_to_non_nullable + as ActiveConversationsBlocMapCubit?, + activeSingleContactChatBlocMapCubit: freezed == + activeSingleContactChatBlocMapCubit + ? _self.activeSingleContactChatBlocMapCubit + : activeSingleContactChatBlocMapCubit // ignore: cast_nullable_to_non_nullable + as ActiveSingleContactChatBlocMapCubit?, + )); + } + + /// Create a copy of PerAccountCollectionState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AsyncValueCopyWith? get avAccountRecordState { + if (_self.avAccountRecordState == null) { + return null; + } + + return $AsyncValueCopyWith(_self.avAccountRecordState!, + (value) { + return _then(_self.copyWith(avAccountRecordState: value)); + }); + } +} + +// dart format on diff --git a/lib/account_manager/models/user_login/user_login.dart b/lib/account_manager/models/user_login/user_login.dart index 7c024cf..d43fdfd 100644 --- a/lib/account_manager/models/user_login/user_login.dart +++ b/lib/account_manager/models/user_login/user_login.dart @@ -9,7 +9,7 @@ part 'user_login.g.dart'; // User logins are stored in the user_logins tablestore table // indexed by the accountSuperIdentityRecordKey @freezed -class UserLogin with _$UserLogin { +sealed class UserLogin with _$UserLogin { const factory UserLogin({ // SuperIdentity record key for the user // used to index the local accounts table diff --git a/lib/account_manager/models/user_login/user_login.freezed.dart b/lib/account_manager/models/user_login/user_login.freezed.dart index 2804a77..b0c6070 100644 --- a/lib/account_manager/models/user_login/user_login.freezed.dart +++ b/lib/account_manager/models/user_login/user_login.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,195 +10,36 @@ part of 'user_login.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -UserLogin _$UserLoginFromJson(Map json) { - return _UserLogin.fromJson(json); -} - /// @nodoc mixin _$UserLogin { // SuperIdentity record key for the user // used to index the local accounts table - Typed get superIdentityRecordKey => - throw _privateConstructorUsedError; // The identity secret as unlocked from the local accounts table - Typed get identitySecret => - throw _privateConstructorUsedError; // The account record key, owner key and secret pulled from the identity - AccountRecordInfo get accountRecordInfo => - throw _privateConstructorUsedError; // The time this login was most recently used - Timestamp get lastActive => throw _privateConstructorUsedError; - - /// Serializes this UserLogin to a JSON map. - Map toJson() => throw _privateConstructorUsedError; + TypedKey + get superIdentityRecordKey; // The identity secret as unlocked from the local accounts table + TypedSecret + get identitySecret; // The account record key, owner key and secret pulled from the identity + AccountRecordInfo + get accountRecordInfo; // The time this login was most recently used + Timestamp get lastActive; /// Create a copy of UserLogin /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $UserLoginCopyWith get copyWith => - throw _privateConstructorUsedError; -} + _$UserLoginCopyWithImpl(this as UserLogin, _$identity); -/// @nodoc -abstract class $UserLoginCopyWith<$Res> { - factory $UserLoginCopyWith(UserLogin value, $Res Function(UserLogin) then) = - _$UserLoginCopyWithImpl<$Res, UserLogin>; - @useResult - $Res call( - {Typed superIdentityRecordKey, - Typed identitySecret, - AccountRecordInfo accountRecordInfo, - Timestamp lastActive}); - - $AccountRecordInfoCopyWith<$Res> get accountRecordInfo; -} - -/// @nodoc -class _$UserLoginCopyWithImpl<$Res, $Val extends UserLogin> - implements $UserLoginCopyWith<$Res> { - _$UserLoginCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of UserLogin - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? superIdentityRecordKey = null, - Object? identitySecret = null, - Object? accountRecordInfo = null, - Object? lastActive = null, - }) { - return _then(_value.copyWith( - superIdentityRecordKey: null == superIdentityRecordKey - ? _value.superIdentityRecordKey - : superIdentityRecordKey // ignore: cast_nullable_to_non_nullable - as Typed, - identitySecret: null == identitySecret - ? _value.identitySecret - : identitySecret // ignore: cast_nullable_to_non_nullable - as Typed, - accountRecordInfo: null == accountRecordInfo - ? _value.accountRecordInfo - : accountRecordInfo // ignore: cast_nullable_to_non_nullable - as AccountRecordInfo, - lastActive: null == lastActive - ? _value.lastActive - : lastActive // ignore: cast_nullable_to_non_nullable - as Timestamp, - ) as $Val); - } - - /// Create a copy of UserLogin - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $AccountRecordInfoCopyWith<$Res> get accountRecordInfo { - return $AccountRecordInfoCopyWith<$Res>(_value.accountRecordInfo, (value) { - return _then(_value.copyWith(accountRecordInfo: value) as $Val); - }); - } -} - -/// @nodoc -abstract class _$$UserLoginImplCopyWith<$Res> - implements $UserLoginCopyWith<$Res> { - factory _$$UserLoginImplCopyWith( - _$UserLoginImpl value, $Res Function(_$UserLoginImpl) then) = - __$$UserLoginImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {Typed superIdentityRecordKey, - Typed identitySecret, - AccountRecordInfo accountRecordInfo, - Timestamp lastActive}); - - @override - $AccountRecordInfoCopyWith<$Res> get accountRecordInfo; -} - -/// @nodoc -class __$$UserLoginImplCopyWithImpl<$Res> - extends _$UserLoginCopyWithImpl<$Res, _$UserLoginImpl> - implements _$$UserLoginImplCopyWith<$Res> { - __$$UserLoginImplCopyWithImpl( - _$UserLoginImpl _value, $Res Function(_$UserLoginImpl) _then) - : super(_value, _then); - - /// Create a copy of UserLogin - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? superIdentityRecordKey = null, - Object? identitySecret = null, - Object? accountRecordInfo = null, - Object? lastActive = null, - }) { - return _then(_$UserLoginImpl( - superIdentityRecordKey: null == superIdentityRecordKey - ? _value.superIdentityRecordKey - : superIdentityRecordKey // ignore: cast_nullable_to_non_nullable - as Typed, - identitySecret: null == identitySecret - ? _value.identitySecret - : identitySecret // ignore: cast_nullable_to_non_nullable - as Typed, - accountRecordInfo: null == accountRecordInfo - ? _value.accountRecordInfo - : accountRecordInfo // ignore: cast_nullable_to_non_nullable - as AccountRecordInfo, - lastActive: null == lastActive - ? _value.lastActive - : lastActive // ignore: cast_nullable_to_non_nullable - as Timestamp, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$UserLoginImpl implements _UserLogin { - const _$UserLoginImpl( - {required this.superIdentityRecordKey, - required this.identitySecret, - required this.accountRecordInfo, - required this.lastActive}); - - factory _$UserLoginImpl.fromJson(Map json) => - _$$UserLoginImplFromJson(json); - -// SuperIdentity record key for the user -// used to index the local accounts table - @override - final Typed superIdentityRecordKey; -// The identity secret as unlocked from the local accounts table - @override - final Typed identitySecret; -// The account record key, owner key and secret pulled from the identity - @override - final AccountRecordInfo accountRecordInfo; -// The time this login was most recently used - @override - final Timestamp lastActive; - - @override - String toString() { - return 'UserLogin(superIdentityRecordKey: $superIdentityRecordKey, identitySecret: $identitySecret, accountRecordInfo: $accountRecordInfo, lastActive: $lastActive)'; - } + /// Serializes this UserLogin to a JSON map. + Map toJson(); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$UserLoginImpl && + other is UserLogin && (identical(other.superIdentityRecordKey, superIdentityRecordKey) || other.superIdentityRecordKey == superIdentityRecordKey) && (identical(other.identitySecret, identitySecret) || @@ -213,50 +55,204 @@ class _$UserLoginImpl implements _UserLogin { int get hashCode => Object.hash(runtimeType, superIdentityRecordKey, identitySecret, accountRecordInfo, lastActive); - /// Create a copy of UserLogin - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$UserLoginImplCopyWith<_$UserLoginImpl> get copyWith => - __$$UserLoginImplCopyWithImpl<_$UserLoginImpl>(this, _$identity); - - @override - Map toJson() { - return _$$UserLoginImplToJson( - this, - ); + String toString() { + return 'UserLogin(superIdentityRecordKey: $superIdentityRecordKey, identitySecret: $identitySecret, accountRecordInfo: $accountRecordInfo, lastActive: $lastActive)'; } } -abstract class _UserLogin implements UserLogin { - const factory _UserLogin( - {required final Typed superIdentityRecordKey, - required final Typed identitySecret, - required final AccountRecordInfo accountRecordInfo, - required final Timestamp lastActive}) = _$UserLoginImpl; +/// @nodoc +abstract mixin class $UserLoginCopyWith<$Res> { + factory $UserLoginCopyWith(UserLogin value, $Res Function(UserLogin) _then) = + _$UserLoginCopyWithImpl; + @useResult + $Res call( + {Typed superIdentityRecordKey, + Typed identitySecret, + AccountRecordInfo accountRecordInfo, + Timestamp lastActive}); - factory _UserLogin.fromJson(Map json) = - _$UserLoginImpl.fromJson; + $AccountRecordInfoCopyWith<$Res> get accountRecordInfo; +} + +/// @nodoc +class _$UserLoginCopyWithImpl<$Res> implements $UserLoginCopyWith<$Res> { + _$UserLoginCopyWithImpl(this._self, this._then); + + final UserLogin _self; + final $Res Function(UserLogin) _then; + + /// Create a copy of UserLogin + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? superIdentityRecordKey = null, + Object? identitySecret = null, + Object? accountRecordInfo = null, + Object? lastActive = null, + }) { + return _then(_self.copyWith( + superIdentityRecordKey: null == superIdentityRecordKey + ? _self.superIdentityRecordKey! + : superIdentityRecordKey // ignore: cast_nullable_to_non_nullable + as Typed, + identitySecret: null == identitySecret + ? _self.identitySecret! + : identitySecret // ignore: cast_nullable_to_non_nullable + as Typed, + accountRecordInfo: null == accountRecordInfo + ? _self.accountRecordInfo + : accountRecordInfo // ignore: cast_nullable_to_non_nullable + as AccountRecordInfo, + lastActive: null == lastActive + ? _self.lastActive + : lastActive // ignore: cast_nullable_to_non_nullable + as Timestamp, + )); + } + + /// Create a copy of UserLogin + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AccountRecordInfoCopyWith<$Res> get accountRecordInfo { + return $AccountRecordInfoCopyWith<$Res>(_self.accountRecordInfo, (value) { + return _then(_self.copyWith(accountRecordInfo: value)); + }); + } +} + +/// @nodoc +@JsonSerializable() +class _UserLogin implements UserLogin { + const _UserLogin( + {required this.superIdentityRecordKey, + required this.identitySecret, + required this.accountRecordInfo, + required this.lastActive}); + factory _UserLogin.fromJson(Map json) => + _$UserLoginFromJson(json); // SuperIdentity record key for the user // used to index the local accounts table @override - Typed - get superIdentityRecordKey; // The identity secret as unlocked from the local accounts table + final Typed superIdentityRecordKey; +// The identity secret as unlocked from the local accounts table @override - Typed - get identitySecret; // The account record key, owner key and secret pulled from the identity + final Typed identitySecret; +// The account record key, owner key and secret pulled from the identity @override - AccountRecordInfo - get accountRecordInfo; // The time this login was most recently used + final AccountRecordInfo accountRecordInfo; +// The time this login was most recently used @override - Timestamp get lastActive; + final Timestamp lastActive; /// Create a copy of UserLogin /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$UserLoginImplCopyWith<_$UserLoginImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + _$UserLoginCopyWith<_UserLogin> get copyWith => + __$UserLoginCopyWithImpl<_UserLogin>(this, _$identity); + + @override + Map toJson() { + return _$UserLoginToJson( + this, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _UserLogin && + (identical(other.superIdentityRecordKey, superIdentityRecordKey) || + other.superIdentityRecordKey == superIdentityRecordKey) && + (identical(other.identitySecret, identitySecret) || + other.identitySecret == identitySecret) && + (identical(other.accountRecordInfo, accountRecordInfo) || + other.accountRecordInfo == accountRecordInfo) && + (identical(other.lastActive, lastActive) || + other.lastActive == lastActive)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, superIdentityRecordKey, + identitySecret, accountRecordInfo, lastActive); + + @override + String toString() { + return 'UserLogin(superIdentityRecordKey: $superIdentityRecordKey, identitySecret: $identitySecret, accountRecordInfo: $accountRecordInfo, lastActive: $lastActive)'; + } } + +/// @nodoc +abstract mixin class _$UserLoginCopyWith<$Res> + implements $UserLoginCopyWith<$Res> { + factory _$UserLoginCopyWith( + _UserLogin value, $Res Function(_UserLogin) _then) = + __$UserLoginCopyWithImpl; + @override + @useResult + $Res call( + {Typed superIdentityRecordKey, + Typed identitySecret, + AccountRecordInfo accountRecordInfo, + Timestamp lastActive}); + + @override + $AccountRecordInfoCopyWith<$Res> get accountRecordInfo; +} + +/// @nodoc +class __$UserLoginCopyWithImpl<$Res> implements _$UserLoginCopyWith<$Res> { + __$UserLoginCopyWithImpl(this._self, this._then); + + final _UserLogin _self; + final $Res Function(_UserLogin) _then; + + /// Create a copy of UserLogin + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? superIdentityRecordKey = null, + Object? identitySecret = null, + Object? accountRecordInfo = null, + Object? lastActive = null, + }) { + return _then(_UserLogin( + superIdentityRecordKey: null == superIdentityRecordKey + ? _self.superIdentityRecordKey + : superIdentityRecordKey // ignore: cast_nullable_to_non_nullable + as Typed, + identitySecret: null == identitySecret + ? _self.identitySecret + : identitySecret // ignore: cast_nullable_to_non_nullable + as Typed, + accountRecordInfo: null == accountRecordInfo + ? _self.accountRecordInfo + : accountRecordInfo // ignore: cast_nullable_to_non_nullable + as AccountRecordInfo, + lastActive: null == lastActive + ? _self.lastActive + : lastActive // ignore: cast_nullable_to_non_nullable + as Timestamp, + )); + } + + /// Create a copy of UserLogin + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AccountRecordInfoCopyWith<$Res> get accountRecordInfo { + return $AccountRecordInfoCopyWith<$Res>(_self.accountRecordInfo, (value) { + return _then(_self.copyWith(accountRecordInfo: value)); + }); + } +} + +// dart format on diff --git a/lib/account_manager/models/user_login/user_login.g.dart b/lib/account_manager/models/user_login/user_login.g.dart index 173d853..fa5314b 100644 --- a/lib/account_manager/models/user_login/user_login.g.dart +++ b/lib/account_manager/models/user_login/user_login.g.dart @@ -6,8 +6,7 @@ part of 'user_login.dart'; // JsonSerializableGenerator // ************************************************************************** -_$UserLoginImpl _$$UserLoginImplFromJson(Map json) => - _$UserLoginImpl( +_UserLogin _$UserLoginFromJson(Map json) => _UserLogin( superIdentityRecordKey: Typed.fromJson( json['super_identity_record_key']), identitySecret: @@ -17,7 +16,7 @@ _$UserLoginImpl _$$UserLoginImplFromJson(Map json) => lastActive: Timestamp.fromJson(json['last_active']), ); -Map _$$UserLoginImplToJson(_$UserLoginImpl instance) => +Map _$UserLoginToJson(_UserLogin instance) => { 'super_identity_record_key': instance.superIdentityRecordKey.toJson(), 'identity_secret': instance.identitySecret.toJson(), diff --git a/lib/chat/cubits/chat_component_cubit.dart b/lib/chat/cubits/chat_component_cubit.dart index 9e50e02..7ea9e95 100644 --- a/lib/chat/cubits/chat_component_cubit.dart +++ b/lib/chat/cubits/chat_component_cubit.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:async_tools/async_tools.dart'; -import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:fixnum/fixnum.dart'; import 'package:flutter/widgets.dart'; @@ -184,9 +183,7 @@ class ChatComponentCubit extends Cubit { emit(_convertMessages(state, avMessagesState)); } - void _onChangedContacts( - BlocBusyState>>> - bavContacts) { + void _onChangedContacts(DHTShortArrayCubitState bavContacts) { // Rewrite users when contacts change singleFuture((this, _sfChangedContacts), _updateConversationSubscriptions); } @@ -353,6 +350,7 @@ class ChatComponentCubit extends Cubit { case proto.Message_Kind.membership: case proto.Message_Kind.moderation: case proto.Message_Kind.notSet: + case proto.Message_Kind.readReceipt: return (currentState, null); } } @@ -440,9 +438,7 @@ class ChatComponentCubit extends Cubit { final Map>> _conversationSubscriptions = {}; late StreamSubscription _messagesSubscription; - late StreamSubscription< - BlocBusyState< - AsyncValue>>>> + late StreamSubscription> _contactListSubscription; double scrollOffset = 0; } diff --git a/lib/chat/models/chat_component_state.dart b/lib/chat/models/chat_component_state.dart index b06b413..82e492d 100644 --- a/lib/chat/models/chat_component_state.dart +++ b/lib/chat/models/chat_component_state.dart @@ -13,7 +13,7 @@ import 'window_state.dart'; part 'chat_component_state.freezed.dart'; @freezed -class ChatComponentState with _$ChatComponentState { +sealed class ChatComponentState with _$ChatComponentState { const factory ChatComponentState( { // GlobalKey for the chat diff --git a/lib/chat/models/chat_component_state.freezed.dart b/lib/chat/models/chat_component_state.freezed.dart index f967997..ae3acee 100644 --- a/lib/chat/models/chat_component_state.freezed.dart +++ b/lib/chat/models/chat_component_state.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,44 +10,78 @@ part of 'chat_component_state.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - /// @nodoc mixin _$ChatComponentState { // GlobalKey for the chat - GlobalKey get chatKey => - throw _privateConstructorUsedError; // ScrollController for the chat - AutoScrollController get scrollController => - throw _privateConstructorUsedError; // TextEditingController for the chat - InputTextFieldController get textEditingController => - throw _privateConstructorUsedError; // Local user - User? get localUser => - throw _privateConstructorUsedError; // Active remote users - IMap, User> get remoteUsers => - throw _privateConstructorUsedError; // Historical remote users - IMap, User> get historicalRemoteUsers => - throw _privateConstructorUsedError; // Unknown users - IMap, User> get unknownUsers => - throw _privateConstructorUsedError; // Messages state - AsyncValue> get messageWindow => - throw _privateConstructorUsedError; // Title of the chat - String get title => throw _privateConstructorUsedError; + GlobalKey get chatKey; // ScrollController for the chat + AutoScrollController + get scrollController; // TextEditingController for the chat + InputTextFieldController get textEditingController; // Local user + User? get localUser; // Active remote users + IMap get remoteUsers; // Historical remote users + IMap get historicalRemoteUsers; // Unknown users + IMap get unknownUsers; // Messages state + AsyncValue> get messageWindow; // Title of the chat + String get title; /// Create a copy of ChatComponentState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $ChatComponentStateCopyWith get copyWith => - throw _privateConstructorUsedError; + _$ChatComponentStateCopyWithImpl( + this as ChatComponentState, _$identity); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is ChatComponentState && + (identical(other.chatKey, chatKey) || other.chatKey == chatKey) && + (identical(other.scrollController, scrollController) || + other.scrollController == scrollController) && + (identical(other.textEditingController, textEditingController) || + other.textEditingController == textEditingController) && + (identical(other.localUser, localUser) || + other.localUser == localUser) && + (identical(other.remoteUsers, remoteUsers) || + other.remoteUsers == remoteUsers) && + (identical(other.historicalRemoteUsers, historicalRemoteUsers) || + other.historicalRemoteUsers == historicalRemoteUsers) && + (identical(other.unknownUsers, unknownUsers) || + other.unknownUsers == unknownUsers) && + (identical(other.messageWindow, messageWindow) || + other.messageWindow == messageWindow) && + (identical(other.title, title) || other.title == title)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + chatKey, + scrollController, + textEditingController, + localUser, + remoteUsers, + historicalRemoteUsers, + unknownUsers, + messageWindow, + title); + + @override + String toString() { + return 'ChatComponentState(chatKey: $chatKey, scrollController: $scrollController, textEditingController: $textEditingController, localUser: $localUser, remoteUsers: $remoteUsers, historicalRemoteUsers: $historicalRemoteUsers, unknownUsers: $unknownUsers, messageWindow: $messageWindow, title: $title)'; + } } /// @nodoc -abstract class $ChatComponentStateCopyWith<$Res> { +abstract mixin class $ChatComponentStateCopyWith<$Res> { factory $ChatComponentStateCopyWith( - ChatComponentState value, $Res Function(ChatComponentState) then) = - _$ChatComponentStateCopyWithImpl<$Res, ChatComponentState>; + ChatComponentState value, $Res Function(ChatComponentState) _then) = + _$ChatComponentStateCopyWithImpl; @useResult $Res call( {GlobalKey chatKey, @@ -63,14 +98,12 @@ abstract class $ChatComponentStateCopyWith<$Res> { } /// @nodoc -class _$ChatComponentStateCopyWithImpl<$Res, $Val extends ChatComponentState> +class _$ChatComponentStateCopyWithImpl<$Res> implements $ChatComponentStateCopyWith<$Res> { - _$ChatComponentStateCopyWithImpl(this._value, this._then); + _$ChatComponentStateCopyWithImpl(this._self, this._then); - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; + final ChatComponentState _self; + final $Res Function(ChatComponentState) _then; /// Create a copy of ChatComponentState /// with the given fields replaced by the non-null parameter values. @@ -87,44 +120,44 @@ class _$ChatComponentStateCopyWithImpl<$Res, $Val extends ChatComponentState> Object? messageWindow = null, Object? title = null, }) { - return _then(_value.copyWith( + return _then(_self.copyWith( chatKey: null == chatKey - ? _value.chatKey + ? _self.chatKey : chatKey // ignore: cast_nullable_to_non_nullable as GlobalKey, scrollController: null == scrollController - ? _value.scrollController + ? _self.scrollController : scrollController // ignore: cast_nullable_to_non_nullable as AutoScrollController, textEditingController: null == textEditingController - ? _value.textEditingController + ? _self.textEditingController : textEditingController // ignore: cast_nullable_to_non_nullable as InputTextFieldController, localUser: freezed == localUser - ? _value.localUser + ? _self.localUser : localUser // ignore: cast_nullable_to_non_nullable as User?, remoteUsers: null == remoteUsers - ? _value.remoteUsers + ? _self.remoteUsers! : remoteUsers // ignore: cast_nullable_to_non_nullable as IMap, User>, historicalRemoteUsers: null == historicalRemoteUsers - ? _value.historicalRemoteUsers + ? _self.historicalRemoteUsers! : historicalRemoteUsers // ignore: cast_nullable_to_non_nullable as IMap, User>, unknownUsers: null == unknownUsers - ? _value.unknownUsers + ? _self.unknownUsers! : unknownUsers // ignore: cast_nullable_to_non_nullable as IMap, User>, messageWindow: null == messageWindow - ? _value.messageWindow + ? _self.messageWindow : messageWindow // ignore: cast_nullable_to_non_nullable as AsyncValue>, title: null == title - ? _value.title + ? _self.title : title // ignore: cast_nullable_to_non_nullable as String, - ) as $Val); + )); } /// Create a copy of ChatComponentState @@ -132,104 +165,17 @@ class _$ChatComponentStateCopyWithImpl<$Res, $Val extends ChatComponentState> @override @pragma('vm:prefer-inline') $AsyncValueCopyWith, $Res> get messageWindow { - return $AsyncValueCopyWith, $Res>(_value.messageWindow, + return $AsyncValueCopyWith, $Res>(_self.messageWindow, (value) { - return _then(_value.copyWith(messageWindow: value) as $Val); + return _then(_self.copyWith(messageWindow: value)); }); } } /// @nodoc -abstract class _$$ChatComponentStateImplCopyWith<$Res> - implements $ChatComponentStateCopyWith<$Res> { - factory _$$ChatComponentStateImplCopyWith(_$ChatComponentStateImpl value, - $Res Function(_$ChatComponentStateImpl) then) = - __$$ChatComponentStateImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {GlobalKey chatKey, - AutoScrollController scrollController, - InputTextFieldController textEditingController, - User? localUser, - IMap, User> remoteUsers, - IMap, User> historicalRemoteUsers, - IMap, User> unknownUsers, - AsyncValue> messageWindow, - String title}); - @override - $AsyncValueCopyWith, $Res> get messageWindow; -} - -/// @nodoc -class __$$ChatComponentStateImplCopyWithImpl<$Res> - extends _$ChatComponentStateCopyWithImpl<$Res, _$ChatComponentStateImpl> - implements _$$ChatComponentStateImplCopyWith<$Res> { - __$$ChatComponentStateImplCopyWithImpl(_$ChatComponentStateImpl _value, - $Res Function(_$ChatComponentStateImpl) _then) - : super(_value, _then); - - /// Create a copy of ChatComponentState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? chatKey = null, - Object? scrollController = null, - Object? textEditingController = null, - Object? localUser = freezed, - Object? remoteUsers = null, - Object? historicalRemoteUsers = null, - Object? unknownUsers = null, - Object? messageWindow = null, - Object? title = null, - }) { - return _then(_$ChatComponentStateImpl( - chatKey: null == chatKey - ? _value.chatKey - : chatKey // ignore: cast_nullable_to_non_nullable - as GlobalKey, - scrollController: null == scrollController - ? _value.scrollController - : scrollController // ignore: cast_nullable_to_non_nullable - as AutoScrollController, - textEditingController: null == textEditingController - ? _value.textEditingController - : textEditingController // ignore: cast_nullable_to_non_nullable - as InputTextFieldController, - localUser: freezed == localUser - ? _value.localUser - : localUser // ignore: cast_nullable_to_non_nullable - as User?, - remoteUsers: null == remoteUsers - ? _value.remoteUsers - : remoteUsers // ignore: cast_nullable_to_non_nullable - as IMap, User>, - historicalRemoteUsers: null == historicalRemoteUsers - ? _value.historicalRemoteUsers - : historicalRemoteUsers // ignore: cast_nullable_to_non_nullable - as IMap, User>, - unknownUsers: null == unknownUsers - ? _value.unknownUsers - : unknownUsers // ignore: cast_nullable_to_non_nullable - as IMap, User>, - messageWindow: null == messageWindow - ? _value.messageWindow - : messageWindow // ignore: cast_nullable_to_non_nullable - as AsyncValue>, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc - -class _$ChatComponentStateImpl implements _ChatComponentState { - const _$ChatComponentStateImpl( +class _ChatComponentState implements ChatComponentState { + const _ChatComponentState( {required this.chatKey, required this.scrollController, required this.textEditingController, @@ -268,16 +214,19 @@ class _$ChatComponentStateImpl implements _ChatComponentState { @override final String title; + /// Create a copy of ChatComponentState + /// with the given fields replaced by the non-null parameter values. @override - String toString() { - return 'ChatComponentState(chatKey: $chatKey, scrollController: $scrollController, textEditingController: $textEditingController, localUser: $localUser, remoteUsers: $remoteUsers, historicalRemoteUsers: $historicalRemoteUsers, unknownUsers: $unknownUsers, messageWindow: $messageWindow, title: $title)'; - } + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$ChatComponentStateCopyWith<_ChatComponentState> get copyWith => + __$ChatComponentStateCopyWithImpl<_ChatComponentState>(this, _$identity); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$ChatComponentStateImpl && + other is _ChatComponentState && (identical(other.chatKey, chatKey) || other.chatKey == chatKey) && (identical(other.scrollController, scrollController) || other.scrollController == scrollController) && @@ -309,56 +258,108 @@ class _$ChatComponentStateImpl implements _ChatComponentState { messageWindow, title); + @override + String toString() { + return 'ChatComponentState(chatKey: $chatKey, scrollController: $scrollController, textEditingController: $textEditingController, localUser: $localUser, remoteUsers: $remoteUsers, historicalRemoteUsers: $historicalRemoteUsers, unknownUsers: $unknownUsers, messageWindow: $messageWindow, title: $title)'; + } +} + +/// @nodoc +abstract mixin class _$ChatComponentStateCopyWith<$Res> + implements $ChatComponentStateCopyWith<$Res> { + factory _$ChatComponentStateCopyWith( + _ChatComponentState value, $Res Function(_ChatComponentState) _then) = + __$ChatComponentStateCopyWithImpl; + @override + @useResult + $Res call( + {GlobalKey chatKey, + AutoScrollController scrollController, + InputTextFieldController textEditingController, + User? localUser, + IMap, User> remoteUsers, + IMap, User> historicalRemoteUsers, + IMap, User> unknownUsers, + AsyncValue> messageWindow, + String title}); + + @override + $AsyncValueCopyWith, $Res> get messageWindow; +} + +/// @nodoc +class __$ChatComponentStateCopyWithImpl<$Res> + implements _$ChatComponentStateCopyWith<$Res> { + __$ChatComponentStateCopyWithImpl(this._self, this._then); + + final _ChatComponentState _self; + final $Res Function(_ChatComponentState) _then; + /// Create a copy of ChatComponentState /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$ChatComponentStateImplCopyWith<_$ChatComponentStateImpl> get copyWith => - __$$ChatComponentStateImplCopyWithImpl<_$ChatComponentStateImpl>( - this, _$identity); -} - -abstract class _ChatComponentState implements ChatComponentState { - const factory _ChatComponentState( - {required final GlobalKey chatKey, - required final AutoScrollController scrollController, - required final InputTextFieldController textEditingController, - required final User? localUser, - required final IMap, User> remoteUsers, - required final IMap, User> - historicalRemoteUsers, - required final IMap, User> unknownUsers, - required final AsyncValue> messageWindow, - required final String title}) = _$ChatComponentStateImpl; - -// GlobalKey for the chat - @override - GlobalKey get chatKey; // ScrollController for the chat - @override - AutoScrollController - get scrollController; // TextEditingController for the chat - @override - InputTextFieldController get textEditingController; // Local user - @override - User? get localUser; // Active remote users - @override - IMap, User> - get remoteUsers; // Historical remote users - @override - IMap, User> - get historicalRemoteUsers; // Unknown users - @override - IMap, User> get unknownUsers; // Messages state - @override - AsyncValue> get messageWindow; // Title of the chat - @override - String get title; + $Res call({ + Object? chatKey = null, + Object? scrollController = null, + Object? textEditingController = null, + Object? localUser = freezed, + Object? remoteUsers = null, + Object? historicalRemoteUsers = null, + Object? unknownUsers = null, + Object? messageWindow = null, + Object? title = null, + }) { + return _then(_ChatComponentState( + chatKey: null == chatKey + ? _self.chatKey + : chatKey // ignore: cast_nullable_to_non_nullable + as GlobalKey, + scrollController: null == scrollController + ? _self.scrollController + : scrollController // ignore: cast_nullable_to_non_nullable + as AutoScrollController, + textEditingController: null == textEditingController + ? _self.textEditingController + : textEditingController // ignore: cast_nullable_to_non_nullable + as InputTextFieldController, + localUser: freezed == localUser + ? _self.localUser + : localUser // ignore: cast_nullable_to_non_nullable + as User?, + remoteUsers: null == remoteUsers + ? _self.remoteUsers + : remoteUsers // ignore: cast_nullable_to_non_nullable + as IMap, User>, + historicalRemoteUsers: null == historicalRemoteUsers + ? _self.historicalRemoteUsers + : historicalRemoteUsers // ignore: cast_nullable_to_non_nullable + as IMap, User>, + unknownUsers: null == unknownUsers + ? _self.unknownUsers + : unknownUsers // ignore: cast_nullable_to_non_nullable + as IMap, User>, + messageWindow: null == messageWindow + ? _self.messageWindow + : messageWindow // ignore: cast_nullable_to_non_nullable + as AsyncValue>, + title: null == title + ? _self.title + : title // ignore: cast_nullable_to_non_nullable + as String, + )); + } /// Create a copy of ChatComponentState /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$ChatComponentStateImplCopyWith<_$ChatComponentStateImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + $AsyncValueCopyWith, $Res> get messageWindow { + return $AsyncValueCopyWith, $Res>(_self.messageWindow, + (value) { + return _then(_self.copyWith(messageWindow: value)); + }); + } } + +// dart format on diff --git a/lib/chat/models/message_state.dart b/lib/chat/models/message_state.dart index 8eacc8e..cf82021 100644 --- a/lib/chat/models/message_state.dart +++ b/lib/chat/models/message_state.dart @@ -24,7 +24,7 @@ enum MessageSendState { } @freezed -class MessageState with _$MessageState { +sealed class MessageState with _$MessageState { const factory MessageState({ // Content of the message @JsonKey(fromJson: messageFromJson, toJson: messageToJson) diff --git a/lib/chat/models/message_state.freezed.dart b/lib/chat/models/message_state.freezed.dart index baafea6..0900f8b 100644 --- a/lib/chat/models/message_state.freezed.dart +++ b/lib/chat/models/message_state.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,99 +10,69 @@ part of 'message_state.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -MessageState _$MessageStateFromJson(Map json) { - return _MessageState.fromJson(json); -} - /// @nodoc -mixin _$MessageState { +mixin _$MessageState implements DiagnosticableTreeMixin { // Content of the message @JsonKey(fromJson: messageFromJson, toJson: messageToJson) - proto.Message get content => - throw _privateConstructorUsedError; // Sent timestamp - Timestamp get sentTimestamp => - throw _privateConstructorUsedError; // Reconciled timestamp - Timestamp? get reconciledTimestamp => - throw _privateConstructorUsedError; // The state of the message - MessageSendState? get sendState => throw _privateConstructorUsedError; - - /// Serializes this MessageState to a JSON map. - Map toJson() => throw _privateConstructorUsedError; + proto.Message get content; // Sent timestamp + Timestamp get sentTimestamp; // Reconciled timestamp + Timestamp? get reconciledTimestamp; // The state of the message + MessageSendState? get sendState; /// Create a copy of MessageState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $MessageStateCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $MessageStateCopyWith<$Res> { - factory $MessageStateCopyWith( - MessageState value, $Res Function(MessageState) then) = - _$MessageStateCopyWithImpl<$Res, MessageState>; - @useResult - $Res call( - {@JsonKey(fromJson: messageFromJson, toJson: messageToJson) - proto.Message content, - Timestamp sentTimestamp, - Timestamp? reconciledTimestamp, - MessageSendState? sendState}); -} - -/// @nodoc -class _$MessageStateCopyWithImpl<$Res, $Val extends MessageState> - implements $MessageStateCopyWith<$Res> { - _$MessageStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of MessageState - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') + $MessageStateCopyWith get copyWith => + _$MessageStateCopyWithImpl( + this as MessageState, _$identity); + + /// Serializes this MessageState to a JSON map. + Map toJson(); + @override - $Res call({ - Object? content = null, - Object? sentTimestamp = null, - Object? reconciledTimestamp = freezed, - Object? sendState = freezed, - }) { - return _then(_value.copyWith( - content: null == content - ? _value.content - : content // ignore: cast_nullable_to_non_nullable - as proto.Message, - sentTimestamp: null == sentTimestamp - ? _value.sentTimestamp - : sentTimestamp // ignore: cast_nullable_to_non_nullable - as Timestamp, - reconciledTimestamp: freezed == reconciledTimestamp - ? _value.reconciledTimestamp - : reconciledTimestamp // ignore: cast_nullable_to_non_nullable - as Timestamp?, - sendState: freezed == sendState - ? _value.sendState - : sendState // ignore: cast_nullable_to_non_nullable - as MessageSendState?, - ) as $Val); + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'MessageState')) + ..add(DiagnosticsProperty('content', content)) + ..add(DiagnosticsProperty('sentTimestamp', sentTimestamp)) + ..add(DiagnosticsProperty('reconciledTimestamp', reconciledTimestamp)) + ..add(DiagnosticsProperty('sendState', sendState)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is MessageState && + (identical(other.content, content) || other.content == content) && + (identical(other.sentTimestamp, sentTimestamp) || + other.sentTimestamp == sentTimestamp) && + (identical(other.reconciledTimestamp, reconciledTimestamp) || + other.reconciledTimestamp == reconciledTimestamp) && + (identical(other.sendState, sendState) || + other.sendState == sendState)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, content, sentTimestamp, reconciledTimestamp, sendState); + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'MessageState(content: $content, sentTimestamp: $sentTimestamp, reconciledTimestamp: $reconciledTimestamp, sendState: $sendState)'; } } /// @nodoc -abstract class _$$MessageStateImplCopyWith<$Res> - implements $MessageStateCopyWith<$Res> { - factory _$$MessageStateImplCopyWith( - _$MessageStateImpl value, $Res Function(_$MessageStateImpl) then) = - __$$MessageStateImplCopyWithImpl<$Res>; - @override +abstract mixin class $MessageStateCopyWith<$Res> { + factory $MessageStateCopyWith( + MessageState value, $Res Function(MessageState) _then) = + _$MessageStateCopyWithImpl; @useResult $Res call( {@JsonKey(fromJson: messageFromJson, toJson: messageToJson) @@ -112,12 +83,11 @@ abstract class _$$MessageStateImplCopyWith<$Res> } /// @nodoc -class __$$MessageStateImplCopyWithImpl<$Res> - extends _$MessageStateCopyWithImpl<$Res, _$MessageStateImpl> - implements _$$MessageStateImplCopyWith<$Res> { - __$$MessageStateImplCopyWithImpl( - _$MessageStateImpl _value, $Res Function(_$MessageStateImpl) _then) - : super(_value, _then); +class _$MessageStateCopyWithImpl<$Res> implements $MessageStateCopyWith<$Res> { + _$MessageStateCopyWithImpl(this._self, this._then); + + final MessageState _self; + final $Res Function(MessageState) _then; /// Create a copy of MessageState /// with the given fields replaced by the non-null parameter values. @@ -129,21 +99,21 @@ class __$$MessageStateImplCopyWithImpl<$Res> Object? reconciledTimestamp = freezed, Object? sendState = freezed, }) { - return _then(_$MessageStateImpl( + return _then(_self.copyWith( content: null == content - ? _value.content + ? _self.content : content // ignore: cast_nullable_to_non_nullable as proto.Message, sentTimestamp: null == sentTimestamp - ? _value.sentTimestamp + ? _self.sentTimestamp : sentTimestamp // ignore: cast_nullable_to_non_nullable as Timestamp, reconciledTimestamp: freezed == reconciledTimestamp - ? _value.reconciledTimestamp + ? _self.reconciledTimestamp : reconciledTimestamp // ignore: cast_nullable_to_non_nullable as Timestamp?, sendState: freezed == sendState - ? _value.sendState + ? _self.sendState : sendState // ignore: cast_nullable_to_non_nullable as MessageSendState?, )); @@ -152,16 +122,15 @@ class __$$MessageStateImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$MessageStateImpl with DiagnosticableTreeMixin implements _MessageState { - const _$MessageStateImpl( +class _MessageState with DiagnosticableTreeMixin implements MessageState { + const _MessageState( {@JsonKey(fromJson: messageFromJson, toJson: messageToJson) required this.content, required this.sentTimestamp, required this.reconciledTimestamp, required this.sendState}); - - factory _$MessageStateImpl.fromJson(Map json) => - _$$MessageStateImplFromJson(json); + factory _MessageState.fromJson(Map json) => + _$MessageStateFromJson(json); // Content of the message @override @@ -177,14 +146,23 @@ class _$MessageStateImpl with DiagnosticableTreeMixin implements _MessageState { @override final MessageSendState? sendState; + /// Create a copy of MessageState + /// with the given fields replaced by the non-null parameter values. @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'MessageState(content: $content, sentTimestamp: $sentTimestamp, reconciledTimestamp: $reconciledTimestamp, sendState: $sendState)'; + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$MessageStateCopyWith<_MessageState> get copyWith => + __$MessageStateCopyWithImpl<_MessageState>(this, _$identity); + + @override + Map toJson() { + return _$MessageStateToJson( + this, + ); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); properties ..add(DiagnosticsProperty('type', 'MessageState')) ..add(DiagnosticsProperty('content', content)) @@ -197,7 +175,7 @@ class _$MessageStateImpl with DiagnosticableTreeMixin implements _MessageState { bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$MessageStateImpl && + other is _MessageState && (identical(other.content, content) || other.content == content) && (identical(other.sentTimestamp, sentTimestamp) || other.sentTimestamp == sentTimestamp) && @@ -212,48 +190,65 @@ class _$MessageStateImpl with DiagnosticableTreeMixin implements _MessageState { int get hashCode => Object.hash( runtimeType, content, sentTimestamp, reconciledTimestamp, sendState); - /// Create a copy of MessageState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$MessageStateImplCopyWith<_$MessageStateImpl> get copyWith => - __$$MessageStateImplCopyWithImpl<_$MessageStateImpl>(this, _$identity); - - @override - Map toJson() { - return _$$MessageStateImplToJson( - this, - ); + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'MessageState(content: $content, sentTimestamp: $sentTimestamp, reconciledTimestamp: $reconciledTimestamp, sendState: $sendState)'; } } -abstract class _MessageState implements MessageState { - const factory _MessageState( +/// @nodoc +abstract mixin class _$MessageStateCopyWith<$Res> + implements $MessageStateCopyWith<$Res> { + factory _$MessageStateCopyWith( + _MessageState value, $Res Function(_MessageState) _then) = + __$MessageStateCopyWithImpl; + @override + @useResult + $Res call( {@JsonKey(fromJson: messageFromJson, toJson: messageToJson) - required final proto.Message content, - required final Timestamp sentTimestamp, - required final Timestamp? reconciledTimestamp, - required final MessageSendState? sendState}) = _$MessageStateImpl; + proto.Message content, + Timestamp sentTimestamp, + Timestamp? reconciledTimestamp, + MessageSendState? sendState}); +} - factory _MessageState.fromJson(Map json) = - _$MessageStateImpl.fromJson; +/// @nodoc +class __$MessageStateCopyWithImpl<$Res> + implements _$MessageStateCopyWith<$Res> { + __$MessageStateCopyWithImpl(this._self, this._then); -// Content of the message - @override - @JsonKey(fromJson: messageFromJson, toJson: messageToJson) - proto.Message get content; // Sent timestamp - @override - Timestamp get sentTimestamp; // Reconciled timestamp - @override - Timestamp? get reconciledTimestamp; // The state of the message - @override - MessageSendState? get sendState; + final _MessageState _self; + final $Res Function(_MessageState) _then; /// Create a copy of MessageState /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$MessageStateImplCopyWith<_$MessageStateImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + $Res call({ + Object? content = null, + Object? sentTimestamp = null, + Object? reconciledTimestamp = freezed, + Object? sendState = freezed, + }) { + return _then(_MessageState( + content: null == content + ? _self.content + : content // ignore: cast_nullable_to_non_nullable + as proto.Message, + sentTimestamp: null == sentTimestamp + ? _self.sentTimestamp + : sentTimestamp // ignore: cast_nullable_to_non_nullable + as Timestamp, + reconciledTimestamp: freezed == reconciledTimestamp + ? _self.reconciledTimestamp + : reconciledTimestamp // ignore: cast_nullable_to_non_nullable + as Timestamp?, + sendState: freezed == sendState + ? _self.sendState + : sendState // ignore: cast_nullable_to_non_nullable + as MessageSendState?, + )); + } } + +// dart format on diff --git a/lib/chat/models/message_state.g.dart b/lib/chat/models/message_state.g.dart index 99899a7..daae37f 100644 --- a/lib/chat/models/message_state.g.dart +++ b/lib/chat/models/message_state.g.dart @@ -6,8 +6,8 @@ part of 'message_state.dart'; // JsonSerializableGenerator // ************************************************************************** -_$MessageStateImpl _$$MessageStateImplFromJson(Map json) => - _$MessageStateImpl( +_MessageState _$MessageStateFromJson(Map json) => + _MessageState( content: messageFromJson(json['content'] as Map), sentTimestamp: Timestamp.fromJson(json['sent_timestamp']), reconciledTimestamp: json['reconciled_timestamp'] == null @@ -18,7 +18,7 @@ _$MessageStateImpl _$$MessageStateImplFromJson(Map json) => : MessageSendState.fromJson(json['send_state']), ); -Map _$$MessageStateImplToJson(_$MessageStateImpl instance) => +Map _$MessageStateToJson(_MessageState instance) => { 'content': messageToJson(instance.content), 'sent_timestamp': instance.sentTimestamp.toJson(), diff --git a/lib/chat/models/window_state.dart b/lib/chat/models/window_state.dart index 91cde8a..14a94a5 100644 --- a/lib/chat/models/window_state.dart +++ b/lib/chat/models/window_state.dart @@ -5,7 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'window_state.freezed.dart'; @freezed -class WindowState with _$WindowState { +sealed class WindowState with _$WindowState { const factory WindowState({ // List of objects in the window required IList window, diff --git a/lib/chat/models/window_state.freezed.dart b/lib/chat/models/window_state.freezed.dart index 59ff754..38a2ec1 100644 --- a/lib/chat/models/window_state.freezed.dart +++ b/lib/chat/models/window_state.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,98 +10,71 @@ part of 'window_state.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - /// @nodoc -mixin _$WindowState { +mixin _$WindowState implements DiagnosticableTreeMixin { // List of objects in the window - IList get window => - throw _privateConstructorUsedError; // Total number of objects (windowTail max) - int get length => - throw _privateConstructorUsedError; // One past the end of the last element - int get windowTail => - throw _privateConstructorUsedError; // The total number of elements to try to keep in the window - int get windowCount => - throw _privateConstructorUsedError; // If we should have the tail following the array - bool get follow => throw _privateConstructorUsedError; + IList get window; // Total number of objects (windowTail max) + int get length; // One past the end of the last element + int get windowTail; // The total number of elements to try to keep in the window + int get windowCount; // If we should have the tail following the array + bool get follow; /// Create a copy of WindowState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $WindowStateCopyWith> get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $WindowStateCopyWith { - factory $WindowStateCopyWith( - WindowState value, $Res Function(WindowState) then) = - _$WindowStateCopyWithImpl>; - @useResult - $Res call( - {IList window, - int length, - int windowTail, - int windowCount, - bool follow}); -} - -/// @nodoc -class _$WindowStateCopyWithImpl> - implements $WindowStateCopyWith { - _$WindowStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of WindowState - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') + $WindowStateCopyWith> get copyWith => + _$WindowStateCopyWithImpl>( + this as WindowState, _$identity); + @override - $Res call({ - Object? window = null, - Object? length = null, - Object? windowTail = null, - Object? windowCount = null, - Object? follow = null, - }) { - return _then(_value.copyWith( - window: null == window - ? _value.window - : window // ignore: cast_nullable_to_non_nullable - as IList, - length: null == length - ? _value.length - : length // ignore: cast_nullable_to_non_nullable - as int, - windowTail: null == windowTail - ? _value.windowTail - : windowTail // ignore: cast_nullable_to_non_nullable - as int, - windowCount: null == windowCount - ? _value.windowCount - : windowCount // ignore: cast_nullable_to_non_nullable - as int, - follow: null == follow - ? _value.follow - : follow // ignore: cast_nullable_to_non_nullable - as bool, - ) as $Val); + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'WindowState<$T>')) + ..add(DiagnosticsProperty('window', window)) + ..add(DiagnosticsProperty('length', length)) + ..add(DiagnosticsProperty('windowTail', windowTail)) + ..add(DiagnosticsProperty('windowCount', windowCount)) + ..add(DiagnosticsProperty('follow', follow)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is WindowState && + const DeepCollectionEquality().equals(other.window, window) && + (identical(other.length, length) || other.length == length) && + (identical(other.windowTail, windowTail) || + other.windowTail == windowTail) && + (identical(other.windowCount, windowCount) || + other.windowCount == windowCount) && + (identical(other.follow, follow) || other.follow == follow)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(window), + length, + windowTail, + windowCount, + follow); + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'WindowState<$T>(window: $window, length: $length, windowTail: $windowTail, windowCount: $windowCount, follow: $follow)'; } } /// @nodoc -abstract class _$$WindowStateImplCopyWith - implements $WindowStateCopyWith { - factory _$$WindowStateImplCopyWith(_$WindowStateImpl value, - $Res Function(_$WindowStateImpl) then) = - __$$WindowStateImplCopyWithImpl; - @override +abstract mixin class $WindowStateCopyWith { + factory $WindowStateCopyWith( + WindowState value, $Res Function(WindowState) _then) = + _$WindowStateCopyWithImpl; @useResult $Res call( {IList window, @@ -111,12 +85,12 @@ abstract class _$$WindowStateImplCopyWith } /// @nodoc -class __$$WindowStateImplCopyWithImpl - extends _$WindowStateCopyWithImpl> - implements _$$WindowStateImplCopyWith { - __$$WindowStateImplCopyWithImpl( - _$WindowStateImpl _value, $Res Function(_$WindowStateImpl) _then) - : super(_value, _then); +class _$WindowStateCopyWithImpl + implements $WindowStateCopyWith { + _$WindowStateCopyWithImpl(this._self, this._then); + + final WindowState _self; + final $Res Function(WindowState) _then; /// Create a copy of WindowState /// with the given fields replaced by the non-null parameter values. @@ -129,25 +103,25 @@ class __$$WindowStateImplCopyWithImpl Object? windowCount = null, Object? follow = null, }) { - return _then(_$WindowStateImpl( + return _then(_self.copyWith( window: null == window - ? _value.window + ? _self.window : window // ignore: cast_nullable_to_non_nullable as IList, length: null == length - ? _value.length + ? _self.length : length // ignore: cast_nullable_to_non_nullable as int, windowTail: null == windowTail - ? _value.windowTail + ? _self.windowTail : windowTail // ignore: cast_nullable_to_non_nullable as int, windowCount: null == windowCount - ? _value.windowCount + ? _self.windowCount : windowCount // ignore: cast_nullable_to_non_nullable as int, follow: null == follow - ? _value.follow + ? _self.follow : follow // ignore: cast_nullable_to_non_nullable as bool, )); @@ -156,10 +130,8 @@ class __$$WindowStateImplCopyWithImpl /// @nodoc -class _$WindowStateImpl - with DiagnosticableTreeMixin - implements _WindowState { - const _$WindowStateImpl( +class _WindowState with DiagnosticableTreeMixin implements WindowState { + const _WindowState( {required this.window, required this.length, required this.windowTail, @@ -182,14 +154,16 @@ class _$WindowStateImpl @override final bool follow; + /// Create a copy of WindowState + /// with the given fields replaced by the non-null parameter values. @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'WindowState<$T>(window: $window, length: $length, windowTail: $windowTail, windowCount: $windowCount, follow: $follow)'; - } + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$WindowStateCopyWith> get copyWith => + __$WindowStateCopyWithImpl>(this, _$identity); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); properties ..add(DiagnosticsProperty('type', 'WindowState<$T>')) ..add(DiagnosticsProperty('window', window)) @@ -203,7 +177,7 @@ class _$WindowStateImpl bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$WindowStateImpl && + other is _WindowState && const DeepCollectionEquality().equals(other.window, window) && (identical(other.length, length) || other.length == length) && (identical(other.windowTail, windowTail) || @@ -222,40 +196,70 @@ class _$WindowStateImpl windowCount, follow); + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'WindowState<$T>(window: $window, length: $length, windowTail: $windowTail, windowCount: $windowCount, follow: $follow)'; + } +} + +/// @nodoc +abstract mixin class _$WindowStateCopyWith + implements $WindowStateCopyWith { + factory _$WindowStateCopyWith( + _WindowState value, $Res Function(_WindowState) _then) = + __$WindowStateCopyWithImpl; + @override + @useResult + $Res call( + {IList window, + int length, + int windowTail, + int windowCount, + bool follow}); +} + +/// @nodoc +class __$WindowStateCopyWithImpl + implements _$WindowStateCopyWith { + __$WindowStateCopyWithImpl(this._self, this._then); + + final _WindowState _self; + final $Res Function(_WindowState) _then; + /// Create a copy of WindowState /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$WindowStateImplCopyWith> get copyWith => - __$$WindowStateImplCopyWithImpl>( - this, _$identity); + $Res call({ + Object? window = null, + Object? length = null, + Object? windowTail = null, + Object? windowCount = null, + Object? follow = null, + }) { + return _then(_WindowState( + window: null == window + ? _self.window + : window // ignore: cast_nullable_to_non_nullable + as IList, + length: null == length + ? _self.length + : length // ignore: cast_nullable_to_non_nullable + as int, + windowTail: null == windowTail + ? _self.windowTail + : windowTail // ignore: cast_nullable_to_non_nullable + as int, + windowCount: null == windowCount + ? _self.windowCount + : windowCount // ignore: cast_nullable_to_non_nullable + as int, + follow: null == follow + ? _self.follow + : follow // ignore: cast_nullable_to_non_nullable + as bool, + )); + } } -abstract class _WindowState implements WindowState { - const factory _WindowState( - {required final IList window, - required final int length, - required final int windowTail, - required final int windowCount, - required final bool follow}) = _$WindowStateImpl; - -// List of objects in the window - @override - IList get window; // Total number of objects (windowTail max) - @override - int get length; // One past the end of the last element - @override - int get windowTail; // The total number of elements to try to keep in the window - @override - int get windowCount; // If we should have the tail following the array - @override - bool get follow; - - /// Create a copy of WindowState - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$WindowStateImplCopyWith> get copyWith => - throw _privateConstructorUsedError; -} +// dart format on diff --git a/lib/chat_list/cubits/chat_list_cubit.dart b/lib/chat_list/cubits/chat_list_cubit.dart index 3ae3db1..ae31f29 100644 --- a/lib/chat_list/cubits/chat_list_cubit.dart +++ b/lib/chat_list/cubits/chat_list_cubit.dart @@ -13,7 +13,7 @@ import '../../proto/proto.dart' as proto; ////////////////////////////////////////////////// // Mutable state for per-account chat list -typedef ChatListCubitState = DHTShortArrayBusyState; +typedef ChatListCubitState = DHTShortArrayCubitState; class ChatListCubit extends DHTShortArrayCubit with StateMapFollowable { diff --git a/lib/chat_list/views/chat_list_widget.dart b/lib/chat_list/views/chat_list_widget.dart index 73bd7e6..cce416c 100644 --- a/lib/chat_list/views/chat_list_widget.dart +++ b/lib/chat_list/views/chat_list_widget.dart @@ -8,7 +8,6 @@ import 'package:veilid_support/veilid_support.dart'; import '../../contacts/contacts.dart'; import '../../proto/proto.dart' as proto; -import '../../proto/proto.dart'; import '../../theme/theme.dart'; import '../chat_list.dart'; @@ -26,7 +25,7 @@ class ChatListWidget extends StatelessWidget { } List _itemFilter(IMap contactMap, - IList> chatList, String filter) { + IList> chatList, String filter) { final lowerValue = filter.toLowerCase(); return chatList.map((x) => x.value).where((c) { switch (c.whichKind()) { @@ -48,7 +47,6 @@ class ChatListWidget extends StatelessWidget { } @override - // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { final contactListV = context.watch().state; diff --git a/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart b/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart index 768cf2f..332341d 100644 --- a/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart +++ b/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart @@ -27,7 +27,7 @@ typedef GetEncryptionKeyCallback = Future Function( ////////////////////////////////////////////////// typedef ContactInvitiationListState - = DHTShortArrayBusyState; + = DHTShortArrayCubitState; ////////////////////////////////////////////////// // Mutable state for per-account contact invitations diff --git a/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart b/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart index 953575d..197ba8c 100644 --- a/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart +++ b/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart @@ -18,7 +18,7 @@ typedef WaitingInvitationsBlocMapState class WaitingInvitationsBlocMapCubit extends BlocMapCubit, WaitingInvitationCubit> with - StateMapFollower, + StateMapFollower, TypedKey, proto.ContactInvitationRecord> { WaitingInvitationsBlocMapCubit( {required AccountInfo accountInfo, diff --git a/lib/notifications/models/notifications_preference.dart b/lib/notifications/models/notifications_preference.dart index 35385c6..913cb25 100644 --- a/lib/notifications/models/notifications_preference.dart +++ b/lib/notifications/models/notifications_preference.dart @@ -5,7 +5,7 @@ part 'notifications_preference.freezed.dart'; part 'notifications_preference.g.dart'; @freezed -class NotificationsPreference with _$NotificationsPreference { +sealed class NotificationsPreference with _$NotificationsPreference { const factory NotificationsPreference({ @Default(true) bool displayBetaWarning, @Default(true) bool enableBadge, diff --git a/lib/notifications/models/notifications_preference.freezed.dart b/lib/notifications/models/notifications_preference.freezed.dart index 0335ad8..55f700a 100644 --- a/lib/notifications/models/notifications_preference.freezed.dart +++ b/lib/notifications/models/notifications_preference.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,270 +10,37 @@ part of 'notifications_preference.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -NotificationsPreference _$NotificationsPreferenceFromJson( - Map json) { - return _NotificationsPreference.fromJson(json); -} - /// @nodoc mixin _$NotificationsPreference { - bool get displayBetaWarning => throw _privateConstructorUsedError; - bool get enableBadge => throw _privateConstructorUsedError; - bool get enableNotifications => throw _privateConstructorUsedError; - MessageNotificationContent get messageNotificationContent => - throw _privateConstructorUsedError; - NotificationMode get onInvitationAcceptedMode => - throw _privateConstructorUsedError; - SoundEffect get onInvitationAcceptedSound => - throw _privateConstructorUsedError; - NotificationMode get onMessageReceivedMode => - throw _privateConstructorUsedError; - SoundEffect get onMessageReceivedSound => throw _privateConstructorUsedError; - SoundEffect get onMessageSentSound => throw _privateConstructorUsedError; - - /// Serializes this NotificationsPreference to a JSON map. - Map toJson() => throw _privateConstructorUsedError; + bool get displayBetaWarning; + bool get enableBadge; + bool get enableNotifications; + MessageNotificationContent get messageNotificationContent; + NotificationMode get onInvitationAcceptedMode; + SoundEffect get onInvitationAcceptedSound; + NotificationMode get onMessageReceivedMode; + SoundEffect get onMessageReceivedSound; + SoundEffect get onMessageSentSound; /// Create a copy of NotificationsPreference /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $NotificationsPreferenceCopyWith get copyWith => - throw _privateConstructorUsedError; -} + _$NotificationsPreferenceCopyWithImpl( + this as NotificationsPreference, _$identity); -/// @nodoc -abstract class $NotificationsPreferenceCopyWith<$Res> { - factory $NotificationsPreferenceCopyWith(NotificationsPreference value, - $Res Function(NotificationsPreference) then) = - _$NotificationsPreferenceCopyWithImpl<$Res, NotificationsPreference>; - @useResult - $Res call( - {bool displayBetaWarning, - bool enableBadge, - bool enableNotifications, - MessageNotificationContent messageNotificationContent, - NotificationMode onInvitationAcceptedMode, - SoundEffect onInvitationAcceptedSound, - NotificationMode onMessageReceivedMode, - SoundEffect onMessageReceivedSound, - SoundEffect onMessageSentSound}); -} - -/// @nodoc -class _$NotificationsPreferenceCopyWithImpl<$Res, - $Val extends NotificationsPreference> - implements $NotificationsPreferenceCopyWith<$Res> { - _$NotificationsPreferenceCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of NotificationsPreference - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? displayBetaWarning = null, - Object? enableBadge = null, - Object? enableNotifications = null, - Object? messageNotificationContent = null, - Object? onInvitationAcceptedMode = null, - Object? onInvitationAcceptedSound = null, - Object? onMessageReceivedMode = null, - Object? onMessageReceivedSound = null, - Object? onMessageSentSound = null, - }) { - return _then(_value.copyWith( - displayBetaWarning: null == displayBetaWarning - ? _value.displayBetaWarning - : displayBetaWarning // ignore: cast_nullable_to_non_nullable - as bool, - enableBadge: null == enableBadge - ? _value.enableBadge - : enableBadge // ignore: cast_nullable_to_non_nullable - as bool, - enableNotifications: null == enableNotifications - ? _value.enableNotifications - : enableNotifications // ignore: cast_nullable_to_non_nullable - as bool, - messageNotificationContent: null == messageNotificationContent - ? _value.messageNotificationContent - : messageNotificationContent // ignore: cast_nullable_to_non_nullable - as MessageNotificationContent, - onInvitationAcceptedMode: null == onInvitationAcceptedMode - ? _value.onInvitationAcceptedMode - : onInvitationAcceptedMode // ignore: cast_nullable_to_non_nullable - as NotificationMode, - onInvitationAcceptedSound: null == onInvitationAcceptedSound - ? _value.onInvitationAcceptedSound - : onInvitationAcceptedSound // ignore: cast_nullable_to_non_nullable - as SoundEffect, - onMessageReceivedMode: null == onMessageReceivedMode - ? _value.onMessageReceivedMode - : onMessageReceivedMode // ignore: cast_nullable_to_non_nullable - as NotificationMode, - onMessageReceivedSound: null == onMessageReceivedSound - ? _value.onMessageReceivedSound - : onMessageReceivedSound // ignore: cast_nullable_to_non_nullable - as SoundEffect, - onMessageSentSound: null == onMessageSentSound - ? _value.onMessageSentSound - : onMessageSentSound // ignore: cast_nullable_to_non_nullable - as SoundEffect, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$NotificationsPreferenceImplCopyWith<$Res> - implements $NotificationsPreferenceCopyWith<$Res> { - factory _$$NotificationsPreferenceImplCopyWith( - _$NotificationsPreferenceImpl value, - $Res Function(_$NotificationsPreferenceImpl) then) = - __$$NotificationsPreferenceImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {bool displayBetaWarning, - bool enableBadge, - bool enableNotifications, - MessageNotificationContent messageNotificationContent, - NotificationMode onInvitationAcceptedMode, - SoundEffect onInvitationAcceptedSound, - NotificationMode onMessageReceivedMode, - SoundEffect onMessageReceivedSound, - SoundEffect onMessageSentSound}); -} - -/// @nodoc -class __$$NotificationsPreferenceImplCopyWithImpl<$Res> - extends _$NotificationsPreferenceCopyWithImpl<$Res, - _$NotificationsPreferenceImpl> - implements _$$NotificationsPreferenceImplCopyWith<$Res> { - __$$NotificationsPreferenceImplCopyWithImpl( - _$NotificationsPreferenceImpl _value, - $Res Function(_$NotificationsPreferenceImpl) _then) - : super(_value, _then); - - /// Create a copy of NotificationsPreference - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? displayBetaWarning = null, - Object? enableBadge = null, - Object? enableNotifications = null, - Object? messageNotificationContent = null, - Object? onInvitationAcceptedMode = null, - Object? onInvitationAcceptedSound = null, - Object? onMessageReceivedMode = null, - Object? onMessageReceivedSound = null, - Object? onMessageSentSound = null, - }) { - return _then(_$NotificationsPreferenceImpl( - displayBetaWarning: null == displayBetaWarning - ? _value.displayBetaWarning - : displayBetaWarning // ignore: cast_nullable_to_non_nullable - as bool, - enableBadge: null == enableBadge - ? _value.enableBadge - : enableBadge // ignore: cast_nullable_to_non_nullable - as bool, - enableNotifications: null == enableNotifications - ? _value.enableNotifications - : enableNotifications // ignore: cast_nullable_to_non_nullable - as bool, - messageNotificationContent: null == messageNotificationContent - ? _value.messageNotificationContent - : messageNotificationContent // ignore: cast_nullable_to_non_nullable - as MessageNotificationContent, - onInvitationAcceptedMode: null == onInvitationAcceptedMode - ? _value.onInvitationAcceptedMode - : onInvitationAcceptedMode // ignore: cast_nullable_to_non_nullable - as NotificationMode, - onInvitationAcceptedSound: null == onInvitationAcceptedSound - ? _value.onInvitationAcceptedSound - : onInvitationAcceptedSound // ignore: cast_nullable_to_non_nullable - as SoundEffect, - onMessageReceivedMode: null == onMessageReceivedMode - ? _value.onMessageReceivedMode - : onMessageReceivedMode // ignore: cast_nullable_to_non_nullable - as NotificationMode, - onMessageReceivedSound: null == onMessageReceivedSound - ? _value.onMessageReceivedSound - : onMessageReceivedSound // ignore: cast_nullable_to_non_nullable - as SoundEffect, - onMessageSentSound: null == onMessageSentSound - ? _value.onMessageSentSound - : onMessageSentSound // ignore: cast_nullable_to_non_nullable - as SoundEffect, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$NotificationsPreferenceImpl implements _NotificationsPreference { - const _$NotificationsPreferenceImpl( - {this.displayBetaWarning = true, - this.enableBadge = true, - this.enableNotifications = true, - this.messageNotificationContent = - MessageNotificationContent.nameAndContent, - this.onInvitationAcceptedMode = NotificationMode.inAppOrPush, - this.onInvitationAcceptedSound = SoundEffect.beepBaDeep, - this.onMessageReceivedMode = NotificationMode.inAppOrPush, - this.onMessageReceivedSound = SoundEffect.boop, - this.onMessageSentSound = SoundEffect.bonk}); - - factory _$NotificationsPreferenceImpl.fromJson(Map json) => - _$$NotificationsPreferenceImplFromJson(json); - - @override - @JsonKey() - final bool displayBetaWarning; - @override - @JsonKey() - final bool enableBadge; - @override - @JsonKey() - final bool enableNotifications; - @override - @JsonKey() - final MessageNotificationContent messageNotificationContent; - @override - @JsonKey() - final NotificationMode onInvitationAcceptedMode; - @override - @JsonKey() - final SoundEffect onInvitationAcceptedSound; - @override - @JsonKey() - final NotificationMode onMessageReceivedMode; - @override - @JsonKey() - final SoundEffect onMessageReceivedSound; - @override - @JsonKey() - final SoundEffect onMessageSentSound; - - @override - String toString() { - return 'NotificationsPreference(displayBetaWarning: $displayBetaWarning, enableBadge: $enableBadge, enableNotifications: $enableNotifications, messageNotificationContent: $messageNotificationContent, onInvitationAcceptedMode: $onInvitationAcceptedMode, onInvitationAcceptedSound: $onInvitationAcceptedSound, onMessageReceivedMode: $onMessageReceivedMode, onMessageReceivedSound: $onMessageReceivedSound, onMessageSentSound: $onMessageSentSound)'; - } + /// Serializes this NotificationsPreference to a JSON map. + Map toJson(); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$NotificationsPreferenceImpl && + other is NotificationsPreference && (identical(other.displayBetaWarning, displayBetaWarning) || other.displayBetaWarning == displayBetaWarning) && (identical(other.enableBadge, enableBadge) || @@ -311,61 +79,286 @@ class _$NotificationsPreferenceImpl implements _NotificationsPreference { onMessageReceivedSound, onMessageSentSound); - /// Create a copy of NotificationsPreference - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$NotificationsPreferenceImplCopyWith<_$NotificationsPreferenceImpl> - get copyWith => __$$NotificationsPreferenceImplCopyWithImpl< - _$NotificationsPreferenceImpl>(this, _$identity); - - @override - Map toJson() { - return _$$NotificationsPreferenceImplToJson( - this, - ); + String toString() { + return 'NotificationsPreference(displayBetaWarning: $displayBetaWarning, enableBadge: $enableBadge, enableNotifications: $enableNotifications, messageNotificationContent: $messageNotificationContent, onInvitationAcceptedMode: $onInvitationAcceptedMode, onInvitationAcceptedSound: $onInvitationAcceptedSound, onMessageReceivedMode: $onMessageReceivedMode, onMessageReceivedSound: $onMessageReceivedSound, onMessageSentSound: $onMessageSentSound)'; } } -abstract class _NotificationsPreference implements NotificationsPreference { - const factory _NotificationsPreference( - {final bool displayBetaWarning, - final bool enableBadge, - final bool enableNotifications, - final MessageNotificationContent messageNotificationContent, - final NotificationMode onInvitationAcceptedMode, - final SoundEffect onInvitationAcceptedSound, - final NotificationMode onMessageReceivedMode, - final SoundEffect onMessageReceivedSound, - final SoundEffect onMessageSentSound}) = _$NotificationsPreferenceImpl; +/// @nodoc +abstract mixin class $NotificationsPreferenceCopyWith<$Res> { + factory $NotificationsPreferenceCopyWith(NotificationsPreference value, + $Res Function(NotificationsPreference) _then) = + _$NotificationsPreferenceCopyWithImpl; + @useResult + $Res call( + {bool displayBetaWarning, + bool enableBadge, + bool enableNotifications, + MessageNotificationContent messageNotificationContent, + NotificationMode onInvitationAcceptedMode, + SoundEffect onInvitationAcceptedSound, + NotificationMode onMessageReceivedMode, + SoundEffect onMessageReceivedSound, + SoundEffect onMessageSentSound}); +} - factory _NotificationsPreference.fromJson(Map json) = - _$NotificationsPreferenceImpl.fromJson; +/// @nodoc +class _$NotificationsPreferenceCopyWithImpl<$Res> + implements $NotificationsPreferenceCopyWith<$Res> { + _$NotificationsPreferenceCopyWithImpl(this._self, this._then); + + final NotificationsPreference _self; + final $Res Function(NotificationsPreference) _then; + + /// Create a copy of NotificationsPreference + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? displayBetaWarning = null, + Object? enableBadge = null, + Object? enableNotifications = null, + Object? messageNotificationContent = null, + Object? onInvitationAcceptedMode = null, + Object? onInvitationAcceptedSound = null, + Object? onMessageReceivedMode = null, + Object? onMessageReceivedSound = null, + Object? onMessageSentSound = null, + }) { + return _then(_self.copyWith( + displayBetaWarning: null == displayBetaWarning + ? _self.displayBetaWarning + : displayBetaWarning // ignore: cast_nullable_to_non_nullable + as bool, + enableBadge: null == enableBadge + ? _self.enableBadge + : enableBadge // ignore: cast_nullable_to_non_nullable + as bool, + enableNotifications: null == enableNotifications + ? _self.enableNotifications + : enableNotifications // ignore: cast_nullable_to_non_nullable + as bool, + messageNotificationContent: null == messageNotificationContent + ? _self.messageNotificationContent + : messageNotificationContent // ignore: cast_nullable_to_non_nullable + as MessageNotificationContent, + onInvitationAcceptedMode: null == onInvitationAcceptedMode + ? _self.onInvitationAcceptedMode + : onInvitationAcceptedMode // ignore: cast_nullable_to_non_nullable + as NotificationMode, + onInvitationAcceptedSound: null == onInvitationAcceptedSound + ? _self.onInvitationAcceptedSound + : onInvitationAcceptedSound // ignore: cast_nullable_to_non_nullable + as SoundEffect, + onMessageReceivedMode: null == onMessageReceivedMode + ? _self.onMessageReceivedMode + : onMessageReceivedMode // ignore: cast_nullable_to_non_nullable + as NotificationMode, + onMessageReceivedSound: null == onMessageReceivedSound + ? _self.onMessageReceivedSound + : onMessageReceivedSound // ignore: cast_nullable_to_non_nullable + as SoundEffect, + onMessageSentSound: null == onMessageSentSound + ? _self.onMessageSentSound + : onMessageSentSound // ignore: cast_nullable_to_non_nullable + as SoundEffect, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _NotificationsPreference implements NotificationsPreference { + const _NotificationsPreference( + {this.displayBetaWarning = true, + this.enableBadge = true, + this.enableNotifications = true, + this.messageNotificationContent = + MessageNotificationContent.nameAndContent, + this.onInvitationAcceptedMode = NotificationMode.inAppOrPush, + this.onInvitationAcceptedSound = SoundEffect.beepBaDeep, + this.onMessageReceivedMode = NotificationMode.inAppOrPush, + this.onMessageReceivedSound = SoundEffect.boop, + this.onMessageSentSound = SoundEffect.bonk}); + factory _NotificationsPreference.fromJson(Map json) => + _$NotificationsPreferenceFromJson(json); @override - bool get displayBetaWarning; + @JsonKey() + final bool displayBetaWarning; @override - bool get enableBadge; + @JsonKey() + final bool enableBadge; @override - bool get enableNotifications; + @JsonKey() + final bool enableNotifications; @override - MessageNotificationContent get messageNotificationContent; + @JsonKey() + final MessageNotificationContent messageNotificationContent; @override - NotificationMode get onInvitationAcceptedMode; + @JsonKey() + final NotificationMode onInvitationAcceptedMode; @override - SoundEffect get onInvitationAcceptedSound; + @JsonKey() + final SoundEffect onInvitationAcceptedSound; @override - NotificationMode get onMessageReceivedMode; + @JsonKey() + final NotificationMode onMessageReceivedMode; @override - SoundEffect get onMessageReceivedSound; + @JsonKey() + final SoundEffect onMessageReceivedSound; @override - SoundEffect get onMessageSentSound; + @JsonKey() + final SoundEffect onMessageSentSound; /// Create a copy of NotificationsPreference /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$NotificationsPreferenceImplCopyWith<_$NotificationsPreferenceImpl> - get copyWith => throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + _$NotificationsPreferenceCopyWith<_NotificationsPreference> get copyWith => + __$NotificationsPreferenceCopyWithImpl<_NotificationsPreference>( + this, _$identity); + + @override + Map toJson() { + return _$NotificationsPreferenceToJson( + this, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _NotificationsPreference && + (identical(other.displayBetaWarning, displayBetaWarning) || + other.displayBetaWarning == displayBetaWarning) && + (identical(other.enableBadge, enableBadge) || + other.enableBadge == enableBadge) && + (identical(other.enableNotifications, enableNotifications) || + other.enableNotifications == enableNotifications) && + (identical(other.messageNotificationContent, + messageNotificationContent) || + other.messageNotificationContent == + messageNotificationContent) && + (identical( + other.onInvitationAcceptedMode, onInvitationAcceptedMode) || + other.onInvitationAcceptedMode == onInvitationAcceptedMode) && + (identical(other.onInvitationAcceptedSound, + onInvitationAcceptedSound) || + other.onInvitationAcceptedSound == onInvitationAcceptedSound) && + (identical(other.onMessageReceivedMode, onMessageReceivedMode) || + other.onMessageReceivedMode == onMessageReceivedMode) && + (identical(other.onMessageReceivedSound, onMessageReceivedSound) || + other.onMessageReceivedSound == onMessageReceivedSound) && + (identical(other.onMessageSentSound, onMessageSentSound) || + other.onMessageSentSound == onMessageSentSound)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + displayBetaWarning, + enableBadge, + enableNotifications, + messageNotificationContent, + onInvitationAcceptedMode, + onInvitationAcceptedSound, + onMessageReceivedMode, + onMessageReceivedSound, + onMessageSentSound); + + @override + String toString() { + return 'NotificationsPreference(displayBetaWarning: $displayBetaWarning, enableBadge: $enableBadge, enableNotifications: $enableNotifications, messageNotificationContent: $messageNotificationContent, onInvitationAcceptedMode: $onInvitationAcceptedMode, onInvitationAcceptedSound: $onInvitationAcceptedSound, onMessageReceivedMode: $onMessageReceivedMode, onMessageReceivedSound: $onMessageReceivedSound, onMessageSentSound: $onMessageSentSound)'; + } } + +/// @nodoc +abstract mixin class _$NotificationsPreferenceCopyWith<$Res> + implements $NotificationsPreferenceCopyWith<$Res> { + factory _$NotificationsPreferenceCopyWith(_NotificationsPreference value, + $Res Function(_NotificationsPreference) _then) = + __$NotificationsPreferenceCopyWithImpl; + @override + @useResult + $Res call( + {bool displayBetaWarning, + bool enableBadge, + bool enableNotifications, + MessageNotificationContent messageNotificationContent, + NotificationMode onInvitationAcceptedMode, + SoundEffect onInvitationAcceptedSound, + NotificationMode onMessageReceivedMode, + SoundEffect onMessageReceivedSound, + SoundEffect onMessageSentSound}); +} + +/// @nodoc +class __$NotificationsPreferenceCopyWithImpl<$Res> + implements _$NotificationsPreferenceCopyWith<$Res> { + __$NotificationsPreferenceCopyWithImpl(this._self, this._then); + + final _NotificationsPreference _self; + final $Res Function(_NotificationsPreference) _then; + + /// Create a copy of NotificationsPreference + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? displayBetaWarning = null, + Object? enableBadge = null, + Object? enableNotifications = null, + Object? messageNotificationContent = null, + Object? onInvitationAcceptedMode = null, + Object? onInvitationAcceptedSound = null, + Object? onMessageReceivedMode = null, + Object? onMessageReceivedSound = null, + Object? onMessageSentSound = null, + }) { + return _then(_NotificationsPreference( + displayBetaWarning: null == displayBetaWarning + ? _self.displayBetaWarning + : displayBetaWarning // ignore: cast_nullable_to_non_nullable + as bool, + enableBadge: null == enableBadge + ? _self.enableBadge + : enableBadge // ignore: cast_nullable_to_non_nullable + as bool, + enableNotifications: null == enableNotifications + ? _self.enableNotifications + : enableNotifications // ignore: cast_nullable_to_non_nullable + as bool, + messageNotificationContent: null == messageNotificationContent + ? _self.messageNotificationContent + : messageNotificationContent // ignore: cast_nullable_to_non_nullable + as MessageNotificationContent, + onInvitationAcceptedMode: null == onInvitationAcceptedMode + ? _self.onInvitationAcceptedMode + : onInvitationAcceptedMode // ignore: cast_nullable_to_non_nullable + as NotificationMode, + onInvitationAcceptedSound: null == onInvitationAcceptedSound + ? _self.onInvitationAcceptedSound + : onInvitationAcceptedSound // ignore: cast_nullable_to_non_nullable + as SoundEffect, + onMessageReceivedMode: null == onMessageReceivedMode + ? _self.onMessageReceivedMode + : onMessageReceivedMode // ignore: cast_nullable_to_non_nullable + as NotificationMode, + onMessageReceivedSound: null == onMessageReceivedSound + ? _self.onMessageReceivedSound + : onMessageReceivedSound // ignore: cast_nullable_to_non_nullable + as SoundEffect, + onMessageSentSound: null == onMessageSentSound + ? _self.onMessageSentSound + : onMessageSentSound // ignore: cast_nullable_to_non_nullable + as SoundEffect, + )); + } +} + +// dart format on diff --git a/lib/notifications/models/notifications_preference.g.dart b/lib/notifications/models/notifications_preference.g.dart index d22b4b5..d1d5e89 100644 --- a/lib/notifications/models/notifications_preference.g.dart +++ b/lib/notifications/models/notifications_preference.g.dart @@ -6,9 +6,9 @@ part of 'notifications_preference.dart'; // JsonSerializableGenerator // ************************************************************************** -_$NotificationsPreferenceImpl _$$NotificationsPreferenceImplFromJson( +_NotificationsPreference _$NotificationsPreferenceFromJson( Map json) => - _$NotificationsPreferenceImpl( + _NotificationsPreference( displayBetaWarning: json['display_beta_warning'] as bool? ?? true, enableBadge: json['enable_badge'] as bool? ?? true, enableNotifications: json['enable_notifications'] as bool? ?? true, @@ -33,8 +33,8 @@ _$NotificationsPreferenceImpl _$$NotificationsPreferenceImplFromJson( : SoundEffect.fromJson(json['on_message_sent_sound']), ); -Map _$$NotificationsPreferenceImplToJson( - _$NotificationsPreferenceImpl instance) => +Map _$NotificationsPreferenceToJson( + _NotificationsPreference instance) => { 'display_beta_warning': instance.displayBetaWarning, 'enable_badge': instance.enableBadge, diff --git a/lib/notifications/models/notifications_state.dart b/lib/notifications/models/notifications_state.dart index d001ce2..6248bba 100644 --- a/lib/notifications/models/notifications_state.dart +++ b/lib/notifications/models/notifications_state.dart @@ -9,7 +9,7 @@ enum NotificationType { } @freezed -class NotificationItem with _$NotificationItem { +sealed class NotificationItem with _$NotificationItem { const factory NotificationItem( {required NotificationType type, required String text, @@ -17,7 +17,7 @@ class NotificationItem with _$NotificationItem { } @freezed -class NotificationsState with _$NotificationsState { +sealed class NotificationsState with _$NotificationsState { const factory NotificationsState({required IList queue}) = _NotificationsState; } diff --git a/lib/notifications/models/notifications_state.freezed.dart b/lib/notifications/models/notifications_state.freezed.dart index e052e7a..8633702 100644 --- a/lib/notifications/models/notifications_state.freezed.dart +++ b/lib/notifications/models/notifications_state.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,137 +10,28 @@ part of 'notifications_state.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - /// @nodoc mixin _$NotificationItem { - NotificationType get type => throw _privateConstructorUsedError; - String get text => throw _privateConstructorUsedError; - String? get title => throw _privateConstructorUsedError; + NotificationType get type; + String get text; + String? get title; /// Create a copy of NotificationItem /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $NotificationItemCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $NotificationItemCopyWith<$Res> { - factory $NotificationItemCopyWith( - NotificationItem value, $Res Function(NotificationItem) then) = - _$NotificationItemCopyWithImpl<$Res, NotificationItem>; - @useResult - $Res call({NotificationType type, String text, String? title}); -} - -/// @nodoc -class _$NotificationItemCopyWithImpl<$Res, $Val extends NotificationItem> - implements $NotificationItemCopyWith<$Res> { - _$NotificationItemCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of NotificationItem - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? type = null, - Object? text = null, - Object? title = freezed, - }) { - return _then(_value.copyWith( - type: null == type - ? _value.type - : type // ignore: cast_nullable_to_non_nullable - as NotificationType, - text: null == text - ? _value.text - : text // ignore: cast_nullable_to_non_nullable - as String, - title: freezed == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as String?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$NotificationItemImplCopyWith<$Res> - implements $NotificationItemCopyWith<$Res> { - factory _$$NotificationItemImplCopyWith(_$NotificationItemImpl value, - $Res Function(_$NotificationItemImpl) then) = - __$$NotificationItemImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({NotificationType type, String text, String? title}); -} - -/// @nodoc -class __$$NotificationItemImplCopyWithImpl<$Res> - extends _$NotificationItemCopyWithImpl<$Res, _$NotificationItemImpl> - implements _$$NotificationItemImplCopyWith<$Res> { - __$$NotificationItemImplCopyWithImpl(_$NotificationItemImpl _value, - $Res Function(_$NotificationItemImpl) _then) - : super(_value, _then); - - /// Create a copy of NotificationItem - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? type = null, - Object? text = null, - Object? title = freezed, - }) { - return _then(_$NotificationItemImpl( - type: null == type - ? _value.type - : type // ignore: cast_nullable_to_non_nullable - as NotificationType, - text: null == text - ? _value.text - : text // ignore: cast_nullable_to_non_nullable - as String, - title: freezed == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as String?, - )); - } -} - -/// @nodoc - -class _$NotificationItemImpl implements _NotificationItem { - const _$NotificationItemImpl( - {required this.type, required this.text, this.title}); - - @override - final NotificationType type; - @override - final String text; - @override - final String? title; - - @override - String toString() { - return 'NotificationItem(type: $type, text: $text, title: $title)'; - } + _$NotificationItemCopyWithImpl( + this as NotificationItem, _$identity); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$NotificationItemImpl && + other is NotificationItem && (identical(other.type, type) || other.type == type) && (identical(other.text, text) || other.text == text) && (identical(other.title, title) || other.title == title)); @@ -148,101 +40,185 @@ class _$NotificationItemImpl implements _NotificationItem { @override int get hashCode => Object.hash(runtimeType, type, text, title); - /// Create a copy of NotificationItem - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$NotificationItemImplCopyWith<_$NotificationItemImpl> get copyWith => - __$$NotificationItemImplCopyWithImpl<_$NotificationItemImpl>( - this, _$identity); -} - -abstract class _NotificationItem implements NotificationItem { - const factory _NotificationItem( - {required final NotificationType type, - required final String text, - final String? title}) = _$NotificationItemImpl; - - @override - NotificationType get type; - @override - String get text; - @override - String? get title; - - /// Create a copy of NotificationItem - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$NotificationItemImplCopyWith<_$NotificationItemImpl> get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -mixin _$NotificationsState { - IList get queue => throw _privateConstructorUsedError; - - /// Create a copy of NotificationsState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $NotificationsStateCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $NotificationsStateCopyWith<$Res> { - factory $NotificationsStateCopyWith( - NotificationsState value, $Res Function(NotificationsState) then) = - _$NotificationsStateCopyWithImpl<$Res, NotificationsState>; - @useResult - $Res call({IList queue}); -} - -/// @nodoc -class _$NotificationsStateCopyWithImpl<$Res, $Val extends NotificationsState> - implements $NotificationsStateCopyWith<$Res> { - _$NotificationsStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of NotificationsState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? queue = null, - }) { - return _then(_value.copyWith( - queue: null == queue - ? _value.queue - : queue // ignore: cast_nullable_to_non_nullable - as IList, - ) as $Val); + String toString() { + return 'NotificationItem(type: $type, text: $text, title: $title)'; } } /// @nodoc -abstract class _$$NotificationsStateImplCopyWith<$Res> - implements $NotificationsStateCopyWith<$Res> { - factory _$$NotificationsStateImplCopyWith(_$NotificationsStateImpl value, - $Res Function(_$NotificationsStateImpl) then) = - __$$NotificationsStateImplCopyWithImpl<$Res>; +abstract mixin class $NotificationItemCopyWith<$Res> { + factory $NotificationItemCopyWith( + NotificationItem value, $Res Function(NotificationItem) _then) = + _$NotificationItemCopyWithImpl; + @useResult + $Res call({NotificationType type, String text, String? title}); +} + +/// @nodoc +class _$NotificationItemCopyWithImpl<$Res> + implements $NotificationItemCopyWith<$Res> { + _$NotificationItemCopyWithImpl(this._self, this._then); + + final NotificationItem _self; + final $Res Function(NotificationItem) _then; + + /// Create a copy of NotificationItem + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') @override + $Res call({ + Object? type = null, + Object? text = null, + Object? title = freezed, + }) { + return _then(_self.copyWith( + type: null == type + ? _self.type + : type // ignore: cast_nullable_to_non_nullable + as NotificationType, + text: null == text + ? _self.text + : text // ignore: cast_nullable_to_non_nullable + as String, + title: freezed == title + ? _self.title + : title // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _NotificationItem implements NotificationItem { + const _NotificationItem({required this.type, required this.text, this.title}); + + @override + final NotificationType type; + @override + final String text; + @override + final String? title; + + /// Create a copy of NotificationItem + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$NotificationItemCopyWith<_NotificationItem> get copyWith => + __$NotificationItemCopyWithImpl<_NotificationItem>(this, _$identity); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _NotificationItem && + (identical(other.type, type) || other.type == type) && + (identical(other.text, text) || other.text == text) && + (identical(other.title, title) || other.title == title)); + } + + @override + int get hashCode => Object.hash(runtimeType, type, text, title); + + @override + String toString() { + return 'NotificationItem(type: $type, text: $text, title: $title)'; + } +} + +/// @nodoc +abstract mixin class _$NotificationItemCopyWith<$Res> + implements $NotificationItemCopyWith<$Res> { + factory _$NotificationItemCopyWith( + _NotificationItem value, $Res Function(_NotificationItem) _then) = + __$NotificationItemCopyWithImpl; + @override + @useResult + $Res call({NotificationType type, String text, String? title}); +} + +/// @nodoc +class __$NotificationItemCopyWithImpl<$Res> + implements _$NotificationItemCopyWith<$Res> { + __$NotificationItemCopyWithImpl(this._self, this._then); + + final _NotificationItem _self; + final $Res Function(_NotificationItem) _then; + + /// Create a copy of NotificationItem + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? type = null, + Object? text = null, + Object? title = freezed, + }) { + return _then(_NotificationItem( + type: null == type + ? _self.type + : type // ignore: cast_nullable_to_non_nullable + as NotificationType, + text: null == text + ? _self.text + : text // ignore: cast_nullable_to_non_nullable + as String, + title: freezed == title + ? _self.title + : title // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +mixin _$NotificationsState { + IList get queue; + + /// Create a copy of NotificationsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + $NotificationsStateCopyWith get copyWith => + _$NotificationsStateCopyWithImpl( + this as NotificationsState, _$identity); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is NotificationsState && + const DeepCollectionEquality().equals(other.queue, queue)); + } + + @override + int get hashCode => + Object.hash(runtimeType, const DeepCollectionEquality().hash(queue)); + + @override + String toString() { + return 'NotificationsState(queue: $queue)'; + } +} + +/// @nodoc +abstract mixin class $NotificationsStateCopyWith<$Res> { + factory $NotificationsStateCopyWith( + NotificationsState value, $Res Function(NotificationsState) _then) = + _$NotificationsStateCopyWithImpl; @useResult $Res call({IList queue}); } /// @nodoc -class __$$NotificationsStateImplCopyWithImpl<$Res> - extends _$NotificationsStateCopyWithImpl<$Res, _$NotificationsStateImpl> - implements _$$NotificationsStateImplCopyWith<$Res> { - __$$NotificationsStateImplCopyWithImpl(_$NotificationsStateImpl _value, - $Res Function(_$NotificationsStateImpl) _then) - : super(_value, _then); +class _$NotificationsStateCopyWithImpl<$Res> + implements $NotificationsStateCopyWith<$Res> { + _$NotificationsStateCopyWithImpl(this._self, this._then); + + final NotificationsState _self; + final $Res Function(NotificationsState) _then; /// Create a copy of NotificationsState /// with the given fields replaced by the non-null parameter values. @@ -251,9 +227,9 @@ class __$$NotificationsStateImplCopyWithImpl<$Res> $Res call({ Object? queue = null, }) { - return _then(_$NotificationsStateImpl( + return _then(_self.copyWith( queue: null == queue - ? _value.queue + ? _self.queue : queue // ignore: cast_nullable_to_non_nullable as IList, )); @@ -262,22 +238,25 @@ class __$$NotificationsStateImplCopyWithImpl<$Res> /// @nodoc -class _$NotificationsStateImpl implements _NotificationsState { - const _$NotificationsStateImpl({required this.queue}); +class _NotificationsState implements NotificationsState { + const _NotificationsState({required this.queue}); @override final IList queue; + /// Create a copy of NotificationsState + /// with the given fields replaced by the non-null parameter values. @override - String toString() { - return 'NotificationsState(queue: $queue)'; - } + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$NotificationsStateCopyWith<_NotificationsState> get copyWith => + __$NotificationsStateCopyWithImpl<_NotificationsState>(this, _$identity); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$NotificationsStateImpl && + other is _NotificationsState && const DeepCollectionEquality().equals(other.queue, queue)); } @@ -285,28 +264,45 @@ class _$NotificationsStateImpl implements _NotificationsState { int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(queue)); + @override + String toString() { + return 'NotificationsState(queue: $queue)'; + } +} + +/// @nodoc +abstract mixin class _$NotificationsStateCopyWith<$Res> + implements $NotificationsStateCopyWith<$Res> { + factory _$NotificationsStateCopyWith( + _NotificationsState value, $Res Function(_NotificationsState) _then) = + __$NotificationsStateCopyWithImpl; + @override + @useResult + $Res call({IList queue}); +} + +/// @nodoc +class __$NotificationsStateCopyWithImpl<$Res> + implements _$NotificationsStateCopyWith<$Res> { + __$NotificationsStateCopyWithImpl(this._self, this._then); + + final _NotificationsState _self; + final $Res Function(_NotificationsState) _then; + /// Create a copy of NotificationsState /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$NotificationsStateImplCopyWith<_$NotificationsStateImpl> get copyWith => - __$$NotificationsStateImplCopyWithImpl<_$NotificationsStateImpl>( - this, _$identity); + $Res call({ + Object? queue = null, + }) { + return _then(_NotificationsState( + queue: null == queue + ? _self.queue + : queue // ignore: cast_nullable_to_non_nullable + as IList, + )); + } } -abstract class _NotificationsState implements NotificationsState { - const factory _NotificationsState( - {required final IList queue}) = - _$NotificationsStateImpl; - - @override - IList get queue; - - /// Create a copy of NotificationsState - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$NotificationsStateImplCopyWith<_$NotificationsStateImpl> get copyWith => - throw _privateConstructorUsedError; -} +// dart format on diff --git a/lib/proto/proto.dart b/lib/proto/proto.dart index 6ad8432..21c988a 100644 --- a/lib/proto/proto.dart +++ b/lib/proto/proto.dart @@ -1,3 +1,6 @@ +import 'package:veilid_support/veilid_support.dart'; +import 'veilidchat.pb.dart' as vcproto; + export 'package:veilid_support/dht_support/proto/proto.dart'; export 'package:veilid_support/proto/proto.dart'; @@ -6,3 +9,292 @@ export 'veilidchat.pb.dart'; export 'veilidchat.pbenum.dart'; export 'veilidchat.pbjson.dart'; export 'veilidchat.pbserver.dart'; + +void registerVeilidchatProtoToDebug() { + dynamic toDebug(dynamic obj) { + if (obj is vcproto.DHTDataReference) { + return { + 'dhtData': obj.dhtData, + 'hash': obj.hash, + }; + } + if (obj is vcproto.BlockStoreDataReference) { + return { + 'block': obj.block, + }; + } + if (obj is vcproto.DataReference) { + return { + 'kind': obj.whichKind(), + if (obj.whichKind() == vcproto.DataReference_Kind.dhtData) + 'dhtData': obj.dhtData, + if (obj.whichKind() == vcproto.DataReference_Kind.blockStoreData) + 'blockStoreData': obj.blockStoreData, + }; + } + if (obj is vcproto.Attachment) { + return { + 'kind': obj.whichKind(), + if (obj.whichKind() == vcproto.Attachment_Kind.media) + 'media': obj.media, + 'signature': obj.signature, + }; + } + if (obj is vcproto.AttachmentMedia) { + return { + 'mime': obj.mime, + 'name': obj.name, + 'content': obj.content, + }; + } + if (obj is vcproto.Permissions) { + return { + 'canAddMembers': obj.canAddMembers, + 'canEditInfo': obj.canEditInfo, + 'moderated': obj.moderated, + }; + } + if (obj is vcproto.Membership) { + return { + 'watchers': obj.watchers, + 'moderated': obj.moderated, + 'talkers': obj.talkers, + 'moderators': obj.moderators, + 'admins': obj.admins, + }; + } + if (obj is vcproto.ChatSettings) { + return { + 'title': obj.title, + 'description': obj.description, + 'icon': obj.icon, + 'defaultExpiration': obj.defaultExpiration, + }; + } + if (obj is vcproto.ChatSettings) { + return { + 'title': obj.title, + 'description': obj.description, + 'icon': obj.icon, + 'defaultExpiration': obj.defaultExpiration, + }; + } + if (obj is vcproto.Message) { + return { + 'id': obj.id, + 'author': obj.author, + 'timestamp': obj.timestamp, + 'kind': obj.whichKind(), + if (obj.whichKind() == vcproto.Message_Kind.text) 'text': obj.text, + if (obj.whichKind() == vcproto.Message_Kind.secret) + 'secret': obj.secret, + if (obj.whichKind() == vcproto.Message_Kind.delete) + 'delete': obj.delete, + if (obj.whichKind() == vcproto.Message_Kind.erase) 'erase': obj.erase, + if (obj.whichKind() == vcproto.Message_Kind.settings) + 'settings': obj.settings, + if (obj.whichKind() == vcproto.Message_Kind.permissions) + 'permissions': obj.permissions, + if (obj.whichKind() == vcproto.Message_Kind.membership) + 'membership': obj.membership, + if (obj.whichKind() == vcproto.Message_Kind.moderation) + 'moderation': obj.moderation, + 'signature': obj.signature, + }; + } + if (obj is vcproto.Message_Text) { + return { + 'text': obj.text, + 'topic': obj.topic, + 'replyId': obj.replyId, + 'expiration': obj.expiration, + 'viewLimit': obj.viewLimit, + 'attachments': obj.attachments, + }; + } + if (obj is vcproto.Message_Secret) { + return { + 'ciphertext': obj.ciphertext, + 'expiration': obj.expiration, + }; + } + if (obj is vcproto.Message_ControlDelete) { + return { + 'ids': obj.ids, + }; + } + if (obj is vcproto.Message_ControlErase) { + return { + 'timestamp': obj.timestamp, + }; + } + if (obj is vcproto.Message_ControlSettings) { + return { + 'settings': obj.settings, + }; + } + if (obj is vcproto.Message_ControlPermissions) { + return { + 'permissions': obj.permissions, + }; + } + if (obj is vcproto.Message_ControlMembership) { + return { + 'membership': obj.membership, + }; + } + if (obj is vcproto.Message_ControlModeration) { + return { + 'acceptedIds': obj.acceptedIds, + 'rejectdIds': obj.rejectedIds, + }; + } + if (obj is vcproto.Message_ControlModeration) { + return { + 'acceptedIds': obj.acceptedIds, + 'rejectdIds': obj.rejectedIds, + }; + } + if (obj is vcproto.Message_ControlReadReceipt) { + return { + 'readIds': obj.readIds, + }; + } + if (obj is vcproto.ReconciledMessage) { + return { + 'content': obj.content, + 'reconciledTime': obj.reconciledTime, + }; + } + if (obj is vcproto.Conversation) { + return { + 'profile': obj.profile, + 'superIdentityJson': obj.superIdentityJson, + 'messages': obj.messages + }; + } + if (obj is vcproto.ChatMember) { + return { + 'remoteIdentityPublicKey': obj.remoteIdentityPublicKey, + 'remoteConversationRecordKey': obj.remoteConversationRecordKey, + }; + } + if (obj is vcproto.DirectChat) { + return { + 'settings': obj.settings, + 'localConversationRecordKey': obj.localConversationRecordKey, + 'remoteMember': obj.remoteMember, + }; + } + if (obj is vcproto.GroupChat) { + return { + 'settings': obj.settings, + 'membership': obj.membership, + 'permissions': obj.permissions, + 'localConversationRecordKey': obj.localConversationRecordKey, + 'remoteMembers': obj.remoteMembers, + }; + } + if (obj is vcproto.Chat) { + return { + 'kind': obj.whichKind(), + if (obj.whichKind() == vcproto.Chat_Kind.direct) 'direct': obj.direct, + if (obj.whichKind() == vcproto.Chat_Kind.group) 'group': obj.group, + }; + } + if (obj is vcproto.Profile) { + return { + 'name': obj.name, + 'pronouns': obj.pronouns, + 'about': obj.about, + 'status': obj.status, + 'availability': obj.availability, + 'avatar': obj.avatar, + 'timestamp': obj.timestamp, + }; + } + if (obj is vcproto.Account) { + return { + 'profile': obj.profile, + 'invisible': obj.invisible, + 'autoAwayTimeoutMin': obj.autoAwayTimeoutMin, + 'contact_list': obj.contactList, + 'contactInvitationRecords': obj.contactInvitationRecords, + 'chatList': obj.chatList, + 'groupChatList': obj.groupChatList, + 'freeMessage': obj.freeMessage, + 'busyMessage': obj.busyMessage, + 'awayMessage': obj.awayMessage, + 'autodetectAway': obj.autodetectAway, + }; + } + if (obj is vcproto.Contact) { + return { + 'nickname': obj.nickname, + 'profile': obj.profile, + 'superIdentityJson': obj.superIdentityJson, + 'identityPublicKey': obj.identityPublicKey, + 'remoteConversationRecordKey': obj.remoteConversationRecordKey, + 'localConversationRecordKey': obj.localConversationRecordKey, + 'showAvailability': obj.showAvailability, + 'notes': obj.notes, + }; + } + if (obj is vcproto.ContactInvitation) { + return { + 'contactRequestInboxKey': obj.contactRequestInboxKey, + 'writerSecret': obj.writerSecret, + }; + } + if (obj is vcproto.SignedContactInvitation) { + return { + 'contactInvitation': obj.contactInvitation, + 'identitySignature': obj.identitySignature, + }; + } + if (obj is vcproto.ContactRequest) { + return { + 'encryptionKeyType': obj.encryptionKeyType, + 'private': obj.private, + }; + } + if (obj is vcproto.ContactRequestPrivate) { + return { + 'writerKey': obj.writerKey, + 'profile': obj.profile, + 'superIdentityRecordKey': obj.superIdentityRecordKey, + 'chatRecordKey': obj.chatRecordKey, + 'expiration': obj.expiration, + }; + } + if (obj is vcproto.ContactResponse) { + return { + 'accept': obj.accept, + 'superIdentityRecordKey': obj.superIdentityRecordKey, + 'remoteConversationRecordKey': obj.remoteConversationRecordKey, + }; + } + if (obj is vcproto.SignedContactResponse) { + return { + 'contactResponse': obj.contactResponse, + 'identitySignature': obj.identitySignature, + }; + } + if (obj is vcproto.ContactInvitationRecord) { + return { + 'contactRequestInbox': obj.contactRequestInbox, + 'writerKey': obj.writerKey, + 'writerSecret': obj.writerSecret, + 'localConversationRecordKey': obj.localConversationRecordKey, + 'expiration': obj.expiration, + 'invitation': obj.invitation, + 'message': obj.message, + 'recipient': obj.recipient, + }; + } + + return obj; + } + + DynamicDebug.registerToDebug(toDebug); +} diff --git a/lib/proto/veilidchat.pb.dart b/lib/proto/veilidchat.pb.dart index 245f9f3..67805ae 100644 --- a/lib/proto/veilidchat.pb.dart +++ b/lib/proto/veilidchat.pb.dart @@ -1216,6 +1216,7 @@ enum Message_Kind { permissions, membership, moderation, + readReceipt, notSet } @@ -1234,6 +1235,7 @@ class Message extends $pb.GeneratedMessage { Message_ControlMembership? membership, Message_ControlModeration? moderation, $0.Signature? signature, + Message_ControlReadReceipt? readReceipt, }) { final $result = create(); if (id != null) { @@ -1272,6 +1274,9 @@ class Message extends $pb.GeneratedMessage { if (signature != null) { $result.signature = signature; } + if (readReceipt != null) { + $result.readReceipt = readReceipt; + } return $result; } Message._() : super(); @@ -1287,10 +1292,11 @@ class Message extends $pb.GeneratedMessage { 9 : Message_Kind.permissions, 10 : Message_Kind.membership, 11 : Message_Kind.moderation, + 13 : Message_Kind.readReceipt, 0 : Message_Kind.notSet }; static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Message', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create) - ..oo(0, [4, 5, 6, 7, 8, 9, 10, 11]) + ..oo(0, [4, 5, 6, 7, 8, 9, 10, 11, 13]) ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'id', $pb.PbFieldType.OY) ..aOM<$0.TypedKey>(2, _omitFieldNames ? '' : 'author', subBuilder: $0.TypedKey.create) ..a<$fixnum.Int64>(3, _omitFieldNames ? '' : 'timestamp', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO) @@ -1303,6 +1309,7 @@ class Message extends $pb.GeneratedMessage { ..aOM(10, _omitFieldNames ? '' : 'membership', subBuilder: Message_ControlMembership.create) ..aOM(11, _omitFieldNames ? '' : 'moderation', subBuilder: Message_ControlModeration.create) ..aOM<$0.Signature>(12, _omitFieldNames ? '' : 'signature', subBuilder: $0.Signature.create) + ..aOM(13, _omitFieldNames ? '' : 'readReceipt', protoName: 'readReceipt', subBuilder: Message_ControlReadReceipt.create) ..hasRequiredFields = false ; @@ -1462,6 +1469,17 @@ class Message extends $pb.GeneratedMessage { void clearSignature() => clearField(12); @$pb.TagNumber(12) $0.Signature ensureSignature() => $_ensure(11); + + @$pb.TagNumber(13) + Message_ControlReadReceipt get readReceipt => $_getN(12); + @$pb.TagNumber(13) + set readReceipt(Message_ControlReadReceipt v) { setField(13, v); } + @$pb.TagNumber(13) + $core.bool hasReadReceipt() => $_has(12); + @$pb.TagNumber(13) + void clearReadReceipt() => clearField(13); + @$pb.TagNumber(13) + Message_ControlReadReceipt ensureReadReceipt() => $_ensure(12); } /// Locally stored messages for chats diff --git a/lib/proto/veilidchat.pbjson.dart b/lib/proto/veilidchat.pbjson.dart index 81bf741..0958343 100644 --- a/lib/proto/veilidchat.pbjson.dart +++ b/lib/proto/veilidchat.pbjson.dart @@ -215,6 +215,7 @@ const Message$json = { {'1': 'permissions', '3': 9, '4': 1, '5': 11, '6': '.veilidchat.Message.ControlPermissions', '9': 0, '10': 'permissions'}, {'1': 'membership', '3': 10, '4': 1, '5': 11, '6': '.veilidchat.Message.ControlMembership', '9': 0, '10': 'membership'}, {'1': 'moderation', '3': 11, '4': 1, '5': 11, '6': '.veilidchat.Message.ControlModeration', '9': 0, '10': 'moderation'}, + {'1': 'readReceipt', '3': 13, '4': 1, '5': 11, '6': '.veilidchat.Message.ControlReadReceipt', '9': 0, '10': 'readReceipt'}, {'1': 'signature', '3': 12, '4': 1, '5': 11, '6': '.veilid.Signature', '10': 'signature'}, ], '3': [Message_Text$json, Message_Secret$json, Message_ControlDelete$json, Message_ControlErase$json, Message_ControlSettings$json, Message_ControlPermissions$json, Message_ControlMembership$json, Message_ControlModeration$json, Message_ControlReadReceipt$json], @@ -318,22 +319,24 @@ final $typed_data.Uint8List messageDescriptor = $convert.base64Decode( 'twZXJtaXNzaW9ucxgJIAEoCzImLnZlaWxpZGNoYXQuTWVzc2FnZS5Db250cm9sUGVybWlzc2lv' 'bnNIAFILcGVybWlzc2lvbnMSRwoKbWVtYmVyc2hpcBgKIAEoCzIlLnZlaWxpZGNoYXQuTWVzc2' 'FnZS5Db250cm9sTWVtYmVyc2hpcEgAUgptZW1iZXJzaGlwEkcKCm1vZGVyYXRpb24YCyABKAsy' - 'JS52ZWlsaWRjaGF0Lk1lc3NhZ2UuQ29udHJvbE1vZGVyYXRpb25IAFIKbW9kZXJhdGlvbhIvCg' - 'lzaWduYXR1cmUYDCABKAsyES52ZWlsaWQuU2lnbmF0dXJlUglzaWduYXR1cmUa5QEKBFRleHQS' - 'EgoEdGV4dBgBIAEoCVIEdGV4dBIZCgV0b3BpYxgCIAEoCUgAUgV0b3BpY4gBARIeCghyZXBseV' - '9pZBgDIAEoDEgBUgdyZXBseUlkiAEBEh4KCmV4cGlyYXRpb24YBCABKARSCmV4cGlyYXRpb24S' - 'HQoKdmlld19saW1pdBgFIAEoDVIJdmlld0xpbWl0EjgKC2F0dGFjaG1lbnRzGAYgAygLMhYudm' - 'VpbGlkY2hhdC5BdHRhY2htZW50UgthdHRhY2htZW50c0IICgZfdG9waWNCCwoJX3JlcGx5X2lk' - 'GkgKBlNlY3JldBIeCgpjaXBoZXJ0ZXh0GAEgASgMUgpjaXBoZXJ0ZXh0Eh4KCmV4cGlyYXRpb2' - '4YAiABKARSCmV4cGlyYXRpb24aIQoNQ29udHJvbERlbGV0ZRIQCgNpZHMYASADKAxSA2lkcxos' - 'CgxDb250cm9sRXJhc2USHAoJdGltZXN0YW1wGAEgASgEUgl0aW1lc3RhbXAaRwoPQ29udHJvbF' - 'NldHRpbmdzEjQKCHNldHRpbmdzGAEgASgLMhgudmVpbGlkY2hhdC5DaGF0U2V0dGluZ3NSCHNl' - 'dHRpbmdzGk8KEkNvbnRyb2xQZXJtaXNzaW9ucxI5CgtwZXJtaXNzaW9ucxgBIAEoCzIXLnZlaW' - 'xpZGNoYXQuUGVybWlzc2lvbnNSC3Blcm1pc3Npb25zGksKEUNvbnRyb2xNZW1iZXJzaGlwEjYK' - 'Cm1lbWJlcnNoaXAYASABKAsyFi52ZWlsaWRjaGF0Lk1lbWJlcnNoaXBSCm1lbWJlcnNoaXAaWQ' - 'oRQ29udHJvbE1vZGVyYXRpb24SIQoMYWNjZXB0ZWRfaWRzGAEgAygMUgthY2NlcHRlZElkcxIh' - 'CgxyZWplY3RlZF9pZHMYAiADKAxSC3JlamVjdGVkSWRzGi8KEkNvbnRyb2xSZWFkUmVjZWlwdB' - 'IZCghyZWFkX2lkcxgBIAMoDFIHcmVhZElkc0IGCgRraW5k'); + 'JS52ZWlsaWRjaGF0Lk1lc3NhZ2UuQ29udHJvbE1vZGVyYXRpb25IAFIKbW9kZXJhdGlvbhJKCg' + 'tyZWFkUmVjZWlwdBgNIAEoCzImLnZlaWxpZGNoYXQuTWVzc2FnZS5Db250cm9sUmVhZFJlY2Vp' + 'cHRIAFILcmVhZFJlY2VpcHQSLwoJc2lnbmF0dXJlGAwgASgLMhEudmVpbGlkLlNpZ25hdHVyZV' + 'IJc2lnbmF0dXJlGuUBCgRUZXh0EhIKBHRleHQYASABKAlSBHRleHQSGQoFdG9waWMYAiABKAlI' + 'AFIFdG9waWOIAQESHgoIcmVwbHlfaWQYAyABKAxIAVIHcmVwbHlJZIgBARIeCgpleHBpcmF0aW' + '9uGAQgASgEUgpleHBpcmF0aW9uEh0KCnZpZXdfbGltaXQYBSABKA1SCXZpZXdMaW1pdBI4Cgth' + 'dHRhY2htZW50cxgGIAMoCzIWLnZlaWxpZGNoYXQuQXR0YWNobWVudFILYXR0YWNobWVudHNCCA' + 'oGX3RvcGljQgsKCV9yZXBseV9pZBpICgZTZWNyZXQSHgoKY2lwaGVydGV4dBgBIAEoDFIKY2lw' + 'aGVydGV4dBIeCgpleHBpcmF0aW9uGAIgASgEUgpleHBpcmF0aW9uGiEKDUNvbnRyb2xEZWxldG' + 'USEAoDaWRzGAEgAygMUgNpZHMaLAoMQ29udHJvbEVyYXNlEhwKCXRpbWVzdGFtcBgBIAEoBFIJ' + 'dGltZXN0YW1wGkcKD0NvbnRyb2xTZXR0aW5ncxI0CghzZXR0aW5ncxgBIAEoCzIYLnZlaWxpZG' + 'NoYXQuQ2hhdFNldHRpbmdzUghzZXR0aW5ncxpPChJDb250cm9sUGVybWlzc2lvbnMSOQoLcGVy' + 'bWlzc2lvbnMYASABKAsyFy52ZWlsaWRjaGF0LlBlcm1pc3Npb25zUgtwZXJtaXNzaW9ucxpLCh' + 'FDb250cm9sTWVtYmVyc2hpcBI2CgptZW1iZXJzaGlwGAEgASgLMhYudmVpbGlkY2hhdC5NZW1i' + 'ZXJzaGlwUgptZW1iZXJzaGlwGlkKEUNvbnRyb2xNb2RlcmF0aW9uEiEKDGFjY2VwdGVkX2lkcx' + 'gBIAMoDFILYWNjZXB0ZWRJZHMSIQoMcmVqZWN0ZWRfaWRzGAIgAygMUgtyZWplY3RlZElkcxov' + 'ChJDb250cm9sUmVhZFJlY2VpcHQSGQoIcmVhZF9pZHMYASADKAxSB3JlYWRJZHNCBgoEa2luZA' + '=='); @$core.Deprecated('Use reconciledMessageDescriptor instead') const ReconciledMessage$json = { diff --git a/lib/proto/veilidchat.proto b/lib/proto/veilidchat.proto index e669959..5bff89c 100644 --- a/lib/proto/veilidchat.proto +++ b/lib/proto/veilidchat.proto @@ -228,6 +228,7 @@ message Message { ControlPermissions permissions = 9; ControlMembership membership = 10; ControlModeration moderation = 11; + ControlReadReceipt readReceipt = 13; } // Author signature over all of the fields and attachment signatures diff --git a/lib/router/cubits/router_cubit.dart b/lib/router/cubits/router_cubit.dart index 6684c45..1492c51 100644 --- a/lib/router/cubits/router_cubit.dart +++ b/lib/router/cubits/router_cubit.dart @@ -22,7 +22,7 @@ part 'router_cubit.g.dart'; final _rootNavKey = GlobalKey(debugLabel: 'rootNavKey'); @freezed -class RouterState with _$RouterState { +sealed class RouterState with _$RouterState { const factory RouterState({ required bool hasAnyAccount, }) = _RouterState; diff --git a/lib/router/cubits/router_cubit.freezed.dart b/lib/router/cubits/router_cubit.freezed.dart index 8377607..0f5b285 100644 --- a/lib/router/cubits/router_cubit.freezed.dart +++ b/lib/router/cubits/router_cubit.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,118 +10,25 @@ part of 'router_cubit.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -RouterState _$RouterStateFromJson(Map json) { - return _RouterState.fromJson(json); -} - /// @nodoc -mixin _$RouterState { - bool get hasAnyAccount => throw _privateConstructorUsedError; - - /// Serializes this RouterState to a JSON map. - Map toJson() => throw _privateConstructorUsedError; +mixin _$RouterState implements DiagnosticableTreeMixin { + bool get hasAnyAccount; /// Create a copy of RouterState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $RouterStateCopyWith get copyWith => - throw _privateConstructorUsedError; -} + _$RouterStateCopyWithImpl(this as RouterState, _$identity); -/// @nodoc -abstract class $RouterStateCopyWith<$Res> { - factory $RouterStateCopyWith( - RouterState value, $Res Function(RouterState) then) = - _$RouterStateCopyWithImpl<$Res, RouterState>; - @useResult - $Res call({bool hasAnyAccount}); -} - -/// @nodoc -class _$RouterStateCopyWithImpl<$Res, $Val extends RouterState> - implements $RouterStateCopyWith<$Res> { - _$RouterStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of RouterState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? hasAnyAccount = null, - }) { - return _then(_value.copyWith( - hasAnyAccount: null == hasAnyAccount - ? _value.hasAnyAccount - : hasAnyAccount // ignore: cast_nullable_to_non_nullable - as bool, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$RouterStateImplCopyWith<$Res> - implements $RouterStateCopyWith<$Res> { - factory _$$RouterStateImplCopyWith( - _$RouterStateImpl value, $Res Function(_$RouterStateImpl) then) = - __$$RouterStateImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({bool hasAnyAccount}); -} - -/// @nodoc -class __$$RouterStateImplCopyWithImpl<$Res> - extends _$RouterStateCopyWithImpl<$Res, _$RouterStateImpl> - implements _$$RouterStateImplCopyWith<$Res> { - __$$RouterStateImplCopyWithImpl( - _$RouterStateImpl _value, $Res Function(_$RouterStateImpl) _then) - : super(_value, _then); - - /// Create a copy of RouterState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? hasAnyAccount = null, - }) { - return _then(_$RouterStateImpl( - hasAnyAccount: null == hasAnyAccount - ? _value.hasAnyAccount - : hasAnyAccount // ignore: cast_nullable_to_non_nullable - as bool, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState { - const _$RouterStateImpl({required this.hasAnyAccount}); - - factory _$RouterStateImpl.fromJson(Map json) => - _$$RouterStateImplFromJson(json); - - @override - final bool hasAnyAccount; - - @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'RouterState(hasAnyAccount: $hasAnyAccount)'; - } + /// Serializes this RouterState to a JSON map. + Map toJson(); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); properties ..add(DiagnosticsProperty('type', 'RouterState')) ..add(DiagnosticsProperty('hasAnyAccount', hasAnyAccount)); @@ -130,7 +38,7 @@ class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState { bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$RouterStateImpl && + other is RouterState && (identical(other.hasAnyAccount, hasAnyAccount) || other.hasAnyAccount == hasAnyAccount)); } @@ -139,36 +47,127 @@ class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState { @override int get hashCode => Object.hash(runtimeType, hasAnyAccount); - /// Create a copy of RouterState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$RouterStateImplCopyWith<_$RouterStateImpl> get copyWith => - __$$RouterStateImplCopyWithImpl<_$RouterStateImpl>(this, _$identity); - - @override - Map toJson() { - return _$$RouterStateImplToJson( - this, - ); + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'RouterState(hasAnyAccount: $hasAnyAccount)'; } } -abstract class _RouterState implements RouterState { - const factory _RouterState({required final bool hasAnyAccount}) = - _$RouterStateImpl; +/// @nodoc +abstract mixin class $RouterStateCopyWith<$Res> { + factory $RouterStateCopyWith( + RouterState value, $Res Function(RouterState) _then) = + _$RouterStateCopyWithImpl; + @useResult + $Res call({bool hasAnyAccount}); +} - factory _RouterState.fromJson(Map json) = - _$RouterStateImpl.fromJson; +/// @nodoc +class _$RouterStateCopyWithImpl<$Res> implements $RouterStateCopyWith<$Res> { + _$RouterStateCopyWithImpl(this._self, this._then); + + final RouterState _self; + final $Res Function(RouterState) _then; + + /// Create a copy of RouterState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? hasAnyAccount = null, + }) { + return _then(_self.copyWith( + hasAnyAccount: null == hasAnyAccount + ? _self.hasAnyAccount + : hasAnyAccount // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _RouterState with DiagnosticableTreeMixin implements RouterState { + const _RouterState({required this.hasAnyAccount}); + factory _RouterState.fromJson(Map json) => + _$RouterStateFromJson(json); @override - bool get hasAnyAccount; + final bool hasAnyAccount; /// Create a copy of RouterState /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$RouterStateImplCopyWith<_$RouterStateImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + _$RouterStateCopyWith<_RouterState> get copyWith => + __$RouterStateCopyWithImpl<_RouterState>(this, _$identity); + + @override + Map toJson() { + return _$RouterStateToJson( + this, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'RouterState')) + ..add(DiagnosticsProperty('hasAnyAccount', hasAnyAccount)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _RouterState && + (identical(other.hasAnyAccount, hasAnyAccount) || + other.hasAnyAccount == hasAnyAccount)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, hasAnyAccount); + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'RouterState(hasAnyAccount: $hasAnyAccount)'; + } } + +/// @nodoc +abstract mixin class _$RouterStateCopyWith<$Res> + implements $RouterStateCopyWith<$Res> { + factory _$RouterStateCopyWith( + _RouterState value, $Res Function(_RouterState) _then) = + __$RouterStateCopyWithImpl; + @override + @useResult + $Res call({bool hasAnyAccount}); +} + +/// @nodoc +class __$RouterStateCopyWithImpl<$Res> implements _$RouterStateCopyWith<$Res> { + __$RouterStateCopyWithImpl(this._self, this._then); + + final _RouterState _self; + final $Res Function(_RouterState) _then; + + /// Create a copy of RouterState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? hasAnyAccount = null, + }) { + return _then(_RouterState( + hasAnyAccount: null == hasAnyAccount + ? _self.hasAnyAccount + : hasAnyAccount // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +// dart format on diff --git a/lib/router/cubits/router_cubit.g.dart b/lib/router/cubits/router_cubit.g.dart index 4d9241c..3623d0e 100644 --- a/lib/router/cubits/router_cubit.g.dart +++ b/lib/router/cubits/router_cubit.g.dart @@ -6,12 +6,11 @@ part of 'router_cubit.dart'; // JsonSerializableGenerator // ************************************************************************** -_$RouterStateImpl _$$RouterStateImplFromJson(Map json) => - _$RouterStateImpl( +_RouterState _$RouterStateFromJson(Map json) => _RouterState( hasAnyAccount: json['has_any_account'] as bool, ); -Map _$$RouterStateImplToJson(_$RouterStateImpl instance) => +Map _$RouterStateToJson(_RouterState instance) => { 'has_any_account': instance.hasAnyAccount, }; diff --git a/lib/settings/models/preferences.dart b/lib/settings/models/preferences.dart index e646c61..3ef683e 100644 --- a/lib/settings/models/preferences.dart +++ b/lib/settings/models/preferences.dart @@ -10,7 +10,7 @@ part 'preferences.g.dart'; // Lock preference changes how frequently the messenger locks its // interface and requires the identitySecretKey to be entered (pin/password/etc) @freezed -class LockPreference with _$LockPreference { +sealed class LockPreference with _$LockPreference { const factory LockPreference({ @Default(0) int inactivityLockSecs, @Default(false) bool lockWhenSwitching, @@ -37,7 +37,7 @@ enum LanguagePreference { // Preferences are stored in a table locally and globally affect all // accounts imported/added and the app in general @freezed -class Preferences with _$Preferences { +sealed class Preferences with _$Preferences { const factory Preferences({ @Default(ThemePreferences.defaults) ThemePreferences themePreference, @Default(LanguagePreference.defaults) LanguagePreference languagePreference, diff --git a/lib/settings/models/preferences.freezed.dart b/lib/settings/models/preferences.freezed.dart index a7ebed3..9e090f5 100644 --- a/lib/settings/models/preferences.freezed.dart +++ b/lib/settings/models/preferences.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,158 +10,31 @@ part of 'preferences.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -LockPreference _$LockPreferenceFromJson(Map json) { - return _LockPreference.fromJson(json); -} - /// @nodoc mixin _$LockPreference { - int get inactivityLockSecs => throw _privateConstructorUsedError; - bool get lockWhenSwitching => throw _privateConstructorUsedError; - bool get lockWithSystemLock => throw _privateConstructorUsedError; - - /// Serializes this LockPreference to a JSON map. - Map toJson() => throw _privateConstructorUsedError; + int get inactivityLockSecs; + bool get lockWhenSwitching; + bool get lockWithSystemLock; /// Create a copy of LockPreference /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $LockPreferenceCopyWith get copyWith => - throw _privateConstructorUsedError; -} + _$LockPreferenceCopyWithImpl( + this as LockPreference, _$identity); -/// @nodoc -abstract class $LockPreferenceCopyWith<$Res> { - factory $LockPreferenceCopyWith( - LockPreference value, $Res Function(LockPreference) then) = - _$LockPreferenceCopyWithImpl<$Res, LockPreference>; - @useResult - $Res call( - {int inactivityLockSecs, - bool lockWhenSwitching, - bool lockWithSystemLock}); -} - -/// @nodoc -class _$LockPreferenceCopyWithImpl<$Res, $Val extends LockPreference> - implements $LockPreferenceCopyWith<$Res> { - _$LockPreferenceCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of LockPreference - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? inactivityLockSecs = null, - Object? lockWhenSwitching = null, - Object? lockWithSystemLock = null, - }) { - return _then(_value.copyWith( - inactivityLockSecs: null == inactivityLockSecs - ? _value.inactivityLockSecs - : inactivityLockSecs // ignore: cast_nullable_to_non_nullable - as int, - lockWhenSwitching: null == lockWhenSwitching - ? _value.lockWhenSwitching - : lockWhenSwitching // ignore: cast_nullable_to_non_nullable - as bool, - lockWithSystemLock: null == lockWithSystemLock - ? _value.lockWithSystemLock - : lockWithSystemLock // ignore: cast_nullable_to_non_nullable - as bool, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$LockPreferenceImplCopyWith<$Res> - implements $LockPreferenceCopyWith<$Res> { - factory _$$LockPreferenceImplCopyWith(_$LockPreferenceImpl value, - $Res Function(_$LockPreferenceImpl) then) = - __$$LockPreferenceImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {int inactivityLockSecs, - bool lockWhenSwitching, - bool lockWithSystemLock}); -} - -/// @nodoc -class __$$LockPreferenceImplCopyWithImpl<$Res> - extends _$LockPreferenceCopyWithImpl<$Res, _$LockPreferenceImpl> - implements _$$LockPreferenceImplCopyWith<$Res> { - __$$LockPreferenceImplCopyWithImpl( - _$LockPreferenceImpl _value, $Res Function(_$LockPreferenceImpl) _then) - : super(_value, _then); - - /// Create a copy of LockPreference - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? inactivityLockSecs = null, - Object? lockWhenSwitching = null, - Object? lockWithSystemLock = null, - }) { - return _then(_$LockPreferenceImpl( - inactivityLockSecs: null == inactivityLockSecs - ? _value.inactivityLockSecs - : inactivityLockSecs // ignore: cast_nullable_to_non_nullable - as int, - lockWhenSwitching: null == lockWhenSwitching - ? _value.lockWhenSwitching - : lockWhenSwitching // ignore: cast_nullable_to_non_nullable - as bool, - lockWithSystemLock: null == lockWithSystemLock - ? _value.lockWithSystemLock - : lockWithSystemLock // ignore: cast_nullable_to_non_nullable - as bool, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$LockPreferenceImpl implements _LockPreference { - const _$LockPreferenceImpl( - {this.inactivityLockSecs = 0, - this.lockWhenSwitching = false, - this.lockWithSystemLock = false}); - - factory _$LockPreferenceImpl.fromJson(Map json) => - _$$LockPreferenceImplFromJson(json); - - @override - @JsonKey() - final int inactivityLockSecs; - @override - @JsonKey() - final bool lockWhenSwitching; - @override - @JsonKey() - final bool lockWithSystemLock; - - @override - String toString() { - return 'LockPreference(inactivityLockSecs: $inactivityLockSecs, lockWhenSwitching: $lockWhenSwitching, lockWithSystemLock: $lockWithSystemLock)'; - } + /// Serializes this LockPreference to a JSON map. + Map toJson(); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$LockPreferenceImpl && + other is LockPreference && (identical(other.inactivityLockSecs, inactivityLockSecs) || other.inactivityLockSecs == inactivityLockSecs) && (identical(other.lockWhenSwitching, lockWhenSwitching) || @@ -174,255 +48,187 @@ class _$LockPreferenceImpl implements _LockPreference { int get hashCode => Object.hash( runtimeType, inactivityLockSecs, lockWhenSwitching, lockWithSystemLock); - /// Create a copy of LockPreference - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$LockPreferenceImplCopyWith<_$LockPreferenceImpl> get copyWith => - __$$LockPreferenceImplCopyWithImpl<_$LockPreferenceImpl>( - this, _$identity); - - @override - Map toJson() { - return _$$LockPreferenceImplToJson( - this, - ); + String toString() { + return 'LockPreference(inactivityLockSecs: $inactivityLockSecs, lockWhenSwitching: $lockWhenSwitching, lockWithSystemLock: $lockWithSystemLock)'; } } -abstract class _LockPreference implements LockPreference { - const factory _LockPreference( - {final int inactivityLockSecs, - final bool lockWhenSwitching, - final bool lockWithSystemLock}) = _$LockPreferenceImpl; - - factory _LockPreference.fromJson(Map json) = - _$LockPreferenceImpl.fromJson; - - @override - int get inactivityLockSecs; - @override - bool get lockWhenSwitching; - @override - bool get lockWithSystemLock; - - /// Create a copy of LockPreference - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$LockPreferenceImplCopyWith<_$LockPreferenceImpl> get copyWith => - throw _privateConstructorUsedError; -} - -Preferences _$PreferencesFromJson(Map json) { - return _Preferences.fromJson(json); -} - /// @nodoc -mixin _$Preferences { - ThemePreferences get themePreference => throw _privateConstructorUsedError; - LanguagePreference get languagePreference => - throw _privateConstructorUsedError; - LockPreference get lockPreference => throw _privateConstructorUsedError; - NotificationsPreference get notificationsPreference => - throw _privateConstructorUsedError; - - /// Serializes this Preferences to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of Preferences - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $PreferencesCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $PreferencesCopyWith<$Res> { - factory $PreferencesCopyWith( - Preferences value, $Res Function(Preferences) then) = - _$PreferencesCopyWithImpl<$Res, Preferences>; +abstract mixin class $LockPreferenceCopyWith<$Res> { + factory $LockPreferenceCopyWith( + LockPreference value, $Res Function(LockPreference) _then) = + _$LockPreferenceCopyWithImpl; @useResult $Res call( - {ThemePreferences themePreference, - LanguagePreference languagePreference, - LockPreference lockPreference, - NotificationsPreference notificationsPreference}); - - $ThemePreferencesCopyWith<$Res> get themePreference; - $LockPreferenceCopyWith<$Res> get lockPreference; - $NotificationsPreferenceCopyWith<$Res> get notificationsPreference; + {int inactivityLockSecs, + bool lockWhenSwitching, + bool lockWithSystemLock}); } /// @nodoc -class _$PreferencesCopyWithImpl<$Res, $Val extends Preferences> - implements $PreferencesCopyWith<$Res> { - _$PreferencesCopyWithImpl(this._value, this._then); +class _$LockPreferenceCopyWithImpl<$Res> + implements $LockPreferenceCopyWith<$Res> { + _$LockPreferenceCopyWithImpl(this._self, this._then); - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; + final LockPreference _self; + final $Res Function(LockPreference) _then; - /// Create a copy of Preferences + /// Create a copy of LockPreference /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? themePreference = null, - Object? languagePreference = null, - Object? lockPreference = null, - Object? notificationsPreference = null, + Object? inactivityLockSecs = null, + Object? lockWhenSwitching = null, + Object? lockWithSystemLock = null, }) { - return _then(_value.copyWith( - themePreference: null == themePreference - ? _value.themePreference - : themePreference // ignore: cast_nullable_to_non_nullable - as ThemePreferences, - languagePreference: null == languagePreference - ? _value.languagePreference - : languagePreference // ignore: cast_nullable_to_non_nullable - as LanguagePreference, - lockPreference: null == lockPreference - ? _value.lockPreference - : lockPreference // ignore: cast_nullable_to_non_nullable - as LockPreference, - notificationsPreference: null == notificationsPreference - ? _value.notificationsPreference - : notificationsPreference // ignore: cast_nullable_to_non_nullable - as NotificationsPreference, - ) as $Val); - } - - /// Create a copy of Preferences - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $ThemePreferencesCopyWith<$Res> get themePreference { - return $ThemePreferencesCopyWith<$Res>(_value.themePreference, (value) { - return _then(_value.copyWith(themePreference: value) as $Val); - }); - } - - /// Create a copy of Preferences - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $LockPreferenceCopyWith<$Res> get lockPreference { - return $LockPreferenceCopyWith<$Res>(_value.lockPreference, (value) { - return _then(_value.copyWith(lockPreference: value) as $Val); - }); - } - - /// Create a copy of Preferences - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $NotificationsPreferenceCopyWith<$Res> get notificationsPreference { - return $NotificationsPreferenceCopyWith<$Res>( - _value.notificationsPreference, (value) { - return _then(_value.copyWith(notificationsPreference: value) as $Val); - }); - } -} - -/// @nodoc -abstract class _$$PreferencesImplCopyWith<$Res> - implements $PreferencesCopyWith<$Res> { - factory _$$PreferencesImplCopyWith( - _$PreferencesImpl value, $Res Function(_$PreferencesImpl) then) = - __$$PreferencesImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {ThemePreferences themePreference, - LanguagePreference languagePreference, - LockPreference lockPreference, - NotificationsPreference notificationsPreference}); - - @override - $ThemePreferencesCopyWith<$Res> get themePreference; - @override - $LockPreferenceCopyWith<$Res> get lockPreference; - @override - $NotificationsPreferenceCopyWith<$Res> get notificationsPreference; -} - -/// @nodoc -class __$$PreferencesImplCopyWithImpl<$Res> - extends _$PreferencesCopyWithImpl<$Res, _$PreferencesImpl> - implements _$$PreferencesImplCopyWith<$Res> { - __$$PreferencesImplCopyWithImpl( - _$PreferencesImpl _value, $Res Function(_$PreferencesImpl) _then) - : super(_value, _then); - - /// Create a copy of Preferences - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? themePreference = null, - Object? languagePreference = null, - Object? lockPreference = null, - Object? notificationsPreference = null, - }) { - return _then(_$PreferencesImpl( - themePreference: null == themePreference - ? _value.themePreference - : themePreference // ignore: cast_nullable_to_non_nullable - as ThemePreferences, - languagePreference: null == languagePreference - ? _value.languagePreference - : languagePreference // ignore: cast_nullable_to_non_nullable - as LanguagePreference, - lockPreference: null == lockPreference - ? _value.lockPreference - : lockPreference // ignore: cast_nullable_to_non_nullable - as LockPreference, - notificationsPreference: null == notificationsPreference - ? _value.notificationsPreference - : notificationsPreference // ignore: cast_nullable_to_non_nullable - as NotificationsPreference, + return _then(_self.copyWith( + inactivityLockSecs: null == inactivityLockSecs + ? _self.inactivityLockSecs + : inactivityLockSecs // ignore: cast_nullable_to_non_nullable + as int, + lockWhenSwitching: null == lockWhenSwitching + ? _self.lockWhenSwitching + : lockWhenSwitching // ignore: cast_nullable_to_non_nullable + as bool, + lockWithSystemLock: null == lockWithSystemLock + ? _self.lockWithSystemLock + : lockWithSystemLock // ignore: cast_nullable_to_non_nullable + as bool, )); } } /// @nodoc @JsonSerializable() -class _$PreferencesImpl implements _Preferences { - const _$PreferencesImpl( - {this.themePreference = ThemePreferences.defaults, - this.languagePreference = LanguagePreference.defaults, - this.lockPreference = LockPreference.defaults, - this.notificationsPreference = NotificationsPreference.defaults}); - - factory _$PreferencesImpl.fromJson(Map json) => - _$$PreferencesImplFromJson(json); +class _LockPreference implements LockPreference { + const _LockPreference( + {this.inactivityLockSecs = 0, + this.lockWhenSwitching = false, + this.lockWithSystemLock = false}); + factory _LockPreference.fromJson(Map json) => + _$LockPreferenceFromJson(json); @override @JsonKey() - final ThemePreferences themePreference; + final int inactivityLockSecs; @override @JsonKey() - final LanguagePreference languagePreference; + final bool lockWhenSwitching; @override @JsonKey() - final LockPreference lockPreference; + final bool lockWithSystemLock; + + /// Create a copy of LockPreference + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey() - final NotificationsPreference notificationsPreference; + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$LockPreferenceCopyWith<_LockPreference> get copyWith => + __$LockPreferenceCopyWithImpl<_LockPreference>(this, _$identity); @override - String toString() { - return 'Preferences(themePreference: $themePreference, languagePreference: $languagePreference, lockPreference: $lockPreference, notificationsPreference: $notificationsPreference)'; + Map toJson() { + return _$LockPreferenceToJson( + this, + ); } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$PreferencesImpl && + other is _LockPreference && + (identical(other.inactivityLockSecs, inactivityLockSecs) || + other.inactivityLockSecs == inactivityLockSecs) && + (identical(other.lockWhenSwitching, lockWhenSwitching) || + other.lockWhenSwitching == lockWhenSwitching) && + (identical(other.lockWithSystemLock, lockWithSystemLock) || + other.lockWithSystemLock == lockWithSystemLock)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, inactivityLockSecs, lockWhenSwitching, lockWithSystemLock); + + @override + String toString() { + return 'LockPreference(inactivityLockSecs: $inactivityLockSecs, lockWhenSwitching: $lockWhenSwitching, lockWithSystemLock: $lockWithSystemLock)'; + } +} + +/// @nodoc +abstract mixin class _$LockPreferenceCopyWith<$Res> + implements $LockPreferenceCopyWith<$Res> { + factory _$LockPreferenceCopyWith( + _LockPreference value, $Res Function(_LockPreference) _then) = + __$LockPreferenceCopyWithImpl; + @override + @useResult + $Res call( + {int inactivityLockSecs, + bool lockWhenSwitching, + bool lockWithSystemLock}); +} + +/// @nodoc +class __$LockPreferenceCopyWithImpl<$Res> + implements _$LockPreferenceCopyWith<$Res> { + __$LockPreferenceCopyWithImpl(this._self, this._then); + + final _LockPreference _self; + final $Res Function(_LockPreference) _then; + + /// Create a copy of LockPreference + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? inactivityLockSecs = null, + Object? lockWhenSwitching = null, + Object? lockWithSystemLock = null, + }) { + return _then(_LockPreference( + inactivityLockSecs: null == inactivityLockSecs + ? _self.inactivityLockSecs + : inactivityLockSecs // ignore: cast_nullable_to_non_nullable + as int, + lockWhenSwitching: null == lockWhenSwitching + ? _self.lockWhenSwitching + : lockWhenSwitching // ignore: cast_nullable_to_non_nullable + as bool, + lockWithSystemLock: null == lockWithSystemLock + ? _self.lockWithSystemLock + : lockWithSystemLock // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +mixin _$Preferences { + ThemePreferences get themePreference; + LanguagePreference get languagePreference; + LockPreference get lockPreference; + NotificationsPreference get notificationsPreference; + + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + $PreferencesCopyWith get copyWith => + _$PreferencesCopyWithImpl(this as Preferences, _$identity); + + /// Serializes this Preferences to a JSON map. + Map toJson(); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is Preferences && (identical(other.themePreference, themePreference) || other.themePreference == themePreference) && (identical(other.languagePreference, languagePreference) || @@ -439,46 +245,253 @@ class _$PreferencesImpl implements _Preferences { int get hashCode => Object.hash(runtimeType, themePreference, languagePreference, lockPreference, notificationsPreference); - /// Create a copy of Preferences - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$PreferencesImplCopyWith<_$PreferencesImpl> get copyWith => - __$$PreferencesImplCopyWithImpl<_$PreferencesImpl>(this, _$identity); - - @override - Map toJson() { - return _$$PreferencesImplToJson( - this, - ); + String toString() { + return 'Preferences(themePreference: $themePreference, languagePreference: $languagePreference, lockPreference: $lockPreference, notificationsPreference: $notificationsPreference)'; } } -abstract class _Preferences implements Preferences { - const factory _Preferences( - {final ThemePreferences themePreference, - final LanguagePreference languagePreference, - final LockPreference lockPreference, - final NotificationsPreference notificationsPreference}) = - _$PreferencesImpl; +/// @nodoc +abstract mixin class $PreferencesCopyWith<$Res> { + factory $PreferencesCopyWith( + Preferences value, $Res Function(Preferences) _then) = + _$PreferencesCopyWithImpl; + @useResult + $Res call( + {ThemePreferences themePreference, + LanguagePreference languagePreference, + LockPreference lockPreference, + NotificationsPreference notificationsPreference}); - factory _Preferences.fromJson(Map json) = - _$PreferencesImpl.fromJson; + $ThemePreferencesCopyWith<$Res> get themePreference; + $LockPreferenceCopyWith<$Res> get lockPreference; + $NotificationsPreferenceCopyWith<$Res> get notificationsPreference; +} + +/// @nodoc +class _$PreferencesCopyWithImpl<$Res> implements $PreferencesCopyWith<$Res> { + _$PreferencesCopyWithImpl(this._self, this._then); + + final Preferences _self; + final $Res Function(Preferences) _then; + + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? themePreference = null, + Object? languagePreference = null, + Object? lockPreference = null, + Object? notificationsPreference = null, + }) { + return _then(_self.copyWith( + themePreference: null == themePreference + ? _self.themePreference + : themePreference // ignore: cast_nullable_to_non_nullable + as ThemePreferences, + languagePreference: null == languagePreference + ? _self.languagePreference + : languagePreference // ignore: cast_nullable_to_non_nullable + as LanguagePreference, + lockPreference: null == lockPreference + ? _self.lockPreference + : lockPreference // ignore: cast_nullable_to_non_nullable + as LockPreference, + notificationsPreference: null == notificationsPreference + ? _self.notificationsPreference + : notificationsPreference // ignore: cast_nullable_to_non_nullable + as NotificationsPreference, + )); + } + + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ThemePreferencesCopyWith<$Res> get themePreference { + return $ThemePreferencesCopyWith<$Res>(_self.themePreference, (value) { + return _then(_self.copyWith(themePreference: value)); + }); + } + + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $LockPreferenceCopyWith<$Res> get lockPreference { + return $LockPreferenceCopyWith<$Res>(_self.lockPreference, (value) { + return _then(_self.copyWith(lockPreference: value)); + }); + } + + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $NotificationsPreferenceCopyWith<$Res> get notificationsPreference { + return $NotificationsPreferenceCopyWith<$Res>(_self.notificationsPreference, + (value) { + return _then(_self.copyWith(notificationsPreference: value)); + }); + } +} + +/// @nodoc +@JsonSerializable() +class _Preferences implements Preferences { + const _Preferences( + {this.themePreference = ThemePreferences.defaults, + this.languagePreference = LanguagePreference.defaults, + this.lockPreference = LockPreference.defaults, + this.notificationsPreference = NotificationsPreference.defaults}); + factory _Preferences.fromJson(Map json) => + _$PreferencesFromJson(json); @override - ThemePreferences get themePreference; + @JsonKey() + final ThemePreferences themePreference; @override - LanguagePreference get languagePreference; + @JsonKey() + final LanguagePreference languagePreference; @override - LockPreference get lockPreference; + @JsonKey() + final LockPreference lockPreference; @override - NotificationsPreference get notificationsPreference; + @JsonKey() + final NotificationsPreference notificationsPreference; /// Create a copy of Preferences /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$PreferencesImplCopyWith<_$PreferencesImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + _$PreferencesCopyWith<_Preferences> get copyWith => + __$PreferencesCopyWithImpl<_Preferences>(this, _$identity); + + @override + Map toJson() { + return _$PreferencesToJson( + this, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _Preferences && + (identical(other.themePreference, themePreference) || + other.themePreference == themePreference) && + (identical(other.languagePreference, languagePreference) || + other.languagePreference == languagePreference) && + (identical(other.lockPreference, lockPreference) || + other.lockPreference == lockPreference) && + (identical( + other.notificationsPreference, notificationsPreference) || + other.notificationsPreference == notificationsPreference)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, themePreference, + languagePreference, lockPreference, notificationsPreference); + + @override + String toString() { + return 'Preferences(themePreference: $themePreference, languagePreference: $languagePreference, lockPreference: $lockPreference, notificationsPreference: $notificationsPreference)'; + } } + +/// @nodoc +abstract mixin class _$PreferencesCopyWith<$Res> + implements $PreferencesCopyWith<$Res> { + factory _$PreferencesCopyWith( + _Preferences value, $Res Function(_Preferences) _then) = + __$PreferencesCopyWithImpl; + @override + @useResult + $Res call( + {ThemePreferences themePreference, + LanguagePreference languagePreference, + LockPreference lockPreference, + NotificationsPreference notificationsPreference}); + + @override + $ThemePreferencesCopyWith<$Res> get themePreference; + @override + $LockPreferenceCopyWith<$Res> get lockPreference; + @override + $NotificationsPreferenceCopyWith<$Res> get notificationsPreference; +} + +/// @nodoc +class __$PreferencesCopyWithImpl<$Res> implements _$PreferencesCopyWith<$Res> { + __$PreferencesCopyWithImpl(this._self, this._then); + + final _Preferences _self; + final $Res Function(_Preferences) _then; + + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? themePreference = null, + Object? languagePreference = null, + Object? lockPreference = null, + Object? notificationsPreference = null, + }) { + return _then(_Preferences( + themePreference: null == themePreference + ? _self.themePreference + : themePreference // ignore: cast_nullable_to_non_nullable + as ThemePreferences, + languagePreference: null == languagePreference + ? _self.languagePreference + : languagePreference // ignore: cast_nullable_to_non_nullable + as LanguagePreference, + lockPreference: null == lockPreference + ? _self.lockPreference + : lockPreference // ignore: cast_nullable_to_non_nullable + as LockPreference, + notificationsPreference: null == notificationsPreference + ? _self.notificationsPreference + : notificationsPreference // ignore: cast_nullable_to_non_nullable + as NotificationsPreference, + )); + } + + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ThemePreferencesCopyWith<$Res> get themePreference { + return $ThemePreferencesCopyWith<$Res>(_self.themePreference, (value) { + return _then(_self.copyWith(themePreference: value)); + }); + } + + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $LockPreferenceCopyWith<$Res> get lockPreference { + return $LockPreferenceCopyWith<$Res>(_self.lockPreference, (value) { + return _then(_self.copyWith(lockPreference: value)); + }); + } + + /// Create a copy of Preferences + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $NotificationsPreferenceCopyWith<$Res> get notificationsPreference { + return $NotificationsPreferenceCopyWith<$Res>(_self.notificationsPreference, + (value) { + return _then(_self.copyWith(notificationsPreference: value)); + }); + } +} + +// dart format on diff --git a/lib/settings/models/preferences.g.dart b/lib/settings/models/preferences.g.dart index 5813f67..55f21a7 100644 --- a/lib/settings/models/preferences.g.dart +++ b/lib/settings/models/preferences.g.dart @@ -6,23 +6,21 @@ part of 'preferences.dart'; // JsonSerializableGenerator // ************************************************************************** -_$LockPreferenceImpl _$$LockPreferenceImplFromJson(Map json) => - _$LockPreferenceImpl( +_LockPreference _$LockPreferenceFromJson(Map json) => + _LockPreference( inactivityLockSecs: (json['inactivity_lock_secs'] as num?)?.toInt() ?? 0, lockWhenSwitching: json['lock_when_switching'] as bool? ?? false, lockWithSystemLock: json['lock_with_system_lock'] as bool? ?? false, ); -Map _$$LockPreferenceImplToJson( - _$LockPreferenceImpl instance) => +Map _$LockPreferenceToJson(_LockPreference instance) => { 'inactivity_lock_secs': instance.inactivityLockSecs, 'lock_when_switching': instance.lockWhenSwitching, 'lock_with_system_lock': instance.lockWithSystemLock, }; -_$PreferencesImpl _$$PreferencesImplFromJson(Map json) => - _$PreferencesImpl( +_Preferences _$PreferencesFromJson(Map json) => _Preferences( themePreference: json['theme_preference'] == null ? ThemePreferences.defaults : ThemePreferences.fromJson(json['theme_preference']), @@ -37,7 +35,7 @@ _$PreferencesImpl _$$PreferencesImplFromJson(Map json) => : NotificationsPreference.fromJson(json['notifications_preference']), ); -Map _$$PreferencesImplToJson(_$PreferencesImpl instance) => +Map _$PreferencesToJson(_Preferences instance) => { 'theme_preference': instance.themePreference.toJson(), 'language_preference': instance.languagePreference.toJson(), diff --git a/lib/theme/models/theme_preference.dart b/lib/theme/models/theme_preference.dart index 4be6b4e..aaad52d 100644 --- a/lib/theme/models/theme_preference.dart +++ b/lib/theme/models/theme_preference.dart @@ -49,7 +49,7 @@ enum ColorPreference { } @freezed -class ThemePreferences with _$ThemePreferences { +sealed class ThemePreferences with _$ThemePreferences { const factory ThemePreferences({ @Default(BrightnessPreference.system) BrightnessPreference brightnessPreference, diff --git a/lib/theme/models/theme_preference.freezed.dart b/lib/theme/models/theme_preference.freezed.dart index d96ed38..c915bca 100644 --- a/lib/theme/models/theme_preference.freezed.dart +++ b/lib/theme/models/theme_preference.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,176 +10,32 @@ part of 'theme_preference.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -ThemePreferences _$ThemePreferencesFromJson(Map json) { - return _ThemePreferences.fromJson(json); -} - /// @nodoc mixin _$ThemePreferences { - BrightnessPreference get brightnessPreference => - throw _privateConstructorUsedError; - ColorPreference get colorPreference => throw _privateConstructorUsedError; - double get displayScale => throw _privateConstructorUsedError; - bool get enableWallpaper => throw _privateConstructorUsedError; - - /// Serializes this ThemePreferences to a JSON map. - Map toJson() => throw _privateConstructorUsedError; + BrightnessPreference get brightnessPreference; + ColorPreference get colorPreference; + double get displayScale; + bool get enableWallpaper; /// Create a copy of ThemePreferences /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $ThemePreferencesCopyWith get copyWith => - throw _privateConstructorUsedError; -} + _$ThemePreferencesCopyWithImpl( + this as ThemePreferences, _$identity); -/// @nodoc -abstract class $ThemePreferencesCopyWith<$Res> { - factory $ThemePreferencesCopyWith( - ThemePreferences value, $Res Function(ThemePreferences) then) = - _$ThemePreferencesCopyWithImpl<$Res, ThemePreferences>; - @useResult - $Res call( - {BrightnessPreference brightnessPreference, - ColorPreference colorPreference, - double displayScale, - bool enableWallpaper}); -} - -/// @nodoc -class _$ThemePreferencesCopyWithImpl<$Res, $Val extends ThemePreferences> - implements $ThemePreferencesCopyWith<$Res> { - _$ThemePreferencesCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of ThemePreferences - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? brightnessPreference = null, - Object? colorPreference = null, - Object? displayScale = null, - Object? enableWallpaper = null, - }) { - return _then(_value.copyWith( - brightnessPreference: null == brightnessPreference - ? _value.brightnessPreference - : brightnessPreference // ignore: cast_nullable_to_non_nullable - as BrightnessPreference, - colorPreference: null == colorPreference - ? _value.colorPreference - : colorPreference // ignore: cast_nullable_to_non_nullable - as ColorPreference, - displayScale: null == displayScale - ? _value.displayScale - : displayScale // ignore: cast_nullable_to_non_nullable - as double, - enableWallpaper: null == enableWallpaper - ? _value.enableWallpaper - : enableWallpaper // ignore: cast_nullable_to_non_nullable - as bool, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$ThemePreferencesImplCopyWith<$Res> - implements $ThemePreferencesCopyWith<$Res> { - factory _$$ThemePreferencesImplCopyWith(_$ThemePreferencesImpl value, - $Res Function(_$ThemePreferencesImpl) then) = - __$$ThemePreferencesImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {BrightnessPreference brightnessPreference, - ColorPreference colorPreference, - double displayScale, - bool enableWallpaper}); -} - -/// @nodoc -class __$$ThemePreferencesImplCopyWithImpl<$Res> - extends _$ThemePreferencesCopyWithImpl<$Res, _$ThemePreferencesImpl> - implements _$$ThemePreferencesImplCopyWith<$Res> { - __$$ThemePreferencesImplCopyWithImpl(_$ThemePreferencesImpl _value, - $Res Function(_$ThemePreferencesImpl) _then) - : super(_value, _then); - - /// Create a copy of ThemePreferences - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? brightnessPreference = null, - Object? colorPreference = null, - Object? displayScale = null, - Object? enableWallpaper = null, - }) { - return _then(_$ThemePreferencesImpl( - brightnessPreference: null == brightnessPreference - ? _value.brightnessPreference - : brightnessPreference // ignore: cast_nullable_to_non_nullable - as BrightnessPreference, - colorPreference: null == colorPreference - ? _value.colorPreference - : colorPreference // ignore: cast_nullable_to_non_nullable - as ColorPreference, - displayScale: null == displayScale - ? _value.displayScale - : displayScale // ignore: cast_nullable_to_non_nullable - as double, - enableWallpaper: null == enableWallpaper - ? _value.enableWallpaper - : enableWallpaper // ignore: cast_nullable_to_non_nullable - as bool, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$ThemePreferencesImpl implements _ThemePreferences { - const _$ThemePreferencesImpl( - {this.brightnessPreference = BrightnessPreference.system, - this.colorPreference = ColorPreference.vapor, - this.displayScale = 1, - this.enableWallpaper = true}); - - factory _$ThemePreferencesImpl.fromJson(Map json) => - _$$ThemePreferencesImplFromJson(json); - - @override - @JsonKey() - final BrightnessPreference brightnessPreference; - @override - @JsonKey() - final ColorPreference colorPreference; - @override - @JsonKey() - final double displayScale; - @override - @JsonKey() - final bool enableWallpaper; - - @override - String toString() { - return 'ThemePreferences(brightnessPreference: $brightnessPreference, colorPreference: $colorPreference, displayScale: $displayScale, enableWallpaper: $enableWallpaper)'; - } + /// Serializes this ThemePreferences to a JSON map. + Map toJson(); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$ThemePreferencesImpl && + other is ThemePreferences && (identical(other.brightnessPreference, brightnessPreference) || other.brightnessPreference == brightnessPreference) && (identical(other.colorPreference, colorPreference) || @@ -194,46 +51,181 @@ class _$ThemePreferencesImpl implements _ThemePreferences { int get hashCode => Object.hash(runtimeType, brightnessPreference, colorPreference, displayScale, enableWallpaper); - /// Create a copy of ThemePreferences - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$ThemePreferencesImplCopyWith<_$ThemePreferencesImpl> get copyWith => - __$$ThemePreferencesImplCopyWithImpl<_$ThemePreferencesImpl>( - this, _$identity); - - @override - Map toJson() { - return _$$ThemePreferencesImplToJson( - this, - ); + String toString() { + return 'ThemePreferences(brightnessPreference: $brightnessPreference, colorPreference: $colorPreference, displayScale: $displayScale, enableWallpaper: $enableWallpaper)'; } } -abstract class _ThemePreferences implements ThemePreferences { - const factory _ThemePreferences( - {final BrightnessPreference brightnessPreference, - final ColorPreference colorPreference, - final double displayScale, - final bool enableWallpaper}) = _$ThemePreferencesImpl; +/// @nodoc +abstract mixin class $ThemePreferencesCopyWith<$Res> { + factory $ThemePreferencesCopyWith( + ThemePreferences value, $Res Function(ThemePreferences) _then) = + _$ThemePreferencesCopyWithImpl; + @useResult + $Res call( + {BrightnessPreference brightnessPreference, + ColorPreference colorPreference, + double displayScale, + bool enableWallpaper}); +} - factory _ThemePreferences.fromJson(Map json) = - _$ThemePreferencesImpl.fromJson; +/// @nodoc +class _$ThemePreferencesCopyWithImpl<$Res> + implements $ThemePreferencesCopyWith<$Res> { + _$ThemePreferencesCopyWithImpl(this._self, this._then); + + final ThemePreferences _self; + final $Res Function(ThemePreferences) _then; + + /// Create a copy of ThemePreferences + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? brightnessPreference = null, + Object? colorPreference = null, + Object? displayScale = null, + Object? enableWallpaper = null, + }) { + return _then(_self.copyWith( + brightnessPreference: null == brightnessPreference + ? _self.brightnessPreference + : brightnessPreference // ignore: cast_nullable_to_non_nullable + as BrightnessPreference, + colorPreference: null == colorPreference + ? _self.colorPreference + : colorPreference // ignore: cast_nullable_to_non_nullable + as ColorPreference, + displayScale: null == displayScale + ? _self.displayScale + : displayScale // ignore: cast_nullable_to_non_nullable + as double, + enableWallpaper: null == enableWallpaper + ? _self.enableWallpaper + : enableWallpaper // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _ThemePreferences implements ThemePreferences { + const _ThemePreferences( + {this.brightnessPreference = BrightnessPreference.system, + this.colorPreference = ColorPreference.vapor, + this.displayScale = 1, + this.enableWallpaper = true}); + factory _ThemePreferences.fromJson(Map json) => + _$ThemePreferencesFromJson(json); @override - BrightnessPreference get brightnessPreference; + @JsonKey() + final BrightnessPreference brightnessPreference; @override - ColorPreference get colorPreference; + @JsonKey() + final ColorPreference colorPreference; @override - double get displayScale; + @JsonKey() + final double displayScale; @override - bool get enableWallpaper; + @JsonKey() + final bool enableWallpaper; /// Create a copy of ThemePreferences /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$ThemePreferencesImplCopyWith<_$ThemePreferencesImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + _$ThemePreferencesCopyWith<_ThemePreferences> get copyWith => + __$ThemePreferencesCopyWithImpl<_ThemePreferences>(this, _$identity); + + @override + Map toJson() { + return _$ThemePreferencesToJson( + this, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _ThemePreferences && + (identical(other.brightnessPreference, brightnessPreference) || + other.brightnessPreference == brightnessPreference) && + (identical(other.colorPreference, colorPreference) || + other.colorPreference == colorPreference) && + (identical(other.displayScale, displayScale) || + other.displayScale == displayScale) && + (identical(other.enableWallpaper, enableWallpaper) || + other.enableWallpaper == enableWallpaper)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, brightnessPreference, + colorPreference, displayScale, enableWallpaper); + + @override + String toString() { + return 'ThemePreferences(brightnessPreference: $brightnessPreference, colorPreference: $colorPreference, displayScale: $displayScale, enableWallpaper: $enableWallpaper)'; + } } + +/// @nodoc +abstract mixin class _$ThemePreferencesCopyWith<$Res> + implements $ThemePreferencesCopyWith<$Res> { + factory _$ThemePreferencesCopyWith( + _ThemePreferences value, $Res Function(_ThemePreferences) _then) = + __$ThemePreferencesCopyWithImpl; + @override + @useResult + $Res call( + {BrightnessPreference brightnessPreference, + ColorPreference colorPreference, + double displayScale, + bool enableWallpaper}); +} + +/// @nodoc +class __$ThemePreferencesCopyWithImpl<$Res> + implements _$ThemePreferencesCopyWith<$Res> { + __$ThemePreferencesCopyWithImpl(this._self, this._then); + + final _ThemePreferences _self; + final $Res Function(_ThemePreferences) _then; + + /// Create a copy of ThemePreferences + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? brightnessPreference = null, + Object? colorPreference = null, + Object? displayScale = null, + Object? enableWallpaper = null, + }) { + return _then(_ThemePreferences( + brightnessPreference: null == brightnessPreference + ? _self.brightnessPreference + : brightnessPreference // ignore: cast_nullable_to_non_nullable + as BrightnessPreference, + colorPreference: null == colorPreference + ? _self.colorPreference + : colorPreference // ignore: cast_nullable_to_non_nullable + as ColorPreference, + displayScale: null == displayScale + ? _self.displayScale + : displayScale // ignore: cast_nullable_to_non_nullable + as double, + enableWallpaper: null == enableWallpaper + ? _self.enableWallpaper + : enableWallpaper // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +// dart format on diff --git a/lib/theme/models/theme_preference.g.dart b/lib/theme/models/theme_preference.g.dart index 23c3d38..f052e2c 100644 --- a/lib/theme/models/theme_preference.g.dart +++ b/lib/theme/models/theme_preference.g.dart @@ -6,9 +6,8 @@ part of 'theme_preference.dart'; // JsonSerializableGenerator // ************************************************************************** -_$ThemePreferencesImpl _$$ThemePreferencesImplFromJson( - Map json) => - _$ThemePreferencesImpl( +_ThemePreferences _$ThemePreferencesFromJson(Map json) => + _ThemePreferences( brightnessPreference: json['brightness_preference'] == null ? BrightnessPreference.system : BrightnessPreference.fromJson(json['brightness_preference']), @@ -19,8 +18,7 @@ _$ThemePreferencesImpl _$$ThemePreferencesImplFromJson( enableWallpaper: json['enable_wallpaper'] as bool? ?? true, ); -Map _$$ThemePreferencesImplToJson( - _$ThemePreferencesImpl instance) => +Map _$ThemePreferencesToJson(_ThemePreferences instance) => { 'brightness_preference': instance.brightnessPreference.toJson(), 'color_preference': instance.colorPreference.toJson(), diff --git a/lib/tools/loggy.dart b/lib/tools/loggy.dart index 2730888..47a1ffd 100644 --- a/lib/tools/loggy.dart +++ b/lib/tools/loggy.dart @@ -8,6 +8,7 @@ import 'package:intl/intl.dart'; import 'package:loggy/loggy.dart'; import 'package:veilid_support/veilid_support.dart'; +import '../proto/proto.dart'; import '../veilid_processor/views/developer.dart'; import 'state_logger.dart'; @@ -121,6 +122,7 @@ class CallbackPrinter extends LoggyPrinter { callback?.call(record); } + // Change callback function // ignore: use_setters_to_change_properties void setCallback(void Function(LogRecord)? cb) { callback = cb; @@ -147,6 +149,7 @@ void initLoggy() { logOptions: getLogOptions(null), ); + // Allow trace logging from the command line // ignore: do_not_use_environment const isTrace = String.fromEnvironment('LOG_TRACE') != ''; LogLevel logLevel; @@ -159,5 +162,8 @@ void initLoggy() { Loggy('').level = getLogOptions(logLevel); // Create state logger + registerVeilidProtoToDebug(); + registerVeilidDHTProtoToDebug(); + registerVeilidchatProtoToDebug(); Bloc.observer = const StateLogger(); } diff --git a/lib/tools/state_logger.dart b/lib/tools/state_logger.dart index 08e32b3..50dec46 100644 --- a/lib/tools/state_logger.dart +++ b/lib/tools/state_logger.dart @@ -1,5 +1,8 @@ +import 'dart:convert'; + import 'package:bloc/bloc.dart'; import 'package:loggy/loggy.dart'; +import 'package:veilid_support/veilid_support.dart'; import 'loggy.dart'; const Map _blocChangeLogLevels = { @@ -38,7 +41,12 @@ class StateLogger extends BlocObserver { void onChange(BlocBase bloc, Change change) { super.onChange(bloc, change); _checkLogLevel(_blocChangeLogLevels, LogLevel.debug, bloc, (logLevel) { - log.log(logLevel, 'Change: ${bloc.runtimeType} $change'); + const encoder = JsonEncoder.withIndent(' ', DynamicDebug.toDebug); + log.log( + logLevel, + 'Change: ${bloc.runtimeType}\n' + 'currentState: ${encoder.convert(change.currentState)}\n' + 'nextState: ${encoder.convert(change.nextState)}\n'); }); } diff --git a/lib/veilid_processor/models/processor_connection_state.dart b/lib/veilid_processor/models/processor_connection_state.dart index e92ebdc..6b68a8e 100644 --- a/lib/veilid_processor/models/processor_connection_state.dart +++ b/lib/veilid_processor/models/processor_connection_state.dart @@ -4,7 +4,7 @@ import 'package:veilid_support/veilid_support.dart'; part 'processor_connection_state.freezed.dart'; @freezed -class ProcessorConnectionState with _$ProcessorConnectionState { +sealed class ProcessorConnectionState with _$ProcessorConnectionState { const factory ProcessorConnectionState({ required VeilidStateAttachment attachment, required VeilidStateNetwork network, diff --git a/lib/veilid_processor/models/processor_connection_state.freezed.dart b/lib/veilid_processor/models/processor_connection_state.freezed.dart index 87ad295..c7c5288 100644 --- a/lib/veilid_processor/models/processor_connection_state.freezed.dart +++ b/lib/veilid_processor/models/processor_connection_state.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,157 +10,27 @@ part of 'processor_connection_state.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - /// @nodoc mixin _$ProcessorConnectionState { - VeilidStateAttachment get attachment => throw _privateConstructorUsedError; - VeilidStateNetwork get network => throw _privateConstructorUsedError; + VeilidStateAttachment get attachment; + VeilidStateNetwork get network; /// Create a copy of ProcessorConnectionState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $ProcessorConnectionStateCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $ProcessorConnectionStateCopyWith<$Res> { - factory $ProcessorConnectionStateCopyWith(ProcessorConnectionState value, - $Res Function(ProcessorConnectionState) then) = - _$ProcessorConnectionStateCopyWithImpl<$Res, ProcessorConnectionState>; - @useResult - $Res call({VeilidStateAttachment attachment, VeilidStateNetwork network}); - - $VeilidStateAttachmentCopyWith<$Res> get attachment; - $VeilidStateNetworkCopyWith<$Res> get network; -} - -/// @nodoc -class _$ProcessorConnectionStateCopyWithImpl<$Res, - $Val extends ProcessorConnectionState> - implements $ProcessorConnectionStateCopyWith<$Res> { - _$ProcessorConnectionStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of ProcessorConnectionState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? attachment = null, - Object? network = null, - }) { - return _then(_value.copyWith( - attachment: null == attachment - ? _value.attachment - : attachment // ignore: cast_nullable_to_non_nullable - as VeilidStateAttachment, - network: null == network - ? _value.network - : network // ignore: cast_nullable_to_non_nullable - as VeilidStateNetwork, - ) as $Val); - } - - /// Create a copy of ProcessorConnectionState - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $VeilidStateAttachmentCopyWith<$Res> get attachment { - return $VeilidStateAttachmentCopyWith<$Res>(_value.attachment, (value) { - return _then(_value.copyWith(attachment: value) as $Val); - }); - } - - /// Create a copy of ProcessorConnectionState - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $VeilidStateNetworkCopyWith<$Res> get network { - return $VeilidStateNetworkCopyWith<$Res>(_value.network, (value) { - return _then(_value.copyWith(network: value) as $Val); - }); - } -} - -/// @nodoc -abstract class _$$ProcessorConnectionStateImplCopyWith<$Res> - implements $ProcessorConnectionStateCopyWith<$Res> { - factory _$$ProcessorConnectionStateImplCopyWith( - _$ProcessorConnectionStateImpl value, - $Res Function(_$ProcessorConnectionStateImpl) then) = - __$$ProcessorConnectionStateImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({VeilidStateAttachment attachment, VeilidStateNetwork network}); - - @override - $VeilidStateAttachmentCopyWith<$Res> get attachment; - @override - $VeilidStateNetworkCopyWith<$Res> get network; -} - -/// @nodoc -class __$$ProcessorConnectionStateImplCopyWithImpl<$Res> - extends _$ProcessorConnectionStateCopyWithImpl<$Res, - _$ProcessorConnectionStateImpl> - implements _$$ProcessorConnectionStateImplCopyWith<$Res> { - __$$ProcessorConnectionStateImplCopyWithImpl( - _$ProcessorConnectionStateImpl _value, - $Res Function(_$ProcessorConnectionStateImpl) _then) - : super(_value, _then); - - /// Create a copy of ProcessorConnectionState - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? attachment = null, - Object? network = null, - }) { - return _then(_$ProcessorConnectionStateImpl( - attachment: null == attachment - ? _value.attachment - : attachment // ignore: cast_nullable_to_non_nullable - as VeilidStateAttachment, - network: null == network - ? _value.network - : network // ignore: cast_nullable_to_non_nullable - as VeilidStateNetwork, - )); - } -} - -/// @nodoc - -class _$ProcessorConnectionStateImpl extends _ProcessorConnectionState { - const _$ProcessorConnectionStateImpl( - {required this.attachment, required this.network}) - : super._(); - - @override - final VeilidStateAttachment attachment; - @override - final VeilidStateNetwork network; - - @override - String toString() { - return 'ProcessorConnectionState(attachment: $attachment, network: $network)'; - } + _$ProcessorConnectionStateCopyWithImpl( + this as ProcessorConnectionState, _$identity); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$ProcessorConnectionStateImpl && + other is ProcessorConnectionState && (identical(other.attachment, attachment) || other.attachment == attachment) && (identical(other.network, network) || other.network == network)); @@ -168,32 +39,176 @@ class _$ProcessorConnectionStateImpl extends _ProcessorConnectionState { @override int get hashCode => Object.hash(runtimeType, attachment, network); + @override + String toString() { + return 'ProcessorConnectionState(attachment: $attachment, network: $network)'; + } +} + +/// @nodoc +abstract mixin class $ProcessorConnectionStateCopyWith<$Res> { + factory $ProcessorConnectionStateCopyWith(ProcessorConnectionState value, + $Res Function(ProcessorConnectionState) _then) = + _$ProcessorConnectionStateCopyWithImpl; + @useResult + $Res call({VeilidStateAttachment attachment, VeilidStateNetwork network}); + + $VeilidStateAttachmentCopyWith<$Res> get attachment; + $VeilidStateNetworkCopyWith<$Res> get network; +} + +/// @nodoc +class _$ProcessorConnectionStateCopyWithImpl<$Res> + implements $ProcessorConnectionStateCopyWith<$Res> { + _$ProcessorConnectionStateCopyWithImpl(this._self, this._then); + + final ProcessorConnectionState _self; + final $Res Function(ProcessorConnectionState) _then; + + /// Create a copy of ProcessorConnectionState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? attachment = null, + Object? network = null, + }) { + return _then(_self.copyWith( + attachment: null == attachment + ? _self.attachment + : attachment // ignore: cast_nullable_to_non_nullable + as VeilidStateAttachment, + network: null == network + ? _self.network + : network // ignore: cast_nullable_to_non_nullable + as VeilidStateNetwork, + )); + } + /// Create a copy of ProcessorConnectionState /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$ProcessorConnectionStateImplCopyWith<_$ProcessorConnectionStateImpl> - get copyWith => __$$ProcessorConnectionStateImplCopyWithImpl< - _$ProcessorConnectionStateImpl>(this, _$identity); + $VeilidStateAttachmentCopyWith<$Res> get attachment { + return $VeilidStateAttachmentCopyWith<$Res>(_self.attachment, (value) { + return _then(_self.copyWith(attachment: value)); + }); + } + + /// Create a copy of ProcessorConnectionState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $VeilidStateNetworkCopyWith<$Res> get network { + return $VeilidStateNetworkCopyWith<$Res>(_self.network, (value) { + return _then(_self.copyWith(network: value)); + }); + } } -abstract class _ProcessorConnectionState extends ProcessorConnectionState { - const factory _ProcessorConnectionState( - {required final VeilidStateAttachment attachment, - required final VeilidStateNetwork network}) = - _$ProcessorConnectionStateImpl; - const _ProcessorConnectionState._() : super._(); +/// @nodoc + +class _ProcessorConnectionState extends ProcessorConnectionState { + const _ProcessorConnectionState( + {required this.attachment, required this.network}) + : super._(); @override - VeilidStateAttachment get attachment; + final VeilidStateAttachment attachment; @override - VeilidStateNetwork get network; + final VeilidStateNetwork network; /// Create a copy of ProcessorConnectionState /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$ProcessorConnectionStateImplCopyWith<_$ProcessorConnectionStateImpl> - get copyWith => throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + _$ProcessorConnectionStateCopyWith<_ProcessorConnectionState> get copyWith => + __$ProcessorConnectionStateCopyWithImpl<_ProcessorConnectionState>( + this, _$identity); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _ProcessorConnectionState && + (identical(other.attachment, attachment) || + other.attachment == attachment) && + (identical(other.network, network) || other.network == network)); + } + + @override + int get hashCode => Object.hash(runtimeType, attachment, network); + + @override + String toString() { + return 'ProcessorConnectionState(attachment: $attachment, network: $network)'; + } } + +/// @nodoc +abstract mixin class _$ProcessorConnectionStateCopyWith<$Res> + implements $ProcessorConnectionStateCopyWith<$Res> { + factory _$ProcessorConnectionStateCopyWith(_ProcessorConnectionState value, + $Res Function(_ProcessorConnectionState) _then) = + __$ProcessorConnectionStateCopyWithImpl; + @override + @useResult + $Res call({VeilidStateAttachment attachment, VeilidStateNetwork network}); + + @override + $VeilidStateAttachmentCopyWith<$Res> get attachment; + @override + $VeilidStateNetworkCopyWith<$Res> get network; +} + +/// @nodoc +class __$ProcessorConnectionStateCopyWithImpl<$Res> + implements _$ProcessorConnectionStateCopyWith<$Res> { + __$ProcessorConnectionStateCopyWithImpl(this._self, this._then); + + final _ProcessorConnectionState _self; + final $Res Function(_ProcessorConnectionState) _then; + + /// Create a copy of ProcessorConnectionState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? attachment = null, + Object? network = null, + }) { + return _then(_ProcessorConnectionState( + attachment: null == attachment + ? _self.attachment + : attachment // ignore: cast_nullable_to_non_nullable + as VeilidStateAttachment, + network: null == network + ? _self.network + : network // ignore: cast_nullable_to_non_nullable + as VeilidStateNetwork, + )); + } + + /// Create a copy of ProcessorConnectionState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $VeilidStateAttachmentCopyWith<$Res> get attachment { + return $VeilidStateAttachmentCopyWith<$Res>(_self.attachment, (value) { + return _then(_self.copyWith(attachment: value)); + }); + } + + /// Create a copy of ProcessorConnectionState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $VeilidStateNetworkCopyWith<$Res> get network { + return $VeilidStateNetworkCopyWith<$Res>(_self.network, (value) { + return _then(_self.copyWith(network: value)); + }); + } +} + +// dart format on diff --git a/packages/veilid_support/lib/dht_support/proto/proto.dart b/packages/veilid_support/lib/dht_support/proto/proto.dart index 6b36970..ceac3d5 100644 --- a/packages/veilid_support/lib/dht_support/proto/proto.dart +++ b/packages/veilid_support/lib/dht_support/proto/proto.dart @@ -1,5 +1,6 @@ import '../../proto/dht.pb.dart' as dhtproto; import '../../proto/proto.dart' as veilidproto; +import '../../src/dynamic_debug.dart'; import '../dht_support.dart'; export '../../proto/dht.pb.dart'; @@ -23,3 +24,44 @@ extension ProtoOwnedDHTRecordPointer on dhtproto.OwnedDHTRecordPointer { OwnedDHTRecordPointer toVeilid() => OwnedDHTRecordPointer( recordKey: recordKey.toVeilid(), owner: owner.toVeilid()); } + +void registerVeilidDHTProtoToDebug() { + dynamic toDebug(dynamic obj) { + if (obj is dhtproto.OwnedDHTRecordPointer) { + return { + r'$runtimeType': obj.runtimeType, + 'recordKey': obj.recordKey, + 'owner': obj.owner, + }; + } + if (obj is dhtproto.DHTData) { + return { + r'$runtimeType': obj.runtimeType, + 'keys': obj.keys, + 'hash': obj.hash, + 'chunk': obj.chunk, + 'size': obj.size + }; + } + if (obj is dhtproto.DHTLog) { + return { + r'$runtimeType': obj.runtimeType, + 'head': obj.head, + 'tail': obj.tail, + 'stride': obj.stride, + }; + } + if (obj is dhtproto.DHTShortArray) { + return { + r'$runtimeType': obj.runtimeType, + 'keys': obj.keys, + 'index': obj.index, + 'seqs': obj.seqs, + }; + } + + return obj; + } + + DynamicDebug.registerToDebug(toDebug); +} diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_cubit.dart index c299bdc..492312f 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_cubit.dart @@ -31,6 +31,14 @@ class DHTLogStateData extends Equatable { @override List get props => [length, window, windowTail, windowSize, follow]; + + @override + String toString() => 'DHTLogStateData(' + 'length: $length, ' + 'windowTail: $windowTail, ' + 'windowSize: $windowSize, ' + 'follow: $follow, ' + 'window: ${DynamicDebug.toDebug(window)})'; } typedef DHTLogState = AsyncValue>; diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart index 8eff1b6..bb27e04 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart @@ -126,10 +126,7 @@ class _DHTLogSpine { Future delete() async => _spineMutex.protect(_spineRecord.delete); Future operate(Future Function(_DHTLogSpine) closure) async => - // ignore: prefer_expression_function_bodies - _spineMutex.protect(() async { - return closure(this); - }); + _spineMutex.protect(() async => closure(this)); Future operateAppend(Future Function(_DHTLogSpine) closure) async => _spineMutex.protect(() async { diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart index 0a51ba1..4e632fc 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart @@ -511,7 +511,7 @@ class DHTRecord implements DHTDeleteable { key, subkeys: [ValueSubkeyRange.single(subkey)], ); - return rr.localSeqs.firstOrNull ?? 0xFFFFFFFF; + return rr.localSeqs.firstOrNull ?? emptySeq; } void _addValueChange( @@ -566,4 +566,6 @@ class DHTRecord implements DHTDeleteable { int _openCount; StreamController? _watchController; _WatchState? _watchState; + + static const int emptySeq = 0xFFFFFFFF; } diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart index 15c955d..9027799 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart @@ -9,6 +9,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:protobuf/protobuf.dart'; import '../../../../veilid_support.dart'; +import 'extensions.dart'; export 'package:fast_immutable_collections/fast_immutable_collections.dart' show Output; @@ -32,7 +33,7 @@ typedef DHTRecordPoolLogger = void Function(String message); /// Record pool that managed DHTRecords and allows for tagged deletion /// String versions of keys due to IMap<> json unsupported in key @freezed -class DHTRecordPoolAllocations with _$DHTRecordPoolAllocations { +sealed class DHTRecordPoolAllocations with _$DHTRecordPoolAllocations { const factory DHTRecordPoolAllocations({ @Default(IMapConst>({})) IMap> childrenByParent, @@ -49,7 +50,7 @@ class DHTRecordPoolAllocations with _$DHTRecordPoolAllocations { /// Pointer to an owned record, with key, owner key and owner secret /// Ensure that these are only serialized encrypted @freezed -class OwnedDHTRecordPointer with _$OwnedDHTRecordPointer { +sealed class OwnedDHTRecordPointer with _$OwnedDHTRecordPointer { const factory OwnedDHTRecordPointer({ required TypedKey recordKey, required KeyPair owner, @@ -843,8 +844,12 @@ class DHTRecordPool with TableDBBackedJson { log('Timeout in watch cancel for key=$openedRecordKey'); } on VeilidAPIException catch (e) { // Failed to cancel DHT watch, try again next tick - log('Exception in watch cancel for key=$openedRecordKey: $e'); + log('VeilidAPIException in watch cancel for key=$openedRecordKey: $e'); + } catch (e) { + log('Unhandled exception in watch cancel for key=$openedRecordKey: $e'); + rethrow; } + return; } @@ -887,7 +892,10 @@ class DHTRecordPool with TableDBBackedJson { log('Timeout in watch update for key=$openedRecordKey'); } on VeilidAPIException catch (e) { // Failed to update DHT watch, try again next tick - log('Exception in watch update for key=$openedRecordKey: $e'); + log('VeilidAPIException in watch update for key=$openedRecordKey: $e'); + } catch (e) { + log('Unhandled exception in watch update for key=$openedRecordKey: $e'); + rethrow; } // If we still need a state update after this then do a poll instead @@ -904,28 +912,29 @@ class DHTRecordPool with TableDBBackedJson { singleFuture((this, _sfPollWatch, openedRecordKey), () async { final dhtctx = openedRecordInfo.shared.defaultRoutingContext; - // Get single subkey to poll - // XXX: veilid api limits this for now until everyone supports - // inspectDHTRecord - final pollSubkey = unionWatchState.subkeys?.firstSubkey; - if (pollSubkey == null) { - return; + final currentReport = await dhtctx.inspectDHTRecord(openedRecordKey, + subkeys: unionWatchState.subkeys, scope: DHTReportScope.syncGet); + + final fsc = currentReport.firstSeqChange; + if (fsc == null) { + return null; } - final pollSubkeys = [ValueSubkeyRange.single(pollSubkey)]; + final newerSubkeys = currentReport.newerSubkeys; - final currentReport = - await dhtctx.inspectDHTRecord(openedRecordKey, subkeys: pollSubkeys); - final currentSeq = currentReport.localSeqs.firstOrNull ?? -1; - - final valueData = await dhtctx.getDHTValue(openedRecordKey, pollSubkey, + final valueData = await dhtctx.getDHTValue(openedRecordKey, fsc.subkey, forceRefresh: true); if (valueData == null) { return; } - if (valueData.seq > currentSeq) { + + if (valueData.seq < fsc.newSeq) { + log('inspect returned a newer seq than get: ${valueData.seq} < $fsc'); + } + + if (valueData.seq > fsc.oldSeq && valueData.seq != DHTRecord.emptySeq) { processRemoteValueChange(VeilidUpdateValueChange( key: openedRecordKey, - subkeys: pollSubkeys, + subkeys: newerSubkeys, count: 0xFFFFFFFF, value: valueData)); } diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.freezed.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.freezed.dart index 9e51ef8..48372bb 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.freezed.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,183 +10,32 @@ part of 'dht_record_pool.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -DHTRecordPoolAllocations _$DHTRecordPoolAllocationsFromJson( - Map json) { - return _DHTRecordPoolAllocations.fromJson(json); -} - /// @nodoc mixin _$DHTRecordPoolAllocations { - IMap>> get childrenByParent => - throw _privateConstructorUsedError; - IMap> get parentByChild => - throw _privateConstructorUsedError; - ISet> get rootRecords => - throw _privateConstructorUsedError; - IMap get debugNames => throw _privateConstructorUsedError; - - /// Serializes this DHTRecordPoolAllocations to a JSON map. - Map toJson() => throw _privateConstructorUsedError; + IMap> get childrenByParent; + IMap get parentByChild; + ISet get rootRecords; + IMap get debugNames; /// Create a copy of DHTRecordPoolAllocations /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $DHTRecordPoolAllocationsCopyWith get copyWith => - throw _privateConstructorUsedError; -} + _$DHTRecordPoolAllocationsCopyWithImpl( + this as DHTRecordPoolAllocations, _$identity); -/// @nodoc -abstract class $DHTRecordPoolAllocationsCopyWith<$Res> { - factory $DHTRecordPoolAllocationsCopyWith(DHTRecordPoolAllocations value, - $Res Function(DHTRecordPoolAllocations) then) = - _$DHTRecordPoolAllocationsCopyWithImpl<$Res, DHTRecordPoolAllocations>; - @useResult - $Res call( - {IMap>> childrenByParent, - IMap> parentByChild, - ISet> rootRecords, - IMap debugNames}); -} - -/// @nodoc -class _$DHTRecordPoolAllocationsCopyWithImpl<$Res, - $Val extends DHTRecordPoolAllocations> - implements $DHTRecordPoolAllocationsCopyWith<$Res> { - _$DHTRecordPoolAllocationsCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of DHTRecordPoolAllocations - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? childrenByParent = null, - Object? parentByChild = null, - Object? rootRecords = null, - Object? debugNames = null, - }) { - return _then(_value.copyWith( - childrenByParent: null == childrenByParent - ? _value.childrenByParent - : childrenByParent // ignore: cast_nullable_to_non_nullable - as IMap>>, - parentByChild: null == parentByChild - ? _value.parentByChild - : parentByChild // ignore: cast_nullable_to_non_nullable - as IMap>, - rootRecords: null == rootRecords - ? _value.rootRecords - : rootRecords // ignore: cast_nullable_to_non_nullable - as ISet>, - debugNames: null == debugNames - ? _value.debugNames - : debugNames // ignore: cast_nullable_to_non_nullable - as IMap, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$DHTRecordPoolAllocationsImplCopyWith<$Res> - implements $DHTRecordPoolAllocationsCopyWith<$Res> { - factory _$$DHTRecordPoolAllocationsImplCopyWith( - _$DHTRecordPoolAllocationsImpl value, - $Res Function(_$DHTRecordPoolAllocationsImpl) then) = - __$$DHTRecordPoolAllocationsImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {IMap>> childrenByParent, - IMap> parentByChild, - ISet> rootRecords, - IMap debugNames}); -} - -/// @nodoc -class __$$DHTRecordPoolAllocationsImplCopyWithImpl<$Res> - extends _$DHTRecordPoolAllocationsCopyWithImpl<$Res, - _$DHTRecordPoolAllocationsImpl> - implements _$$DHTRecordPoolAllocationsImplCopyWith<$Res> { - __$$DHTRecordPoolAllocationsImplCopyWithImpl( - _$DHTRecordPoolAllocationsImpl _value, - $Res Function(_$DHTRecordPoolAllocationsImpl) _then) - : super(_value, _then); - - /// Create a copy of DHTRecordPoolAllocations - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? childrenByParent = null, - Object? parentByChild = null, - Object? rootRecords = null, - Object? debugNames = null, - }) { - return _then(_$DHTRecordPoolAllocationsImpl( - childrenByParent: null == childrenByParent - ? _value.childrenByParent - : childrenByParent // ignore: cast_nullable_to_non_nullable - as IMap>>, - parentByChild: null == parentByChild - ? _value.parentByChild - : parentByChild // ignore: cast_nullable_to_non_nullable - as IMap>, - rootRecords: null == rootRecords - ? _value.rootRecords - : rootRecords // ignore: cast_nullable_to_non_nullable - as ISet>, - debugNames: null == debugNames - ? _value.debugNames - : debugNames // ignore: cast_nullable_to_non_nullable - as IMap, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$DHTRecordPoolAllocationsImpl implements _DHTRecordPoolAllocations { - const _$DHTRecordPoolAllocationsImpl( - {this.childrenByParent = const IMapConst>({}), - this.parentByChild = const IMapConst({}), - this.rootRecords = const ISetConst({}), - this.debugNames = const IMapConst({})}); - - factory _$DHTRecordPoolAllocationsImpl.fromJson(Map json) => - _$$DHTRecordPoolAllocationsImplFromJson(json); - - @override - @JsonKey() - final IMap>> childrenByParent; - @override - @JsonKey() - final IMap> parentByChild; - @override - @JsonKey() - final ISet> rootRecords; - @override - @JsonKey() - final IMap debugNames; - - @override - String toString() { - return 'DHTRecordPoolAllocations(childrenByParent: $childrenByParent, parentByChild: $parentByChild, rootRecords: $rootRecords, debugNames: $debugNames)'; - } + /// Serializes this DHTRecordPoolAllocations to a JSON map. + Map toJson(); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$DHTRecordPoolAllocationsImpl && + other is DHTRecordPoolAllocations && (identical(other.childrenByParent, childrenByParent) || other.childrenByParent == childrenByParent) && (identical(other.parentByChild, parentByChild) || @@ -201,178 +51,205 @@ class _$DHTRecordPoolAllocationsImpl implements _DHTRecordPoolAllocations { int get hashCode => Object.hash(runtimeType, childrenByParent, parentByChild, const DeepCollectionEquality().hash(rootRecords), debugNames); - /// Create a copy of DHTRecordPoolAllocations - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$DHTRecordPoolAllocationsImplCopyWith<_$DHTRecordPoolAllocationsImpl> - get copyWith => __$$DHTRecordPoolAllocationsImplCopyWithImpl< - _$DHTRecordPoolAllocationsImpl>(this, _$identity); - - @override - Map toJson() { - return _$$DHTRecordPoolAllocationsImplToJson( - this, - ); - } -} - -abstract class _DHTRecordPoolAllocations implements DHTRecordPoolAllocations { - const factory _DHTRecordPoolAllocations( - {final IMap>> childrenByParent, - final IMap> parentByChild, - final ISet> rootRecords, - final IMap debugNames}) = _$DHTRecordPoolAllocationsImpl; - - factory _DHTRecordPoolAllocations.fromJson(Map json) = - _$DHTRecordPoolAllocationsImpl.fromJson; - - @override - IMap>> get childrenByParent; - @override - IMap> get parentByChild; - @override - ISet> get rootRecords; - @override - IMap get debugNames; - - /// Create a copy of DHTRecordPoolAllocations - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$DHTRecordPoolAllocationsImplCopyWith<_$DHTRecordPoolAllocationsImpl> - get copyWith => throw _privateConstructorUsedError; -} - -OwnedDHTRecordPointer _$OwnedDHTRecordPointerFromJson( - Map json) { - return _OwnedDHTRecordPointer.fromJson(json); -} - -/// @nodoc -mixin _$OwnedDHTRecordPointer { - Typed get recordKey => - throw _privateConstructorUsedError; - KeyPair get owner => throw _privateConstructorUsedError; - - /// Serializes this OwnedDHTRecordPointer to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of OwnedDHTRecordPointer - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $OwnedDHTRecordPointerCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $OwnedDHTRecordPointerCopyWith<$Res> { - factory $OwnedDHTRecordPointerCopyWith(OwnedDHTRecordPointer value, - $Res Function(OwnedDHTRecordPointer) then) = - _$OwnedDHTRecordPointerCopyWithImpl<$Res, OwnedDHTRecordPointer>; - @useResult - $Res call({Typed recordKey, KeyPair owner}); -} - -/// @nodoc -class _$OwnedDHTRecordPointerCopyWithImpl<$Res, - $Val extends OwnedDHTRecordPointer> - implements $OwnedDHTRecordPointerCopyWith<$Res> { - _$OwnedDHTRecordPointerCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of OwnedDHTRecordPointer - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? recordKey = null, - Object? owner = null, - }) { - return _then(_value.copyWith( - recordKey: null == recordKey - ? _value.recordKey - : recordKey // ignore: cast_nullable_to_non_nullable - as Typed, - owner: null == owner - ? _value.owner - : owner // ignore: cast_nullable_to_non_nullable - as KeyPair, - ) as $Val); + String toString() { + return 'DHTRecordPoolAllocations(childrenByParent: $childrenByParent, parentByChild: $parentByChild, rootRecords: $rootRecords, debugNames: $debugNames)'; } } /// @nodoc -abstract class _$$OwnedDHTRecordPointerImplCopyWith<$Res> - implements $OwnedDHTRecordPointerCopyWith<$Res> { - factory _$$OwnedDHTRecordPointerImplCopyWith( - _$OwnedDHTRecordPointerImpl value, - $Res Function(_$OwnedDHTRecordPointerImpl) then) = - __$$OwnedDHTRecordPointerImplCopyWithImpl<$Res>; - @override +abstract mixin class $DHTRecordPoolAllocationsCopyWith<$Res> { + factory $DHTRecordPoolAllocationsCopyWith(DHTRecordPoolAllocations value, + $Res Function(DHTRecordPoolAllocations) _then) = + _$DHTRecordPoolAllocationsCopyWithImpl; @useResult - $Res call({Typed recordKey, KeyPair owner}); + $Res call( + {IMap>> childrenByParent, + IMap> parentByChild, + ISet> rootRecords, + IMap debugNames}); } /// @nodoc -class __$$OwnedDHTRecordPointerImplCopyWithImpl<$Res> - extends _$OwnedDHTRecordPointerCopyWithImpl<$Res, - _$OwnedDHTRecordPointerImpl> - implements _$$OwnedDHTRecordPointerImplCopyWith<$Res> { - __$$OwnedDHTRecordPointerImplCopyWithImpl(_$OwnedDHTRecordPointerImpl _value, - $Res Function(_$OwnedDHTRecordPointerImpl) _then) - : super(_value, _then); +class _$DHTRecordPoolAllocationsCopyWithImpl<$Res> + implements $DHTRecordPoolAllocationsCopyWith<$Res> { + _$DHTRecordPoolAllocationsCopyWithImpl(this._self, this._then); - /// Create a copy of OwnedDHTRecordPointer + final DHTRecordPoolAllocations _self; + final $Res Function(DHTRecordPoolAllocations) _then; + + /// Create a copy of DHTRecordPoolAllocations /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? recordKey = null, - Object? owner = null, + Object? childrenByParent = null, + Object? parentByChild = null, + Object? rootRecords = null, + Object? debugNames = null, }) { - return _then(_$OwnedDHTRecordPointerImpl( - recordKey: null == recordKey - ? _value.recordKey - : recordKey // ignore: cast_nullable_to_non_nullable - as Typed, - owner: null == owner - ? _value.owner - : owner // ignore: cast_nullable_to_non_nullable - as KeyPair, + return _then(_self.copyWith( + childrenByParent: null == childrenByParent + ? _self.childrenByParent! + : childrenByParent // ignore: cast_nullable_to_non_nullable + as IMap>>, + parentByChild: null == parentByChild + ? _self.parentByChild! + : parentByChild // ignore: cast_nullable_to_non_nullable + as IMap>, + rootRecords: null == rootRecords + ? _self.rootRecords! + : rootRecords // ignore: cast_nullable_to_non_nullable + as ISet>, + debugNames: null == debugNames + ? _self.debugNames + : debugNames // ignore: cast_nullable_to_non_nullable + as IMap, )); } } /// @nodoc @JsonSerializable() -class _$OwnedDHTRecordPointerImpl implements _OwnedDHTRecordPointer { - const _$OwnedDHTRecordPointerImpl( - {required this.recordKey, required this.owner}); - - factory _$OwnedDHTRecordPointerImpl.fromJson(Map json) => - _$$OwnedDHTRecordPointerImplFromJson(json); +class _DHTRecordPoolAllocations implements DHTRecordPoolAllocations { + const _DHTRecordPoolAllocations( + {this.childrenByParent = const IMapConst>({}), + this.parentByChild = const IMapConst({}), + this.rootRecords = const ISetConst({}), + this.debugNames = const IMapConst({})}); + factory _DHTRecordPoolAllocations.fromJson(Map json) => + _$DHTRecordPoolAllocationsFromJson(json); @override - final Typed recordKey; + @JsonKey() + final IMap>> childrenByParent; @override - final KeyPair owner; + @JsonKey() + final IMap> parentByChild; + @override + @JsonKey() + final ISet> rootRecords; + @override + @JsonKey() + final IMap debugNames; + + /// Create a copy of DHTRecordPoolAllocations + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$DHTRecordPoolAllocationsCopyWith<_DHTRecordPoolAllocations> get copyWith => + __$DHTRecordPoolAllocationsCopyWithImpl<_DHTRecordPoolAllocations>( + this, _$identity); @override - String toString() { - return 'OwnedDHTRecordPointer(recordKey: $recordKey, owner: $owner)'; + Map toJson() { + return _$DHTRecordPoolAllocationsToJson( + this, + ); } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$OwnedDHTRecordPointerImpl && + other is _DHTRecordPoolAllocations && + (identical(other.childrenByParent, childrenByParent) || + other.childrenByParent == childrenByParent) && + (identical(other.parentByChild, parentByChild) || + other.parentByChild == parentByChild) && + const DeepCollectionEquality() + .equals(other.rootRecords, rootRecords) && + (identical(other.debugNames, debugNames) || + other.debugNames == debugNames)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, childrenByParent, parentByChild, + const DeepCollectionEquality().hash(rootRecords), debugNames); + + @override + String toString() { + return 'DHTRecordPoolAllocations(childrenByParent: $childrenByParent, parentByChild: $parentByChild, rootRecords: $rootRecords, debugNames: $debugNames)'; + } +} + +/// @nodoc +abstract mixin class _$DHTRecordPoolAllocationsCopyWith<$Res> + implements $DHTRecordPoolAllocationsCopyWith<$Res> { + factory _$DHTRecordPoolAllocationsCopyWith(_DHTRecordPoolAllocations value, + $Res Function(_DHTRecordPoolAllocations) _then) = + __$DHTRecordPoolAllocationsCopyWithImpl; + @override + @useResult + $Res call( + {IMap>> childrenByParent, + IMap> parentByChild, + ISet> rootRecords, + IMap debugNames}); +} + +/// @nodoc +class __$DHTRecordPoolAllocationsCopyWithImpl<$Res> + implements _$DHTRecordPoolAllocationsCopyWith<$Res> { + __$DHTRecordPoolAllocationsCopyWithImpl(this._self, this._then); + + final _DHTRecordPoolAllocations _self; + final $Res Function(_DHTRecordPoolAllocations) _then; + + /// Create a copy of DHTRecordPoolAllocations + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? childrenByParent = null, + Object? parentByChild = null, + Object? rootRecords = null, + Object? debugNames = null, + }) { + return _then(_DHTRecordPoolAllocations( + childrenByParent: null == childrenByParent + ? _self.childrenByParent + : childrenByParent // ignore: cast_nullable_to_non_nullable + as IMap>>, + parentByChild: null == parentByChild + ? _self.parentByChild + : parentByChild // ignore: cast_nullable_to_non_nullable + as IMap>, + rootRecords: null == rootRecords + ? _self.rootRecords + : rootRecords // ignore: cast_nullable_to_non_nullable + as ISet>, + debugNames: null == debugNames + ? _self.debugNames + : debugNames // ignore: cast_nullable_to_non_nullable + as IMap, + )); + } +} + +/// @nodoc +mixin _$OwnedDHTRecordPointer { + TypedKey get recordKey; + KeyPair get owner; + + /// Create a copy of OwnedDHTRecordPointer + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + $OwnedDHTRecordPointerCopyWith get copyWith => + _$OwnedDHTRecordPointerCopyWithImpl( + this as OwnedDHTRecordPointer, _$identity); + + /// Serializes this OwnedDHTRecordPointer to a JSON map. + Map toJson(); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is OwnedDHTRecordPointer && (identical(other.recordKey, recordKey) || other.recordKey == recordKey) && (identical(other.owner, owner) || other.owner == owner)); @@ -382,40 +259,136 @@ class _$OwnedDHTRecordPointerImpl implements _OwnedDHTRecordPointer { @override int get hashCode => Object.hash(runtimeType, recordKey, owner); - /// Create a copy of OwnedDHTRecordPointer - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$OwnedDHTRecordPointerImplCopyWith<_$OwnedDHTRecordPointerImpl> - get copyWith => __$$OwnedDHTRecordPointerImplCopyWithImpl< - _$OwnedDHTRecordPointerImpl>(this, _$identity); - - @override - Map toJson() { - return _$$OwnedDHTRecordPointerImplToJson( - this, - ); + String toString() { + return 'OwnedDHTRecordPointer(recordKey: $recordKey, owner: $owner)'; } } -abstract class _OwnedDHTRecordPointer implements OwnedDHTRecordPointer { - const factory _OwnedDHTRecordPointer( - {required final Typed recordKey, - required final KeyPair owner}) = _$OwnedDHTRecordPointerImpl; +/// @nodoc +abstract mixin class $OwnedDHTRecordPointerCopyWith<$Res> { + factory $OwnedDHTRecordPointerCopyWith(OwnedDHTRecordPointer value, + $Res Function(OwnedDHTRecordPointer) _then) = + _$OwnedDHTRecordPointerCopyWithImpl; + @useResult + $Res call({Typed recordKey, KeyPair owner}); +} - factory _OwnedDHTRecordPointer.fromJson(Map json) = - _$OwnedDHTRecordPointerImpl.fromJson; +/// @nodoc +class _$OwnedDHTRecordPointerCopyWithImpl<$Res> + implements $OwnedDHTRecordPointerCopyWith<$Res> { + _$OwnedDHTRecordPointerCopyWithImpl(this._self, this._then); + + final OwnedDHTRecordPointer _self; + final $Res Function(OwnedDHTRecordPointer) _then; + + /// Create a copy of OwnedDHTRecordPointer + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? recordKey = null, + Object? owner = null, + }) { + return _then(_self.copyWith( + recordKey: null == recordKey + ? _self.recordKey! + : recordKey // ignore: cast_nullable_to_non_nullable + as Typed, + owner: null == owner + ? _self.owner + : owner // ignore: cast_nullable_to_non_nullable + as KeyPair, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _OwnedDHTRecordPointer implements OwnedDHTRecordPointer { + const _OwnedDHTRecordPointer({required this.recordKey, required this.owner}); + factory _OwnedDHTRecordPointer.fromJson(Map json) => + _$OwnedDHTRecordPointerFromJson(json); @override - Typed get recordKey; + final Typed recordKey; @override - KeyPair get owner; + final KeyPair owner; /// Create a copy of OwnedDHTRecordPointer /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$OwnedDHTRecordPointerImplCopyWith<_$OwnedDHTRecordPointerImpl> - get copyWith => throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + _$OwnedDHTRecordPointerCopyWith<_OwnedDHTRecordPointer> get copyWith => + __$OwnedDHTRecordPointerCopyWithImpl<_OwnedDHTRecordPointer>( + this, _$identity); + + @override + Map toJson() { + return _$OwnedDHTRecordPointerToJson( + this, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _OwnedDHTRecordPointer && + (identical(other.recordKey, recordKey) || + other.recordKey == recordKey) && + (identical(other.owner, owner) || other.owner == owner)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, recordKey, owner); + + @override + String toString() { + return 'OwnedDHTRecordPointer(recordKey: $recordKey, owner: $owner)'; + } } + +/// @nodoc +abstract mixin class _$OwnedDHTRecordPointerCopyWith<$Res> + implements $OwnedDHTRecordPointerCopyWith<$Res> { + factory _$OwnedDHTRecordPointerCopyWith(_OwnedDHTRecordPointer value, + $Res Function(_OwnedDHTRecordPointer) _then) = + __$OwnedDHTRecordPointerCopyWithImpl; + @override + @useResult + $Res call({Typed recordKey, KeyPair owner}); +} + +/// @nodoc +class __$OwnedDHTRecordPointerCopyWithImpl<$Res> + implements _$OwnedDHTRecordPointerCopyWith<$Res> { + __$OwnedDHTRecordPointerCopyWithImpl(this._self, this._then); + + final _OwnedDHTRecordPointer _self; + final $Res Function(_OwnedDHTRecordPointer) _then; + + /// Create a copy of OwnedDHTRecordPointer + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? recordKey = null, + Object? owner = null, + }) { + return _then(_OwnedDHTRecordPointer( + recordKey: null == recordKey + ? _self.recordKey + : recordKey // ignore: cast_nullable_to_non_nullable + as Typed, + owner: null == owner + ? _self.owner + : owner // ignore: cast_nullable_to_non_nullable + as KeyPair, + )); + } +} + +// dart format on diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.g.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.g.dart index 12b3a1e..c2c031f 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.g.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.g.dart @@ -6,9 +6,9 @@ part of 'dht_record_pool.dart'; // JsonSerializableGenerator // ************************************************************************** -_$DHTRecordPoolAllocationsImpl _$$DHTRecordPoolAllocationsImplFromJson( +_DHTRecordPoolAllocations _$DHTRecordPoolAllocationsFromJson( Map json) => - _$DHTRecordPoolAllocationsImpl( + _DHTRecordPoolAllocations( childrenByParent: json['children_by_parent'] == null ? const IMapConst>({}) : IMap>>.fromJson( @@ -34,8 +34,8 @@ _$DHTRecordPoolAllocationsImpl _$$DHTRecordPoolAllocationsImplFromJson( (value) => value as String), ); -Map _$$DHTRecordPoolAllocationsImplToJson( - _$DHTRecordPoolAllocationsImpl instance) => +Map _$DHTRecordPoolAllocationsToJson( + _DHTRecordPoolAllocations instance) => { 'children_by_parent': instance.childrenByParent.toJson( (value) => value, @@ -56,15 +56,15 @@ Map _$$DHTRecordPoolAllocationsImplToJson( ), }; -_$OwnedDHTRecordPointerImpl _$$OwnedDHTRecordPointerImplFromJson( +_OwnedDHTRecordPointer _$OwnedDHTRecordPointerFromJson( Map json) => - _$OwnedDHTRecordPointerImpl( + _OwnedDHTRecordPointer( recordKey: Typed.fromJson(json['record_key']), owner: KeyPair.fromJson(json['owner']), ); -Map _$$OwnedDHTRecordPointerImplToJson( - _$OwnedDHTRecordPointerImpl instance) => +Map _$OwnedDHTRecordPointerToJson( + _OwnedDHTRecordPointer instance) => { 'record_key': instance.recordKey.toJson(), 'owner': instance.owner.toJson(), diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/extensions.dart b/packages/veilid_support/lib/dht_support/src/dht_record/extensions.dart new file mode 100644 index 0000000..e62403e --- /dev/null +++ b/packages/veilid_support/lib/dht_support/src/dht_record/extensions.dart @@ -0,0 +1,57 @@ +import 'package:veilid/veilid.dart'; +import 'dht_record_pool.dart'; + +class DHTSeqChange { + const DHTSeqChange(this.subkey, this.oldSeq, this.newSeq); + final int subkey; + final int oldSeq; + final int newSeq; +} + +extension DHTReportReportExt on DHTRecordReport { + List get newerSubkeys { + if (networkSeqs.isEmpty || localSeqs.isEmpty || subkeys.isEmpty) { + return []; + } + + final currentSubkeys = []; + + var i = 0; + for (final skr in subkeys) { + for (var sk = skr.low; sk <= skr.high; sk++) { + if (networkSeqs[i] > localSeqs[i] && + networkSeqs[i] != DHTRecord.emptySeq) { + if (currentSubkeys.isNotEmpty && + currentSubkeys.last.high == (sk - 1)) { + currentSubkeys.add(ValueSubkeyRange( + low: currentSubkeys.removeLast().low, high: sk)); + } else { + currentSubkeys.add(ValueSubkeyRange.single(sk)); + } + } + i++; + } + } + + return currentSubkeys; + } + + DHTSeqChange? get firstSeqChange { + if (networkSeqs.isEmpty || localSeqs.isEmpty || subkeys.isEmpty) { + return null; + } + + var i = 0; + for (final skr in subkeys) { + for (var sk = skr.low; sk <= skr.high; sk++) { + if (networkSeqs[i] > localSeqs[i] && + networkSeqs[i] != DHTRecord.emptySeq) { + return DHTSeqChange(sk, localSeqs[i], networkSeqs[i]); + } + i++; + } + } + + return null; + } +} diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart index ab56c77..6ff6d95 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart @@ -3,27 +3,14 @@ import 'dart:async'; import 'package:async_tools/async_tools.dart'; import 'package:bloc/bloc.dart'; import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; -import 'package:equatable/equatable.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -import 'package:meta/meta.dart'; import '../../../veilid_support.dart'; -@immutable -class DHTShortArrayElementState extends Equatable { - const DHTShortArrayElementState( - {required this.value, required this.isOffline}); - final T value; - final bool isOffline; +typedef DHTShortArrayState = AsyncValue>>; +typedef DHTShortArrayCubitState = BlocBusyState>; - @override - List get props => [value, isOffline]; -} - -typedef DHTShortArrayState = AsyncValue>>; -typedef DHTShortArrayBusyState = BlocBusyState>; - -class DHTShortArrayCubit extends Cubit> +class DHTShortArrayCubit extends Cubit> with BlocBusyWrapper>, RefreshableCubit { DHTShortArrayCubit({ required Future Function() open, @@ -46,7 +33,7 @@ class DHTShortArrayCubit extends Cubit> } } on Exception catch (e, st) { addError(e, st); - emit(DHTShortArrayBusyState(AsyncValue.error(e, st))); + emit(DHTShortArrayCubitState(AsyncValue.error(e, st))); return; } @@ -83,7 +70,7 @@ class DHTShortArrayCubit extends Cubit> // Get the items final allItems = (await reader.getRange(0, forceRefresh: forceRefresh)) ?.indexed - .map((x) => DHTShortArrayElementState( + .map((x) => OnlineElementState( value: _decodeElement(x.$2), isOffline: offlinePositions?.contains(x.$1) ?? false)) .toIList(); diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart index 0aaed19..49659cd 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart @@ -333,7 +333,7 @@ class _DHTShortArrayHead { } Future lookupIndex(int idx, bool allowCreate) async { - final seq = idx < _seqs.length ? _seqs[idx] : 0xFFFFFFFF; + final seq = idx < _seqs.length ? _seqs[idx] : DHTRecord.emptySeq; final recordNumber = idx ~/ _stride; final record = await _getOrCreateLinkedRecord(recordNumber, allowCreate); final recordSubkey = (idx % _stride) + ((recordNumber == 0) ? 1 : 0); @@ -427,14 +427,14 @@ class _DHTShortArrayHead { // If our local sequence number is unknown or hasnt been written yet // then a normal DHT operation is going to pull from the network anyway - if (_localSeqs.length < idx || _localSeqs[idx] == 0xFFFFFFFF) { + if (_localSeqs.length < idx || _localSeqs[idx] == DHTRecord.emptySeq) { return false; } // If the remote sequence number record is unknown or hasnt been written // at this index yet, then we also do not refresh at this time as it // is the first time the index is being written to - if (_seqs.length < idx || _seqs[idx] == 0xFFFFFFFF) { + if (_seqs.length < idx || _seqs[idx] == DHTRecord.emptySeq) { return false; } @@ -448,12 +448,12 @@ class _DHTShortArrayHead { final idx = _index[pos]; while (_localSeqs.length <= idx) { - _localSeqs.add(0xFFFFFFFF); + _localSeqs.add(DHTRecord.emptySeq); } _localSeqs[idx] = newSeq; if (write) { while (_seqs.length <= idx) { - _seqs.add(0xFFFFFFFF); + _seqs.add(DHTRecord.emptySeq); } _seqs[idx] = newSeq; } diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart index f3e1ac3..51950f6 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart @@ -122,7 +122,7 @@ class _DHTShortArrayWrite extends _DHTShortArrayRead final outSeqNum = Output(); - final result = lookup.seq == 0xFFFFFFFF + final result = lookup.seq == DHTRecord.emptySeq ? null : await lookup.record.get(subkey: lookup.recordSubkey); @@ -151,7 +151,7 @@ class _DHTShortArrayWrite extends _DHTShortArrayRead final lookup = await _head.lookupPosition(pos, true); final outSeqNumRead = Output(); - final oldValue = lookup.seq == 0xFFFFFFFF + final oldValue = lookup.seq == DHTRecord.emptySeq ? null : await lookup.record .get(subkey: lookup.recordSubkey, outSeqNum: outSeqNumRead); diff --git a/packages/veilid_support/lib/identity_support/account_record_info.dart b/packages/veilid_support/lib/identity_support/account_record_info.dart index 60accf9..c74baac 100644 --- a/packages/veilid_support/lib/identity_support/account_record_info.dart +++ b/packages/veilid_support/lib/identity_support/account_record_info.dart @@ -8,7 +8,7 @@ part 'account_record_info.g.dart'; /// AccountRecordInfo is the key and owner info for the account dht record that /// is stored in the identity instance record @freezed -class AccountRecordInfo with _$AccountRecordInfo { +sealed class AccountRecordInfo with _$AccountRecordInfo { const factory AccountRecordInfo({ // Top level account keys and secrets required OwnedDHTRecordPointer accountRecord, diff --git a/packages/veilid_support/lib/identity_support/account_record_info.freezed.dart b/packages/veilid_support/lib/identity_support/account_record_info.freezed.dart index a266230..b1796f6 100644 --- a/packages/veilid_support/lib/identity_support/account_record_info.freezed.dart +++ b/packages/veilid_support/lib/identity_support/account_record_info.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,137 +10,30 @@ part of 'account_record_info.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -AccountRecordInfo _$AccountRecordInfoFromJson(Map json) { - return _AccountRecordInfo.fromJson(json); -} - /// @nodoc mixin _$AccountRecordInfo { // Top level account keys and secrets - OwnedDHTRecordPointer get accountRecord => throw _privateConstructorUsedError; - - /// Serializes this AccountRecordInfo to a JSON map. - Map toJson() => throw _privateConstructorUsedError; + OwnedDHTRecordPointer get accountRecord; /// Create a copy of AccountRecordInfo /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $AccountRecordInfoCopyWith get copyWith => - throw _privateConstructorUsedError; -} + _$AccountRecordInfoCopyWithImpl( + this as AccountRecordInfo, _$identity); -/// @nodoc -abstract class $AccountRecordInfoCopyWith<$Res> { - factory $AccountRecordInfoCopyWith( - AccountRecordInfo value, $Res Function(AccountRecordInfo) then) = - _$AccountRecordInfoCopyWithImpl<$Res, AccountRecordInfo>; - @useResult - $Res call({OwnedDHTRecordPointer accountRecord}); - - $OwnedDHTRecordPointerCopyWith<$Res> get accountRecord; -} - -/// @nodoc -class _$AccountRecordInfoCopyWithImpl<$Res, $Val extends AccountRecordInfo> - implements $AccountRecordInfoCopyWith<$Res> { - _$AccountRecordInfoCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of AccountRecordInfo - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? accountRecord = null, - }) { - return _then(_value.copyWith( - accountRecord: null == accountRecord - ? _value.accountRecord - : accountRecord // ignore: cast_nullable_to_non_nullable - as OwnedDHTRecordPointer, - ) as $Val); - } - - /// Create a copy of AccountRecordInfo - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $OwnedDHTRecordPointerCopyWith<$Res> get accountRecord { - return $OwnedDHTRecordPointerCopyWith<$Res>(_value.accountRecord, (value) { - return _then(_value.copyWith(accountRecord: value) as $Val); - }); - } -} - -/// @nodoc -abstract class _$$AccountRecordInfoImplCopyWith<$Res> - implements $AccountRecordInfoCopyWith<$Res> { - factory _$$AccountRecordInfoImplCopyWith(_$AccountRecordInfoImpl value, - $Res Function(_$AccountRecordInfoImpl) then) = - __$$AccountRecordInfoImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({OwnedDHTRecordPointer accountRecord}); - - @override - $OwnedDHTRecordPointerCopyWith<$Res> get accountRecord; -} - -/// @nodoc -class __$$AccountRecordInfoImplCopyWithImpl<$Res> - extends _$AccountRecordInfoCopyWithImpl<$Res, _$AccountRecordInfoImpl> - implements _$$AccountRecordInfoImplCopyWith<$Res> { - __$$AccountRecordInfoImplCopyWithImpl(_$AccountRecordInfoImpl _value, - $Res Function(_$AccountRecordInfoImpl) _then) - : super(_value, _then); - - /// Create a copy of AccountRecordInfo - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? accountRecord = null, - }) { - return _then(_$AccountRecordInfoImpl( - accountRecord: null == accountRecord - ? _value.accountRecord - : accountRecord // ignore: cast_nullable_to_non_nullable - as OwnedDHTRecordPointer, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$AccountRecordInfoImpl implements _AccountRecordInfo { - const _$AccountRecordInfoImpl({required this.accountRecord}); - - factory _$AccountRecordInfoImpl.fromJson(Map json) => - _$$AccountRecordInfoImplFromJson(json); - -// Top level account keys and secrets - @override - final OwnedDHTRecordPointer accountRecord; - - @override - String toString() { - return 'AccountRecordInfo(accountRecord: $accountRecord)'; - } + /// Serializes this AccountRecordInfo to a JSON map. + Map toJson(); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$AccountRecordInfoImpl && + other is AccountRecordInfo && (identical(other.accountRecord, accountRecord) || other.accountRecord == accountRecord)); } @@ -148,39 +42,148 @@ class _$AccountRecordInfoImpl implements _AccountRecordInfo { @override int get hashCode => Object.hash(runtimeType, accountRecord); - /// Create a copy of AccountRecordInfo - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$AccountRecordInfoImplCopyWith<_$AccountRecordInfoImpl> get copyWith => - __$$AccountRecordInfoImplCopyWithImpl<_$AccountRecordInfoImpl>( - this, _$identity); - - @override - Map toJson() { - return _$$AccountRecordInfoImplToJson( - this, - ); + String toString() { + return 'AccountRecordInfo(accountRecord: $accountRecord)'; } } -abstract class _AccountRecordInfo implements AccountRecordInfo { - const factory _AccountRecordInfo( - {required final OwnedDHTRecordPointer accountRecord}) = - _$AccountRecordInfoImpl; +/// @nodoc +abstract mixin class $AccountRecordInfoCopyWith<$Res> { + factory $AccountRecordInfoCopyWith( + AccountRecordInfo value, $Res Function(AccountRecordInfo) _then) = + _$AccountRecordInfoCopyWithImpl; + @useResult + $Res call({OwnedDHTRecordPointer accountRecord}); - factory _AccountRecordInfo.fromJson(Map json) = - _$AccountRecordInfoImpl.fromJson; + $OwnedDHTRecordPointerCopyWith<$Res> get accountRecord; +} + +/// @nodoc +class _$AccountRecordInfoCopyWithImpl<$Res> + implements $AccountRecordInfoCopyWith<$Res> { + _$AccountRecordInfoCopyWithImpl(this._self, this._then); + + final AccountRecordInfo _self; + final $Res Function(AccountRecordInfo) _then; + + /// Create a copy of AccountRecordInfo + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accountRecord = null, + }) { + return _then(_self.copyWith( + accountRecord: null == accountRecord + ? _self.accountRecord + : accountRecord // ignore: cast_nullable_to_non_nullable + as OwnedDHTRecordPointer, + )); + } + + /// Create a copy of AccountRecordInfo + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $OwnedDHTRecordPointerCopyWith<$Res> get accountRecord { + return $OwnedDHTRecordPointerCopyWith<$Res>(_self.accountRecord, (value) { + return _then(_self.copyWith(accountRecord: value)); + }); + } +} + +/// @nodoc +@JsonSerializable() +class _AccountRecordInfo implements AccountRecordInfo { + const _AccountRecordInfo({required this.accountRecord}); + factory _AccountRecordInfo.fromJson(Map json) => + _$AccountRecordInfoFromJson(json); // Top level account keys and secrets @override - OwnedDHTRecordPointer get accountRecord; + final OwnedDHTRecordPointer accountRecord; /// Create a copy of AccountRecordInfo /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$AccountRecordInfoImplCopyWith<_$AccountRecordInfoImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + _$AccountRecordInfoCopyWith<_AccountRecordInfo> get copyWith => + __$AccountRecordInfoCopyWithImpl<_AccountRecordInfo>(this, _$identity); + + @override + Map toJson() { + return _$AccountRecordInfoToJson( + this, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _AccountRecordInfo && + (identical(other.accountRecord, accountRecord) || + other.accountRecord == accountRecord)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, accountRecord); + + @override + String toString() { + return 'AccountRecordInfo(accountRecord: $accountRecord)'; + } } + +/// @nodoc +abstract mixin class _$AccountRecordInfoCopyWith<$Res> + implements $AccountRecordInfoCopyWith<$Res> { + factory _$AccountRecordInfoCopyWith( + _AccountRecordInfo value, $Res Function(_AccountRecordInfo) _then) = + __$AccountRecordInfoCopyWithImpl; + @override + @useResult + $Res call({OwnedDHTRecordPointer accountRecord}); + + @override + $OwnedDHTRecordPointerCopyWith<$Res> get accountRecord; +} + +/// @nodoc +class __$AccountRecordInfoCopyWithImpl<$Res> + implements _$AccountRecordInfoCopyWith<$Res> { + __$AccountRecordInfoCopyWithImpl(this._self, this._then); + + final _AccountRecordInfo _self; + final $Res Function(_AccountRecordInfo) _then; + + /// Create a copy of AccountRecordInfo + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? accountRecord = null, + }) { + return _then(_AccountRecordInfo( + accountRecord: null == accountRecord + ? _self.accountRecord + : accountRecord // ignore: cast_nullable_to_non_nullable + as OwnedDHTRecordPointer, + )); + } + + /// Create a copy of AccountRecordInfo + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $OwnedDHTRecordPointerCopyWith<$Res> get accountRecord { + return $OwnedDHTRecordPointerCopyWith<$Res>(_self.accountRecord, (value) { + return _then(_self.copyWith(accountRecord: value)); + }); + } +} + +// dart format on diff --git a/packages/veilid_support/lib/identity_support/account_record_info.g.dart b/packages/veilid_support/lib/identity_support/account_record_info.g.dart index ad9318c..429f9d0 100644 --- a/packages/veilid_support/lib/identity_support/account_record_info.g.dart +++ b/packages/veilid_support/lib/identity_support/account_record_info.g.dart @@ -6,14 +6,12 @@ part of 'account_record_info.dart'; // JsonSerializableGenerator // ************************************************************************** -_$AccountRecordInfoImpl _$$AccountRecordInfoImplFromJson( - Map json) => - _$AccountRecordInfoImpl( +_AccountRecordInfo _$AccountRecordInfoFromJson(Map json) => + _AccountRecordInfo( accountRecord: OwnedDHTRecordPointer.fromJson(json['account_record']), ); -Map _$$AccountRecordInfoImplToJson( - _$AccountRecordInfoImpl instance) => +Map _$AccountRecordInfoToJson(_AccountRecordInfo instance) => { 'account_record': instance.accountRecord.toJson(), }; diff --git a/packages/veilid_support/lib/identity_support/identity.dart b/packages/veilid_support/lib/identity_support/identity.dart index ea9c38c..c1c7113 100644 --- a/packages/veilid_support/lib/identity_support/identity.dart +++ b/packages/veilid_support/lib/identity_support/identity.dart @@ -14,7 +14,7 @@ part 'identity.g.dart'; /// DHT Secret: IdentityInstance Secret Key (stored encrypted with unlock code /// in local table store) @freezed -class Identity with _$Identity { +sealed class Identity with _$Identity { const factory Identity({ // Top level account keys and secrets required IMap> accountRecords, diff --git a/packages/veilid_support/lib/identity_support/identity.freezed.dart b/packages/veilid_support/lib/identity_support/identity.freezed.dart index 3a276b0..d9f08f9 100644 --- a/packages/veilid_support/lib/identity_support/identity.freezed.dart +++ b/packages/veilid_support/lib/identity_support/identity.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,122 +10,29 @@ part of 'identity.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -Identity _$IdentityFromJson(Map json) { - return _Identity.fromJson(json); -} - /// @nodoc mixin _$Identity { // Top level account keys and secrets - IMap> get accountRecords => - throw _privateConstructorUsedError; - - /// Serializes this Identity to a JSON map. - Map toJson() => throw _privateConstructorUsedError; + IMap> get accountRecords; /// Create a copy of Identity /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $IdentityCopyWith get copyWith => - throw _privateConstructorUsedError; -} + _$IdentityCopyWithImpl(this as Identity, _$identity); -/// @nodoc -abstract class $IdentityCopyWith<$Res> { - factory $IdentityCopyWith(Identity value, $Res Function(Identity) then) = - _$IdentityCopyWithImpl<$Res, Identity>; - @useResult - $Res call({IMap> accountRecords}); -} - -/// @nodoc -class _$IdentityCopyWithImpl<$Res, $Val extends Identity> - implements $IdentityCopyWith<$Res> { - _$IdentityCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of Identity - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? accountRecords = null, - }) { - return _then(_value.copyWith( - accountRecords: null == accountRecords - ? _value.accountRecords - : accountRecords // ignore: cast_nullable_to_non_nullable - as IMap>, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$IdentityImplCopyWith<$Res> - implements $IdentityCopyWith<$Res> { - factory _$$IdentityImplCopyWith( - _$IdentityImpl value, $Res Function(_$IdentityImpl) then) = - __$$IdentityImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({IMap> accountRecords}); -} - -/// @nodoc -class __$$IdentityImplCopyWithImpl<$Res> - extends _$IdentityCopyWithImpl<$Res, _$IdentityImpl> - implements _$$IdentityImplCopyWith<$Res> { - __$$IdentityImplCopyWithImpl( - _$IdentityImpl _value, $Res Function(_$IdentityImpl) _then) - : super(_value, _then); - - /// Create a copy of Identity - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? accountRecords = null, - }) { - return _then(_$IdentityImpl( - accountRecords: null == accountRecords - ? _value.accountRecords - : accountRecords // ignore: cast_nullable_to_non_nullable - as IMap>, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$IdentityImpl implements _Identity { - const _$IdentityImpl({required this.accountRecords}); - - factory _$IdentityImpl.fromJson(Map json) => - _$$IdentityImplFromJson(json); - -// Top level account keys and secrets - @override - final IMap> accountRecords; - - @override - String toString() { - return 'Identity(accountRecords: $accountRecords)'; - } + /// Serializes this Identity to a JSON map. + Map toJson(); @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$IdentityImpl && + other is Identity && (identical(other.accountRecords, accountRecords) || other.accountRecords == accountRecords)); } @@ -133,38 +41,119 @@ class _$IdentityImpl implements _Identity { @override int get hashCode => Object.hash(runtimeType, accountRecords); - /// Create a copy of Identity - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$IdentityImplCopyWith<_$IdentityImpl> get copyWith => - __$$IdentityImplCopyWithImpl<_$IdentityImpl>(this, _$identity); - - @override - Map toJson() { - return _$$IdentityImplToJson( - this, - ); + String toString() { + return 'Identity(accountRecords: $accountRecords)'; } } -abstract class _Identity implements Identity { - const factory _Identity( - {required final IMap> - accountRecords}) = _$IdentityImpl; +/// @nodoc +abstract mixin class $IdentityCopyWith<$Res> { + factory $IdentityCopyWith(Identity value, $Res Function(Identity) _then) = + _$IdentityCopyWithImpl; + @useResult + $Res call({IMap> accountRecords}); +} - factory _Identity.fromJson(Map json) = - _$IdentityImpl.fromJson; +/// @nodoc +class _$IdentityCopyWithImpl<$Res> implements $IdentityCopyWith<$Res> { + _$IdentityCopyWithImpl(this._self, this._then); + + final Identity _self; + final $Res Function(Identity) _then; + + /// Create a copy of Identity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accountRecords = null, + }) { + return _then(_self.copyWith( + accountRecords: null == accountRecords + ? _self.accountRecords + : accountRecords // ignore: cast_nullable_to_non_nullable + as IMap>, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _Identity implements Identity { + const _Identity({required this.accountRecords}); + factory _Identity.fromJson(Map json) => + _$IdentityFromJson(json); // Top level account keys and secrets @override - IMap> get accountRecords; + final IMap> accountRecords; /// Create a copy of Identity /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$IdentityImplCopyWith<_$IdentityImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + _$IdentityCopyWith<_Identity> get copyWith => + __$IdentityCopyWithImpl<_Identity>(this, _$identity); + + @override + Map toJson() { + return _$IdentityToJson( + this, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _Identity && + (identical(other.accountRecords, accountRecords) || + other.accountRecords == accountRecords)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, accountRecords); + + @override + String toString() { + return 'Identity(accountRecords: $accountRecords)'; + } } + +/// @nodoc +abstract mixin class _$IdentityCopyWith<$Res> + implements $IdentityCopyWith<$Res> { + factory _$IdentityCopyWith(_Identity value, $Res Function(_Identity) _then) = + __$IdentityCopyWithImpl; + @override + @useResult + $Res call({IMap> accountRecords}); +} + +/// @nodoc +class __$IdentityCopyWithImpl<$Res> implements _$IdentityCopyWith<$Res> { + __$IdentityCopyWithImpl(this._self, this._then); + + final _Identity _self; + final $Res Function(_Identity) _then; + + /// Create a copy of Identity + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? accountRecords = null, + }) { + return _then(_Identity( + accountRecords: null == accountRecords + ? _self.accountRecords + : accountRecords // ignore: cast_nullable_to_non_nullable + as IMap>, + )); + } +} + +// dart format on diff --git a/packages/veilid_support/lib/identity_support/identity.g.dart b/packages/veilid_support/lib/identity_support/identity.g.dart index afc9088..1ee10b8 100644 --- a/packages/veilid_support/lib/identity_support/identity.g.dart +++ b/packages/veilid_support/lib/identity_support/identity.g.dart @@ -6,8 +6,7 @@ part of 'identity.dart'; // JsonSerializableGenerator // ************************************************************************** -_$IdentityImpl _$$IdentityImplFromJson(Map json) => - _$IdentityImpl( +_Identity _$IdentityFromJson(Map json) => _Identity( accountRecords: IMap>.fromJson( json['account_records'] as Map, (value) => value as String, @@ -15,8 +14,7 @@ _$IdentityImpl _$$IdentityImplFromJson(Map json) => value, (value) => AccountRecordInfo.fromJson(value))), ); -Map _$$IdentityImplToJson(_$IdentityImpl instance) => - { +Map _$IdentityToJson(_Identity instance) => { 'account_records': instance.accountRecords.toJson( (value) => value, (value) => value.toJson( diff --git a/packages/veilid_support/lib/identity_support/identity_instance.dart b/packages/veilid_support/lib/identity_support/identity_instance.dart index 1b6bf1f..d2bc323 100644 --- a/packages/veilid_support/lib/identity_support/identity_instance.dart +++ b/packages/veilid_support/lib/identity_support/identity_instance.dart @@ -10,7 +10,7 @@ part 'identity_instance.freezed.dart'; part 'identity_instance.g.dart'; @freezed -class IdentityInstance with _$IdentityInstance { +sealed class IdentityInstance with _$IdentityInstance { const factory IdentityInstance({ // Private DHT record storing identity account mapping required TypedKey recordKey, diff --git a/packages/veilid_support/lib/identity_support/identity_instance.freezed.dart b/packages/veilid_support/lib/identity_support/identity_instance.freezed.dart index 28bbad4..42522d4 100644 --- a/packages/veilid_support/lib/identity_support/identity_instance.freezed.dart +++ b/packages/veilid_support/lib/identity_support/identity_instance.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,112 +10,76 @@ part of 'identity_instance.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -IdentityInstance _$IdentityInstanceFromJson(Map json) { - return _IdentityInstance.fromJson(json); -} - /// @nodoc mixin _$IdentityInstance { // Private DHT record storing identity account mapping - Typed get recordKey => - throw _privateConstructorUsedError; // Public key of identity instance - FixedEncodedString43 get publicKey => - throw _privateConstructorUsedError; // Secret key of identity instance + TypedKey get recordKey; // Public key of identity instance + PublicKey get publicKey; // Secret key of identity instance // Encrypted with appended salt, key is DeriveSharedSecret( // password = SuperIdentity.secret, // salt = publicKey) // Used to recover accounts without generating a new instance @Uint8ListJsonConverter() - Uint8List get encryptedSecretKey => - throw _privateConstructorUsedError; // Signature of SuperInstance recordKey and SuperInstance publicKey + Uint8List + get encryptedSecretKey; // Signature of SuperInstance recordKey and SuperInstance publicKey // by publicKey - FixedEncodedString86 get superSignature => - throw _privateConstructorUsedError; // Signature of recordKey, publicKey, encryptedSecretKey, and superSignature + Signature + get superSignature; // Signature of recordKey, publicKey, encryptedSecretKey, and superSignature // by SuperIdentity publicKey - FixedEncodedString86 get signature => throw _privateConstructorUsedError; - - /// Serializes this IdentityInstance to a JSON map. - Map toJson() => throw _privateConstructorUsedError; + Signature get signature; /// Create a copy of IdentityInstance /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $IdentityInstanceCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $IdentityInstanceCopyWith<$Res> { - factory $IdentityInstanceCopyWith( - IdentityInstance value, $Res Function(IdentityInstance) then) = - _$IdentityInstanceCopyWithImpl<$Res, IdentityInstance>; - @useResult - $Res call( - {Typed recordKey, - FixedEncodedString43 publicKey, - @Uint8ListJsonConverter() Uint8List encryptedSecretKey, - FixedEncodedString86 superSignature, - FixedEncodedString86 signature}); -} - -/// @nodoc -class _$IdentityInstanceCopyWithImpl<$Res, $Val extends IdentityInstance> - implements $IdentityInstanceCopyWith<$Res> { - _$IdentityInstanceCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of IdentityInstance - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') + $IdentityInstanceCopyWith get copyWith => + _$IdentityInstanceCopyWithImpl( + this as IdentityInstance, _$identity); + + /// Serializes this IdentityInstance to a JSON map. + Map toJson(); + @override - $Res call({ - Object? recordKey = null, - Object? publicKey = null, - Object? encryptedSecretKey = null, - Object? superSignature = null, - Object? signature = null, - }) { - return _then(_value.copyWith( - recordKey: null == recordKey - ? _value.recordKey - : recordKey // ignore: cast_nullable_to_non_nullable - as Typed, - publicKey: null == publicKey - ? _value.publicKey - : publicKey // ignore: cast_nullable_to_non_nullable - as FixedEncodedString43, - encryptedSecretKey: null == encryptedSecretKey - ? _value.encryptedSecretKey - : encryptedSecretKey // ignore: cast_nullable_to_non_nullable - as Uint8List, - superSignature: null == superSignature - ? _value.superSignature - : superSignature // ignore: cast_nullable_to_non_nullable - as FixedEncodedString86, - signature: null == signature - ? _value.signature - : signature // ignore: cast_nullable_to_non_nullable - as FixedEncodedString86, - ) as $Val); + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is IdentityInstance && + (identical(other.recordKey, recordKey) || + other.recordKey == recordKey) && + (identical(other.publicKey, publicKey) || + other.publicKey == publicKey) && + const DeepCollectionEquality() + .equals(other.encryptedSecretKey, encryptedSecretKey) && + (identical(other.superSignature, superSignature) || + other.superSignature == superSignature) && + (identical(other.signature, signature) || + other.signature == signature)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + recordKey, + publicKey, + const DeepCollectionEquality().hash(encryptedSecretKey), + superSignature, + signature); + + @override + String toString() { + return 'IdentityInstance(recordKey: $recordKey, publicKey: $publicKey, encryptedSecretKey: $encryptedSecretKey, superSignature: $superSignature, signature: $signature)'; } } /// @nodoc -abstract class _$$IdentityInstanceImplCopyWith<$Res> - implements $IdentityInstanceCopyWith<$Res> { - factory _$$IdentityInstanceImplCopyWith(_$IdentityInstanceImpl value, - $Res Function(_$IdentityInstanceImpl) then) = - __$$IdentityInstanceImplCopyWithImpl<$Res>; - @override +abstract mixin class $IdentityInstanceCopyWith<$Res> { + factory $IdentityInstanceCopyWith( + IdentityInstance value, $Res Function(IdentityInstance) _then) = + _$IdentityInstanceCopyWithImpl; @useResult $Res call( {Typed recordKey, @@ -125,12 +90,12 @@ abstract class _$$IdentityInstanceImplCopyWith<$Res> } /// @nodoc -class __$$IdentityInstanceImplCopyWithImpl<$Res> - extends _$IdentityInstanceCopyWithImpl<$Res, _$IdentityInstanceImpl> - implements _$$IdentityInstanceImplCopyWith<$Res> { - __$$IdentityInstanceImplCopyWithImpl(_$IdentityInstanceImpl _value, - $Res Function(_$IdentityInstanceImpl) _then) - : super(_value, _then); +class _$IdentityInstanceCopyWithImpl<$Res> + implements $IdentityInstanceCopyWith<$Res> { + _$IdentityInstanceCopyWithImpl(this._self, this._then); + + final IdentityInstance _self; + final $Res Function(IdentityInstance) _then; /// Create a copy of IdentityInstance /// with the given fields replaced by the non-null parameter values. @@ -143,25 +108,25 @@ class __$$IdentityInstanceImplCopyWithImpl<$Res> Object? superSignature = null, Object? signature = null, }) { - return _then(_$IdentityInstanceImpl( + return _then(_self.copyWith( recordKey: null == recordKey - ? _value.recordKey + ? _self.recordKey! : recordKey // ignore: cast_nullable_to_non_nullable as Typed, publicKey: null == publicKey - ? _value.publicKey + ? _self.publicKey! : publicKey // ignore: cast_nullable_to_non_nullable as FixedEncodedString43, encryptedSecretKey: null == encryptedSecretKey - ? _value.encryptedSecretKey + ? _self.encryptedSecretKey : encryptedSecretKey // ignore: cast_nullable_to_non_nullable as Uint8List, superSignature: null == superSignature - ? _value.superSignature + ? _self.superSignature! : superSignature // ignore: cast_nullable_to_non_nullable as FixedEncodedString86, signature: null == signature - ? _value.signature + ? _self.signature! : signature // ignore: cast_nullable_to_non_nullable as FixedEncodedString86, )); @@ -170,17 +135,16 @@ class __$$IdentityInstanceImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$IdentityInstanceImpl extends _IdentityInstance { - const _$IdentityInstanceImpl( +class _IdentityInstance extends IdentityInstance { + const _IdentityInstance( {required this.recordKey, required this.publicKey, @Uint8ListJsonConverter() required this.encryptedSecretKey, required this.superSignature, required this.signature}) : super._(); - - factory _$IdentityInstanceImpl.fromJson(Map json) => - _$$IdentityInstanceImplFromJson(json); + factory _IdentityInstance.fromJson(Map json) => + _$IdentityInstanceFromJson(json); // Private DHT record storing identity account mapping @override @@ -205,16 +169,26 @@ class _$IdentityInstanceImpl extends _IdentityInstance { @override final FixedEncodedString86 signature; + /// Create a copy of IdentityInstance + /// with the given fields replaced by the non-null parameter values. @override - String toString() { - return 'IdentityInstance(recordKey: $recordKey, publicKey: $publicKey, encryptedSecretKey: $encryptedSecretKey, superSignature: $superSignature, signature: $signature)'; + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$IdentityInstanceCopyWith<_IdentityInstance> get copyWith => + __$IdentityInstanceCopyWithImpl<_IdentityInstance>(this, _$identity); + + @override + Map toJson() { + return _$IdentityInstanceToJson( + this, + ); } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$IdentityInstanceImpl && + other is _IdentityInstance && (identical(other.recordKey, recordKey) || other.recordKey == recordKey) && (identical(other.publicKey, publicKey) || @@ -237,60 +211,70 @@ class _$IdentityInstanceImpl extends _IdentityInstance { superSignature, signature); - /// Create a copy of IdentityInstance - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$IdentityInstanceImplCopyWith<_$IdentityInstanceImpl> get copyWith => - __$$IdentityInstanceImplCopyWithImpl<_$IdentityInstanceImpl>( - this, _$identity); - - @override - Map toJson() { - return _$$IdentityInstanceImplToJson( - this, - ); + String toString() { + return 'IdentityInstance(recordKey: $recordKey, publicKey: $publicKey, encryptedSecretKey: $encryptedSecretKey, superSignature: $superSignature, signature: $signature)'; } } -abstract class _IdentityInstance extends IdentityInstance { - const factory _IdentityInstance( - {required final Typed recordKey, - required final FixedEncodedString43 publicKey, - @Uint8ListJsonConverter() required final Uint8List encryptedSecretKey, - required final FixedEncodedString86 superSignature, - required final FixedEncodedString86 signature}) = _$IdentityInstanceImpl; - const _IdentityInstance._() : super._(); +/// @nodoc +abstract mixin class _$IdentityInstanceCopyWith<$Res> + implements $IdentityInstanceCopyWith<$Res> { + factory _$IdentityInstanceCopyWith( + _IdentityInstance value, $Res Function(_IdentityInstance) _then) = + __$IdentityInstanceCopyWithImpl; + @override + @useResult + $Res call( + {Typed recordKey, + FixedEncodedString43 publicKey, + @Uint8ListJsonConverter() Uint8List encryptedSecretKey, + FixedEncodedString86 superSignature, + FixedEncodedString86 signature}); +} - factory _IdentityInstance.fromJson(Map json) = - _$IdentityInstanceImpl.fromJson; +/// @nodoc +class __$IdentityInstanceCopyWithImpl<$Res> + implements _$IdentityInstanceCopyWith<$Res> { + __$IdentityInstanceCopyWithImpl(this._self, this._then); -// Private DHT record storing identity account mapping - @override - Typed get recordKey; // Public key of identity instance - @override - FixedEncodedString43 get publicKey; // Secret key of identity instance -// Encrypted with appended salt, key is DeriveSharedSecret( -// password = SuperIdentity.secret, -// salt = publicKey) -// Used to recover accounts without generating a new instance - @override - @Uint8ListJsonConverter() - Uint8List - get encryptedSecretKey; // Signature of SuperInstance recordKey and SuperInstance publicKey -// by publicKey - @override - FixedEncodedString86 - get superSignature; // Signature of recordKey, publicKey, encryptedSecretKey, and superSignature -// by SuperIdentity publicKey - @override - FixedEncodedString86 get signature; + final _IdentityInstance _self; + final $Res Function(_IdentityInstance) _then; /// Create a copy of IdentityInstance /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$IdentityInstanceImplCopyWith<_$IdentityInstanceImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + $Res call({ + Object? recordKey = null, + Object? publicKey = null, + Object? encryptedSecretKey = null, + Object? superSignature = null, + Object? signature = null, + }) { + return _then(_IdentityInstance( + recordKey: null == recordKey + ? _self.recordKey + : recordKey // ignore: cast_nullable_to_non_nullable + as Typed, + publicKey: null == publicKey + ? _self.publicKey + : publicKey // ignore: cast_nullable_to_non_nullable + as FixedEncodedString43, + encryptedSecretKey: null == encryptedSecretKey + ? _self.encryptedSecretKey + : encryptedSecretKey // ignore: cast_nullable_to_non_nullable + as Uint8List, + superSignature: null == superSignature + ? _self.superSignature + : superSignature // ignore: cast_nullable_to_non_nullable + as FixedEncodedString86, + signature: null == signature + ? _self.signature + : signature // ignore: cast_nullable_to_non_nullable + as FixedEncodedString86, + )); + } } + +// dart format on diff --git a/packages/veilid_support/lib/identity_support/identity_instance.g.dart b/packages/veilid_support/lib/identity_support/identity_instance.g.dart index cb228e6..eddbcf6 100644 --- a/packages/veilid_support/lib/identity_support/identity_instance.g.dart +++ b/packages/veilid_support/lib/identity_support/identity_instance.g.dart @@ -6,9 +6,8 @@ part of 'identity_instance.dart'; // JsonSerializableGenerator // ************************************************************************** -_$IdentityInstanceImpl _$$IdentityInstanceImplFromJson( - Map json) => - _$IdentityInstanceImpl( +_IdentityInstance _$IdentityInstanceFromJson(Map json) => + _IdentityInstance( recordKey: Typed.fromJson(json['record_key']), publicKey: FixedEncodedString43.fromJson(json['public_key']), encryptedSecretKey: @@ -17,8 +16,7 @@ _$IdentityInstanceImpl _$$IdentityInstanceImplFromJson( signature: FixedEncodedString86.fromJson(json['signature']), ); -Map _$$IdentityInstanceImplToJson( - _$IdentityInstanceImpl instance) => +Map _$IdentityInstanceToJson(_IdentityInstance instance) => { 'record_key': instance.recordKey.toJson(), 'public_key': instance.publicKey.toJson(), diff --git a/packages/veilid_support/lib/identity_support/super_identity.dart b/packages/veilid_support/lib/identity_support/super_identity.dart index e4ec8fc..5ee8c43 100644 --- a/packages/veilid_support/lib/identity_support/super_identity.dart +++ b/packages/veilid_support/lib/identity_support/super_identity.dart @@ -22,7 +22,7 @@ part 'super_identity.g.dart'; /// DHT Owner Secret: SuperIdentity Secret Key (kept offline) /// Encryption: None @freezed -class SuperIdentity with _$SuperIdentity { +sealed class SuperIdentity with _$SuperIdentity { const factory SuperIdentity({ /// Public DHT record storing this structure for account recovery /// changing this can migrate/forward the SuperIdentity to a new DHT record diff --git a/packages/veilid_support/lib/identity_support/super_identity.freezed.dart b/packages/veilid_support/lib/identity_support/super_identity.freezed.dart index 9c5c6a7..b142373 100644 --- a/packages/veilid_support/lib/identity_support/super_identity.freezed.dart +++ b/packages/veilid_support/lib/identity_support/super_identity.freezed.dart @@ -1,3 +1,4 @@ +// dart format width=80 // coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint @@ -9,65 +10,93 @@ part of 'super_identity.dart'; // FreezedGenerator // ************************************************************************** +// dart format off T _$identity(T value) => value; -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -SuperIdentity _$SuperIdentityFromJson(Map json) { - return _SuperIdentity.fromJson(json); -} - /// @nodoc mixin _$SuperIdentity { /// Public DHT record storing this structure for account recovery /// changing this can migrate/forward the SuperIdentity to a new DHT record /// Instances should not hash this recordKey, rather the actual record /// key used to store the superIdentity, as this may change. - Typed get recordKey => - throw _privateConstructorUsedError; + TypedKey get recordKey; /// Public key of the SuperIdentity used to sign identity keys for recovery /// This must match the owner of the superRecord DHT record and can not be /// changed without changing the record - FixedEncodedString43 get publicKey => throw _privateConstructorUsedError; + PublicKey get publicKey; /// Current identity instance /// The most recently generated identity instance for this SuperIdentity - IdentityInstance get currentInstance => throw _privateConstructorUsedError; + IdentityInstance get currentInstance; /// Deprecated identity instances /// These may be compromised and should not be considered valid for /// new signatures, but may be used to validate old signatures - List get deprecatedInstances => - throw _privateConstructorUsedError; + List get deprecatedInstances; /// Deprecated superRecords /// These may be compromised and should not be considered valid for /// new signatures, but may be used to validate old signatures - List> get deprecatedSuperRecordKeys => - throw _privateConstructorUsedError; + List get deprecatedSuperRecordKeys; /// Signature of recordKey, currentInstance signature, /// signatures of deprecatedInstances, and deprecatedSuperRecordKeys /// by publicKey - FixedEncodedString86 get signature => throw _privateConstructorUsedError; - - /// Serializes this SuperIdentity to a JSON map. - Map toJson() => throw _privateConstructorUsedError; + Signature get signature; /// Create a copy of SuperIdentity /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') $SuperIdentityCopyWith get copyWith => - throw _privateConstructorUsedError; + _$SuperIdentityCopyWithImpl( + this as SuperIdentity, _$identity); + + /// Serializes this SuperIdentity to a JSON map. + Map toJson(); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is SuperIdentity && + (identical(other.recordKey, recordKey) || + other.recordKey == recordKey) && + (identical(other.publicKey, publicKey) || + other.publicKey == publicKey) && + (identical(other.currentInstance, currentInstance) || + other.currentInstance == currentInstance) && + const DeepCollectionEquality() + .equals(other.deprecatedInstances, deprecatedInstances) && + const DeepCollectionEquality().equals( + other.deprecatedSuperRecordKeys, deprecatedSuperRecordKeys) && + (identical(other.signature, signature) || + other.signature == signature)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + recordKey, + publicKey, + currentInstance, + const DeepCollectionEquality().hash(deprecatedInstances), + const DeepCollectionEquality().hash(deprecatedSuperRecordKeys), + signature); + + @override + String toString() { + return 'SuperIdentity(recordKey: $recordKey, publicKey: $publicKey, currentInstance: $currentInstance, deprecatedInstances: $deprecatedInstances, deprecatedSuperRecordKeys: $deprecatedSuperRecordKeys, signature: $signature)'; + } } /// @nodoc -abstract class $SuperIdentityCopyWith<$Res> { +abstract mixin class $SuperIdentityCopyWith<$Res> { factory $SuperIdentityCopyWith( - SuperIdentity value, $Res Function(SuperIdentity) then) = - _$SuperIdentityCopyWithImpl<$Res, SuperIdentity>; + SuperIdentity value, $Res Function(SuperIdentity) _then) = + _$SuperIdentityCopyWithImpl; @useResult $Res call( {Typed recordKey, @@ -81,14 +110,12 @@ abstract class $SuperIdentityCopyWith<$Res> { } /// @nodoc -class _$SuperIdentityCopyWithImpl<$Res, $Val extends SuperIdentity> +class _$SuperIdentityCopyWithImpl<$Res> implements $SuperIdentityCopyWith<$Res> { - _$SuperIdentityCopyWithImpl(this._value, this._then); + _$SuperIdentityCopyWithImpl(this._self, this._then); - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; + final SuperIdentity _self; + final $Res Function(SuperIdentity) _then; /// Create a copy of SuperIdentity /// with the given fields replaced by the non-null parameter values. @@ -102,32 +129,32 @@ class _$SuperIdentityCopyWithImpl<$Res, $Val extends SuperIdentity> Object? deprecatedSuperRecordKeys = null, Object? signature = null, }) { - return _then(_value.copyWith( + return _then(_self.copyWith( recordKey: null == recordKey - ? _value.recordKey + ? _self.recordKey! : recordKey // ignore: cast_nullable_to_non_nullable as Typed, publicKey: null == publicKey - ? _value.publicKey + ? _self.publicKey! : publicKey // ignore: cast_nullable_to_non_nullable as FixedEncodedString43, currentInstance: null == currentInstance - ? _value.currentInstance + ? _self.currentInstance : currentInstance // ignore: cast_nullable_to_non_nullable as IdentityInstance, deprecatedInstances: null == deprecatedInstances - ? _value.deprecatedInstances + ? _self.deprecatedInstances : deprecatedInstances // ignore: cast_nullable_to_non_nullable as List, deprecatedSuperRecordKeys: null == deprecatedSuperRecordKeys - ? _value.deprecatedSuperRecordKeys + ? _self.deprecatedSuperRecordKeys! : deprecatedSuperRecordKeys // ignore: cast_nullable_to_non_nullable as List>, signature: null == signature - ? _value.signature + ? _self.signature! : signature // ignore: cast_nullable_to_non_nullable as FixedEncodedString86, - ) as $Val); + )); } /// Create a copy of SuperIdentity @@ -135,85 +162,16 @@ class _$SuperIdentityCopyWithImpl<$Res, $Val extends SuperIdentity> @override @pragma('vm:prefer-inline') $IdentityInstanceCopyWith<$Res> get currentInstance { - return $IdentityInstanceCopyWith<$Res>(_value.currentInstance, (value) { - return _then(_value.copyWith(currentInstance: value) as $Val); + return $IdentityInstanceCopyWith<$Res>(_self.currentInstance, (value) { + return _then(_self.copyWith(currentInstance: value)); }); } } -/// @nodoc -abstract class _$$SuperIdentityImplCopyWith<$Res> - implements $SuperIdentityCopyWith<$Res> { - factory _$$SuperIdentityImplCopyWith( - _$SuperIdentityImpl value, $Res Function(_$SuperIdentityImpl) then) = - __$$SuperIdentityImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {Typed recordKey, - FixedEncodedString43 publicKey, - IdentityInstance currentInstance, - List deprecatedInstances, - List> deprecatedSuperRecordKeys, - FixedEncodedString86 signature}); - - @override - $IdentityInstanceCopyWith<$Res> get currentInstance; -} - -/// @nodoc -class __$$SuperIdentityImplCopyWithImpl<$Res> - extends _$SuperIdentityCopyWithImpl<$Res, _$SuperIdentityImpl> - implements _$$SuperIdentityImplCopyWith<$Res> { - __$$SuperIdentityImplCopyWithImpl( - _$SuperIdentityImpl _value, $Res Function(_$SuperIdentityImpl) _then) - : super(_value, _then); - - /// Create a copy of SuperIdentity - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? recordKey = null, - Object? publicKey = null, - Object? currentInstance = null, - Object? deprecatedInstances = null, - Object? deprecatedSuperRecordKeys = null, - Object? signature = null, - }) { - return _then(_$SuperIdentityImpl( - recordKey: null == recordKey - ? _value.recordKey - : recordKey // ignore: cast_nullable_to_non_nullable - as Typed, - publicKey: null == publicKey - ? _value.publicKey - : publicKey // ignore: cast_nullable_to_non_nullable - as FixedEncodedString43, - currentInstance: null == currentInstance - ? _value.currentInstance - : currentInstance // ignore: cast_nullable_to_non_nullable - as IdentityInstance, - deprecatedInstances: null == deprecatedInstances - ? _value._deprecatedInstances - : deprecatedInstances // ignore: cast_nullable_to_non_nullable - as List, - deprecatedSuperRecordKeys: null == deprecatedSuperRecordKeys - ? _value._deprecatedSuperRecordKeys - : deprecatedSuperRecordKeys // ignore: cast_nullable_to_non_nullable - as List>, - signature: null == signature - ? _value.signature - : signature // ignore: cast_nullable_to_non_nullable - as FixedEncodedString86, - )); - } -} - /// @nodoc @JsonSerializable() -class _$SuperIdentityImpl extends _SuperIdentity { - const _$SuperIdentityImpl( +class _SuperIdentity extends SuperIdentity { + const _SuperIdentity( {required this.recordKey, required this.publicKey, required this.currentInstance, @@ -224,9 +182,8 @@ class _$SuperIdentityImpl extends _SuperIdentity { : _deprecatedInstances = deprecatedInstances, _deprecatedSuperRecordKeys = deprecatedSuperRecordKeys, super._(); - - factory _$SuperIdentityImpl.fromJson(Map json) => - _$$SuperIdentityImplFromJson(json); + factory _SuperIdentity.fromJson(Map json) => + _$SuperIdentityFromJson(json); /// Public DHT record storing this structure for account recovery /// changing this can migrate/forward the SuperIdentity to a new DHT record @@ -284,16 +241,26 @@ class _$SuperIdentityImpl extends _SuperIdentity { @override final FixedEncodedString86 signature; + /// Create a copy of SuperIdentity + /// with the given fields replaced by the non-null parameter values. @override - String toString() { - return 'SuperIdentity(recordKey: $recordKey, publicKey: $publicKey, currentInstance: $currentInstance, deprecatedInstances: $deprecatedInstances, deprecatedSuperRecordKeys: $deprecatedSuperRecordKeys, signature: $signature)'; + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$SuperIdentityCopyWith<_SuperIdentity> get copyWith => + __$SuperIdentityCopyWithImpl<_SuperIdentity>(this, _$identity); + + @override + Map toJson() { + return _$SuperIdentityToJson( + this, + ); } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$SuperIdentityImpl && + other is _SuperIdentity && (identical(other.recordKey, recordKey) || other.recordKey == recordKey) && (identical(other.publicKey, publicKey) || @@ -319,76 +286,89 @@ class _$SuperIdentityImpl extends _SuperIdentity { const DeepCollectionEquality().hash(_deprecatedSuperRecordKeys), signature); - /// Create a copy of SuperIdentity - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) @override - @pragma('vm:prefer-inline') - _$$SuperIdentityImplCopyWith<_$SuperIdentityImpl> get copyWith => - __$$SuperIdentityImplCopyWithImpl<_$SuperIdentityImpl>(this, _$identity); - - @override - Map toJson() { - return _$$SuperIdentityImplToJson( - this, - ); + String toString() { + return 'SuperIdentity(recordKey: $recordKey, publicKey: $publicKey, currentInstance: $currentInstance, deprecatedInstances: $deprecatedInstances, deprecatedSuperRecordKeys: $deprecatedSuperRecordKeys, signature: $signature)'; } } -abstract class _SuperIdentity extends SuperIdentity { - const factory _SuperIdentity( - {required final Typed recordKey, - required final FixedEncodedString43 publicKey, - required final IdentityInstance currentInstance, - required final List deprecatedInstances, - required final List> - deprecatedSuperRecordKeys, - required final FixedEncodedString86 signature}) = _$SuperIdentityImpl; - const _SuperIdentity._() : super._(); - - factory _SuperIdentity.fromJson(Map json) = - _$SuperIdentityImpl.fromJson; - - /// Public DHT record storing this structure for account recovery - /// changing this can migrate/forward the SuperIdentity to a new DHT record - /// Instances should not hash this recordKey, rather the actual record - /// key used to store the superIdentity, as this may change. +/// @nodoc +abstract mixin class _$SuperIdentityCopyWith<$Res> + implements $SuperIdentityCopyWith<$Res> { + factory _$SuperIdentityCopyWith( + _SuperIdentity value, $Res Function(_SuperIdentity) _then) = + __$SuperIdentityCopyWithImpl; @override - Typed get recordKey; + @useResult + $Res call( + {Typed recordKey, + FixedEncodedString43 publicKey, + IdentityInstance currentInstance, + List deprecatedInstances, + List> deprecatedSuperRecordKeys, + FixedEncodedString86 signature}); - /// Public key of the SuperIdentity used to sign identity keys for recovery - /// This must match the owner of the superRecord DHT record and can not be - /// changed without changing the record @override - FixedEncodedString43 get publicKey; + $IdentityInstanceCopyWith<$Res> get currentInstance; +} - /// Current identity instance - /// The most recently generated identity instance for this SuperIdentity - @override - IdentityInstance get currentInstance; +/// @nodoc +class __$SuperIdentityCopyWithImpl<$Res> + implements _$SuperIdentityCopyWith<$Res> { + __$SuperIdentityCopyWithImpl(this._self, this._then); - /// Deprecated identity instances - /// These may be compromised and should not be considered valid for - /// new signatures, but may be used to validate old signatures - @override - List get deprecatedInstances; - - /// Deprecated superRecords - /// These may be compromised and should not be considered valid for - /// new signatures, but may be used to validate old signatures - @override - List> get deprecatedSuperRecordKeys; - - /// Signature of recordKey, currentInstance signature, - /// signatures of deprecatedInstances, and deprecatedSuperRecordKeys - /// by publicKey - @override - FixedEncodedString86 get signature; + final _SuperIdentity _self; + final $Res Function(_SuperIdentity) _then; /// Create a copy of SuperIdentity /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$SuperIdentityImplCopyWith<_$SuperIdentityImpl> get copyWith => - throw _privateConstructorUsedError; + @pragma('vm:prefer-inline') + $Res call({ + Object? recordKey = null, + Object? publicKey = null, + Object? currentInstance = null, + Object? deprecatedInstances = null, + Object? deprecatedSuperRecordKeys = null, + Object? signature = null, + }) { + return _then(_SuperIdentity( + recordKey: null == recordKey + ? _self.recordKey + : recordKey // ignore: cast_nullable_to_non_nullable + as Typed, + publicKey: null == publicKey + ? _self.publicKey + : publicKey // ignore: cast_nullable_to_non_nullable + as FixedEncodedString43, + currentInstance: null == currentInstance + ? _self.currentInstance + : currentInstance // ignore: cast_nullable_to_non_nullable + as IdentityInstance, + deprecatedInstances: null == deprecatedInstances + ? _self._deprecatedInstances + : deprecatedInstances // ignore: cast_nullable_to_non_nullable + as List, + deprecatedSuperRecordKeys: null == deprecatedSuperRecordKeys + ? _self._deprecatedSuperRecordKeys + : deprecatedSuperRecordKeys // ignore: cast_nullable_to_non_nullable + as List>, + signature: null == signature + ? _self.signature + : signature // ignore: cast_nullable_to_non_nullable + as FixedEncodedString86, + )); + } + + /// Create a copy of SuperIdentity + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $IdentityInstanceCopyWith<$Res> get currentInstance { + return $IdentityInstanceCopyWith<$Res>(_self.currentInstance, (value) { + return _then(_self.copyWith(currentInstance: value)); + }); + } } + +// dart format on diff --git a/packages/veilid_support/lib/identity_support/super_identity.g.dart b/packages/veilid_support/lib/identity_support/super_identity.g.dart index 4c4f4f3..1b52492 100644 --- a/packages/veilid_support/lib/identity_support/super_identity.g.dart +++ b/packages/veilid_support/lib/identity_support/super_identity.g.dart @@ -6,8 +6,8 @@ part of 'super_identity.dart'; // JsonSerializableGenerator // ************************************************************************** -_$SuperIdentityImpl _$$SuperIdentityImplFromJson(Map json) => - _$SuperIdentityImpl( +_SuperIdentity _$SuperIdentityFromJson(Map json) => + _SuperIdentity( recordKey: Typed.fromJson(json['record_key']), publicKey: FixedEncodedString43.fromJson(json['public_key']), currentInstance: IdentityInstance.fromJson(json['current_instance']), @@ -21,7 +21,7 @@ _$SuperIdentityImpl _$$SuperIdentityImplFromJson(Map json) => signature: FixedEncodedString86.fromJson(json['signature']), ); -Map _$$SuperIdentityImplToJson(_$SuperIdentityImpl instance) => +Map _$SuperIdentityToJson(_SuperIdentity instance) => { 'record_key': instance.recordKey.toJson(), 'public_key': instance.publicKey.toJson(), diff --git a/packages/veilid_support/lib/proto/proto.dart b/packages/veilid_support/lib/proto/proto.dart index a7a70bb..936bbdf 100644 --- a/packages/veilid_support/lib/proto/proto.dart +++ b/packages/veilid_support/lib/proto/proto.dart @@ -1,5 +1,6 @@ import 'dart:typed_data'; +import '../src/dynamic_debug.dart'; import '../veilid_support.dart' as veilid; import 'veilid.pb.dart' as proto; @@ -150,3 +151,26 @@ extension ProtoKeyPair on proto.KeyPair { veilid.KeyPair toVeilid() => veilid.KeyPair(key: key.toVeilid(), secret: secret.toVeilid()); } + +void registerVeilidProtoToDebug() { + dynamic toDebug(dynamic protoObj) { + if (protoObj is proto.CryptoKey) { + return protoObj.toVeilid(); + } + if (protoObj is proto.Signature) { + return protoObj.toVeilid(); + } + if (protoObj is proto.Nonce) { + return protoObj.toVeilid(); + } + if (protoObj is proto.TypedKey) { + return protoObj.toVeilid(); + } + if (protoObj is proto.KeyPair) { + return protoObj.toVeilid(); + } + return protoObj; + } + + DynamicDebug.registerToDebug(toDebug); +} diff --git a/packages/veilid_support/lib/src/dynamic_debug.dart b/packages/veilid_support/lib/src/dynamic_debug.dart new file mode 100644 index 0000000..1c38d96 --- /dev/null +++ b/packages/veilid_support/lib/src/dynamic_debug.dart @@ -0,0 +1,130 @@ +import 'package:async_tools/async_tools.dart'; +import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; +import 'package:convert/convert.dart'; +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; + +import 'online_element_state.dart'; + +typedef ToDebugFunction = dynamic Function(dynamic protoObj); + +// This should be implemented to add toDebug capability +// ignore: one_member_abstracts +abstract class ToDebugMap { + Map toDebugMap(); +} + +// We explicitly want this class to avoid having a global function 'toDebug' +// ignore: avoid_classes_with_only_static_members +class DynamicDebug { + /// Add a 'toDebug' handler to the chain + static void registerToDebug(ToDebugFunction toDebugFunction) { + final _oldToDebug = _toDebug; + _toDebug = (obj) => _oldToDebug(toDebugFunction(obj)); + } + + /// Convert a type to a debug version of the same type that + /// has a better `toString` representation and possibly other extra debug + /// information + static dynamic toDebug(dynamic obj) { + try { + return _toDebug(obj); + // In this case we watch to catch everything + // because toDebug need to never fail + // ignore: avoid_catches_without_on_clauses + } catch (e) { + // Ensure this gets printed, but continue + // ignore: avoid_print + print('Exception in toDebug: $e'); + return obj.toString(); + } + } + + ////////////////////////////////////////////////////////////// + static dynamic _baseToDebug(dynamic obj) { + if (obj is AsyncValue) { + if (obj.isLoading) { + return {r'$runtimeType': obj.runtimeType, 'loading': null}; + } + if (obj.isError) { + return { + r'$runtimeType': obj.runtimeType, + 'error': toDebug(obj.asError!.error), + 'stackTrace': toDebug(obj.asError!.stackTrace), + }; + } + if (obj.isData) { + return { + r'$runtimeType': obj.runtimeType, + 'data': toDebug(obj.asData!.value), + }; + } + return obj.toString(); + } + if (obj is IMap) { + // Handled by Map + return _baseToDebug(obj.unlockView); + } + if (obj is IMapOfSets) { + // Handled by Map + return _baseToDebug(obj.unlock); + } + if (obj is ISet) { + // Handled by Iterable + return _baseToDebug(obj.unlockView); + } + if (obj is IList) { + return _baseToDebug(obj.unlockView); + } + if (obj is BlocBusyState) { + return { + r'$runtimeType': obj.runtimeType, + 'busy': obj.busy, + 'state': toDebug(obj.state), + }; + } + if (obj is OnlineElementState) { + return { + r'$runtimeType': obj.runtimeType, + 'isOffline': obj.isOffline, + 'value': toDebug(obj.value), + }; + } + if (obj is List) { + try { + // Do bytes as a hex string for brevity and clarity + return 'List: ${hex.encode(obj)}'; + // One has to be able to catch this + // ignore: avoid_catching_errors + } on RangeError { + // Otherwise directly convert as list of integers + return obj.toString(); + } + } + if (obj is Map) { + return obj.map((k, v) => MapEntry(toDebug(k), toDebug(v))); + } + if (obj is Iterable) { + return obj.map(toDebug).toList(); + } + if (obj is String || obj is bool || obj is num || obj == null) { + return obj; + } + if (obj is ToDebugMap) { + // Handled by Map + return _baseToDebug(obj.toDebugMap()); + } + + try { + // Let's try convering to a json object + // ignore: avoid_dynamic_calls + return obj.toJson(); + + // No matter how this fails, we shouldn't throw + // ignore: avoid_catches_without_on_clauses + } catch (_) {} + + return obj.toString(); + } + + static ToDebugFunction _toDebug = _baseToDebug; +} diff --git a/packages/veilid_support/lib/veilid_support.dart b/packages/veilid_support/lib/veilid_support.dart index f48376f..2f4da90 100644 --- a/packages/veilid_support/lib/veilid_support.dart +++ b/packages/veilid_support/lib/veilid_support.dart @@ -1,13 +1,14 @@ /// Dart Veilid Support Library /// Common functionality for interfacing with Veilid -library veilid_support; +library; export 'package:veilid/veilid.dart'; export 'dht_support/dht_support.dart'; export 'identity_support/identity_support.dart'; export 'src/config.dart'; +export 'src/dynamic_debug.dart'; export 'src/json_tools.dart'; export 'src/memory_tools.dart'; export 'src/online_element_state.dart'; diff --git a/packages/veilid_support/pubspec.lock b/packages/veilid_support/pubspec.lock index e3dfcdd..0c8ca3d 100644 --- a/packages/veilid_support/pubspec.lock +++ b/packages/veilid_support/pubspec.lock @@ -178,7 +178,7 @@ packages: source: hosted version: "1.19.1" convert: - dependency: transitive + dependency: "direct main" description: name: convert sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 diff --git a/packages/veilid_support/pubspec.yaml b/packages/veilid_support/pubspec.yaml index 5aff89e..548c40e 100644 --- a/packages/veilid_support/pubspec.yaml +++ b/packages/veilid_support/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: bloc_advanced_tools: ^0.1.10 charcode: ^1.4.0 collection: ^1.19.1 + convert: ^3.1.2 equatable: ^2.0.7 fast_immutable_collections: ^11.0.3 freezed_annotation: ^3.0.0 From 6830b54ce8e1eecb097b56075deea4834c9d11ba Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 23 Mar 2025 10:41:19 -0400 Subject: [PATCH 43/93] move keyboard shortcuts into own file --- lib/app.dart | 68 +------------------- lib/layout/home/drawer_menu/drawer_menu.dart | 4 +- lib/router/cubits/router_cubit.dart | 13 ++++ lib/router/views/router_shell.dart | 4 +- 4 files changed, 19 insertions(+), 70 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index f8241da..802b0d7 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,15 +1,12 @@ import 'package:animated_theme_switcher/animated_theme_switcher.dart'; -import 'package:async_tools/async_tools.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:provider/provider.dart'; -import 'package:veilid_support/veilid_support.dart'; import 'account_manager/account_manager.dart'; import 'init.dart'; @@ -19,17 +16,8 @@ import 'router/router.dart'; import 'settings/settings.dart'; import 'theme/theme.dart'; import 'tick.dart'; -import 'tools/loggy.dart'; import 'veilid_processor/veilid_processor.dart'; -class ReloadThemeIntent extends Intent { - const ReloadThemeIntent(); -} - -class AttachDetachIntent extends Intent { - const AttachDetachIntent(); -} - class ScrollBehaviorModified extends ScrollBehavior { const ScrollBehaviorModified(); @override @@ -47,59 +35,6 @@ class VeilidChatApp extends StatelessWidget { final ThemeData initialThemeData; - void reloadTheme(BuildContext context) { - singleFuture(this, () async { - log.info('Reloading theme'); - - await VeilidChatGlobalInit.loadAssetManifest(); - - final theme = - PreferencesRepository.instance.value.themePreference.themeData(); - if (context.mounted) { - ThemeSwitcher.of(context).changeTheme(theme: theme); - - // Hack to reload translations - final localizationDelegate = LocalizedApp.of(context).delegate; - await LocalizationDelegate.create( - fallbackLocale: localizationDelegate.fallbackLocale.toString(), - supportedLocales: localizationDelegate.supportedLocales - .map((x) => x.toString()) - .toList()); - } - }); - } - - void _attachDetach(BuildContext context) { - singleFuture(this, () async { - if (ProcessorRepository.instance.processorConnectionState.isAttached) { - log.info('Detaching'); - await Veilid.instance.detach(); - } else if (ProcessorRepository - .instance.processorConnectionState.isDetached) { - log.info('Attaching'); - await Veilid.instance.attach(); - } - }); - } - - Widget _buildShortcuts({required Widget Function(BuildContext) builder}) => - ThemeSwitcher( - builder: (context) => Shortcuts( - shortcuts: { - LogicalKeySet( - LogicalKeyboardKey.alt, LogicalKeyboardKey.keyR): - const ReloadThemeIntent(), - LogicalKeySet( - LogicalKeyboardKey.alt, LogicalKeyboardKey.keyD): - const AttachDetachIntent(), - }, - child: Actions(actions: >{ - ReloadThemeIntent: CallbackAction( - onInvoke: (intent) => reloadTheme(context)), - AttachDetachIntent: CallbackAction( - onInvoke: (intent) => _attachDetach(context)), - }, child: Focus(autofocus: true, child: builder(context))))); - Widget appBuilder( BuildContext context, LocalizationDelegate localizationDelegate) => ThemeProvider( @@ -138,8 +73,7 @@ class VeilidChatApp extends StatelessWidget { accountRepository: AccountRepository.instance, locator: context.read)), ], - child: - BackgroundTicker(child: _buildShortcuts(builder: (context) { + child: BackgroundTicker(child: Builder(builder: (context) { final scale = theme.extension()!; final scaleConfig = theme.extension()!; diff --git a/lib/layout/home/drawer_menu/drawer_menu.dart b/lib/layout/home/drawer_menu/drawer_menu.dart index 3cf1b79..0863b1f 100644 --- a/lib/layout/home/drawer_menu/drawer_menu.dart +++ b/lib/layout/home/drawer_menu/drawer_menu.dart @@ -9,7 +9,7 @@ import 'package:go_router/go_router.dart'; import 'package:veilid_support/veilid_support.dart'; import '../../../account_manager/account_manager.dart'; -import '../../../app.dart'; +import '../../../keyboard_shortcuts.dart'; import '../../../theme/theme.dart'; import '../../../tools/tools.dart'; import '../../../veilid_processor/veilid_processor.dart'; @@ -366,7 +366,7 @@ class _DrawerMenuState extends State { GestureDetector( onLongPress: () async { context - .findAncestorWidgetOfExactType()! + .findAncestorWidgetOfExactType()! .reloadTheme(context); }, child: SvgPicture.asset( diff --git a/lib/router/cubits/router_cubit.dart b/lib/router/cubits/router_cubit.dart index 1492c51..19af5e0 100644 --- a/lib/router/cubits/router_cubit.dart +++ b/lib/router/cubits/router_cubit.dart @@ -16,6 +16,8 @@ import '../../tools/tools.dart'; import '../../veilid_processor/views/developer.dart'; import '../views/router_shell.dart'; +export 'package:go_router/go_router.dart'; + part 'router_cubit.freezed.dart'; part 'router_cubit.g.dart'; @@ -164,3 +166,14 @@ class RouterCubit extends Cubit { _accountRepositorySubscription; GoRouter? _router; } + +extension GoRouterExtension on GoRouter { + String location() { + final lastMatch = routerDelegate.currentConfiguration.last; + final matchList = lastMatch is ImperativeRouteMatch + ? lastMatch.matches + : routerDelegate.currentConfiguration; + final location = matchList.uri.toString(); + return location; + } +} diff --git a/lib/router/views/router_shell.dart b/lib/router/views/router_shell.dart index f2f035b..8a7130f 100644 --- a/lib/router/views/router_shell.dart +++ b/lib/router/views/router_shell.dart @@ -1,12 +1,14 @@ import 'package:flutter/widgets.dart'; +import '../../keyboard_shortcuts.dart'; import '../../notifications/notifications.dart'; class RouterShell extends StatelessWidget { const RouterShell({required Widget child, super.key}) : _child = child; @override - Widget build(BuildContext context) => NotificationsWidget(child: _child); + Widget build(BuildContext context) => + NotificationsWidget(child: KeyboardShortcuts(child: _child)); final Widget _child; } From 89c6bd5e43934942cfdb3a64a2fc33517524cee3 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 29 Mar 2025 18:06:08 -0400 Subject: [PATCH 44/93] changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7e369f..843cc65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## v0.4.6 ## +- Updated veilid-core to v0.4.4 + - See Veilid changelog for specifics +- UI improvements: Theme fixes, wallpaper option added +- Responsiveness improved +- Contacts workflow more consistent +- Safe-area fixes +- Make layout more mobile-friendly +- Improved contact invitation menus +- Deadlock fixes in veilid_support +- _pollWatch was degenerate and only watched first subkey + ## v0.4.5 ## - Updated veilid-core to v0.4.1 - See Veilid changelog for specifics From 57ac0e5c4d373ddd40b938bb99e9f003fc59ca7a Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 30 Mar 2025 11:07:36 -0400 Subject: [PATCH 45/93] oops --- lib/keyboard_shortcuts.dart | 98 +++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 lib/keyboard_shortcuts.dart diff --git a/lib/keyboard_shortcuts.dart b/lib/keyboard_shortcuts.dart new file mode 100644 index 0000000..0c531a9 --- /dev/null +++ b/lib/keyboard_shortcuts.dart @@ -0,0 +1,98 @@ +import 'package:animated_theme_switcher/animated_theme_switcher.dart'; +import 'package:async_tools/async_tools.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_translate/flutter_translate.dart'; +import 'package:veilid_support/veilid_support.dart'; + +import 'init.dart'; +import 'router/router.dart'; +import 'settings/settings.dart'; +import 'theme/theme.dart'; +import 'tools/tools.dart'; +import 'veilid_processor/veilid_processor.dart'; + +class ReloadThemeIntent extends Intent { + const ReloadThemeIntent(); +} + +class AttachDetachIntent extends Intent { + const AttachDetachIntent(); +} + +class DeveloperPageIntent extends Intent { + const DeveloperPageIntent(); +} + +class KeyboardShortcuts extends StatelessWidget { + const KeyboardShortcuts({required this.child, super.key}); + + void reloadTheme(BuildContext context) { + singleFuture(this, () async { + log.info('Reloading theme'); + + await VeilidChatGlobalInit.loadAssetManifest(); + + final theme = + PreferencesRepository.instance.value.themePreference.themeData(); + if (context.mounted) { + ThemeSwitcher.of(context).changeTheme(theme: theme); + + // Hack to reload translations + final localizationDelegate = LocalizedApp.of(context).delegate; + await LocalizationDelegate.create( + fallbackLocale: localizationDelegate.fallbackLocale.toString(), + supportedLocales: localizationDelegate.supportedLocales + .map((x) => x.toString()) + .toList()); + } + }); + } + + void _attachDetach(BuildContext context) { + singleFuture(this, () async { + if (ProcessorRepository.instance.processorConnectionState.isAttached) { + log.info('Detaching'); + await Veilid.instance.detach(); + } else if (ProcessorRepository + .instance.processorConnectionState.isDetached) { + log.info('Attaching'); + await Veilid.instance.attach(); + } + }); + } + + void _developerPage(BuildContext context) { + singleFuture(this, () async { + final path = GoRouter.of(context).location(); + if (path != '/developer') { + await GoRouterHelper(context).push('/developer'); + } + }); + } + + @override + Widget build(BuildContext context) => ThemeSwitcher( + builder: (context) => Shortcuts( + shortcuts: { + LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.keyR): + const ReloadThemeIntent(), + LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.keyD): + const AttachDetachIntent(), + LogicalKeySet( + LogicalKeyboardKey.alt, LogicalKeyboardKey.backquote): + const DeveloperPageIntent(), + }, + child: Actions(actions: >{ + ReloadThemeIntent: CallbackAction( + onInvoke: (intent) => reloadTheme(context)), + AttachDetachIntent: CallbackAction( + onInvoke: (intent) => _attachDetach(context)), + DeveloperPageIntent: CallbackAction( + onInvoke: (intent) => _developerPage(context)), + }, child: Focus(autofocus: true, child: child)))); + + ///////////////////////////////////////////////////////// + + final Widget child; +} From 741d1878175e1eeb358cc5455cb1b5912d1d05db Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Sun, 30 Mar 2025 14:48:58 -0500 Subject: [PATCH 46/93] =?UTF-8?q?Version=20update:=20v0.4.5=20=E2=86=92=20?= =?UTF-8?q?v0.4.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- ios/Podfile.lock | 30 +++++++++---------- ios/Runner.xcodeproj/project.pbxproj | 7 +++++ .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- pubspec.lock | 2 +- pubspec.yaml | 2 +- 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index f122962..874bed6 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.4.5+0 +current_version = 0.4.6+0 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)\+(?P\d+) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2528d2d..9f7e7e6 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -143,10 +143,10 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/veilid/ios" SPEC CHECKSUMS: - camera_avfoundation: 04b44aeb14070126c6529e5ab82cc7c9fca107cf - file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 + camera_avfoundation: dd002b0330f4981e1bbcb46ae9b62829237459a4 + file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf + flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318 GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8 @@ -156,20 +156,20 @@ SPEC CHECKSUMS: MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2 MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e - mobile_scanner: af8f71879eaba2bbcb4d86c6a462c3c0e7f23036 + mobile_scanner: fd0054c52ede661e80bf5a4dea477a2467356bee nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - printing: 54ff03f28fe9ba3aa93358afb80a8595a071dd07 + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + pasteboard: 982969ebaa7c78af3e6cc7761e8f5e77565d9ce0 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + printing: 233e1b73bd1f4a05615548e9b5a324c98588640b PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 - sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 - system_info_plus: 555ce7047fbbf29154726db942ae785c29211740 - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d - veilid: 3ce560a4f2b568a77a9fd5e23090f2fa97581019 + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d + system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + veilid: 51243c25047dbc1ebbfd87d713560260d802b845 PODFILE CHECKSUM: c8bf5b16c34712d5790b0b8d2472cc66ac0a8487 -COCOAPODS: 1.16.2 +COCOAPODS: 1.15.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 06556a5..5b0968b 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -156,6 +156,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -342,6 +343,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -352,6 +354,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -416,6 +419,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -426,6 +430,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -471,6 +476,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -481,6 +487,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3e31b44..1eb7fee 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ =3.2.0 <4.0.0' From f4407e5284d7093b5eb1c358fb92f21a0af8e30e Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 30 Mar 2025 16:23:53 -0400 Subject: [PATCH 47/93] build update --- ios/Podfile.lock | 30 +++++++++---------- ios/Runner.xcodeproj/project.pbxproj | 9 ++++-- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9f7e7e6..2528d2d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -143,10 +143,10 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/veilid/ios" SPEC CHECKSUMS: - camera_avfoundation: dd002b0330f4981e1bbcb46ae9b62829237459a4 - file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 + camera_avfoundation: 04b44aeb14070126c6529e5ab82cc7c9fca107cf + file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 + flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318 GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8 @@ -156,20 +156,20 @@ SPEC CHECKSUMS: MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2 MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e - mobile_scanner: fd0054c52ede661e80bf5a4dea477a2467356bee + mobile_scanner: af8f71879eaba2bbcb4d86c6a462c3c0e7f23036 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - pasteboard: 982969ebaa7c78af3e6cc7761e8f5e77565d9ce0 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - printing: 233e1b73bd1f4a05615548e9b5a324c98588640b + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + printing: 54ff03f28fe9ba3aa93358afb80a8595a071dd07 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d - system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - veilid: 51243c25047dbc1ebbfd87d713560260d802b845 + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + system_info_plus: 555ce7047fbbf29154726db942ae785c29211740 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + veilid: 3ce560a4f2b568a77a9fd5e23090f2fa97581019 PODFILE CHECKSUM: c8bf5b16c34712d5790b0b8d2472cc66ac0a8487 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 5b0968b..e612191 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -354,7 +354,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -380,6 +380,7 @@ CLANG_ENABLE_MODULES = YES; DEVELOPMENT_TEAM = XP5LBLT7M7; ENABLE_BITCODE = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = VeilidChat; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; @@ -430,7 +431,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -487,7 +488,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -515,6 +516,7 @@ CLANG_ENABLE_MODULES = YES; DEVELOPMENT_TEAM = XP5LBLT7M7; ENABLE_BITCODE = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = VeilidChat; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; @@ -540,6 +542,7 @@ CLANG_ENABLE_MODULES = YES; DEVELOPMENT_TEAM = XP5LBLT7M7; ENABLE_BITCODE = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = VeilidChat; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 1eb7fee..3e31b44 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Date: Thu, 3 Apr 2025 13:51:14 -0400 Subject: [PATCH 48/93] Fix getting stuck on splash screen when veilid is already started --- .../repository/processor_repository.dart | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/veilid_processor/repository/processor_repository.dart b/lib/veilid_processor/repository/processor_repository.dart index 7ff2482..a92347c 100644 --- a/lib/veilid_processor/repository/processor_repository.dart +++ b/lib/veilid_processor/repository/processor_repository.dart @@ -44,8 +44,21 @@ class ProcessorRepository { log.info('Veilid version: $veilidVersion'); - final updateStream = await Veilid.instance - .startupVeilidCore(await getVeilidConfig(kIsWeb, VeilidChatApp.name)); + Stream updateStream; + + try { + log.debug('Starting VeilidCore'); + updateStream = await Veilid.instance + .startupVeilidCore(await getVeilidConfig(kIsWeb, VeilidChatApp.name)); + } on VeilidAPIExceptionAlreadyInitialized catch (_) { + log.debug( + 'VeilidCore is already started, shutting down and restarting...'); + startedUp = true; + await shutdown(); + updateStream = await Veilid.instance + .startupVeilidCore(await getVeilidConfig(kIsWeb, VeilidChatApp.name)); + } + _updateSubscription = updateStream.listen((update) { if (update is VeilidLog) { processLog(update); From e1f081105ab32efd6773c8d883196815a762cc9c Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Thu, 3 Apr 2025 14:27:56 -0400 Subject: [PATCH 49/93] Fix routing to home after initial account creation --- lib/account_manager/views/new_account_page.dart | 13 ++++++++++--- .../views/show_recovery_key_page.dart | 15 +++++++++++---- lib/router/cubits/router_cubit.dart | 15 ++++++--------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/lib/account_manager/views/new_account_page.dart b/lib/account_manager/views/new_account_page.dart index a739094..07034df 100644 --- a/lib/account_manager/views/new_account_page.dart +++ b/lib/account_manager/views/new_account_page.dart @@ -63,10 +63,13 @@ class _NewAccountPageState extends WindowSetupState { return false; } + final isFirstAccount = + AccountRepository.instance.getLocalAccounts().isEmpty; + final writableSuperIdentity = await AccountRepository.instance .createWithNewSuperIdentity(accountSpec); GoRouterHelper(context).pushReplacement('/new_account/recovery_key', - extra: [writableSuperIdentity, accountSpec.name]); + extra: [writableSuperIdentity, accountSpec.name, isFirstAccount]); return true; } finally { @@ -92,11 +95,15 @@ class _NewAccountPageState extends WindowSetupState { return StyledScaffold( appBar: DefaultAppBar( title: Text(translate('new_account_page.titlebar')), - leading: Navigator.canPop(context) + leading: GoRouterHelper(context).canPop() ? IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { - Navigator.pop(context); + if (GoRouterHelper(context).canPop()) { + GoRouterHelper(context).pop(); + } else { + GoRouterHelper(context).go('/'); + } }, ) : null, diff --git a/lib/account_manager/views/show_recovery_key_page.dart b/lib/account_manager/views/show_recovery_key_page.dart index acbb3f3..bf44dd7 100644 --- a/lib/account_manager/views/show_recovery_key_page.dart +++ b/lib/account_manager/views/show_recovery_key_page.dart @@ -25,15 +25,18 @@ class ShowRecoveryKeyPage extends StatefulWidget { const ShowRecoveryKeyPage( {required WritableSuperIdentity writableSuperIdentity, required String name, + required bool isFirstAccount, super.key}) : _writableSuperIdentity = writableSuperIdentity, - _name = name; + _name = name, + _isFirstAccount = isFirstAccount; @override State createState() => _ShowRecoveryKeyPageState(); final WritableSuperIdentity _writableSuperIdentity; final String _name; + final bool _isFirstAccount; } class _ShowRecoveryKeyPageState extends WindowSetupState { @@ -248,9 +251,13 @@ class _ShowRecoveryKeyPageState extends WindowSetupState { child: ElevatedButton( onPressed: () { if (context.mounted) { - Navigator.canPop(context) - ? GoRouterHelper(context).pop() - : GoRouterHelper(context).go('/'); + if (widget._isFirstAccount) { + GoRouterHelper(context).go('/'); + } else { + GoRouterHelper(context).canPop() + ? GoRouterHelper(context).pop() + : GoRouterHelper(context).go('/'); + } } }, child: Text(translate('button.finish')).paddingAll(8)) diff --git a/lib/router/cubits/router_cubit.dart b/lib/router/cubits/router_cubit.dart index 19af5e0..a15eb50 100644 --- a/lib/router/cubits/router_cubit.dart +++ b/lib/router/cubits/router_cubit.dart @@ -96,7 +96,8 @@ class RouterCubit extends Cubit { if (extra == null || extra is! List || extra[0] is! WritableSuperIdentity || - extra[1] is! String) { + extra[1] is! String || + extra[2] is! bool) { return '/'; } return null; @@ -107,7 +108,8 @@ class RouterCubit extends Cubit { return ShowRecoveryKeyPage( writableSuperIdentity: extra[0] as WritableSuperIdentity, - name: extra[1] as String); + name: extra[1] as String, + isFirstAccount: extra[2] as bool); }), ]), GoRoute( @@ -123,14 +125,8 @@ class RouterCubit extends Cubit { /// Redirects when our state changes String? redirect(BuildContext context, GoRouterState goRouterState) { - // No matter where we are, if there's not - switch (goRouterState.matchedLocation) { - case '/': - if (!state.hasAnyAccount) { - return '/new_account'; - } - return null; + // We can go to any of these routes without an account. case '/new_account': return null; case '/new_account/recovery_key': @@ -139,6 +135,7 @@ class RouterCubit extends Cubit { return null; case '/developer': return null; + // Otherwise, if there's no account, we need to go to the new account page. default: return state.hasAnyAccount ? null : '/new_account'; } From a3aa7569ab50fd746829597f3454b82ac3b2b48a Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Thu, 3 Apr 2025 14:52:09 -0400 Subject: [PATCH 50/93] edit_account_form visual improvements This changes the icon for the "Waiting for network" button to an hourglass instead of a checkmark, and adds more vertical spacing between the field so that the labels and validation messages don't collide. --- lib/account_manager/views/edit_profile_form.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/account_manager/views/edit_profile_form.dart b/lib/account_manager/views/edit_profile_form.dart index 80bd2b3..4977dc7 100644 --- a/lib/account_manager/views/edit_profile_form.dart +++ b/lib/account_manager/views/edit_profile_form.dart @@ -192,6 +192,8 @@ class _EditProfileFormState extends State { onChanged: _onChanged, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + spacing: 8, children: [ Row(children: [ const Spacer(), @@ -289,7 +291,7 @@ class _EditProfileFormState extends State { _currentValueAutoAway = v ?? false; }); }, - ), + ).paddingLTRB(0, 0, 0, 16), FormBuilderTextField( name: EditProfileForm.formFieldAutoAwayTimeout, enabled: _currentValueAutoAway, @@ -319,7 +321,9 @@ class _EditProfileFormState extends State { return ElevatedButton( onPressed: (networkReady && _isModified) ? _doSubmit : null, child: Row(mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), + Icon(networkReady ? Icons.check : Icons.hourglass_empty, + size: 16) + .paddingLTRB(0, 0, 4, 0), Text(networkReady ? widget.submitText : widget.submitDisabledText) From 02b03c8cabfacb5ed74e649c8062c0c02a6a3e07 Mon Sep 17 00:00:00 2001 From: Paul Sajna Date: Fri, 4 Apr 2025 15:54:30 +0000 Subject: [PATCH 51/93] Flatpak CI Update --- .gitlab-ci.yml | 74 ++++++++++++++++--------------- flatpak/com.veilid.veilidchat.yml | 2 +- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4476fbd..72e4576 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,76 +3,80 @@ stages: - build - - build_flatpak -# - test -.macos_saas_runners: - tags: - - saas-macos-medium-m1 - image: macos-12-xcode-14 - before_script: - - echo "started by ${GITLAB_USER_NAME}" +#.macos_saas_runners: +# tags: +# - saas-macos-medium-m1 +# image: macos-12-xcode-14 +# before_script: +# - echo "started by ${GITLAB_USER_NAME}" -build_macos: - extends: - - .macos_saas_runners - stage: build - script: - - echo "place holder for build" - - sudo softwareupdate --install-rosetta --agree-to-license - - git clone https://gitlab.com/veilid/veilid.git ../veilid +#build_macos: +# extends: +# - .macos_saas_runners +# stage: build +# script: +# - echo "place holder for build" +# - sudo softwareupdate --install-rosetta --agree-to-license +# - git clone https://gitlab.com/veilid/veilid.git ../veilid #- curl –proto ‘=https’ –tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y #- source "$HOME/.cargo/env" #- brew install capnp cmake wabt llvm protobuf openjdk@17 jq cocoapods #- cargo install wasm-bindgen-cli wasm-pack cargo-edit - - wget https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_arm64_3.13.5-stable.zip - - unzip flutter_macos_arm64_3.13.5-stable.zip && export PATH="$PATH:`pwd`/flutter/bin" - - flutter upgrade - - yes | flutter doctor --android-licenses - - flutter config --enable-macos-desktop --enable-ios - - flutter config --no-analytics - - dart --disable-analytics - - flutter doctor -v +# - wget https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_arm64_3.13.5-stable.zip +# - unzip flutter_macos_arm64_3.13.5-stable.zip && export PATH="$PATH:`pwd`/flutter/bin" +# - flutter upgrade +# - yes | flutter doctor --android-licenses +# - flutter config --enable-macos-desktop --enable-ios +# - flutter config --no-analytics +# - dart --disable-analytics +# - flutter doctor -v #- flutter build ipa #- flutter build appbundle - when: manual +# only: +# - schedules build_linux_amd64_bundle: + stage: build tags: - saas-linux-medium-amd64 - image: ghcr.io/cirruslabs/flutter:3.19.4 - stage: build + image: ghcr.io/cirruslabs/flutter:3.29.2 script: - apt-get update - - apt-get install -y --no-install-recommends cmake ninja-build clang build-essential pkg-config libgtk-3-dev liblzma-dev lcov rustc cargo + - apt-get install -y --no-install-recommends cmake ninja-build clang build-essential pkg-config libgtk-3-dev liblzma-dev lcov rustup + - rustup toolchain install 1.81 --profile minimal --no-self-update - flutter config --enable-linux-desktop - git clone https://gitlab.com/veilid/veilid.git ../veilid - flutter build linux artifacts: paths: - build/linux/x64/release/bundle/ - when: manual + only: + - schedules build_linux_amd64_flatpak: tags: - saas-linux-small-amd64 - image: ubuntu:23.04 - stage: build_flatpak + image: ubuntu:24.04 + stage: build dependencies: [build_linux_amd64_bundle] + needs: + - job: build_linux_amd64_bundle + artifacts: true script: - apt-get update - apt-get install -y --no-install-recommends flatpak flatpak-builder gnupg2 elfutils ca-certificates - flatpak remote-add --no-gpg-verify --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - - flatpak install -y --noninteractive org.gnome.Sdk/x86_64/45 org.gnome.Platform/x86_64/45 app/org.flathub.flatpak-external-data-checker/x86_64/stable org.freedesktop.appstream-glib + - flatpak install -y --noninteractive org.gnome.Sdk/x86_64/46 org.gnome.Platform/x86_64/46 app/org.flathub.flatpak-external-data-checker/x86_64/stable org.freedesktop.appstream-glib - pushd flatpak/ - flatpak-builder --force-clean build-dir com.veilid.veilidchat.yml --repo=repo - flatpak build-bundle repo com.veilid.veilidchat.flatpak com.veilid.veilidchat - popd artifacts: - paths: + paths: - flatpak/com.veilid.veilidchat.flatpak - when: manual - + only: + - schedules #test: # extends: # - .macos_saas_runners diff --git a/flatpak/com.veilid.veilidchat.yml b/flatpak/com.veilid.veilidchat.yml index af45e4b..3db5a02 100644 --- a/flatpak/com.veilid.veilidchat.yml +++ b/flatpak/com.veilid.veilidchat.yml @@ -3,7 +3,7 @@ --- app-id: com.veilid.veilidchat runtime: org.gnome.Platform -runtime-version: "45" +runtime-version: "46" sdk: org.gnome.Sdk command: veilidchat separate-locales: false From 0eb181bb0e3cf08840c69ae47d64aa04c956a927 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Fri, 4 Apr 2025 13:16:58 -0400 Subject: [PATCH 52/93] Revert "Merge branch 'fix-linux-desktop' into 'main'" This reverts merge request !32 --- linux/CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index e71febc..b669b11 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -122,12 +122,6 @@ foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) COMPONENT Runtime) endforeach(bundled_library) -# Copy the native assets provided by the build.dart from all packages. -set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") -install(DIRECTORY "${NATIVE_ASSETS_DIR}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") From 5e4ffafacda7574be791cc84161812cd8c1450d8 Mon Sep 17 00:00:00 2001 From: Paul Sajna Date: Fri, 4 Apr 2025 17:17:46 +0000 Subject: [PATCH 53/93] Build Arm64 flatpaks --- .gitlab-ci.yml | 43 +++++++++++++++++++++++++ flatpak/com.veilid.veilidchat.arm64.yml | 35 ++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 flatpak/com.veilid.veilidchat.arm64.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 72e4576..78a5f55 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -77,6 +77,49 @@ build_linux_amd64_flatpak: - flatpak/com.veilid.veilidchat.flatpak only: - schedules + +build_linux_arm64_bundle: + stage: build + tags: + - saas-linux-small-arm64 + image: ghcr.io/cirruslabs/flutter:3.29.2 + script: + - apt-get update + - apt-get install -y --no-install-recommends cmake ninja-build clang build-essential pkg-config libgtk-3-dev liblzma-dev lcov rustup + - rustup toolchain install 1.81 --profile minimal --no-self-update + - flutter config --enable-linux-desktop + - git clone https://gitlab.com/veilid/veilid.git ../veilid + - flutter build linux + artifacts: + paths: + - build/linux/arm64/release/bundle/ + only: + - schedules + +build_linux_arm64_flatpak: + tags: + - saas-linux-small-arm64 + image: ubuntu:24.04 + stage: build + dependencies: [build_linux_arm64_bundle] + needs: + - job: build_linux_arm64_bundle + artifacts: true + script: + - apt-get update + - apt-get install -y --no-install-recommends flatpak flatpak-builder gnupg2 elfutils ca-certificates + - flatpak remote-add --no-gpg-verify --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + - flatpak install -y --noninteractive org.gnome.Sdk/aarch64/46 org.gnome.Platform/aarch64/46 app/org.flathub.flatpak-external-data-checker/aarch64/stable org.freedesktop.appstream-glib + - pushd flatpak/ + - flatpak-builder --force-clean build-dir com.veilid.veilidchat.arm64.yml --repo=repo + - flatpak build-bundle repo com.veilid.veilidchat.flatpak com.veilid.veilidchat + - popd + artifacts: + paths: + - flatpak/com.veilid.veilidchat.flatpak + only: + - schedules + #test: # extends: # - .macos_saas_runners diff --git a/flatpak/com.veilid.veilidchat.arm64.yml b/flatpak/com.veilid.veilidchat.arm64.yml new file mode 100644 index 0000000..f1c3d59 --- /dev/null +++ b/flatpak/com.veilid.veilidchat.arm64.yml @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/flatpak/flatpak-builder/main/data/flatpak-manifest.schema.json + +--- +app-id: com.veilid.veilidchat +runtime: org.gnome.Platform +runtime-version: "46" +sdk: org.gnome.Sdk +command: veilidchat +separate-locales: false +finish-args: + - --share=ipc + - --socket=fallback-x11 + - --socket=wayland + - --device=dri + - --socket=pulseaudio + - --share=network + - --talk-name=org.freedesktop.secrets +modules: + - name: VeilidChat + buildsystem: simple + only-arches: + - aarch64 + build-commands: + - "./build-flatpak.sh" + sources: + - type: dir + path: ../build/linux/arm64/release/ + - type: file + path: build-flatpak.sh + - type: file + path: com.veilid.veilidchat.png + - type: file + path: com.veilid.veilidchat.desktop + - type: file + path: com.veilid.veilidchat.metainfo.xml From 91883d59e5727712cc1d66c34cebdaae67b2e401 Mon Sep 17 00:00:00 2001 From: Brandon Vandegrift <798832-bmv437@users.noreply.gitlab.com> Date: Sun, 6 Apr 2025 13:45:34 -0400 Subject: [PATCH 54/93] Remove duplicate html inside of index.html --- web/index.html | 136 +------------------------------------------------ 1 file changed, 1 insertion(+), 135 deletions(-) diff --git a/web/index.html b/web/index.html index 54ea8ab..99eac34 100644 --- a/web/index.html +++ b/web/index.html @@ -19,7 +19,7 @@ - + @@ -66,137 +66,3 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - VeilidChat - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From bd00636780b3543e26723a8db163c38bb29befdd Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Sun, 6 Apr 2025 17:30:30 -0500 Subject: [PATCH 55/93] Update changelog for v0.4.7 --- CHANGELOG.md | 9 +++++++++ ios/Podfile.lock | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 843cc65..5cd3873 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## v0.4.7 ## +- *Community Contributions* + - Fix getting stuck on splash screen when veilid is already started @bmv437 / @bgrift + - Fix routing to home after initial account creation @bmv437 / @bgrift + - edit_account_form visual improvements @bmv437 / @bgrift + - Flatpak CI Update @sajattack + - Build Arm64 flatpaks @sajattack +- Dependency updates + ## v0.4.6 ## - Updated veilid-core to v0.4.4 - See Veilid changelog for specifics diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2528d2d..d943756 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -168,7 +168,7 @@ SPEC CHECKSUMS: sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 system_info_plus: 555ce7047fbbf29154726db942ae785c29211740 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d - veilid: 3ce560a4f2b568a77a9fd5e23090f2fa97581019 + veilid: b3b9418ae6b083e662396bfa2c635fb115c8510e PODFILE CHECKSUM: c8bf5b16c34712d5790b0b8d2472cc66ac0a8487 From 1ce01d17b01bc83ab6e8d0332eafb919e893ffae Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Sun, 6 Apr 2025 17:33:17 -0500 Subject: [PATCH 56/93] =?UTF-8?q?Version=20update:=20v0.4.6=20=E2=86=92=20?= =?UTF-8?q?v0.4.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 874bed6..a5b4502 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.4.6+0 +current_version = 0.4.7+0 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)\+(?P\d+) diff --git a/pubspec.yaml b/pubspec.yaml index 48244dd..ac985bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: veilidchat description: VeilidChat publish_to: 'none' -version: 0.4.6+19 +version: 0.4.7+20 environment: sdk: '>=3.2.0 <4.0.0' From b34503bbf2e97682e881136322abc04be8b03df9 Mon Sep 17 00:00:00 2001 From: TC Date: Mon, 7 Apr 2025 14:49:00 +0000 Subject: [PATCH 57/93] Update .gitlab-ci.yml file to run builds when a new version tag is published. --- .gitlab-ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 78a5f55..a29048e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -51,8 +51,8 @@ build_linux_amd64_bundle: artifacts: paths: - build/linux/x64/release/bundle/ - only: - - schedules + rules: + - if: '$CI_COMMIT_TAG =~ /v\d.+/' build_linux_amd64_flatpak: tags: @@ -75,8 +75,8 @@ build_linux_amd64_flatpak: artifacts: paths: - flatpak/com.veilid.veilidchat.flatpak - only: - - schedules + rules: + - if: '$CI_COMMIT_TAG =~ /v\d.+/' build_linux_arm64_bundle: stage: build @@ -93,8 +93,8 @@ build_linux_arm64_bundle: artifacts: paths: - build/linux/arm64/release/bundle/ - only: - - schedules + rules: + - if: '$CI_COMMIT_TAG =~ /v\d.+/' build_linux_arm64_flatpak: tags: @@ -117,8 +117,8 @@ build_linux_arm64_flatpak: artifacts: paths: - flatpak/com.veilid.veilidchat.flatpak - only: - - schedules + rules: + - if: '$CI_COMMIT_TAG =~ /v\d.+/' #test: # extends: From f8977147f6084aa08a4e0fc66a0fe170cf74e8e6 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 9 Apr 2025 15:38:30 -0400 Subject: [PATCH 58/93] remove things that fail to deserialize --- .../models/local_account/local_account.dart | 11 ++++++++-- .../models/user_login/user_login.dart | 11 ++++++++-- packages/veilid_support/lib/src/table_db.dart | 22 ++++++++++++++----- .../veilid_support/lib/src/veilid_log.dart | 1 + 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/account_manager/models/local_account/local_account.dart b/lib/account_manager/models/local_account/local_account.dart index 1ec6d22..49506d0 100644 --- a/lib/account_manager/models/local_account/local_account.dart +++ b/lib/account_manager/models/local_account/local_account.dart @@ -40,6 +40,13 @@ sealed class LocalAccount with _$LocalAccount { required String name, }) = _LocalAccount; - factory LocalAccount.fromJson(dynamic json) => - _$LocalAccountFromJson(json as Map); + factory LocalAccount.fromJson(dynamic json) { + try { + return _$LocalAccountFromJson(json as Map); + // Need to catch any errors here too + // ignore: avoid_catches_without_on_clauses + } catch (e, st) { + throw Exception('invalid local account: $e\n$st'); + } + } } diff --git a/lib/account_manager/models/user_login/user_login.dart b/lib/account_manager/models/user_login/user_login.dart index d43fdfd..4d96d38 100644 --- a/lib/account_manager/models/user_login/user_login.dart +++ b/lib/account_manager/models/user_login/user_login.dart @@ -23,6 +23,13 @@ sealed class UserLogin with _$UserLogin { required Timestamp lastActive, }) = _UserLogin; - factory UserLogin.fromJson(dynamic json) => - _$UserLoginFromJson(json as Map); + factory UserLogin.fromJson(dynamic json) { + try { + return _$UserLoginFromJson(json as Map); + // Need to catch any errors here too + // ignore: avoid_catches_without_on_clauses + } catch (e, st) { + throw Exception('invalid user login: $e\n$st'); + } + } } diff --git a/packages/veilid_support/lib/src/table_db.dart b/packages/veilid_support/lib/src/table_db.dart index 522a837..eb7fe99 100644 --- a/packages/veilid_support/lib/src/table_db.dart +++ b/packages/veilid_support/lib/src/table_db.dart @@ -6,6 +6,9 @@ import 'package:async_tools/async_tools.dart'; import 'package:meta/meta.dart'; import 'package:veilid/veilid.dart'; +import '../veilid_support.dart'; +import 'veilid_log.dart'; + Future tableScope( String name, Future Function(VeilidTableDB tdb) callback, {int columnCount = 1}) async { @@ -48,11 +51,20 @@ abstract mixin class TableDBBackedJson { /// Load things from storage @protected Future load() async { - final obj = await tableScope(tableName(), (tdb) async { - final objJson = await tdb.loadStringJson(0, tableKeyName()); - return valueFromJson(objJson); - }); - return obj; + try { + final obj = await tableScope(tableName(), (tdb) async { + final objJson = await tdb.loadStringJson(0, tableKeyName()); + return valueFromJson(objJson); + }); + return obj; + } on Exception catch (e, st) { + veilidLoggy.debug( + 'Unable to load data from table store: ' + '${tableName()}:${tableKeyName()}', + e, + st); + return null; + } } /// Store things to storage diff --git a/packages/veilid_support/lib/src/veilid_log.dart b/packages/veilid_support/lib/src/veilid_log.dart index 3b35b5d..4c9ad0b 100644 --- a/packages/veilid_support/lib/src/veilid_log.dart +++ b/packages/veilid_support/lib/src/veilid_log.dart @@ -69,6 +69,7 @@ void processLog(VeilidLog log) { } void initVeilidLog(bool debugMode) { + // Always allow LOG_TRACE option // ignore: do_not_use_environment const isTrace = String.fromEnvironment('LOG_TRACE') != ''; LogLevel logLevel; From 2313247407bd270266b0f7e8cfc3bf4edd07f5b9 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 9 Apr 2025 18:24:46 -0400 Subject: [PATCH 59/93] fix popcontrol for android --- lib/router/views/router_shell.dart | 6 ++++-- lib/theme/views/pop_control.dart | 9 +++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/router/views/router_shell.dart b/lib/router/views/router_shell.dart index 8a7130f..164c452 100644 --- a/lib/router/views/router_shell.dart +++ b/lib/router/views/router_shell.dart @@ -2,13 +2,15 @@ import 'package:flutter/widgets.dart'; import '../../keyboard_shortcuts.dart'; import '../../notifications/notifications.dart'; +import '../../theme/theme.dart'; class RouterShell extends StatelessWidget { const RouterShell({required Widget child, super.key}) : _child = child; @override - Widget build(BuildContext context) => - NotificationsWidget(child: KeyboardShortcuts(child: _child)); + Widget build(BuildContext context) => PopControl( + dismissible: false, + child: NotificationsWidget(child: KeyboardShortcuts(child: _child))); final Widget _child; } diff --git a/lib/theme/views/pop_control.dart b/lib/theme/views/pop_control.dart index deaf785..d2e98f9 100644 --- a/lib/theme/views/pop_control.dart +++ b/lib/theme/views/pop_control.dart @@ -8,18 +8,15 @@ class PopControl extends StatelessWidget { super.key, }); - void _doDismiss(NavigatorState navigator) { + void _doDismiss(BuildContext context) { if (!dismissible) { return; } - navigator.pop(); + Navigator.of(context).pop(); } @override - // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { - final navigator = Navigator.of(context); - final route = ModalRoute.of(context); if (route != null && route is PopControlDialogRoute) { WidgetsBinding.instance.addPostFrameCallback((_) { @@ -33,7 +30,7 @@ class PopControl extends StatelessWidget { if (didPop) { return; } - _doDismiss(navigator); + _doDismiss(context); return; }, child: child); From d0fe5c5519e18be5a595207a5e0d4d182779b5ac Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 9 Apr 2025 18:36:46 -0400 Subject: [PATCH 60/93] additional popcontrol for child route --- lib/router/cubits/router_cubit.dart | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/router/cubits/router_cubit.dart b/lib/router/cubits/router_cubit.dart index a15eb50..e5a2024 100644 --- a/lib/router/cubits/router_cubit.dart +++ b/lib/router/cubits/router_cubit.dart @@ -12,6 +12,7 @@ import 'package:veilid_support/veilid_support.dart'; import '../../../account_manager/account_manager.dart'; import '../../layout/layout.dart'; import '../../settings/settings.dart'; +import '../../theme/theme.dart'; import '../../tools/tools.dart'; import '../../veilid_processor/views/developer.dart'; import '../views/router_shell.dart'; @@ -105,11 +106,13 @@ class RouterCubit extends Cubit { builder: (context, state) { final extra = state.extra! as List; - return ShowRecoveryKeyPage( - writableSuperIdentity: - extra[0] as WritableSuperIdentity, - name: extra[1] as String, - isFirstAccount: extra[2] as bool); + return PopControl( + dismissible: false, + child: ShowRecoveryKeyPage( + writableSuperIdentity: + extra[0] as WritableSuperIdentity, + name: extra[1] as String, + isFirstAccount: extra[2] as bool)); }), ]), GoRoute( From bf38c2c44df0d88d6e80fc7002dd022f8822ea65 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 17 Apr 2025 18:55:43 -0400 Subject: [PATCH 61/93] clean up a bunch of exceptions --- devtools_options.yaml | 3 +- ios/Podfile.lock | 2 +- .../message_reconciliation.dart | 28 +++-- .../cubits/single_contact_messages_cubit.dart | 56 +++++---- lib/router/cubits/router_cubit.dart | 3 +- lib/veilid_processor/views/developer.dart | 16 ++- packages/veilid_support/example/pubspec.lock | 2 +- .../lib/dht_support/src/dht_log/dht_log.dart | 1 + .../dht_support/src/dht_log/dht_log_read.dart | 12 +- .../src/dht_log/dht_log_spine.dart | 7 +- .../src/dht_log/dht_log_write.dart | 37 ++++-- .../src/dht_record/dht_record_pool.dart | 117 +++++------------- .../dht_record/dht_record_pool_private.dart | 20 +-- .../src/dht_short_array/dht_short_array.dart | 1 + .../dht_short_array/dht_short_array_head.dart | 18 +++ .../dht_short_array/dht_short_array_read.dart | 12 +- .../dht_short_array_write.dart | 20 ++- .../src/interfaces/exceptions.dart | 14 ++- .../lib/src/persistent_queue.dart | 27 +++- .../lib/src/table_db_array.dart | 12 +- packages/veilid_support/pubspec.lock | 2 +- 21 files changed, 244 insertions(+), 166 deletions(-) diff --git a/devtools_options.yaml b/devtools_options.yaml index 5c27c3e..7093540 100644 --- a/devtools_options.yaml +++ b/devtools_options.yaml @@ -1,2 +1,3 @@ extensions: - - provider: true \ No newline at end of file + - provider: true + - shared_preferences: true \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d943756..2528d2d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -168,7 +168,7 @@ SPEC CHECKSUMS: sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 system_info_plus: 555ce7047fbbf29154726db942ae785c29211740 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d - veilid: b3b9418ae6b083e662396bfa2c635fb115c8510e + veilid: 3ce560a4f2b568a77a9fd5e23090f2fa97581019 PODFILE CHECKSUM: c8bf5b16c34712d5790b0b8d2472cc66ac0a8487 diff --git a/lib/chat/cubits/reconciliation/message_reconciliation.dart b/lib/chat/cubits/reconciliation/message_reconciliation.dart index f0b8c4c..9b183b5 100644 --- a/lib/chat/cubits/reconciliation/message_reconciliation.dart +++ b/lib/chat/cubits/reconciliation/message_reconciliation.dart @@ -6,6 +6,7 @@ import 'package:sorted_list/sorted_list.dart'; import 'package:veilid_support/veilid_support.dart'; import '../../../proto/proto.dart' as proto; +import '../../../tools/tools.dart'; import 'author_input_queue.dart'; import 'author_input_source.dart'; import 'output_position.dart'; @@ -62,17 +63,24 @@ class MessageReconciliation { Future _enqueueAuthorInput( {required TypedKey author, required AuthorInputSource inputSource}) async { - // Get the position of our most recent reconciled message from this author - final outputPosition = await _findLastOutputPosition(author: author); + try { + // Get the position of our most recent reconciled message from this author + final outputPosition = await _findLastOutputPosition(author: author); - // Find oldest message we have not yet reconciled - final inputQueue = await AuthorInputQueue.create( - author: author, - inputSource: inputSource, - outputPosition: outputPosition, - onError: _onError, - ); - return inputQueue; + // Find oldest message we have not yet reconciled + final inputQueue = await AuthorInputQueue.create( + author: author, + inputSource: inputSource, + outputPosition: outputPosition, + onError: _onError, + ); + return inputQueue; + // Catch everything so we can avoid ParallelWaitError + // ignore: avoid_catches_without_on_clauses + } catch (e, st) { + log.error('Exception enqueing author input: $e:\n$st\n'); + return null; + } } // Get the position of our most recent reconciled message from this author diff --git a/lib/chat/cubits/single_contact_messages_cubit.dart b/lib/chat/cubits/single_contact_messages_cubit.dart index b4e77db..559eae2 100644 --- a/lib/chat/cubits/single_contact_messages_cubit.dart +++ b/lib/chat/cubits/single_contact_messages_cubit.dart @@ -100,8 +100,8 @@ class SingleContactMessagesCubit extends Cubit { key: _remoteConversationRecordKey.toString(), fromBuffer: proto.Message.fromBuffer, closure: _processUnsentMessages, - onError: (e, sp) { - log.error('Exception while processing unsent messages: $e\n$sp\n'); + onError: (e, st) { + log.error('Exception while processing unsent messages: $e\n$st\n'); }); // Make crypto @@ -310,14 +310,11 @@ class SingleContactMessagesCubit extends Cubit { Future _processMessageToSend( proto.Message message, proto.Message? previousMessage) async { - // Get the previous message if we don't have one - previousMessage ??= await _sentMessagesCubit!.operate((r) async => - r.length == 0 - ? null - : await r.getProtobuf(proto.Message.fromBuffer, r.length - 1)); - - message.id = - await _senderMessageIntegrity.generateMessageId(previousMessage); + // It's possible we had a signature from a previous + // operateAppendEventual attempt, so clear it and make a new message id too + message + ..clearSignature() + ..id = await _senderMessageIntegrity.generateMessageId(previousMessage); // Now sign it await _senderMessageIntegrity.signMessage( @@ -326,26 +323,33 @@ class SingleContactMessagesCubit extends Cubit { // Async process to send messages in the background Future _processUnsentMessages(IList messages) async { - // Go through and assign ids to all the messages in order - proto.Message? previousMessage; - final processedMessages = messages.toList(); - for (final message in processedMessages) { - try { - await _processMessageToSend(message, previousMessage); - previousMessage = message; - } on Exception catch (e) { - log.error('Exception processing unsent message: $e'); - } - } - // _sendingMessages = messages; // _renderState(); try { - await _sentMessagesCubit!.operateAppendEventual((writer) => - writer.addAll(messages.map((m) => m.writeToBuffer()).toList())); - } on Exception catch (e) { - log.error('Exception appending unsent messages: $e'); + await _sentMessagesCubit!.operateAppendEventual((writer) async { + // Get the previous message if we have one + var previousMessage = writer.length == 0 + ? null + : await writer.getProtobuf( + proto.Message.fromBuffer, writer.length - 1); + + // Sign all messages + final processedMessages = messages.toList(); + for (final message in processedMessages) { + try { + await _processMessageToSend(message, previousMessage); + previousMessage = message; + } on Exception catch (e, st) { + log.error('Exception processing unsent message: $e:\n$st\n'); + } + } + final byteMessages = messages.map((m) => m.writeToBuffer()).toList(); + + return writer.addAll(byteMessages); + }); + } on Exception catch (e, st) { + log.error('Exception appending unsent messages: $e:\n$st\n'); } // _sendingMessages = const IList.empty(); diff --git a/lib/router/cubits/router_cubit.dart b/lib/router/cubits/router_cubit.dart index e5a2024..d651611 100644 --- a/lib/router/cubits/router_cubit.dart +++ b/lib/router/cubits/router_cubit.dart @@ -138,7 +138,8 @@ class RouterCubit extends Cubit { return null; case '/developer': return null; - // Otherwise, if there's no account, we need to go to the new account page. + // Otherwise, if there's no account, + // we need to go to the new account page. default: return state.hasAnyAccount ? null : '/new_account'; } diff --git a/lib/veilid_processor/views/developer.dart b/lib/veilid_processor/views/developer.dart index cb64d3d..08463fb 100644 --- a/lib/veilid_processor/views/developer.dart +++ b/lib/veilid_processor/views/developer.dart @@ -124,7 +124,21 @@ class _DeveloperPageState extends State { _debugOut('DEBUG >>>\n$debugCommand\n'); try { - final out = await Veilid.instance.debug(debugCommand); + var out = await Veilid.instance.debug(debugCommand); + + if (debugCommand == 'help') { + out = 'VeilidChat Commands:\n' + ' pool allocations - List DHTRecordPool allocations\n' + ' pool opened - List opened DHTRecord instances' + ' from the pool\n' + ' change_log_ignore change the log' + ' target ignore list for a tracing layer\n' + ' targets to add to the ignore list can be separated by' + ' a comma.\n' + ' to remove a target from the ignore list, prepend it' + ' with a minus.\n\n$out'; + } + _debugOut('<<< DEBUG\n$out\n'); } on Exception catch (e, st) { _debugOut('<<< ERROR\n$e\n<<< STACK\n$st'); diff --git a/packages/veilid_support/example/pubspec.lock b/packages/veilid_support/example/pubspec.lock index 6ef291b..2e87d8c 100644 --- a/packages/veilid_support/example/pubspec.lock +++ b/packages/veilid_support/example/pubspec.lock @@ -650,7 +650,7 @@ packages: path: "../../../../veilid/veilid-flutter" relative: true source: path - version: "0.4.3" + version: "0.4.4" veilid_support: dependency: "direct main" description: diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart index 8f88ce1..1d3fb89 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart @@ -7,6 +7,7 @@ import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; import 'package:meta/meta.dart'; +import '../../../src/veilid_log.dart'; import '../../../veilid_support.dart'; import '../../proto/proto.dart' as proto; diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_read.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_read.dart index 6281d6e..d8634c6 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_read.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_read.dart @@ -47,8 +47,16 @@ class _DHTLogRead implements DHTLogReadOperations { final chunks = Iterable.generate(length) .slices(kMaxDHTConcurrency) - .map((chunk) => chunk - .map((pos) async => get(pos + start, forceRefresh: forceRefresh))); + .map((chunk) => chunk.map((pos) async { + try { + return get(pos + start, forceRefresh: forceRefresh); + // Need some way to debug ParallelWaitError + // ignore: avoid_catches_without_on_clauses + } catch (e, st) { + veilidLoggy.error('$e\n$st\n'); + rethrow; + } + })); for (final chunk in chunks) { final elems = await chunk.wait; diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart index bb27e04..d9f5df2 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart @@ -248,7 +248,12 @@ class _DHTLogSpine { final headDelta = _ringDistance(newHead, oldHead); final tailDelta = _ringDistance(newTail, oldTail); if (headDelta > _positionLimit ~/ 2 || tailDelta > _positionLimit ~/ 2) { - throw const DHTExceptionInvalidData(); + throw DHTExceptionInvalidData('_DHTLogSpine::_updateHead ' + '_head=$_head _tail=$_tail ' + 'oldHead=$oldHead oldTail=$oldTail ' + 'newHead=$newHead newTail=$newTail ' + 'headDelta=$headDelta tailDelta=$tailDelta ' + '_positionLimit=$_positionLimit'); } } diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart index 1b5c09f..8d34280 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart @@ -17,7 +17,8 @@ class _DHTLogWrite extends _DHTLogRead implements DHTLogWriteOperations { } final lookup = await _spine.lookupPosition(pos); if (lookup == null) { - throw const DHTExceptionInvalidData(); + throw DHTExceptionInvalidData( + '_DHTLogRead::tryWriteItem pos=$pos _spine.length=${_spine.length}'); } // Write item to the segment @@ -45,12 +46,14 @@ class _DHTLogWrite extends _DHTLogRead implements DHTLogWriteOperations { } final aLookup = await _spine.lookupPosition(aPos); if (aLookup == null) { - throw const DHTExceptionInvalidData(); + throw DHTExceptionInvalidData('_DHTLogWrite::swap aPos=$aPos bPos=$bPos ' + '_spine.length=${_spine.length}'); } final bLookup = await _spine.lookupPosition(bPos); if (bLookup == null) { await aLookup.close(); - throw const DHTExceptionInvalidData(); + throw DHTExceptionInvalidData('_DHTLogWrite::swap aPos=$aPos bPos=$bPos ' + '_spine.length=${_spine.length}'); } // Swap items in the segments @@ -65,7 +68,10 @@ class _DHTLogWrite extends _DHTLogRead implements DHTLogWriteOperations { if (bItem.value == null) { final aItem = await aWrite.get(aLookup.pos); if (aItem == null) { - throw const DHTExceptionInvalidData(); + throw DHTExceptionInvalidData( + '_DHTLogWrite::swap aPos=$aPos bPos=$bPos ' + 'aLookup.pos=${aLookup.pos} bLookup.pos=${bLookup.pos} ' + '_spine.length=${_spine.length}'); } await sb.operateWriteEventual((bWrite) async { final success = await bWrite @@ -101,7 +107,9 @@ class _DHTLogWrite extends _DHTLogRead implements DHTLogWriteOperations { await write.clear(); } else if (lookup.pos != write.length) { // We should always be appending at the length - throw const DHTExceptionInvalidData(); + throw DHTExceptionInvalidData( + '_DHTLogWrite::add lookup.pos=${lookup.pos} ' + 'write.length=${write.length}'); } return write.add(value); })); @@ -117,12 +125,16 @@ class _DHTLogWrite extends _DHTLogRead implements DHTLogWriteOperations { final dws = DelayedWaitSet(); var success = true; - for (var valueIdx = 0; valueIdx < values.length;) { + for (var valueIdxIter = 0; valueIdxIter < values.length;) { + final valueIdx = valueIdxIter; final remaining = values.length - valueIdx; final lookup = await _spine.lookupPosition(insertPos + valueIdx); if (lookup == null) { - throw const DHTExceptionInvalidData(); + throw DHTExceptionInvalidData('_DHTLogWrite::addAll ' + '_spine.length=${_spine.length}' + 'insertPos=$insertPos valueIdx=$valueIdx ' + 'values.length=${values.length} '); } final sacount = min(remaining, DHTShortArray.maxElements - lookup.pos); @@ -137,16 +149,21 @@ class _DHTLogWrite extends _DHTLogRead implements DHTLogWriteOperations { await write.clear(); } else if (lookup.pos != write.length) { // We should always be appending at the length - throw const DHTExceptionInvalidData(); + await write.truncate(lookup.pos); } - return write.addAll(sublistValues); + await write.addAll(sublistValues); + success = true; })); } on DHTExceptionOutdated { success = false; + // Need some way to debug ParallelWaitError + // ignore: avoid_catches_without_on_clauses + } catch (e, st) { + veilidLoggy.error('$e\n$st\n'); } }); - valueIdx += sacount; + valueIdxIter += sacount; } await dws(); diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart index 9027799..e3c9abe 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart @@ -246,11 +246,13 @@ class DHTRecordPool with TableDBBackedJson { /// Print children String debugChildren(TypedKey recordKey, {List? allDeps}) { allDeps ??= _collectChildrenInner(recordKey); + // Debugging // ignore: avoid_print var out = 'Parent: $recordKey (${_state.debugNames[recordKey.toString()]})\n'; for (final dep in allDeps) { if (dep != recordKey) { + // Debugging // ignore: avoid_print out += ' Child: $dep (${_state.debugNames[dep.toString()]})\n'; } @@ -270,32 +272,25 @@ class DHTRecordPool with TableDBBackedJson { break; } } - } else { - final now = Veilid.instance.now().value; - // Expired, process renewal if desired - for (final entry in _opened.entries) { - final openedKey = entry.key; - final openedRecordInfo = entry.value; - - if (openedKey == updateValueChange.key) { - // Renew watch state for each opened record - for (final rec in openedRecordInfo.records) { - // See if the watch had an expiration and if it has expired - // otherwise the renewal will keep the same parameters - final watchState = rec._watchState; - if (watchState != null) { - final exp = watchState.expiration; - if (exp != null && exp.value < now) { - // Has expiration, and it has expired, clear watch state - rec._watchState = null; - } - } - } - openedRecordInfo.shared.needsWatchStateUpdate = true; - break; - } - } } + // else { + + // XXX: should no longer be necessary + // // Remove watch state + // + // for (final entry in _opened.entries) { + // final openedKey = entry.key; + // final openedRecordInfo = entry.value; + + // if (openedKey == updateValueChange.key) { + // for (final rec in openedRecordInfo.records) { + // rec._watchState = null; + // } + // openedRecordInfo.shared.needsWatchStateUpdate = true; + // break; + // } + // } + //} } /// Log the current record allocations @@ -735,7 +730,6 @@ class DHTRecordPool with TableDBBackedJson { int? totalCount; Timestamp? maxExpiration; List? allSubkeys; - Timestamp? earliestRenewalTime; var noExpiration = false; var everySubkey = false; @@ -768,15 +762,6 @@ class DHTRecordPool with TableDBBackedJson { } else { everySubkey = true; } - final wsRenewalTime = ws.renewalTime; - if (wsRenewalTime != null) { - earliestRenewalTime = earliestRenewalTime == null - ? wsRenewalTime - : Timestamp( - value: (wsRenewalTime.value < earliestRenewalTime.value - ? wsRenewalTime.value - : earliestRenewalTime.value)); - } } } if (noExpiration) { @@ -790,25 +775,10 @@ class DHTRecordPool with TableDBBackedJson { } return _WatchState( - subkeys: allSubkeys, - expiration: maxExpiration, - count: totalCount, - renewalTime: earliestRenewalTime); - } - - static void _updateWatchRealExpirations(Iterable records, - Timestamp realExpiration, Timestamp renewalTime) { - for (final rec in records) { - final ws = rec._watchState; - if (ws != null) { - rec._watchState = _WatchState( - subkeys: ws.subkeys, - expiration: ws.expiration, - count: ws.count, - realExpiration: realExpiration, - renewalTime: renewalTime); - } - } + subkeys: allSubkeys, + expiration: maxExpiration, + count: totalCount, + ); } Future _watchStateChange( @@ -833,9 +803,9 @@ class DHTRecordPool with TableDBBackedJson { // Only try this once, if it doesn't succeed then it can just expire // on its own. try { - final cancelled = await dhtctx.cancelDHTWatch(openedRecordKey); + final stillActive = await dhtctx.cancelDHTWatch(openedRecordKey); - log('cancelDHTWatch: key=$openedRecordKey, cancelled=$cancelled, ' + log('cancelDHTWatch: key=$openedRecordKey, stillActive=$stillActive, ' 'debugNames=${openedRecordInfo.debugNames}'); openedRecordInfo.shared.unionWatchState = null; @@ -858,34 +828,20 @@ class DHTRecordPool with TableDBBackedJson { final subkeys = unionWatchState.subkeys?.toList(); final count = unionWatchState.count; final expiration = unionWatchState.expiration; - final now = veilid.now(); - final realExpiration = await dhtctx.watchDHTValues(openedRecordKey, + final active = await dhtctx.watchDHTValues(openedRecordKey, subkeys: unionWatchState.subkeys?.toList(), count: unionWatchState.count, - expiration: unionWatchState.expiration ?? - (_defaultWatchDurationSecs == null - ? null - : veilid.now().offset(TimestampDuration.fromMillis( - _defaultWatchDurationSecs! * 1000)))); + expiration: unionWatchState.expiration); - final expirationDuration = realExpiration.diff(now); - final renewalTime = now.offset(TimestampDuration( - value: expirationDuration.value * - BigInt.from(_watchRenewalNumerator) ~/ - BigInt.from(_watchRenewalDenominator))); - - log('watchDHTValues: key=$openedRecordKey, subkeys=$subkeys, ' + log('watchDHTValues(active=$active): ' + 'key=$openedRecordKey, subkeys=$subkeys, ' 'count=$count, expiration=$expiration, ' - 'realExpiration=$realExpiration, ' - 'renewalTime=$renewalTime, ' 'debugNames=${openedRecordInfo.debugNames}'); // Update watch states with real expiration - if (realExpiration.value != BigInt.zero) { + if (active) { openedRecordInfo.shared.unionWatchState = unionWatchState; - _updateWatchRealExpirations( - openedRecordInfo.records, realExpiration, renewalTime); openedRecordInfo.shared.needsWatchStateUpdate = false; } } on VeilidAPIExceptionTimeout { @@ -944,22 +900,13 @@ class DHTRecordPool with TableDBBackedJson { /// Ticker to check watch state change requests Future tick() async => _mutex.protect(() async { // See if any opened records need watch state changes - final now = veilid.now(); for (final kv in _opened.entries) { final openedRecordKey = kv.key; final openedRecordInfo = kv.value; - var wantsWatchStateUpdate = + final wantsWatchStateUpdate = openedRecordInfo.shared.needsWatchStateUpdate; - // Check if we have reached renewal time for the watch - if (openedRecordInfo.shared.unionWatchState != null && - openedRecordInfo.shared.unionWatchState!.renewalTime != null && - now.value > - openedRecordInfo.shared.unionWatchState!.renewalTime!.value) { - wantsWatchStateUpdate = true; - } - if (wantsWatchStateUpdate) { // Update union watch state final unionWatchState = diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool_private.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool_private.dart index d1fb5d1..05b93b0 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool_private.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool_private.dart @@ -1,9 +1,5 @@ part of 'dht_record_pool.dart'; -const int? _defaultWatchDurationSecs = null; // 600 -const int _watchRenewalNumerator = 4; -const int _watchRenewalDenominator = 5; - // DHT crypto domain const String _cryptoDomainDHT = 'dht'; @@ -14,21 +10,17 @@ const _sfListen = 'listen'; /// Watch state @immutable class _WatchState extends Equatable { - const _WatchState( - {required this.subkeys, - required this.expiration, - required this.count, - this.realExpiration, - this.renewalTime}); + const _WatchState({ + required this.subkeys, + required this.expiration, + required this.count, + }); final List? subkeys; final Timestamp? expiration; final int? count; - final Timestamp? realExpiration; - final Timestamp? renewalTime; @override - List get props => - [subkeys, expiration, count, realExpiration, renewalTime]; + List get props => [subkeys, expiration, count]; } /// Data shared amongst all DHTRecord instances diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart index 8101a7a..ccf7d18 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'package:async_tools/async_tools.dart'; import 'package:collection/collection.dart'; +import '../../../src/veilid_log.dart'; import '../../../veilid_support.dart'; import '../../proto/proto.dart' as proto; diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart index 49659cd..b0cc41b 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart @@ -383,6 +383,24 @@ class _DHTShortArrayHead { // xxx: free list optimization here? } + /// Truncate index to a particular length + void truncate(int newLength) { + if (newLength >= _index.length) { + return; + } else if (newLength == 0) { + clearIndex(); + return; + } else if (newLength < 0) { + throw StateError('can not truncate to negative length'); + } + + final newIndex = _index.sublist(0, newLength); + final freed = _index.sublist(newLength); + + _index = newIndex; + _free.addAll(freed); + } + /// Validate the head from the DHT is properly formatted /// and calculate the free list from it while we're here List _makeFreeList( diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_read.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_read.dart index ddfdedc..747a892 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_read.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_read.dart @@ -60,8 +60,16 @@ class _DHTShortArrayRead implements DHTShortArrayReadOperations { final chunks = Iterable.generate(length) .slices(kMaxDHTConcurrency) - .map((chunk) => chunk - .map((pos) async => get(pos + start, forceRefresh: forceRefresh))); + .map((chunk) => chunk.map((pos) async { + try { + return get(pos + start, forceRefresh: forceRefresh); + // Need some way to debug ParallelWaitError + // ignore: avoid_catches_without_on_clauses + } catch (e, st) { + veilidLoggy.error('$e\n$st\n'); + rethrow; + } + })); for (final chunk in chunks) { final elems = await chunk.wait; diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart index 51950f6..1705bc0 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart @@ -9,6 +9,7 @@ abstract class DHTShortArrayWriteOperations DHTRandomWrite, DHTInsertRemove, DHTAdd, + DHTTruncate, DHTClear {} class _DHTShortArrayWrite extends _DHTShortArrayRead @@ -72,10 +73,16 @@ class _DHTShortArrayWrite extends _DHTShortArrayRead final value = values[i]; final outSeqNum = outSeqNums[i]; dws.add((_) async { - final outValue = await lookup.record.tryWriteBytes(value, - subkey: lookup.recordSubkey, outSeqNum: outSeqNum); - if (outValue != null) { - success = false; + try { + final outValue = await lookup.record.tryWriteBytes(value, + subkey: lookup.recordSubkey, outSeqNum: outSeqNum); + if (outValue != null) { + success = false; + } + // Need some way to debug ParallelWaitError + // ignore: avoid_catches_without_on_clauses + } catch (e, st) { + veilidLoggy.error('$e\n$st\n'); } }); } @@ -142,6 +149,11 @@ class _DHTShortArrayWrite extends _DHTShortArrayRead _head.clearIndex(); } + @override + Future truncate(int newLength) async { + _head.truncate(newLength); + } + @override Future tryWriteItem(int pos, Uint8List newValue, {Output? output}) async { diff --git a/packages/veilid_support/lib/dht_support/src/interfaces/exceptions.dart b/packages/veilid_support/lib/dht_support/src/interfaces/exceptions.dart index b17dbee..134f5fa 100644 --- a/packages/veilid_support/lib/dht_support/src/interfaces/exceptions.dart +++ b/packages/veilid_support/lib/dht_support/src/interfaces/exceptions.dart @@ -2,20 +2,32 @@ class DHTExceptionOutdated implements Exception { const DHTExceptionOutdated( [this.cause = 'operation failed due to newer dht value']); final String cause; + + @override + String toString() => 'DHTExceptionOutdated: $cause'; } class DHTExceptionInvalidData implements Exception { - const DHTExceptionInvalidData([this.cause = 'dht data structure is corrupt']); + const DHTExceptionInvalidData(this.cause); final String cause; + + @override + String toString() => 'DHTExceptionInvalidData: $cause'; } class DHTExceptionCancelled implements Exception { const DHTExceptionCancelled([this.cause = 'operation was cancelled']); final String cause; + + @override + String toString() => 'DHTExceptionCancelled: $cause'; } class DHTExceptionNotAvailable implements Exception { const DHTExceptionNotAvailable( [this.cause = 'request could not be completed at this time']); final String cause; + + @override + String toString() => 'DHTExceptionNotAvailable: $cause'; } diff --git a/packages/veilid_support/lib/src/persistent_queue.dart b/packages/veilid_support/lib/src/persistent_queue.dart index 750c48e..939a5b3 100644 --- a/packages/veilid_support/lib/src/persistent_queue.dart +++ b/packages/veilid_support/lib/src/persistent_queue.dart @@ -7,6 +7,7 @@ import 'package:protobuf/protobuf.dart'; import 'config.dart'; import 'table_db.dart'; +import 'veilid_log.dart'; class PersistentQueue with TableDBBackedFromBuffer> { @@ -46,7 +47,7 @@ class PersistentQueue } } - Future _init(_) async { + Future _init(Completer _) async { // Start the processor unawaited(Future.delayed(Duration.zero, () async { await _initWait(); @@ -182,10 +183,28 @@ class PersistentQueue @override IList valueFromBuffer(Uint8List bytes) { - final reader = CodedBufferReader(bytes); var out = IList(); - while (!reader.isAtEnd()) { - out = out.add(_fromBuffer(reader.readBytesAsView())); + try { + final reader = CodedBufferReader(bytes); + while (!reader.isAtEnd()) { + final bytes = reader.readBytesAsView(); + try { + final item = _fromBuffer(bytes); + out = out.add(item); + } on Exception catch (e, st) { + veilidLoggy.debug( + 'Dropping invalid item from persistent queue: $bytes\n' + 'tableName=${tableName()}:tableKeyName=${tableKeyName()}\n', + e, + st); + } + } + } on Exception catch (e, st) { + veilidLoggy.debug( + 'Dropping remainder of invalid persistent queue\n' + 'tableName=${tableName()}:tableKeyName=${tableKeyName()}\n', + e, + st); } return out; } diff --git a/packages/veilid_support/lib/src/table_db_array.dart b/packages/veilid_support/lib/src/table_db_array.dart index c1d54cf..8b59336 100644 --- a/packages/veilid_support/lib/src/table_db_array.dart +++ b/packages/veilid_support/lib/src/table_db_array.dart @@ -9,6 +9,7 @@ import 'package:meta/meta.dart'; import 'package:protobuf/protobuf.dart'; import '../veilid_support.dart'; +import 'veilid_log.dart'; @immutable class TableDBArrayUpdate extends Equatable { @@ -262,7 +263,16 @@ class _TableDBArrayBase { final dws = DelayedWaitSet(); while (batchLen > 0) { final entry = await _getIndexEntry(pos); - dws.add((_) async => (await _loadEntry(entry))!); + dws.add((_) async { + try { + return (await _loadEntry(entry))!; + // Need some way to debug ParallelWaitError + // ignore: avoid_catches_without_on_clauses + } catch (e, st) { + veilidLoggy.error('$e\n$st\n'); + rethrow; + } + }); pos++; batchLen--; } diff --git a/packages/veilid_support/pubspec.lock b/packages/veilid_support/pubspec.lock index 0c8ca3d..11c2db6 100644 --- a/packages/veilid_support/pubspec.lock +++ b/packages/veilid_support/pubspec.lock @@ -726,7 +726,7 @@ packages: path: "../../../veilid/veilid-flutter" relative: true source: path - version: "0.4.3" + version: "0.4.4" vm_service: dependency: transitive description: From 4797184a1a8d8a44a0016b2ca0ec108f4157e152 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 19 Apr 2025 22:21:40 -0400 Subject: [PATCH 62/93] simplify reconciliation --- lib/chat/cubits/chat_component_cubit.dart | 13 +- .../reconciliation/author_input_queue.dart | 282 +++++++++++------- .../reconciliation/author_input_source.dart | 94 +++--- .../message_reconciliation.dart | 234 ++++++++++----- .../cubits/single_contact_messages_cubit.dart | 125 ++++---- .../lib/dht_support/src/dht_log/dht_log.dart | 8 +- .../src/dht_log/dht_log_cubit.dart | 26 +- .../dht_support/src/dht_log/dht_log_read.dart | 42 +-- .../src/dht_log/dht_log_write.dart | 4 +- .../dht_short_array_cubit.dart | 3 - .../dht_short_array/dht_short_array_read.dart | 17 +- .../src/interfaces/dht_random_read.dart | 9 +- 12 files changed, 512 insertions(+), 345 deletions(-) diff --git a/lib/chat/cubits/chat_component_cubit.dart b/lib/chat/cubits/chat_component_cubit.dart index 7ea9e95..6112384 100644 --- a/lib/chat/cubits/chat_component_cubit.dart +++ b/lib/chat/cubits/chat_component_cubit.dart @@ -15,6 +15,7 @@ import '../../account_manager/account_manager.dart'; import '../../contacts/contacts.dart'; import '../../conversation/conversation.dart'; import '../../proto/proto.dart' as proto; +import '../../tools/tools.dart'; import '../models/chat_component_state.dart'; import '../models/message_state.dart'; import '../models/window_state.dart'; @@ -383,13 +384,13 @@ class ChatComponentCubit extends Cubit { if (chatMessage == null) { continue; } - chatMessages.insert(0, chatMessage); if (!tsSet.add(chatMessage.id)) { - // ignore: avoid_print - print('duplicate id found: ${chatMessage.id}:\n' - 'Messages:\n${messagesState.window}\n' - 'ChatMessages:\n$chatMessages'); - assert(false, 'should not have duplicate id'); + log.error('duplicate id found: ${chatMessage.id}' + // '\nMessages:\n${messagesState.window}' + // '\nChatMessages:\n$chatMessages' + ); + } else { + chatMessages.insert(0, chatMessage); } } return currentState.copyWith( diff --git a/lib/chat/cubits/reconciliation/author_input_queue.dart b/lib/chat/cubits/reconciliation/author_input_queue.dart index b15d92c..73dddc6 100644 --- a/lib/chat/cubits/reconciliation/author_input_queue.dart +++ b/lib/chat/cubits/reconciliation/author_input_queue.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:math'; import 'package:veilid_support/veilid_support.dart'; import '../../../proto/proto.dart' as proto; @@ -6,106 +7,127 @@ import '../../../proto/proto.dart' as proto; import '../../../tools/tools.dart'; import 'author_input_source.dart'; import 'message_integrity.dart'; -import 'output_position.dart'; class AuthorInputQueue { AuthorInputQueue._({ required TypedKey author, required AuthorInputSource inputSource, - required OutputPosition? outputPosition, + required int inputPosition, + required proto.Message? previousMessage, required void Function(Object, StackTrace?) onError, required MessageIntegrity messageIntegrity, }) : _author = author, _onError = onError, _inputSource = inputSource, - _outputPosition = outputPosition, - _lastMessage = outputPosition?.message.content, + _previousMessage = previousMessage, _messageIntegrity = messageIntegrity, - _currentPosition = inputSource.currentWindow.last; + _inputPosition = inputPosition; static Future create({ required TypedKey author, required AuthorInputSource inputSource, - required OutputPosition? outputPosition, + required proto.Message? previousMessage, required void Function(Object, StackTrace?) onError, }) async { + // Get ending input position + final inputPosition = await inputSource.getTailPosition() - 1; + + // Create an input queue for the input source final queue = AuthorInputQueue._( author: author, inputSource: inputSource, - outputPosition: outputPosition, + inputPosition: inputPosition, + previousMessage: previousMessage, onError: onError, messageIntegrity: await MessageIntegrity.create(author: author)); - if (!await queue._findStartOfWork()) { + + // Rewind the queue's 'inputPosition' to the first unreconciled message + if (!await queue._rewindInputToAfterLastMessage()) { return null; } + return queue; } //////////////////////////////////////////////////////////////////////////// // Public interface - // Check if there are no messages left in this queue to reconcile - bool get isDone => _isDone; + /// Get the input source for this queue + AuthorInputSource get inputSource => _inputSource; - // Get the current message that needs reconciliation - proto.Message? get current => _currentMessage; - - // Get the earliest output position to start inserting - OutputPosition? get outputPosition => _outputPosition; - - // Get the author of this queue + /// Get the author of this queue TypedKey get author => _author; - // Remove a reconciled message and move to the next message - // Returns true if there is more work to do - Future consume() async { - if (_isDone) { + /// Get the current message that needs reconciliation + Future getCurrentMessage() async { + try { + // if we have a current message already, return it + if (_currentMessage != null) { + return _currentMessage; + } + + // Get the window + final currentWindow = await _updateWindow(clampInputPosition: false); + if (currentWindow == null) { + return null; + } + final currentElement = + currentWindow.elements[_inputPosition - currentWindow.firstPosition]; + return _currentMessage = currentElement.value; + // Catch everything so we can avoid ParallelWaitError + // ignore: avoid_catches_without_on_clauses + } catch (e, st) { + log.error('Exception getting current message: $e:\n$st\n'); + _currentMessage = null; + return null; + } + } + + /// Remove a reconciled message and move to the next message + /// Returns true if there is more work to do + Future advance() async { + final currentMessage = await getCurrentMessage(); + if (currentMessage == null) { return false; } - while (true) { - _lastMessage = _currentMessage; - _currentPosition++; + // Move current message to previous + _previousMessage = _currentMessage; + _currentMessage = null; + + while (true) { + // Advance to next position + _inputPosition++; // Get more window if we need to - if (!await _updateWindow()) { - // Window is not available so this queue can't work right now - _isDone = true; + final currentMessage = await getCurrentMessage(); + if (currentMessage == null) { return false; } - final nextMessage = _inputSource.currentWindow - .elements[_currentPosition - _inputSource.currentWindow.first]; - // Drop the 'offline' elements because we don't reconcile - // anything until it has been confirmed to be committed to the DHT - // if (nextMessage.isOffline) { - // continue; - // } - - if (_lastMessage != null) { + if (_previousMessage != null) { // Ensure the timestamp is not moving backward - if (nextMessage.value.timestamp < _lastMessage!.timestamp) { - log.warning('timestamp backward: ${nextMessage.value.timestamp}' - ' < ${_lastMessage!.timestamp}'); + if (currentMessage.timestamp < _previousMessage!.timestamp) { + log.warning('timestamp backward: ${currentMessage.timestamp}' + ' < ${_previousMessage!.timestamp}'); continue; } } // Verify the id chain for the message - final matchId = await _messageIntegrity.generateMessageId(_lastMessage); - if (matchId.compare(nextMessage.value.idBytes) != 0) { - log.warning( - 'id chain invalid: $matchId != ${nextMessage.value.idBytes}'); + final matchId = + await _messageIntegrity.generateMessageId(_previousMessage); + if (matchId.compare(currentMessage.idBytes) != 0) { + log.warning('id chain invalid: $matchId != ${currentMessage.idBytes}'); continue; } // Verify the signature for the message - if (!await _messageIntegrity.verifyMessage(nextMessage.value)) { - log.warning('invalid message signature: ${nextMessage.value}'); + if (!await _messageIntegrity.verifyMessage(currentMessage)) { + log.warning('invalid message signature: $currentMessage'); continue; } - _currentMessage = nextMessage.value; break; } return true; @@ -114,106 +136,166 @@ class AuthorInputQueue { //////////////////////////////////////////////////////////////////////////// // Internal implementation - // Walk backward from the tail of the input queue to find the first - // message newer than our last reconciled message from this author - // Returns false if no work is needed - Future _findStartOfWork() async { + /// Walk backward from the tail of the input queue to find the first + /// message newer than our last reconciled message from this author + /// Returns false if no work is needed + Future _rewindInputToAfterLastMessage() async { // Iterate windows over the inputSource + InputWindow? currentWindow; outer: while (true) { + // Get more window if we need to + currentWindow = await _updateWindow(clampInputPosition: true); + if (currentWindow == null) { + // Window is not available or things are empty so this + // queue can't work right now + return false; + } + // Iterate through current window backward - for (var i = _inputSource.currentWindow.elements.length - 1; - i >= 0 && _currentPosition >= 0; - i--, _currentPosition--) { - final elem = _inputSource.currentWindow.elements[i]; + for (var i = currentWindow.elements.length - 1; + i >= 0 && _inputPosition >= 0; + i--, _inputPosition--) { + final elem = currentWindow.elements[i]; // If we've found an input element that is older or same time as our // last reconciled message for this author, or we find the message // itself then we stop - if (_lastMessage != null) { + if (_previousMessage != null) { if (elem.value.authorUniqueIdBytes - .compare(_lastMessage!.authorUniqueIdBytes) == + .compare(_previousMessage!.authorUniqueIdBytes) == 0 || - elem.value.timestamp <= _lastMessage!.timestamp) { + elem.value.timestamp <= _previousMessage!.timestamp) { break outer; } } } // If we're at the beginning of the inputSource then we stop - if (_currentPosition < 0) { + if (_inputPosition < 0) { break; } - - // Get more window if we need to - if (!await _updateWindow()) { - // Window is not available or things are empty so this - // queue can't work right now - _isDone = true; - return false; - } } - // _currentPosition points to either before the input source starts + // _inputPosition points to either before the input source starts // or the position of the previous element. We still need to set the // _currentMessage to the previous element so consume() can compare // against it if we can. - if (_currentPosition >= 0) { - _currentMessage = _inputSource.currentWindow - .elements[_currentPosition - _inputSource.currentWindow.first].value; + if (_inputPosition >= 0) { + _currentMessage = currentWindow + .elements[_inputPosition - currentWindow.firstPosition].value; } - // After this consume(), the currentPosition and _currentMessage should + // After this advance(), the _inputPosition and _currentMessage should // be equal to the first message to process and the current window to - // process should not be empty - return consume(); + // process should not be empty if there is work to do + return advance(); } - // Slide the window toward the current position and load the batch around it - Future _updateWindow() async { + /// Slide the window toward the current position and load the batch around it + Future _updateWindow({required bool clampInputPosition}) async { + final inputTailPosition = await _inputSource.getTailPosition(); + if (inputTailPosition == 0) { + return null; + } + + // Handle out-of-range input position + if (clampInputPosition) { + _inputPosition = min(max(_inputPosition, 0), inputTailPosition - 1); + } else if (_inputPosition < 0 || _inputPosition >= inputTailPosition) { + return null; + } + // Check if we are still in the window - if (_currentPosition >= _inputSource.currentWindow.first && - _currentPosition <= _inputSource.currentWindow.last) { - return true; + final currentWindow = _currentWindow; + + int firstPosition; + int lastPosition; + if (currentWindow != null) { + firstPosition = currentWindow.firstPosition; + lastPosition = currentWindow.lastPosition; + + // Slide the window if we need to + if (_inputPosition >= firstPosition && _inputPosition <= lastPosition) { + return currentWindow; + } else if (_inputPosition < firstPosition) { + // Slide it backward, current position is now last + firstPosition = max((_inputPosition - _maxWindowLength) + 1, 0); + lastPosition = _inputPosition; + } else if (_inputPosition > lastPosition) { + // Slide it forward, current position is now first + firstPosition = _inputPosition; + lastPosition = + min((_inputPosition + _maxWindowLength) - 1, inputTailPosition - 1); + } + } else { + // need a new window, start with the input position at the end + lastPosition = _inputPosition; + firstPosition = max((_inputPosition - _maxWindowLength) + 1, 0); } // Get another input batch futher back - final avOk = - await _inputSource.updateWindow(_currentPosition, _maxWindowLength); + final avCurrentWindow = await _inputSource.getWindow( + firstPosition, lastPosition - firstPosition + 1); - final asErr = avOk.asError; + final asErr = avCurrentWindow.asError; if (asErr != null) { _onError(asErr.error, asErr.stackTrace); - return false; + _currentWindow = null; + return null; } - final asLoading = avOk.asLoading; + final asLoading = avCurrentWindow.asLoading; if (asLoading != null) { - // xxx: no need to block the cubit here for this - // xxx: might want to switch to a 'busy' state though - // xxx: to let the messages view show a spinner at the bottom - // xxx: while we reconcile... - // emit(const AsyncValue.loading()); - return false; + _currentWindow = null; + return null; } - return avOk.asData!.value; + + final nextWindow = avCurrentWindow.asData!.value; + if (nextWindow == null || nextWindow.length == 0) { + _currentWindow = null; + return null; + } + + // Handle out-of-range input position + // Doing this again because getWindow is allowed to return a smaller + // window than the one requested, possibly due to DHT consistency + // fluctuations and race conditions + if (clampInputPosition) { + _inputPosition = min(max(_inputPosition, nextWindow.firstPosition), + nextWindow.lastPosition); + } else if (_inputPosition < nextWindow.firstPosition || + _inputPosition > nextWindow.lastPosition) { + return null; + } + + return _currentWindow = nextWindow; } //////////////////////////////////////////////////////////////////////////// + /// The author of this messages in the input source final TypedKey _author; + + /// The input source we're pulling messages from final AuthorInputSource _inputSource; - final OutputPosition? _outputPosition; + + /// What to call if an error happens final void Function(Object, StackTrace?) _onError; + + /// The message integrity validator final MessageIntegrity _messageIntegrity; - // The last message we've consumed - proto.Message? _lastMessage; - // The current position in the input log that we are looking at - int _currentPosition; - // The current message we're looking at - proto.Message? _currentMessage; - // If we have reached the end - bool _isDone = false; + /// The last message we reconciled/output + proto.Message? _previousMessage; - // Desired maximum window length + /// The current message we're looking at + proto.Message? _currentMessage; + + /// The current position in the input source that we are looking at + int _inputPosition; + + /// The current input window from the InputSource; + InputWindow? _currentWindow; + + /// Desired maximum window length static const int _maxWindowLength = 256; } diff --git a/lib/chat/cubits/reconciliation/author_input_source.dart b/lib/chat/cubits/reconciliation/author_input_source.dart index 0bd1afb..f974dae 100644 --- a/lib/chat/cubits/reconciliation/author_input_source.dart +++ b/lib/chat/cubits/reconciliation/author_input_source.dart @@ -9,64 +9,68 @@ import '../../../proto/proto.dart' as proto; @immutable class InputWindow { - const InputWindow( - {required this.elements, required this.first, required this.last}); + const InputWindow({required this.elements, required this.firstPosition}) + : lastPosition = firstPosition + elements.length - 1, + isEmpty = elements.length == 0, + length = elements.length; + final IList> elements; - final int first; - final int last; + final int firstPosition; + final int lastPosition; + final bool isEmpty; + final int length; } class AuthorInputSource { - AuthorInputSource.fromCubit( - {required DHTLogStateData cubitState, - required this.cubit}) { - _currentWindow = InputWindow( - elements: cubitState.window, - first: (cubitState.windowTail - cubitState.window.length) % - cubitState.length, - last: (cubitState.windowTail - 1) % cubitState.length); - } + AuthorInputSource.fromDHTLog(DHTLog dhtLog) : _dhtLog = dhtLog; //////////////////////////////////////////////////////////////////////////// - InputWindow get currentWindow => _currentWindow; + Future getTailPosition() async => + _dhtLog.operate((reader) async => reader.length); - Future> updateWindow( - int currentPosition, int windowLength) async => - cubit.operate((reader) async { - // See if we're beyond the input source - if (currentPosition < 0 || currentPosition >= reader.length) { - return const AsyncValue.data(false); - } - - // Slide the window if we need to - var first = _currentWindow.first; - var last = _currentWindow.last; - if (currentPosition < first) { - // Slide it backward, current position is now last - first = max((currentPosition - windowLength) + 1, 0); - last = currentPosition; - } else if (currentPosition > last) { - // Slide it forward, current position is now first - first = currentPosition; - last = min((currentPosition + windowLength) - 1, reader.length - 1); - } else { - return const AsyncValue.data(true); + Future> getWindow( + int startPosition, int windowLength) async => + _dhtLog.operate((reader) async { + // Don't allow negative length + if (windowLength <= 0) { + return const AsyncValue.data(null); } + // Trim if we're beyond input source + var endPosition = startPosition + windowLength - 1; + startPosition = max(startPosition, 0); + endPosition = max(endPosition, 0); // Get another input batch futher back - final nextWindow = await cubit.loadElementsFromReader( - reader, last + 1, (last + 1) - first); - if (nextWindow == null) { - return const AsyncValue.loading(); + try { + Set? offlinePositions; + if (_dhtLog.writer != null) { + offlinePositions = await reader.getOfflinePositions(); + } + + final messages = await reader.getRangeProtobuf( + proto.Message.fromBuffer, startPosition, + length: endPosition - startPosition + 1); + if (messages == null) { + return const AsyncValue.loading(); + } + + final elements = messages.indexed + .map((x) => OnlineElementState( + value: x.$2, + isOffline: offlinePositions?.contains(x.$1 + startPosition) ?? + false)) + .toIList(); + + final window = + InputWindow(elements: elements, firstPosition: startPosition); + + return AsyncValue.data(window); + } on Exception catch (e, st) { + return AsyncValue.error(e, st); } - _currentWindow = - InputWindow(elements: nextWindow, first: first, last: last); - return const AsyncValue.data(true); }); //////////////////////////////////////////////////////////////////////////// - final DHTLogCubit cubit; - - late InputWindow _currentWindow; + final DHTLog _dhtLog; } diff --git a/lib/chat/cubits/reconciliation/message_reconciliation.dart b/lib/chat/cubits/reconciliation/message_reconciliation.dart index 9b183b5..683b46d 100644 --- a/lib/chat/cubits/reconciliation/message_reconciliation.dart +++ b/lib/chat/cubits/reconciliation/message_reconciliation.dart @@ -20,66 +20,113 @@ class MessageReconciliation { //////////////////////////////////////////////////////////////////////////// - void reconcileMessages( - TypedKey author, - DHTLogStateData inputMessagesCubitState, - DHTLogCubit inputMessagesCubit) { - if (inputMessagesCubitState.window.isEmpty) { - return; - } + void addInputSourceFromDHTLog(TypedKey author, DHTLog inputMessagesDHTLog) { + _inputSources[author] = AuthorInputSource.fromDHTLog(inputMessagesDHTLog); + } - _inputSources[author] = AuthorInputSource.fromCubit( - cubitState: inputMessagesCubitState, cubit: inputMessagesCubit); + void reconcileMessages(TypedKey? author) { + // xxx: can we use 'author' here to optimize _updateAuthorInputQueues? singleFuture(this, onError: _onError, () async { - // Take entire list of input sources we have currently and process them - final inputSources = _inputSources; - _inputSources = {}; - - final inputFuts = >[]; - for (final kv in inputSources.entries) { - final author = kv.key; - final inputSource = kv.value; - inputFuts - .add(_enqueueAuthorInput(author: author, inputSource: inputSource)); - } - final inputQueues = await inputFuts.wait; - - // Make this safe to cast by removing inputs that were rejected or empty - inputQueues.removeNulls(); + // Update queues + final activeInputQueues = await _updateAuthorInputQueues(); // Process all input queues together await _outputCubit .operate((reconciledArray) async => _reconcileInputQueues( reconciledArray: reconciledArray, - inputQueues: inputQueues.cast(), + activeInputQueues: activeInputQueues, )); }); } //////////////////////////////////////////////////////////////////////////// - // Set up a single author's message reconciliation - Future _enqueueAuthorInput( - {required TypedKey author, - required AuthorInputSource inputSource}) async { - try { - // Get the position of our most recent reconciled message from this author - final outputPosition = await _findLastOutputPosition(author: author); + // Prepare author input queues by removing dead ones and adding new ones + // Queues that are empty are not added until they have something in them + // Active input queues with a current message are returned in a list + Future> _updateAuthorInputQueues() async { + // Remove any dead input queues + final deadQueues = []; + for (final author in _inputQueues.keys) { + if (!_inputSources.containsKey(author)) { + deadQueues.add(author); + } + } + for (final author in deadQueues) { + _inputQueues.remove(author); + _outputPositions.remove(author); + } - // Find oldest message we have not yet reconciled - final inputQueue = await AuthorInputQueue.create( - author: author, - inputSource: inputSource, - outputPosition: outputPosition, - onError: _onError, - ); - return inputQueue; - // Catch everything so we can avoid ParallelWaitError - // ignore: avoid_catches_without_on_clauses - } catch (e, st) { - log.error('Exception enqueing author input: $e:\n$st\n'); - return null; + await _outputCubit.operate((outputArray) async { + final dws = DelayedWaitSet(); + + for (final kv in _inputSources.entries) { + final author = kv.key; + final inputSource = kv.value; + + final iqExisting = _inputQueues[author]; + if (iqExisting == null || iqExisting.inputSource != inputSource) { + dws.add((_) async { + try { + await _enqueueAuthorInput( + author: author, + inputSource: inputSource, + outputArray: outputArray); + // Catch everything so we can avoid ParallelWaitError + // ignore: avoid_catches_without_on_clauses + } catch (e, st) { + log.error('Exception updating author input queue: $e:\n$st\n'); + _inputQueues.remove(author); + _outputPositions.remove(author); + } + }); + } + } + + await dws(); + }); + + // Get the active input queues + final activeInputQueues = await _inputQueues.entries + .map((entry) async { + if (await entry.value.getCurrentMessage() != null) { + return entry.value; + } else { + return null; + } + }) + .toList() + .wait + ..removeNulls(); + + return activeInputQueues.cast(); + } + + // Set up a single author's message reconciliation + Future _enqueueAuthorInput( + {required TypedKey author, + required AuthorInputSource inputSource, + required TableDBArrayProtobuf + outputArray}) async { + // Get the position of our most recent reconciled message from this author + final outputPosition = + await _findLastOutputPosition(author: author, outputArray: outputArray); + + // Find oldest message we have not yet reconciled + final inputQueue = await AuthorInputQueue.create( + author: author, + inputSource: inputSource, + previousMessage: outputPosition?.message.content, + onError: _onError, + ); + + if (inputQueue != null) { + _inputQueues[author] = inputQueue; + _outputPositions[author] = outputPosition; + } else { + _inputQueues.remove(author); + _outputPositions.remove(author); } } @@ -87,36 +134,38 @@ class MessageReconciliation { // XXX: For a group chat, this should find when the author // was added to the membership so we don't just go back in time forever Future _findLastOutputPosition( - {required TypedKey author}) async => - _outputCubit.operate((arr) async { - var pos = arr.length - 1; - while (pos >= 0) { - final message = await arr.get(pos); - if (message.content.author.toVeilid() == author) { - return OutputPosition(message, pos); - } - pos--; - } - return null; - }); + {required TypedKey author, + required TableDBArrayProtobuf + outputArray}) async { + var pos = outputArray.length - 1; + while (pos >= 0) { + final message = await outputArray.get(pos); + if (message.content.author.toVeilid() == author) { + return OutputPosition(message, pos); + } + pos--; + } + return null; + } // Process a list of author input queues and insert their messages // into the output array, performing validation steps along the way Future _reconcileInputQueues({ required TableDBArrayProtobuf reconciledArray, - required List inputQueues, + required List activeInputQueues, }) async { - // Ensure queues all have something to do - inputQueues.removeWhere((q) => q.isDone); - if (inputQueues.isEmpty) { + // Ensure we have active queues to process + if (activeInputQueues.isEmpty) { return; } // Sort queues from earliest to latest and then by author // to ensure a deterministic insert order - inputQueues.sort((a, b) { - final acmp = a.outputPosition?.pos ?? -1; - final bcmp = b.outputPosition?.pos ?? -1; + activeInputQueues.sort((a, b) { + final aout = _outputPositions[a.author]; + final bout = _outputPositions[b.author]; + final acmp = aout?.pos ?? -1; + final bcmp = bout?.pos ?? -1; if (acmp == bcmp) { return a.author.toString().compareTo(b.author.toString()); } @@ -124,21 +173,28 @@ class MessageReconciliation { }); // Start at the earliest position we know about in all the queues - var currentOutputPosition = inputQueues.first.outputPosition; + var currentOutputPosition = + _outputPositions[activeInputQueues.first.author]; final toInsert = SortedList(proto.MessageExt.compareTimestamp); - while (inputQueues.isNotEmpty) { + while (activeInputQueues.isNotEmpty) { // Get up to '_maxReconcileChunk' of the items from the queues // that we can insert at this location bool added; do { added = false; - var someQueueEmpty = false; - for (final inputQueue in inputQueues) { - final inputCurrent = inputQueue.current!; + + final emptyQueues = {}; + for (final inputQueue in activeInputQueues) { + final inputCurrent = await inputQueue.getCurrentMessage(); + if (inputCurrent == null) { + log.error('Active input queue did not have a current message: ' + '${inputQueue.author}'); + continue; + } if (currentOutputPosition == null || inputCurrent.timestamp < currentOutputPosition.message.content.timestamp) { @@ -146,16 +202,14 @@ class MessageReconciliation { added = true; // Advance this queue - if (!await inputQueue.consume()) { - // Queue is empty now, run a queue purge - someQueueEmpty = true; + if (!await inputQueue.advance()) { + // Mark queue as empty for removal + emptyQueues.add(inputQueue); } } } - // Remove empty queues now that we're done iterating - if (someQueueEmpty) { - inputQueues.removeWhere((q) => q.isDone); - } + // Remove finished queues now that we're done iterating + activeInputQueues.removeWhere(emptyQueues.contains); if (toInsert.length >= _maxReconcileChunk) { break; @@ -173,9 +227,27 @@ class MessageReconciliation { ..content = message) .toList(); - await reconciledArray.insertAll( - currentOutputPosition?.pos ?? reconciledArray.length, - reconciledInserts); + // Figure out where to insert the reconciled messages + final insertPos = currentOutputPosition?.pos ?? reconciledArray.length; + + // Insert them all at once + await reconciledArray.insertAll(insertPos, reconciledInserts); + + // Update output positions for input queues + final updatePositions = _outputPositions.keys.toSet(); + var outputPos = insertPos + reconciledInserts.length; + for (final inserted in reconciledInserts.reversed) { + if (updatePositions.isEmpty) { + // Last seen positions already recorded for each active author + break; + } + outputPos--; + final author = inserted.content.author.toVeilid(); + if (updatePositions.contains(author)) { + _outputPositions[author] = OutputPosition(inserted, outputPos); + updatePositions.remove(author); + } + } toInsert.clear(); } else { @@ -195,7 +267,9 @@ class MessageReconciliation { //////////////////////////////////////////////////////////////////////////// - Map _inputSources = {}; + final Map _inputSources = {}; + final Map _inputQueues = {}; + final Map _outputPositions = {}; final TableDBArrayProtobufCubit _outputCubit; final void Function(Object, StackTrace?) _onError; diff --git a/lib/chat/cubits/single_contact_messages_cubit.dart b/lib/chat/cubits/single_contact_messages_cubit.dart index 559eae2..66032f6 100644 --- a/lib/chat/cubits/single_contact_messages_cubit.dart +++ b/lib/chat/cubits/single_contact_messages_cubit.dart @@ -77,8 +77,8 @@ class SingleContactMessagesCubit extends Cubit { await _sentSubscription?.cancel(); await _rcvdSubscription?.cancel(); await _reconciledSubscription?.cancel(); - await _sentMessagesCubit?.close(); - await _rcvdMessagesCubit?.close(); + await _sentMessagesDHTLog?.close(); + await _rcvdMessagesDHTLog?.close(); await _reconciledMessagesCubit?.close(); // If the local conversation record is gone, then delete the reconciled @@ -111,10 +111,10 @@ class SingleContactMessagesCubit extends Cubit { await _initReconciledMessagesCubit(); // Local messages key - await _initSentMessagesCubit(); + await _initSentMessagesDHTLog(); // Remote messages key - await _initRcvdMessagesCubit(); + await _initRcvdMessagesDHTLog(); // Command execution background process _commandRunnerFut = Future.delayed(Duration.zero, _commandRunner); @@ -129,39 +129,40 @@ class SingleContactMessagesCubit extends Cubit { } // Open local messages key - Future _initSentMessagesCubit() async { + Future _initSentMessagesDHTLog() async { final writer = _accountInfo.identityWriter; - _sentMessagesCubit = DHTLogCubit( - open: () async => DHTLog.openWrite(_localMessagesRecordKey, writer, + final sentMessagesDHTLog = + await DHTLog.openWrite(_localMessagesRecordKey, writer, debugName: 'SingleContactMessagesCubit::_initSentMessagesCubit::' 'SentMessages', parent: _localConversationRecordKey, - crypto: _conversationCrypto), - decodeElement: proto.Message.fromBuffer); - _sentSubscription = - _sentMessagesCubit!.stream.listen(_updateSentMessagesState); - _updateSentMessagesState(_sentMessagesCubit!.state); + crypto: _conversationCrypto); + _sentSubscription = await sentMessagesDHTLog.listen(_updateSentMessages); + + _sentMessagesDHTLog = sentMessagesDHTLog; + _reconciliation.addInputSourceFromDHTLog( + _accountInfo.identityTypedPublicKey, sentMessagesDHTLog); } // Open remote messages key - Future _initRcvdMessagesCubit() async { + Future _initRcvdMessagesDHTLog() async { // Don't bother if we don't have a remote messages record key yet if (_remoteMessagesRecordKey == null) { return; } // Open new cubit if one is desired - _rcvdMessagesCubit = DHTLogCubit( - open: () async => DHTLog.openRead(_remoteMessagesRecordKey!, - debugName: 'SingleContactMessagesCubit::_initRcvdMessagesCubit::' - 'RcvdMessages', - parent: _remoteConversationRecordKey, - crypto: _conversationCrypto), - decodeElement: proto.Message.fromBuffer); - _rcvdSubscription = - _rcvdMessagesCubit!.stream.listen(_updateRcvdMessagesState); - _updateRcvdMessagesState(_rcvdMessagesCubit!.state); + final rcvdMessagesDHTLog = await DHTLog.openRead(_remoteMessagesRecordKey!, + debugName: 'SingleContactMessagesCubit::_initRcvdMessagesCubit::' + 'RcvdMessages', + parent: _remoteConversationRecordKey, + crypto: _conversationCrypto); + _rcvdSubscription = await rcvdMessagesDHTLog.listen(_updateRcvdMessages); + + _rcvdMessagesDHTLog = rcvdMessagesDHTLog; + _reconciliation.addInputSourceFromDHTLog( + _remoteIdentityPublicKey, rcvdMessagesDHTLog); } Future updateRemoteMessagesRecordKey( @@ -175,17 +176,17 @@ class SingleContactMessagesCubit extends Cubit { return; } - // Close existing cubit if we have one - final rcvdMessagesCubit = _rcvdMessagesCubit; - _rcvdMessagesCubit = null; + // Close existing DHTLog if we have one + final rcvdMessagesDHTLog = _rcvdMessagesDHTLog; + _rcvdMessagesDHTLog = null; _remoteMessagesRecordKey = null; await _rcvdSubscription?.cancel(); _rcvdSubscription = null; - await rcvdMessagesCubit?.close(); + await rcvdMessagesDHTLog?.close(); - // Init the new cubit if we should + // Init the new DHTLog if we should _remoteMessagesRecordKey = remoteMessagesRecordKey; - await _initRcvdMessagesCubit(); + await _initRcvdMessagesDHTLog(); }); } @@ -275,30 +276,15 @@ class SingleContactMessagesCubit extends Cubit { //////////////////////////////////////////////////////////////////////////// // Internal implementation - // Called when the sent messages cubit gets a change + // Called when the sent messages DHTLog gets a change // This will re-render when messages are sent from another machine - void _updateSentMessagesState(DHTLogBusyState avmessages) { - final sentMessages = avmessages.state.asData?.value; - if (sentMessages == null) { - return; - } - - _reconciliation.reconcileMessages( - _accountInfo.identityTypedPublicKey, sentMessages, _sentMessagesCubit!); - - // Update the view - _renderState(); + void _updateSentMessages(DHTLogUpdate upd) { + _reconciliation.reconcileMessages(_accountInfo.identityTypedPublicKey); } - // Called when the received messages cubit gets a change - void _updateRcvdMessagesState(DHTLogBusyState avmessages) { - final rcvdMessages = avmessages.state.asData?.value; - if (rcvdMessages == null) { - return; - } - - _reconciliation.reconcileMessages( - _remoteIdentityPublicKey, rcvdMessages, _rcvdMessagesCubit!); + // Called when the received messages DHTLog gets a change + void _updateRcvdMessages(DHTLogUpdate upd) { + _reconciliation.reconcileMessages(_remoteIdentityPublicKey); } // Called when the reconciled messages window gets a change @@ -327,7 +313,7 @@ class SingleContactMessagesCubit extends Cubit { // _renderState(); try { - await _sentMessagesCubit!.operateAppendEventual((writer) async { + await _sentMessagesDHTLog!.operateAppendEventual((writer) async { // Get the previous message if we have one var previousMessage = writer.length == 0 ? null @@ -357,16 +343,17 @@ class SingleContactMessagesCubit extends Cubit { // Produce a state for this cubit from the input cubits and queues void _renderState() { - // Get all reconciled messages + // Get all reconciled messages in the cubit window final reconciledMessages = _reconciledMessagesCubit?.state.state.asData?.value; - // Get all sent messages - final sentMessages = _sentMessagesCubit?.state.state.asData?.value; + + // Get all sent messages that are still offline + //final sentMessages = _sentMessagesDHTLog. //Get all items in the unsent queue //final unsentMessages = _unsentMessagesQueue.queue; // If we aren't ready to render a state, say we're loading - if (reconciledMessages == null || sentMessages == null) { + if (reconciledMessages == null) { emit(const AsyncLoading()); return; } @@ -377,11 +364,11 @@ class SingleContactMessagesCubit extends Cubit { // keyMapper: (x) => x.content.authorUniqueIdString, // values: reconciledMessages.windowElements, // ); - final sentMessagesMap = - IMap>.fromValues( - keyMapper: (x) => x.value.authorUniqueIdString, - values: sentMessages.window, - ); + // final sentMessagesMap = + // IMap>.fromValues( + // keyMapper: (x) => x.value.authorUniqueIdString, + // values: sentMessages.window, + // ); // final unsentMessagesMap = IMap.fromValues( // keyMapper: (x) => x.authorUniqueIdString, // values: unsentMessages, @@ -393,10 +380,12 @@ class SingleContactMessagesCubit extends Cubit { final isLocal = m.content.author.toVeilid() == _accountInfo.identityTypedPublicKey; final reconciledTimestamp = Timestamp.fromInt64(m.reconciledTime); - final sm = - isLocal ? sentMessagesMap[m.content.authorUniqueIdString] : null; - final sent = isLocal && sm != null; - final sentOffline = isLocal && sm != null && sm.isOffline; + //final sm = + //isLocal ? sentMessagesMap[m.content.authorUniqueIdString] : null; + //final sent = isLocal && sm != null; + //final sentOffline = isLocal && sm != null && sm.isOffline; + final sent = isLocal; + final sentOffline = false; // renderedElements.add(RenderStateElement( message: m.content, @@ -491,16 +480,16 @@ class SingleContactMessagesCubit extends Cubit { late final VeilidCrypto _conversationCrypto; late final MessageIntegrity _senderMessageIntegrity; - DHTLogCubit? _sentMessagesCubit; - DHTLogCubit? _rcvdMessagesCubit; + DHTLog? _sentMessagesDHTLog; + DHTLog? _rcvdMessagesDHTLog; TableDBArrayProtobufCubit? _reconciledMessagesCubit; late final MessageReconciliation _reconciliation; late final PersistentQueue _unsentMessagesQueue; // IList _sendingMessages = const IList.empty(); - StreamSubscription>? _sentSubscription; - StreamSubscription>? _rcvdSubscription; + StreamSubscription? _sentSubscription; + StreamSubscription? _rcvdSubscription; StreamSubscription>? _reconciledSubscription; final StreamController Function()> _commandController; diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart index 1d3fb89..2687bdc 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart @@ -213,7 +213,7 @@ class DHTLog implements DHTDeleteable { /// Runs a closure allowing read-only access to the log Future operate(Future Function(DHTLogReadOperations) closure) async { if (!isOpen) { - throw StateError('log is not open"'); + throw StateError('log is not open'); } return _spine.operate((spine) async { @@ -230,7 +230,7 @@ class DHTLog implements DHTDeleteable { Future operateAppend( Future Function(DHTLogWriteOperations) closure) async { if (!isOpen) { - throw StateError('log is not open"'); + throw StateError('log is not open'); } return _spine.operateAppend((spine) async { @@ -249,7 +249,7 @@ class DHTLog implements DHTDeleteable { Future Function(DHTLogWriteOperations) closure, {Duration? timeout}) async { if (!isOpen) { - throw StateError('log is not open"'); + throw StateError('log is not open'); } return _spine.operateAppendEventual((spine) async { @@ -264,7 +264,7 @@ class DHTLog implements DHTDeleteable { void Function(DHTLogUpdate) onChanged, ) { if (!isOpen) { - throw StateError('log is not open"'); + throw StateError('log is not open'); } return _listenMutex.protect(() async { diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_cubit.dart index 492312f..a7884f9 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_cubit.dart @@ -112,33 +112,34 @@ class DHTLogCubit extends Cubit> Future _refreshInner(void Function(AsyncValue>) emit, {bool forceRefresh = false}) async { late final int length; - final window = await _log.operate((reader) async { + final windowElements = await _log.operate((reader) async { length = reader.length; - return loadElementsFromReader(reader, _windowTail, _windowSize); + return _loadElementsFromReader(reader, _windowTail, _windowSize); }); - if (window == null) { + if (windowElements == null) { setWantsRefresh(); return; } + emit(AsyncValue.data(DHTLogStateData( length: length, - window: window, - windowTail: _windowTail, - windowSize: _windowSize, + window: windowElements.$2, + windowTail: windowElements.$1 + windowElements.$2.length, + windowSize: windowElements.$2.length, follow: _follow))); setRefreshed(); } // Tail is one past the last element to load - Future>?> loadElementsFromReader( + Future<(int, IList>)?> _loadElementsFromReader( DHTLogReadOperations reader, int tail, int count, {bool forceRefresh = false}) async { final length = reader.length; - if (length == 0) { - return const IList.empty(); - } final end = ((tail - 1) % length) + 1; final start = (count < end) ? end - count : 0; + if (length == 0) { + return (start, IList>.empty()); + } // If this is writeable get the offline positions Set? offlinePositions; @@ -154,8 +155,11 @@ class DHTLogCubit extends Cubit> value: _decodeElement(x.$2), isOffline: offlinePositions?.contains(x.$1) ?? false)) .toIList(); + if (allItems == null) { + return null; + } - return allItems; + return (start, allItems); } void _update(DHTLogUpdate upd) { diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_read.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_read.dart index d8634c6..3ebb2b8 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_read.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_read.dart @@ -21,8 +21,14 @@ class _DHTLogRead implements DHTLogReadOperations { return null; } - return lookup.scope((sa) => - sa.operate((read) => read.get(lookup.pos, forceRefresh: forceRefresh))); + return lookup.scope((sa) => sa.operate((read) async { + if (lookup.pos >= read.length) { + veilidLoggy.error('DHTLog shortarray read @ ${lookup.pos}' + ' >= length ${read.length}'); + return null; + } + return read.get(lookup.pos, forceRefresh: forceRefresh); + })); } (int, int) _clampStartLen(int start, int? len) { @@ -49,7 +55,7 @@ class _DHTLogRead implements DHTLogReadOperations { .slices(kMaxDHTConcurrency) .map((chunk) => chunk.map((pos) async { try { - return get(pos + start, forceRefresh: forceRefresh); + return await get(pos + start, forceRefresh: forceRefresh); // Need some way to debug ParallelWaitError // ignore: avoid_catches_without_on_clauses } catch (e, st) { @@ -59,36 +65,42 @@ class _DHTLogRead implements DHTLogReadOperations { })); for (final chunk in chunks) { - final elems = await chunk.wait; + var elems = await chunk.wait; - // If any element was unavailable, return null - if (elems.contains(null)) { - return null; + // Return only the first contiguous range, anything else is garbage + // due to a representational error in the head or shortarray legnth + final nullPos = elems.indexOf(null); + if (nullPos != -1) { + elems = elems.sublist(0, nullPos); } + out.addAll(elems.cast()); + + if (nullPos != -1) { + break; + } } return out; } @override - Future?> getOfflinePositions() async { + Future> getOfflinePositions() async { final positionOffline = {}; // Iterate positions backward from most recent for (var pos = _spine.length - 1; pos >= 0; pos--) { + // Get position final lookup = await _spine.lookupPosition(pos); + // If position doesn't exist then it definitely wasn't written to offline if (lookup == null) { - return null; + continue; } // Check each segment for offline positions var foundOffline = false; - final success = await lookup.scope((sa) => sa.operate((read) async { + await lookup.scope((sa) => sa.operate((read) async { final segmentOffline = await read.getOfflinePositions(); - if (segmentOffline == null) { - return false; - } // For each shortarray segment go through their segment positions // in reverse order and see if they are offline @@ -102,11 +114,7 @@ class _DHTLogRead implements DHTLogReadOperations { foundOffline = true; } } - return true; })); - if (!success) { - return null; - } // If we found nothing offline in this segment then we can stop if (!foundOffline) { break; diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart index 8d34280..397a1dc 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart @@ -107,9 +107,7 @@ class _DHTLogWrite extends _DHTLogRead implements DHTLogWriteOperations { await write.clear(); } else if (lookup.pos != write.length) { // We should always be appending at the length - throw DHTExceptionInvalidData( - '_DHTLogWrite::add lookup.pos=${lookup.pos} ' - 'write.length=${write.length}'); + await write.truncate(lookup.pos); } return write.add(value); })); diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart index 6ff6d95..246a990 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_cubit.dart @@ -62,9 +62,6 @@ class DHTShortArrayCubit extends Cubit> Set? offlinePositions; if (_shortArray.writer != null) { offlinePositions = await reader.getOfflinePositions(); - if (offlinePositions == null) { - return null; - } } // Get the items diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_read.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_read.dart index 747a892..eeb9648 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_read.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_read.dart @@ -62,7 +62,7 @@ class _DHTShortArrayRead implements DHTShortArrayReadOperations { .slices(kMaxDHTConcurrency) .map((chunk) => chunk.map((pos) async { try { - return get(pos + start, forceRefresh: forceRefresh); + return await get(pos + start, forceRefresh: forceRefresh); // Need some way to debug ParallelWaitError // ignore: avoid_catches_without_on_clauses } catch (e, st) { @@ -72,13 +72,20 @@ class _DHTShortArrayRead implements DHTShortArrayReadOperations { })); for (final chunk in chunks) { - final elems = await chunk.wait; + var elems = await chunk.wait; - // If any element was unavailable, return null - if (elems.contains(null)) { - return null; + // Return only the first contiguous range, anything else is garbage + // due to a representational error in the head or shortarray legnth + final nullPos = elems.indexOf(null); + if (nullPos != -1) { + elems = elems.sublist(0, nullPos); } + out.addAll(elems.cast()); + + if (nullPos != -1) { + break; + } } return out; diff --git a/packages/veilid_support/lib/dht_support/src/interfaces/dht_random_read.dart b/packages/veilid_support/lib/dht_support/src/interfaces/dht_random_read.dart index 0547332..d361757 100644 --- a/packages/veilid_support/lib/dht_support/src/interfaces/dht_random_read.dart +++ b/packages/veilid_support/lib/dht_support/src/interfaces/dht_random_read.dart @@ -14,19 +14,22 @@ abstract class DHTRandomRead { /// is specified, the network will always be checked for newer values /// rather than returning the existing locally stored copy of the elements. /// Throws an IndexError if the 'pos' is not within the length - /// of the container. + /// of the container. May return null if the item is not available at this + /// time. Future get(int pos, {bool forceRefresh = false}); /// Return a list of a range of items in the DHTArray. If 'forceRefresh' /// is specified, the network will always be checked for newer values /// rather than returning the existing locally stored copy of the elements. /// Throws an IndexError if either 'start' or '(start+length)' is not within - /// the length of the container. + /// the length of the container. May return fewer items than the length + /// expected if the requested items are not available, but will always + /// return a contiguous range starting at 'start'. Future?> getRange(int start, {int? length, bool forceRefresh = false}); /// Get a list of the positions that were written offline and not flushed yet - Future?> getOfflinePositions(); + Future> getOfflinePositions(); } extension DHTRandomReadExt on DHTRandomRead { From 3fabf602cd93d4adc59d01d7b2586916ff9a5dc0 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 21 Apr 2025 13:39:57 -0400 Subject: [PATCH 63/93] fix developer pane crash --- lib/veilid_processor/views/developer.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/veilid_processor/views/developer.dart b/lib/veilid_processor/views/developer.dart index 08463fb..0bfcc0a 100644 --- a/lib/veilid_processor/views/developer.dart +++ b/lib/veilid_processor/views/developer.dart @@ -65,8 +65,9 @@ class _DeveloperPageState extends State { } void _debugOut(String out) { + final sanitizedOut = out.replaceAll('\uFFFD', ''); final pen = AnsiPen()..cyan(bold: true); - final colorOut = pen(out); + final colorOut = pen(sanitizedOut); debugPrint(colorOut); globalDebugTerminal.write(colorOut.replaceAll('\n', '\r\n')); } From 933a22122acda8aa233063850bd2f118864cce0d Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 21 Apr 2025 15:23:36 -0400 Subject: [PATCH 64/93] attempt to deal with focus problem in developer page --- lib/veilid_processor/views/developer.dart | 29 +++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/veilid_processor/views/developer.dart b/lib/veilid_processor/views/developer.dart index 0bfcc0a..e329d47 100644 --- a/lib/veilid_processor/views/developer.dart +++ b/lib/veilid_processor/views/developer.dart @@ -21,7 +21,7 @@ import '../../tools/tools.dart'; import 'history_text_editing_controller.dart'; final globalDebugTerminal = Terminal( - maxLines: 50000, + maxLines: 10000, ); const kDefaultTerminalStyle = TerminalStyle( @@ -187,6 +187,15 @@ class _DeveloperPageState extends State { } } + Future _onSubmitCommand(String debugCommand) async { + final ok = await _sendDebugCommand(debugCommand); + if (ok) { + setState(() { + _historyController.submit(debugCommand); + }); + } + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -282,16 +291,17 @@ class _DeveloperPageState extends State { textStyle: kDefaultTerminalStyle, controller: _terminalController, keyboardType: TextInputType.none, - //autofocus: true, backgroundOpacity: _showEllet ? 0.75 : 1.0, onSecondaryTapDown: (details, offset) async { await copySelection(context); }) ]).expanded(), - TextField( + TextFormField( enabled: !_busy, + autofocus: true, controller: _historyController.controller, focusNode: _historyController.focusNode, + textInputAction: TextInputAction.send, onTapOutside: (event) { FocusManager.instance.primaryFocus?.unfocus(); }, @@ -323,7 +333,7 @@ class _DeveloperPageState extends State { final debugCommand = _historyController.controller.text; _historyController.controller.clear(); - await _sendDebugCommand(debugCommand); + await _onSubmitCommand(debugCommand); }, )), onChanged: (_) { @@ -334,17 +344,12 @@ class _DeveloperPageState extends State { _historyController.controller.clearComposing(); // don't give up focus though }, - onSubmitted: (debugCommand) async { + onFieldSubmitted: (debugCommand) async { if (debugCommand.isEmpty) { return; } - - final ok = await _sendDebugCommand(debugCommand); - if (ok) { - setState(() { - _historyController.submit(debugCommand); - }); - } + await _onSubmitCommand(debugCommand); + _historyController.focusNode.requestFocus(); }, ).paddingAll(4) ]))); From 8194a79ce4e2431968c13c21beb78ce9de23c548 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Fri, 25 Apr 2025 17:13:58 -0400 Subject: [PATCH 65/93] update handling of nulls in inspect results --- .../src/dht_record/dht_record.dart | 4 +-- .../src/dht_record/dht_record_pool.dart | 4 +-- .../src/dht_record/extensions.dart | 20 ++++++++----- .../dht_short_array/dht_short_array_head.dart | 30 +++++++++---------- .../dht_short_array_write.dart | 4 +-- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart index 4e632fc..d632b58 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart @@ -511,7 +511,7 @@ class DHTRecord implements DHTDeleteable { key, subkeys: [ValueSubkeyRange.single(subkey)], ); - return rr.localSeqs.firstOrNull ?? emptySeq; + return rr.localSeqs.firstOrNull; } void _addValueChange( @@ -566,6 +566,4 @@ class DHTRecord implements DHTDeleteable { int _openCount; StreamController? _watchController; _WatchState? _watchState; - - static const int emptySeq = 0xFFFFFFFF; } diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart index e3c9abe..3ee9adc 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart @@ -875,7 +875,7 @@ class DHTRecordPool with TableDBBackedJson { if (fsc == null) { return null; } - final newerSubkeys = currentReport.newerSubkeys; + final newerSubkeys = currentReport.newerOnlineSubkeys; final valueData = await dhtctx.getDHTValue(openedRecordKey, fsc.subkey, forceRefresh: true); @@ -887,7 +887,7 @@ class DHTRecordPool with TableDBBackedJson { log('inspect returned a newer seq than get: ${valueData.seq} < $fsc'); } - if (valueData.seq > fsc.oldSeq && valueData.seq != DHTRecord.emptySeq) { + if (fsc.oldSeq == null || valueData.seq > fsc.oldSeq!) { processRemoteValueChange(VeilidUpdateValueChange( key: openedRecordKey, subkeys: newerSubkeys, diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/extensions.dart b/packages/veilid_support/lib/dht_support/src/dht_record/extensions.dart index e62403e..b0da9e3 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/extensions.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/extensions.dart @@ -1,15 +1,14 @@ import 'package:veilid/veilid.dart'; -import 'dht_record_pool.dart'; class DHTSeqChange { const DHTSeqChange(this.subkey, this.oldSeq, this.newSeq); final int subkey; - final int oldSeq; + final int? oldSeq; final int newSeq; } extension DHTReportReportExt on DHTRecordReport { - List get newerSubkeys { + List get newerOnlineSubkeys { if (networkSeqs.isEmpty || localSeqs.isEmpty || subkeys.isEmpty) { return []; } @@ -19,8 +18,10 @@ extension DHTReportReportExt on DHTRecordReport { var i = 0; for (final skr in subkeys) { for (var sk = skr.low; sk <= skr.high; sk++) { - if (networkSeqs[i] > localSeqs[i] && - networkSeqs[i] != DHTRecord.emptySeq) { + final nseq = networkSeqs[i]; + final lseq = localSeqs[i]; + + if (nseq != null && (lseq == null || nseq > lseq)) { if (currentSubkeys.isNotEmpty && currentSubkeys.last.high == (sk - 1)) { currentSubkeys.add(ValueSubkeyRange( @@ -29,6 +30,7 @@ extension DHTReportReportExt on DHTRecordReport { currentSubkeys.add(ValueSubkeyRange.single(sk)); } } + i++; } } @@ -44,9 +46,11 @@ extension DHTReportReportExt on DHTRecordReport { var i = 0; for (final skr in subkeys) { for (var sk = skr.low; sk <= skr.high; sk++) { - if (networkSeqs[i] > localSeqs[i] && - networkSeqs[i] != DHTRecord.emptySeq) { - return DHTSeqChange(sk, localSeqs[i], networkSeqs[i]); + final nseq = networkSeqs[i]; + final lseq = localSeqs[i]; + + if (nseq != null && (lseq == null || nseq > lseq)) { + return DHTSeqChange(sk, lseq, nseq); } i++; } diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart index b0cc41b..66b5baa 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart @@ -5,7 +5,7 @@ class DHTShortArrayHeadLookup { {required this.record, required this.recordSubkey, required this.seq}); final DHTRecord record; final int recordSubkey; - final int seq; + final int? seq; } class _DHTShortArrayHead { @@ -41,7 +41,7 @@ class _DHTShortArrayHead { final head = proto.DHTShortArray(); head.keys.addAll(_linkedRecords.map((lr) => lr.key.toProto())); head.index = List.of(_index); - head.seqs.addAll(_seqs); + head.seqs.addAll(_seqs.map((x) => x ?? 0xFFFFFFFF)); // Do not serialize free list, it gets recreated // Do not serialize local seqs, they are only locally relevant return head; @@ -70,10 +70,7 @@ class _DHTShortArrayHead { Future delete() => _headMutex.protect(_headRecord.delete); Future operate(Future Function(_DHTShortArrayHead) closure) async => - // ignore: prefer_expression_function_bodies - _headMutex.protect(() async { - return closure(this); - }); + _headMutex.protect(() async => closure(this)); Future operateWrite( Future Function(_DHTShortArrayHead) closure) async => @@ -115,7 +112,7 @@ class _DHTShortArrayHead { late List oldLinkedRecords; late List oldIndex; late List oldFree; - late List oldSeqs; + late List oldSeqs; late T out; try { @@ -197,7 +194,8 @@ class _DHTShortArrayHead { // Get the set of new linked keys and validate it final updatedLinkedKeys = head.keys.map((p) => p.toVeilid()).toList(); final updatedIndex = List.of(head.index); - final updatedSeqs = List.of(head.seqs); + final updatedSeqs = + List.of(head.seqs.map((x) => x == 0xFFFFFFFF ? null : x)); final updatedFree = _makeFreeList(updatedLinkedKeys, updatedIndex); // See which records are actually new @@ -333,7 +331,7 @@ class _DHTShortArrayHead { } Future lookupIndex(int idx, bool allowCreate) async { - final seq = idx < _seqs.length ? _seqs[idx] : DHTRecord.emptySeq; + final seq = idx < _seqs.length ? _seqs[idx] : null; final recordNumber = idx ~/ _stride; final record = await _getOrCreateLinkedRecord(recordNumber, allowCreate); final recordSubkey = (idx % _stride) + ((recordNumber == 0) ? 1 : 0); @@ -445,18 +443,18 @@ class _DHTShortArrayHead { // If our local sequence number is unknown or hasnt been written yet // then a normal DHT operation is going to pull from the network anyway - if (_localSeqs.length < idx || _localSeqs[idx] == DHTRecord.emptySeq) { + if (_localSeqs.length < idx || _localSeqs[idx] == null) { return false; } // If the remote sequence number record is unknown or hasnt been written // at this index yet, then we also do not refresh at this time as it // is the first time the index is being written to - if (_seqs.length < idx || _seqs[idx] == DHTRecord.emptySeq) { + if (_seqs.length < idx || _seqs[idx] == null) { return false; } - return _localSeqs[idx] < _seqs[idx]; + return _localSeqs[idx]! < _seqs[idx]!; } /// Update the sequence number for a particular index in @@ -466,12 +464,12 @@ class _DHTShortArrayHead { final idx = _index[pos]; while (_localSeqs.length <= idx) { - _localSeqs.add(DHTRecord.emptySeq); + _localSeqs.add(null); } _localSeqs[idx] = newSeq; if (write) { while (_seqs.length <= idx) { - _seqs.add(DHTRecord.emptySeq); + _seqs.add(null); } _seqs[idx] = newSeq; } @@ -555,7 +553,7 @@ class _DHTShortArrayHead { // The sequence numbers of each subkey. // Index is by subkey number not by element index. // (n-1 for head record and then the next n for linked records) - List _seqs; + List _seqs; // The local sequence numbers for each subkey. - List _localSeqs; + List _localSeqs; } diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart index 1705bc0..fa3b1c6 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart @@ -129,7 +129,7 @@ class _DHTShortArrayWrite extends _DHTShortArrayRead final outSeqNum = Output(); - final result = lookup.seq == DHTRecord.emptySeq + final result = lookup.seq == null ? null : await lookup.record.get(subkey: lookup.recordSubkey); @@ -163,7 +163,7 @@ class _DHTShortArrayWrite extends _DHTShortArrayRead final lookup = await _head.lookupPosition(pos, true); final outSeqNumRead = Output(); - final oldValue = lookup.seq == DHTRecord.emptySeq + final oldValue = lookup.seq == null ? null : await lookup.record .get(subkey: lookup.recordSubkey, outSeqNum: outSeqNumRead); From ae154f1bed097515023e0886c0c07cf6a835e017 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 12 May 2025 10:15:53 -0400 Subject: [PATCH 66/93] Dht perf --- CHANGELOG.md | 8 + .../reconciliation/author_input_queue.dart | 13 +- lib/veilid_processor/views/developer.dart | 17 +- packages/veilid_support/example/pubspec.lock | 8 + .../dht_support/src/dht_record/barrel.dart | 1 + .../src/dht_record/dht_record.dart | 301 +++++++++--------- .../src/dht_record/dht_record_pool.dart | 264 ++++++++------- packages/veilid_support/pubspec.lock | 8 + packages/veilid_support/pubspec.yaml | 1 + pubspec.lock | 8 + 10 files changed, 340 insertions(+), 289 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cd3873..9182300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## UNRELEASED ## + +- Fix reconciliation `advance()` +- Add `pool stats` command +- Fixed issue with Android 'back' button exiting the app (#331) +- Deprecated accounts no longer crash application at startup +- Simplify SingleContactMessagesCubit and MessageReconciliation + ## v0.4.7 ## - *Community Contributions* - Fix getting stuck on splash screen when veilid is already started @bmv437 / @bgrift diff --git a/lib/chat/cubits/reconciliation/author_input_queue.dart b/lib/chat/cubits/reconciliation/author_input_queue.dart index 73dddc6..9a65e82 100644 --- a/lib/chat/cubits/reconciliation/author_input_queue.dart +++ b/lib/chat/cubits/reconciliation/author_input_queue.dart @@ -83,16 +83,13 @@ class AuthorInputQueue { } } - /// Remove a reconciled message and move to the next message + /// Move the reconciliation cursor (_inputPosition) forward on the input + /// queue and tees up the next message for processing /// Returns true if there is more work to do + /// Returns false if there are no more messages to reconcile in this queue Future advance() async { - final currentMessage = await getCurrentMessage(); - if (currentMessage == null) { - return false; - } - // Move current message to previous - _previousMessage = _currentMessage; + _previousMessage = await getCurrentMessage(); _currentMessage = null; while (true) { @@ -178,7 +175,7 @@ class AuthorInputQueue { // _inputPosition points to either before the input source starts // or the position of the previous element. We still need to set the - // _currentMessage to the previous element so consume() can compare + // _currentMessage to the previous element so advance() can compare // against it if we can. if (_inputPosition >= 0) { _currentMessage = currentWindow diff --git a/lib/veilid_processor/views/developer.dart b/lib/veilid_processor/views/developer.dart index e329d47..d749a9c 100644 --- a/lib/veilid_processor/views/developer.dart +++ b/lib/veilid_processor/views/developer.dart @@ -98,6 +98,16 @@ class _DeveloperPageState extends State { return true; } + if (debugCommand == 'pool stats') { + try { + DHTRecordPool.instance.debugPrintStats(); + } on Exception catch (e, st) { + _debugOut('<<< ERROR\n$e\n<<< STACK\n$st'); + return false; + } + return true; + } + if (debugCommand.startsWith('change_log_ignore ')) { final args = debugCommand.split(' '); if (args.length < 3) { @@ -129,9 +139,10 @@ class _DeveloperPageState extends State { if (debugCommand == 'help') { out = 'VeilidChat Commands:\n' - ' pool allocations - List DHTRecordPool allocations\n' - ' pool opened - List opened DHTRecord instances' - ' from the pool\n' + ' pool \n' + ' allocations - List DHTRecordPool allocations\n' + ' opened - List opened DHTRecord instances\n' + ' stats - Dump DHTRecordPool statistics\n' ' change_log_ignore change the log' ' target ignore list for a tracing layer\n' ' targets to add to the ignore list can be separated by' diff --git a/packages/veilid_support/example/pubspec.lock b/packages/veilid_support/example/pubspec.lock index 2e87d8c..c40540e 100644 --- a/packages/veilid_support/example/pubspec.lock +++ b/packages/veilid_support/example/pubspec.lock @@ -258,6 +258,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + indent: + dependency: transitive + description: + name: indent + sha256: "819319a5c185f7fe412750c798953378b37a0d0d32564ce33e7c5acfd1372d2a" + url: "https://pub.dev" + source: hosted + version: "2.0.0" integration_test: dependency: "direct dev" description: flutter diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/barrel.dart b/packages/veilid_support/lib/dht_support/src/dht_record/barrel.dart index 06933be..2d7e677 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/barrel.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/barrel.dart @@ -1,3 +1,4 @@ export 'default_dht_record_cubit.dart'; export 'dht_record_cubit.dart'; export 'dht_record_pool.dart'; +export 'stats.dart'; diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart index d632b58..b9218e4 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart @@ -119,55 +119,56 @@ class DHTRecord implements DHTDeleteable { /// * 'outSeqNum' optionally returns the sequence number of the value being /// returned if one was returned. Future get( - {int subkey = -1, - VeilidCrypto? crypto, - DHTRecordRefreshMode refreshMode = DHTRecordRefreshMode.cached, - Output? outSeqNum}) async { - subkey = subkeyOrDefault(subkey); + {int subkey = -1, + VeilidCrypto? crypto, + DHTRecordRefreshMode refreshMode = DHTRecordRefreshMode.cached, + Output? outSeqNum}) async => + _wrapStats('get', () async { + subkey = subkeyOrDefault(subkey); - // Get the last sequence number if we need it - final lastSeq = - refreshMode._inspectLocal ? await _localSubkeySeq(subkey) : null; + // Get the last sequence number if we need it + final lastSeq = + refreshMode._inspectLocal ? await _localSubkeySeq(subkey) : null; - // See if we only ever want the locally stored value - if (refreshMode == DHTRecordRefreshMode.local && lastSeq == null) { - // If it's not available locally already just return null now - return null; - } - - var retry = kDHTTryAgainTries; - ValueData? valueData; - while (true) { - try { - valueData = await _routingContext.getDHTValue(key, subkey, - forceRefresh: refreshMode._forceRefresh); - break; - } on VeilidAPIExceptionTryAgain { - retry--; - if (retry == 0) { - throw const DHTExceptionNotAvailable(); + // See if we only ever want the locally stored value + if (refreshMode == DHTRecordRefreshMode.local && lastSeq == null) { + // If it's not available locally already just return null now + return null; } - await asyncSleep(); - } - } - if (valueData == null) { - return null; - } - // See if this get resulted in a newer sequence number - if (refreshMode == DHTRecordRefreshMode.update && - lastSeq != null && - valueData.seq <= lastSeq) { - // If we're only returning updates then punt now - return null; - } - // If we're returning a value, decrypt it - final out = (crypto ?? _crypto).decrypt(valueData.data); - if (outSeqNum != null) { - outSeqNum.save(valueData.seq); - } - return out; - } + var retry = kDHTTryAgainTries; + ValueData? valueData; + while (true) { + try { + valueData = await _routingContext.getDHTValue(key, subkey, + forceRefresh: refreshMode._forceRefresh); + break; + } on VeilidAPIExceptionTryAgain { + retry--; + if (retry == 0) { + throw const DHTExceptionNotAvailable(); + } + await asyncSleep(); + } + } + if (valueData == null) { + return null; + } + + // See if this get resulted in a newer sequence number + if (refreshMode == DHTRecordRefreshMode.update && + lastSeq != null && + valueData.seq <= lastSeq) { + // If we're only returning updates then punt now + return null; + } + // If we're returning a value, decrypt it + final out = (crypto ?? _crypto).decrypt(valueData.data); + if (outSeqNum != null) { + outSeqNum.save(valueData.seq); + } + return out; + }); /// Get a subkey value from this record. /// Process the record returned with a JSON unmarshal function 'fromJson'. @@ -223,97 +224,102 @@ class DHTRecord implements DHTDeleteable { /// If a newer value was found on the network, it is returned /// If the value was succesfully written, null is returned Future tryWriteBytes(Uint8List newValue, - {int subkey = -1, - VeilidCrypto? crypto, - KeyPair? writer, - Output? outSeqNum}) async { - subkey = subkeyOrDefault(subkey); - final lastSeq = await _localSubkeySeq(subkey); - final encryptedNewValue = await (crypto ?? _crypto).encrypt(newValue); + {int subkey = -1, + VeilidCrypto? crypto, + KeyPair? writer, + Output? outSeqNum}) async => + _wrapStats('tryWriteBytes', () async { + subkey = subkeyOrDefault(subkey); + final lastSeq = await _localSubkeySeq(subkey); + final encryptedNewValue = await (crypto ?? _crypto).encrypt(newValue); - // Set the new data if possible - var newValueData = await _routingContext - .setDHTValue(key, subkey, encryptedNewValue, writer: writer ?? _writer); - if (newValueData == null) { - // A newer value wasn't found on the set, but - // we may get a newer value when getting the value for the sequence number - newValueData = await _routingContext.getDHTValue(key, subkey); - if (newValueData == null) { - assert(newValueData != null, "can't get value that was just set"); - return null; - } - } + // Set the new data if possible + var newValueData = await _routingContext.setDHTValue( + key, subkey, encryptedNewValue, + writer: writer ?? _writer); + if (newValueData == null) { + // A newer value wasn't found on the set, but + // we may get a newer value when getting the value for the sequence number + newValueData = await _routingContext.getDHTValue(key, subkey); + if (newValueData == null) { + assert(newValueData != null, "can't get value that was just set"); + return null; + } + } - // Record new sequence number - final isUpdated = newValueData.seq != lastSeq; - if (isUpdated && outSeqNum != null) { - outSeqNum.save(newValueData.seq); - } + // Record new sequence number + final isUpdated = newValueData.seq != lastSeq; + if (isUpdated && outSeqNum != null) { + outSeqNum.save(newValueData.seq); + } - // See if the encrypted data returned is exactly the same - // if so, shortcut and don't bother decrypting it - if (newValueData.data.equals(encryptedNewValue)) { - if (isUpdated) { - DHTRecordPool.instance._processLocalValueChange(key, newValue, subkey); - } - return null; - } + // See if the encrypted data returned is exactly the same + // if so, shortcut and don't bother decrypting it + if (newValueData.data.equals(encryptedNewValue)) { + if (isUpdated) { + DHTRecordPool.instance + ._processLocalValueChange(key, newValue, subkey); + } + return null; + } - // Decrypt value to return it - final decryptedNewValue = - await (crypto ?? _crypto).decrypt(newValueData.data); - if (isUpdated) { - DHTRecordPool.instance - ._processLocalValueChange(key, decryptedNewValue, subkey); - } - return decryptedNewValue; - } + // Decrypt value to return it + final decryptedNewValue = + await (crypto ?? _crypto).decrypt(newValueData.data); + if (isUpdated) { + DHTRecordPool.instance + ._processLocalValueChange(key, decryptedNewValue, subkey); + } + return decryptedNewValue; + }); /// Attempt to write a byte buffer to a DHTRecord subkey /// If a newer value was found on the network, another attempt /// will be made to write the subkey until this succeeds Future eventualWriteBytes(Uint8List newValue, - {int subkey = -1, - VeilidCrypto? crypto, - KeyPair? writer, - Output? outSeqNum}) async { - subkey = subkeyOrDefault(subkey); - final lastSeq = await _localSubkeySeq(subkey); - final encryptedNewValue = await (crypto ?? _crypto).encrypt(newValue); + {int subkey = -1, + VeilidCrypto? crypto, + KeyPair? writer, + Output? outSeqNum}) async => + _wrapStats('eventualWriteBytes', () async { + subkey = subkeyOrDefault(subkey); + final lastSeq = await _localSubkeySeq(subkey); + final encryptedNewValue = await (crypto ?? _crypto).encrypt(newValue); - ValueData? newValueData; - do { - do { - // Set the new data - newValueData = await _routingContext.setDHTValue( - key, subkey, encryptedNewValue, - writer: writer ?? _writer); + ValueData? newValueData; + do { + do { + // Set the new data + newValueData = await _routingContext.setDHTValue( + key, subkey, encryptedNewValue, + writer: writer ?? _writer); - // Repeat if newer data on the network was found - } while (newValueData != null); + // Repeat if newer data on the network was found + } while (newValueData != null); - // Get the data to check its sequence number - newValueData = await _routingContext.getDHTValue(key, subkey); - if (newValueData == null) { - assert(newValueData != null, "can't get value that was just set"); - return; - } + // Get the data to check its sequence number + newValueData = await _routingContext.getDHTValue(key, subkey); + if (newValueData == null) { + assert(newValueData != null, "can't get value that was just set"); + return; + } - // Record new sequence number - if (outSeqNum != null) { - outSeqNum.save(newValueData.seq); - } + // Record new sequence number + if (outSeqNum != null) { + outSeqNum.save(newValueData.seq); + } - // The encrypted data returned should be exactly the same - // as what we are trying to set, - // otherwise we still need to keep trying to set the value - } while (!newValueData.data.equals(encryptedNewValue)); + // The encrypted data returned should be exactly the same + // as what we are trying to set, + // otherwise we still need to keep trying to set the value + } while (!newValueData.data.equals(encryptedNewValue)); - final isUpdated = newValueData.seq != lastSeq; - if (isUpdated) { - DHTRecordPool.instance._processLocalValueChange(key, newValue, subkey); - } - } + final isUpdated = newValueData.seq != lastSeq; + if (isUpdated) { + DHTRecordPool.instance + ._processLocalValueChange(key, newValue, subkey); + } + }); /// Attempt to write a byte buffer to a DHTRecord subkey /// If a newer value was found on the network, another attempt @@ -321,32 +327,36 @@ class DHTRecord implements DHTDeleteable { /// Each attempt to write the value calls an update function with the /// old value to determine what new value should be attempted for that write. Future eventualUpdateBytes( - Future Function(Uint8List? oldValue) update, - {int subkey = -1, - VeilidCrypto? crypto, - KeyPair? writer, - Output? outSeqNum}) async { - subkey = subkeyOrDefault(subkey); + Future Function(Uint8List? oldValue) update, + {int subkey = -1, + VeilidCrypto? crypto, + KeyPair? writer, + Output? outSeqNum}) async => + _wrapStats('eventualUpdateBytes', () async { + subkey = subkeyOrDefault(subkey); - // Get the existing data, do not allow force refresh here - // because if we need a refresh the setDHTValue will fail anyway - var oldValue = - await get(subkey: subkey, crypto: crypto, outSeqNum: outSeqNum); + // Get the existing data, do not allow force refresh here + // because if we need a refresh the setDHTValue will fail anyway + var oldValue = + await get(subkey: subkey, crypto: crypto, outSeqNum: outSeqNum); - do { - // Update the data - final updatedValue = await update(oldValue); - if (updatedValue == null) { - // If null is returned from the update, stop trying to do the update - break; - } - // Try to write it back to the network - oldValue = await tryWriteBytes(updatedValue, - subkey: subkey, crypto: crypto, writer: writer, outSeqNum: outSeqNum); + do { + // Update the data + final updatedValue = await update(oldValue); + if (updatedValue == null) { + // If null is returned from the update, stop trying to do the update + break; + } + // Try to write it back to the network + oldValue = await tryWriteBytes(updatedValue, + subkey: subkey, + crypto: crypto, + writer: writer, + outSeqNum: outSeqNum); - // Repeat update if newer data on the network was found - } while (oldValue != null); - } + // Repeat update if newer data on the network was found + } while (oldValue != null); + }); /// Like 'tryWriteBytes' but with JSON marshal/unmarshal of the value Future tryWriteJson(T Function(dynamic) fromJson, T newValue, @@ -555,6 +565,9 @@ class DHTRecord implements DHTDeleteable { local: false, data: update.value?.data, subkeys: update.subkeys); } + Future _wrapStats(String func, Future Function() closure) => + DHTRecordPool.instance._stats.measure(key, debugName, func, closure); + ////////////////////////////////////////////////////////////// final _SharedDHTRecordData _sharedDHTRecordData; diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart index 3ee9adc..6dc0634 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart @@ -273,24 +273,6 @@ class DHTRecordPool with TableDBBackedJson { } } } - // else { - - // XXX: should no longer be necessary - // // Remove watch state - // - // for (final entry in _opened.entries) { - // final openedKey = entry.key; - // final openedRecordInfo = entry.value; - - // if (openedKey == updateValueChange.key) { - // for (final rec in openedRecordInfo.records) { - // rec._watchState = null; - // } - // openedRecordInfo.shared.needsWatchStateUpdate = true; - // break; - // } - // } - //} } /// Log the current record allocations @@ -320,6 +302,11 @@ class DHTRecordPool with TableDBBackedJson { } } + /// Log the performance stats + void debugPrintStats() { + log('DHTRecordPool Stats:\n${_stats.debugString()}'); + } + /// Public interface to DHTRecordPool logger void log(String message) { _logger?.call(message); @@ -369,109 +356,110 @@ class DHTRecordPool with TableDBBackedJson { } Future _recordOpenCommon( - {required String debugName, - required VeilidRoutingContext dhtctx, - required TypedKey recordKey, - required VeilidCrypto crypto, - required KeyPair? writer, - required TypedKey? parent, - required int defaultSubkey}) async { - log('openDHTRecord: debugName=$debugName key=$recordKey'); + {required String debugName, + required VeilidRoutingContext dhtctx, + required TypedKey recordKey, + required VeilidCrypto crypto, + required KeyPair? writer, + required TypedKey? parent, + required int defaultSubkey}) async => + _stats.measure(recordKey, debugName, '_recordOpenCommon', () async { + log('openDHTRecord: debugName=$debugName key=$recordKey'); - // See if this has been opened yet - final openedRecordInfo = await _mutex.protect(() async { - // If we are opening a key that already exists - // make sure we are using the same parent if one was specified - _validateParentInner(parent, recordKey); + // See if this has been opened yet + final openedRecordInfo = await _mutex.protect(() async { + // If we are opening a key that already exists + // make sure we are using the same parent if one was specified + _validateParentInner(parent, recordKey); - return _opened[recordKey]; - }); + return _opened[recordKey]; + }); - if (openedRecordInfo == null) { - // Fresh open, just open the record - var retry = kDHTKeyNotFoundTries; - late final DHTRecordDescriptor recordDescriptor; - while (true) { - try { - recordDescriptor = - await dhtctx.openDHTRecord(recordKey, writer: writer); - break; - } on VeilidAPIExceptionTryAgain { - throw const DHTExceptionNotAvailable(); - } on VeilidAPIExceptionKeyNotFound { - await asyncSleep(); - retry--; - if (retry == 0) { - throw const DHTExceptionNotAvailable(); + if (openedRecordInfo == null) { + // Fresh open, just open the record + var retry = kDHTKeyNotFoundTries; + late final DHTRecordDescriptor recordDescriptor; + while (true) { + try { + recordDescriptor = + await dhtctx.openDHTRecord(recordKey, writer: writer); + break; + } on VeilidAPIExceptionTryAgain { + throw const DHTExceptionNotAvailable(); + } on VeilidAPIExceptionKeyNotFound { + await asyncSleep(); + retry--; + if (retry == 0) { + throw const DHTExceptionNotAvailable(); + } + } } + + final newOpenedRecordInfo = _OpenedRecordInfo( + recordDescriptor: recordDescriptor, + defaultWriter: writer, + defaultRoutingContext: dhtctx); + + final rec = DHTRecord._( + debugName: debugName, + routingContext: dhtctx, + defaultSubkey: defaultSubkey, + sharedDHTRecordData: newOpenedRecordInfo.shared, + writer: writer, + crypto: crypto); + + await _mutex.protect(() async { + // Register the opened record + _opened[recordDescriptor.key] = newOpenedRecordInfo; + + // Register the dependency + await _addDependencyInner( + parent, + recordKey, + debugName: debugName, + ); + + // Register the newly opened record + newOpenedRecordInfo.records.add(rec); + }); + + return rec; } - } - final newOpenedRecordInfo = _OpenedRecordInfo( - recordDescriptor: recordDescriptor, - defaultWriter: writer, - defaultRoutingContext: dhtctx); + // Already opened - final rec = DHTRecord._( - debugName: debugName, - routingContext: dhtctx, - defaultSubkey: defaultSubkey, - sharedDHTRecordData: newOpenedRecordInfo.shared, - writer: writer, - crypto: crypto); + // See if we need to reopen the record with a default writer and possibly + // a different routing context + if (writer != null && openedRecordInfo.shared.defaultWriter == null) { + await dhtctx.openDHTRecord(recordKey, writer: writer); + // New writer if we didn't specify one before + openedRecordInfo.shared.defaultWriter = writer; + // New default routing context if we opened it again + openedRecordInfo.shared.defaultRoutingContext = dhtctx; + } - await _mutex.protect(() async { - // Register the opened record - _opened[recordDescriptor.key] = newOpenedRecordInfo; + final rec = DHTRecord._( + debugName: debugName, + routingContext: dhtctx, + defaultSubkey: defaultSubkey, + sharedDHTRecordData: openedRecordInfo.shared, + writer: writer, + crypto: crypto); - // Register the dependency - await _addDependencyInner( - parent, - recordKey, - debugName: debugName, - ); + await _mutex.protect(() async { + // Register the dependency + await _addDependencyInner( + parent, + recordKey, + debugName: debugName, + ); - // Register the newly opened record - newOpenedRecordInfo.records.add(rec); + openedRecordInfo.records.add(rec); + }); + + return rec; }); - return rec; - } - - // Already opened - - // See if we need to reopen the record with a default writer and possibly - // a different routing context - if (writer != null && openedRecordInfo.shared.defaultWriter == null) { - await dhtctx.openDHTRecord(recordKey, writer: writer); - // New writer if we didn't specify one before - openedRecordInfo.shared.defaultWriter = writer; - // New default routing context if we opened it again - openedRecordInfo.shared.defaultRoutingContext = dhtctx; - } - - final rec = DHTRecord._( - debugName: debugName, - routingContext: dhtctx, - defaultSubkey: defaultSubkey, - sharedDHTRecordData: openedRecordInfo.shared, - writer: writer, - crypto: crypto); - - await _mutex.protect(() async { - // Register the dependency - await _addDependencyInner( - parent, - recordKey, - debugName: debugName, - ); - - openedRecordInfo.records.add(rec); - }); - - return rec; - } - // Called when a DHTRecord is closed // Cleans up the opened record housekeeping and processes any late deletions Future _recordClosed(DHTRecord record) async { @@ -866,34 +854,37 @@ class DHTRecordPool with TableDBBackedJson { void _pollWatch(TypedKey openedRecordKey, _OpenedRecordInfo openedRecordInfo, _WatchState unionWatchState) { singleFuture((this, _sfPollWatch, openedRecordKey), () async { - final dhtctx = openedRecordInfo.shared.defaultRoutingContext; + await _stats.measure( + openedRecordKey, openedRecordInfo.debugNames, '_pollWatch', () async { + final dhtctx = openedRecordInfo.shared.defaultRoutingContext; - final currentReport = await dhtctx.inspectDHTRecord(openedRecordKey, - subkeys: unionWatchState.subkeys, scope: DHTReportScope.syncGet); + final currentReport = await dhtctx.inspectDHTRecord(openedRecordKey, + subkeys: unionWatchState.subkeys, scope: DHTReportScope.syncGet); - final fsc = currentReport.firstSeqChange; - if (fsc == null) { - return null; - } - final newerSubkeys = currentReport.newerOnlineSubkeys; + final fsc = currentReport.firstSeqChange; + if (fsc == null) { + return null; + } + final newerSubkeys = currentReport.newerOnlineSubkeys; - final valueData = await dhtctx.getDHTValue(openedRecordKey, fsc.subkey, - forceRefresh: true); - if (valueData == null) { - return; - } + final valueData = await dhtctx.getDHTValue(openedRecordKey, fsc.subkey, + forceRefresh: true); + if (valueData == null) { + return; + } - if (valueData.seq < fsc.newSeq) { - log('inspect returned a newer seq than get: ${valueData.seq} < $fsc'); - } + if (valueData.seq < fsc.newSeq) { + log('inspect returned a newer seq than get: ${valueData.seq} < $fsc'); + } - if (fsc.oldSeq == null || valueData.seq > fsc.oldSeq!) { - processRemoteValueChange(VeilidUpdateValueChange( - key: openedRecordKey, - subkeys: newerSubkeys, - count: 0xFFFFFFFF, - value: valueData)); - } + if (fsc.oldSeq == null || valueData.seq > fsc.oldSeq!) { + processRemoteValueChange(VeilidUpdateValueChange( + key: openedRecordKey, + subkeys: newerSubkeys, + count: 0xFFFFFFFF, + value: valueData)); + } + }); }); } @@ -915,8 +906,11 @@ class DHTRecordPool with TableDBBackedJson { _watchStateProcessors.updateState( openedRecordKey, unionWatchState, - (newState) => - _watchStateChange(openedRecordKey, unionWatchState)); + (newState) => _stats.measure( + openedRecordKey, + openedRecordInfo.debugNames, + '_watchStateChange', + () => _watchStateChange(openedRecordKey, unionWatchState))); } } }); @@ -958,6 +952,8 @@ class DHTRecordPool with TableDBBackedJson { // Watch state processors final _watchStateProcessors = SingleStateProcessorMap(); + // Statistics + final _stats = DHTStats(); static DHTRecordPool? _singleton; } diff --git a/packages/veilid_support/pubspec.lock b/packages/veilid_support/pubspec.lock index 11c2db6..d86402b 100644 --- a/packages/veilid_support/pubspec.lock +++ b/packages/veilid_support/pubspec.lock @@ -331,6 +331,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + indent: + dependency: "direct main" + description: + name: indent + sha256: "819319a5c185f7fe412750c798953378b37a0d0d32564ce33e7c5acfd1372d2a" + url: "https://pub.dev" + source: hosted + version: "2.0.0" io: dependency: transitive description: diff --git a/packages/veilid_support/pubspec.yaml b/packages/veilid_support/pubspec.yaml index 548c40e..65ba78d 100644 --- a/packages/veilid_support/pubspec.yaml +++ b/packages/veilid_support/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: equatable: ^2.0.7 fast_immutable_collections: ^11.0.3 freezed_annotation: ^3.0.0 + indent: ^2.0.0 json_annotation: ^4.9.0 loggy: ^2.0.3 meta: ^1.16.0 diff --git a/pubspec.lock b/pubspec.lock index db2adfc..e7252a3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -809,6 +809,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.3" + indent: + dependency: transitive + description: + name: indent + sha256: "819319a5c185f7fe412750c798953378b37a0d0d32564ce33e7c5acfd1372d2a" + url: "https://pub.dev" + source: hosted + version: "2.0.0" intl: dependency: "direct main" description: From 063eeb8d1253990e4ff9e53793fbafd07b4bae6e Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 12 May 2025 20:18:14 -0400 Subject: [PATCH 67/93] stats --- .../lib/dht_support/src/dht_record/stats.dart | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 packages/veilid_support/lib/dht_support/src/dht_record/stats.dart diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/stats.dart b/packages/veilid_support/lib/dht_support/src/dht_record/stats.dart new file mode 100644 index 0000000..6388f5c --- /dev/null +++ b/packages/veilid_support/lib/dht_support/src/dht_record/stats.dart @@ -0,0 +1,175 @@ +import 'package:collection/collection.dart'; +import 'package:indent/indent.dart'; + +import '../../../veilid_support.dart'; + +const maxLatencySamples = 100; +const timeoutDuration = 10; + +extension LatencyStatsExt on LatencyStats { + String debugString() => 'fast($fastest)/avg($average)/slow($slowest)/' + 'tm90($tm90)/tm75($tm75)/p90($p90)/p75($p75)'; +} + +class LatencyStatsAccounting { + LatencyStatsAccounting({required this.maxSamples}); + + LatencyStats record(TimestampDuration dur) { + _samples.add(dur); + if (_samples.length > maxSamples) { + _samples.removeAt(0); + } + + final sortedList = _samples.sorted(); + + final fastest = sortedList.first; + final slowest = sortedList.last; + final average = TimestampDuration( + value: sortedList.fold(BigInt.zero, (acc, x) => acc + x.value) ~/ + BigInt.from(sortedList.length)); + + final tm90len = (sortedList.length * 90 + 99) ~/ 100; + final tm75len = (sortedList.length * 75 + 99) ~/ 100; + final tm90 = TimestampDuration( + value: sortedList + .sublist(0, tm90len) + .fold(BigInt.zero, (acc, x) => acc + x.value) ~/ + BigInt.from(tm90len)); + final tm75 = TimestampDuration( + value: sortedList + .sublist(0, tm75len) + .fold(BigInt.zero, (acc, x) => acc + x.value) ~/ + BigInt.from(tm90len)); + final p90 = sortedList[tm90len - 1]; + final p75 = sortedList[tm75len - 1]; + + final ls = LatencyStats( + fastest: fastest, + slowest: slowest, + average: average, + tm90: tm90, + tm75: tm75, + p90: p90, + p75: p75); + + return ls; + } + + ///////////////////////////// + final int maxSamples; + final _samples = []; +} + +class DHTCallStats { + void record(TimestampDuration dur, Exception? exc) { + final wasTimeout = + exc is VeilidAPIExceptionTimeout || dur.toSecs() >= timeoutDuration; + + calls++; + if (wasTimeout) { + timeouts++; + } else { + successLatency = successLatencyAcct.record(dur); + } + latency = latencyAcct.record(dur); + } + + String debugString() => + ' timeouts/calls: $timeouts/$calls (${(timeouts * 100 / calls).toStringAsFixed(3)}%)\n' + 'success latency: ${successLatency?.debugString()}\n' + ' all latency: ${latency?.debugString()}\n'; + + ///////////////////////////// + int calls = 0; + int timeouts = 0; + LatencyStats? latency; + LatencyStats? successLatency; + final latencyAcct = LatencyStatsAccounting(maxSamples: maxLatencySamples); + final successLatencyAcct = + LatencyStatsAccounting(maxSamples: maxLatencySamples); +} + +class DHTPerKeyStats { + DHTPerKeyStats(this.debugName); + + void record(String func, TimestampDuration dur, Exception? exc) { + final keyFuncStats = _perFuncStats.putIfAbsent(func, DHTCallStats.new); + + _stats.record(dur, exc); + keyFuncStats.record(dur, exc); + } + + String debugString() { + // + final out = StringBuffer() + ..write('Name: $debugName\n') + ..write(_stats.debugString().indent(4)) + ..writeln('Per-Function:'); + for (final entry in _perFuncStats.entries) { + final funcName = entry.key; + final funcStats = entry.value.debugString().indent(4); + out.write('$funcName:\n$funcStats'.indent(4)); + } + + return out.toString(); + } + + ////////////////////////////// + + final String debugName; + final _stats = DHTCallStats(); + final _perFuncStats = {}; +} + +class DHTStats { + DHTStats(); + + Future measure(TypedKey key, String debugName, String func, + Future Function() closure) async { + // + final start = Veilid.instance.now(); + final keyStats = + _statsPerKey.putIfAbsent(key, () => DHTPerKeyStats(debugName)); + final funcStats = _statsPerFunc.putIfAbsent(func, DHTCallStats.new); + + VeilidAPIException? exc; + + try { + final res = await closure(); + + return res; + } on VeilidAPIException catch (e) { + exc = e; + rethrow; + } finally { + final end = Veilid.instance.now(); + final dur = end.diff(start); + + keyStats.record(func, dur, exc); + funcStats.record(dur, exc); + } + } + + String debugString() { + // + final out = StringBuffer()..writeln('Per-Function:'); + for (final entry in _statsPerFunc.entries) { + final funcName = entry.key; + final funcStats = entry.value.debugString().indent(4); + out.write('$funcName:\n$funcStats\n'.indent(4)); + } + out.writeln('Per-Key:'); + for (final entry in _statsPerKey.entries) { + final keyName = entry.key; + final keyStats = entry.value.debugString().indent(4); + out.write('$keyName:\n$keyStats\n'.indent(4)); + } + + return out.toString(); + } + + ////////////////////////////// + + final _statsPerKey = {}; + final _statsPerFunc = {}; +} From 1a9cca0667614cff17209a285b3a7615955b706b Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 17 May 2025 18:02:17 -0400 Subject: [PATCH 68/93] new chat widget --- CHANGELOG.md | 1 + assets/i18n/en.json | 4 + flutter_01.png | 0 flutter_02.png | 0 flutter_03.png | 0 .../models/local_account/local_account.dart | 5 +- .../local_account/local_account.freezed.dart | 3 +- .../models/user_login/user_login.dart | 3 +- .../models/user_login/user_login.freezed.dart | 3 +- .../views/edit_account_page.dart | 1 - .../views/show_recovery_key_page.dart | 1 - lib/chat/cubits/chat_component_cubit.dart | 177 +++---- .../cubits/single_contact_messages_cubit.dart | 64 ++- lib/chat/models/chat_component_state.dart | 23 +- .../models/chat_component_state.freezed.dart | 167 +++--- lib/chat/models/message_state.dart | 3 + lib/chat/models/message_state.freezed.dart | 42 +- lib/chat/models/message_state.g.dart | 2 + .../views/chat_builders/chat_builders.dart | 2 + .../chat_builders/vc_composer_widget.dart | 431 ++++++++++++++++ .../chat_builders/vc_text_message_widget.dart | 269 ++++++++++ lib/chat/views/chat_component_widget.dart | 333 ++++++++---- lib/chat/views/date_formatter.dart | 41 ++ ..._length_limiting_text_input_formatter.dart | 54 ++ lib/chat/views/views.dart | 1 + lib/init.dart | 8 +- lib/keyboard_shortcuts.dart | 80 ++- lib/layout/home/home_account_ready.dart | 22 +- lib/layout/home/home_screen.dart | 54 +- lib/theme/models/chat_theme.dart | 488 ------------------ lib/theme/models/models.dart | 1 - .../models/scale_theme/scale_chat_theme.dart | 369 +++++++++++++ lib/theme/models/scale_theme/scale_color.dart | 1 + lib/theme/models/scale_theme/scale_theme.dart | 1 + lib/theme/views/responsive.dart | 25 +- lib/theme/views/styled_alert.dart | 2 +- lib/tools/loggy.dart | 5 +- lib/tools/state_logger.dart | 21 +- .../lib/identity_support/super_identity.dart | 1 + .../super_identity.freezed.dart | 1 + packages/veilid_support/lib/src/config.dart | 15 +- .../src/table_db_array_protobuf_cubit.dart | 2 +- pubspec.lock | 109 ++-- pubspec.yaml | 50 +- 44 files changed, 1904 insertions(+), 981 deletions(-) create mode 100644 flutter_01.png create mode 100644 flutter_02.png create mode 100644 flutter_03.png create mode 100644 lib/chat/views/chat_builders/chat_builders.dart create mode 100644 lib/chat/views/chat_builders/vc_composer_widget.dart create mode 100644 lib/chat/views/chat_builders/vc_text_message_widget.dart create mode 100644 lib/chat/views/date_formatter.dart create mode 100644 lib/chat/views/utf8_length_limiting_text_input_formatter.dart delete mode 100644 lib/theme/models/chat_theme.dart create mode 100644 lib/theme/models/scale_theme/scale_chat_theme.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 9182300..a7b1930 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Fixed issue with Android 'back' button exiting the app (#331) - Deprecated accounts no longer crash application at startup - Simplify SingleContactMessagesCubit and MessageReconciliation +- Update flutter_chat_ui to 2.0.0 ## v0.4.7 ## - *Community Contributions* diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 9192851..f0b84a0 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -313,5 +313,9 @@ "info": "Info", "debug": "Debug", "trace": "Trace" + }, + "date_formatter": { + "just_now": "Just now", + "yesterday": "Yesterday" } } \ No newline at end of file diff --git a/flutter_01.png b/flutter_01.png new file mode 100644 index 0000000..e69de29 diff --git a/flutter_02.png b/flutter_02.png new file mode 100644 index 0000000..e69de29 diff --git a/flutter_03.png b/flutter_03.png new file mode 100644 index 0000000..e69de29 diff --git a/lib/account_manager/models/local_account/local_account.dart b/lib/account_manager/models/local_account/local_account.dart index 49506d0..81cfb8c 100644 --- a/lib/account_manager/models/local_account/local_account.dart +++ b/lib/account_manager/models/local_account/local_account.dart @@ -15,8 +15,9 @@ part 'local_account.freezed.dart'; // and the identitySecretKey optionally encrypted by an unlock code // This is the root of the account information tree for VeilidChat // -@freezed -sealed class LocalAccount with _$LocalAccount { +@Freezed(toJson: true) +abstract class LocalAccount with _$LocalAccount { + @JsonSerializable() const factory LocalAccount({ // The super identity key record for the account, // containing the publicKey in the currentIdentity diff --git a/lib/account_manager/models/local_account/local_account.freezed.dart b/lib/account_manager/models/local_account/local_account.freezed.dart index e2c3c55..8d7aed1 100644 --- a/lib/account_manager/models/local_account/local_account.freezed.dart +++ b/lib/account_manager/models/local_account/local_account.freezed.dart @@ -153,6 +153,7 @@ class _$LocalAccountCopyWithImpl<$Res> implements $LocalAccountCopyWith<$Res> { } /// @nodoc + @JsonSerializable() class _LocalAccount implements LocalAccount { const _LocalAccount( @@ -162,8 +163,6 @@ class _LocalAccount implements LocalAccount { required this.biometricsEnabled, required this.hiddenAccount, required this.name}); - factory _LocalAccount.fromJson(Map json) => - _$LocalAccountFromJson(json); // The super identity key record for the account, // containing the publicKey in the currentIdentity diff --git a/lib/account_manager/models/user_login/user_login.dart b/lib/account_manager/models/user_login/user_login.dart index 4d96d38..4e2f680 100644 --- a/lib/account_manager/models/user_login/user_login.dart +++ b/lib/account_manager/models/user_login/user_login.dart @@ -8,8 +8,9 @@ part 'user_login.g.dart'; // Represents a currently logged in account // User logins are stored in the user_logins tablestore table // indexed by the accountSuperIdentityRecordKey -@freezed +@Freezed(toJson: true) sealed class UserLogin with _$UserLogin { + @JsonSerializable() const factory UserLogin({ // SuperIdentity record key for the user // used to index the local accounts table diff --git a/lib/account_manager/models/user_login/user_login.freezed.dart b/lib/account_manager/models/user_login/user_login.freezed.dart index b0c6070..c406812 100644 --- a/lib/account_manager/models/user_login/user_login.freezed.dart +++ b/lib/account_manager/models/user_login/user_login.freezed.dart @@ -124,6 +124,7 @@ class _$UserLoginCopyWithImpl<$Res> implements $UserLoginCopyWith<$Res> { } /// @nodoc + @JsonSerializable() class _UserLogin implements UserLogin { const _UserLogin( @@ -131,8 +132,6 @@ class _UserLogin implements UserLogin { required this.identitySecret, required this.accountRecordInfo, required this.lastActive}); - factory _UserLogin.fromJson(Map json) => - _$UserLoginFromJson(json); // SuperIdentity record key for the user // used to index the local accounts table diff --git a/lib/account_manager/views/edit_account_page.dart b/lib/account_manager/views/edit_account_page.dart index bd18967..9af4719 100644 --- a/lib/account_manager/views/edit_account_page.dart +++ b/lib/account_manager/views/edit_account_page.dart @@ -249,7 +249,6 @@ class _EditAccountPageState extends WindowSetupState { final displayModalHUD = _isInAsyncCall; return StyledScaffold( - // resizeToAvoidBottomInset: false, appBar: DefaultAppBar( title: Text(translate('edit_account_page.titlebar')), leading: Navigator.canPop(context) diff --git a/lib/account_manager/views/show_recovery_key_page.dart b/lib/account_manager/views/show_recovery_key_page.dart index bf44dd7..7c971e0 100644 --- a/lib/account_manager/views/show_recovery_key_page.dart +++ b/lib/account_manager/views/show_recovery_key_page.dart @@ -163,7 +163,6 @@ class _ShowRecoveryKeyPageState extends WindowSetupState { final displayModalHUD = _isInAsyncCall; return StyledScaffold( - // resizeToAvoidBottomInset: false, appBar: DefaultAppBar( title: Text(translate('show_recovery_key_page.titlebar')), actions: [ diff --git a/lib/chat/cubits/chat_component_cubit.dart b/lib/chat/cubits/chat_component_cubit.dart index 6112384..76ff660 100644 --- a/lib/chat/cubits/chat_component_cubit.dart +++ b/lib/chat/cubits/chat_component_cubit.dart @@ -4,11 +4,8 @@ import 'dart:typed_data'; import 'package:async_tools/async_tools.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:fixnum/fixnum.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_chat_types/flutter_chat_types.dart' as types; -import 'package:flutter_chat_ui/flutter_chat_ui.dart'; -import 'package:scroll_to_index/scroll_to_index.dart'; +import 'package:flutter_chat_core/flutter_chat_core.dart' as core; import 'package:veilid_support/veilid_support.dart'; import '../../account_manager/account_manager.dart'; @@ -19,6 +16,7 @@ import '../../tools/tools.dart'; import '../models/chat_component_state.dart'; import '../models/message_state.dart'; import '../models/window_state.dart'; +import '../views/chat_component_widget.dart'; import 'cubits.dart'; const metadataKeyIdentityPublicKey = 'identityPublicKey'; @@ -39,15 +37,12 @@ class ChatComponentCubit extends Cubit { _contactListCubit = contactListCubit, _conversationCubits = conversationCubits, _messagesCubit = messagesCubit, - super(ChatComponentState( - chatKey: GlobalKey(), - scrollController: AutoScrollController(), - textEditingController: InputTextFieldController(), + super(const ChatComponentState( localUser: null, - remoteUsers: const IMap.empty(), - historicalRemoteUsers: const IMap.empty(), - unknownUsers: const IMap.empty(), - messageWindow: const AsyncLoading(), + remoteUsers: IMap.empty(), + historicalRemoteUsers: IMap.empty(), + unknownUsers: IMap.empty(), + messageWindow: AsyncLoading(), title: '', )) { // Immediate Init @@ -102,6 +97,7 @@ class ChatComponentCubit extends Cubit { await _accountRecordSubscription.cancel(); await _messagesSubscription.cancel(); await _conversationSubscriptions.values.map((v) => v.cancel()).wait; + await super.close(); } @@ -122,32 +118,15 @@ class ChatComponentCubit extends Cubit { } // Send a message - void sendMessage(types.PartialText message) { - final text = message.text; - - final replyId = (message.repliedMessage != null) - ? base64UrlNoPadDecode(message.repliedMessage!.id) + void sendMessage( + {required String text, + String? replyToMessageId, + Timestamp? expiration, + int? viewLimit, + List? attachments}) { + final replyId = (replyToMessageId != null) + ? base64UrlNoPadDecode(replyToMessageId) : null; - Timestamp? expiration; - int? viewLimit; - List? attachments; - final metadata = message.metadata; - if (metadata != null) { - final expirationValue = - metadata[metadataKeyExpirationDuration] as TimestampDuration?; - if (expirationValue != null) { - expiration = Veilid.instance.now().offset(expirationValue); - } - final viewLimitValue = metadata[metadataKeyViewLimit] as int?; - if (viewLimitValue != null) { - viewLimit = viewLimitValue; - } - final attachmentsValue = - metadata[metadataKeyAttachments] as List?; - if (attachmentsValue != null) { - attachments = attachmentsValue; - } - } _addTextMessage( text: text, @@ -172,9 +151,9 @@ class ChatComponentCubit extends Cubit { emit(state.copyWith(localUser: null)); return; } - final localUser = types.User( + final localUser = core.User( id: _localUserIdentityKey.toString(), - firstName: account.profile.name, + name: account.profile.name, metadata: {metadataKeyIdentityPublicKey: _localUserIdentityKey}); emit(state.copyWith(localUser: localUser)); } @@ -199,11 +178,12 @@ class ChatComponentCubit extends Cubit { // Don't change user information on loading state return; } + + final remoteUser = + _convertRemoteUser(remoteIdentityPublicKey, activeConversationState); + emit(_updateTitle(state.copyWith( - remoteUsers: state.remoteUsers.add( - remoteIdentityPublicKey, - _convertRemoteUser( - remoteIdentityPublicKey, activeConversationState))))); + remoteUsers: state.remoteUsers.add(remoteUser.id, remoteUser)))); } static ChatComponentState _updateTitle(ChatComponentState currentState) { @@ -212,13 +192,13 @@ class ChatComponentCubit extends Cubit { } if (currentState.remoteUsers.length == 1) { final remoteUser = currentState.remoteUsers.values.first; - return currentState.copyWith(title: remoteUser.firstName ?? ''); + return currentState.copyWith(title: remoteUser.name ?? ''); } return currentState.copyWith( title: ''); } - types.User _convertRemoteUser(TypedKey remoteIdentityPublicKey, + core.User _convertRemoteUser(TypedKey remoteIdentityPublicKey, ActiveConversationState activeConversationState) { // See if we have a contact for this remote user final contacts = _contactListCubit.state.state.asData?.value; @@ -227,25 +207,24 @@ class ChatComponentCubit extends Cubit { x.value.identityPublicKey.toVeilid() == remoteIdentityPublicKey); if (contactIdx != -1) { final contact = contacts[contactIdx].value; - return types.User( + return core.User( id: remoteIdentityPublicKey.toString(), - firstName: contact.displayName, + name: contact.displayName, metadata: {metadataKeyIdentityPublicKey: remoteIdentityPublicKey}); } } - return types.User( + return core.User( id: remoteIdentityPublicKey.toString(), - firstName: activeConversationState.remoteConversation?.profile.name ?? + name: activeConversationState.remoteConversation?.profile.name ?? '', metadata: {metadataKeyIdentityPublicKey: remoteIdentityPublicKey}); } - types.User _convertUnknownUser(TypedKey remoteIdentityPublicKey) => - types.User( - id: remoteIdentityPublicKey.toString(), - firstName: '<$remoteIdentityPublicKey>', - metadata: {metadataKeyIdentityPublicKey: remoteIdentityPublicKey}); + core.User _convertUnknownUser(TypedKey remoteIdentityPublicKey) => core.User( + id: remoteIdentityPublicKey.toString(), + name: '<$remoteIdentityPublicKey>', + metadata: {metadataKeyIdentityPublicKey: remoteIdentityPublicKey}); Future _updateConversationSubscriptions() async { // Get existing subscription keys and state @@ -267,16 +246,17 @@ class ChatComponentCubit extends Cubit { final activeConversationState = cc.state.asData?.value; if (activeConversationState != null) { - currentRemoteUsersState = currentRemoteUsersState.add( - remoteIdentityPublicKey, - _convertRemoteUser( - remoteIdentityPublicKey, activeConversationState)); + final remoteUser = _convertRemoteUser( + remoteIdentityPublicKey, activeConversationState); + currentRemoteUsersState = + currentRemoteUsersState.add(remoteUser.id, remoteUser); } } // Purge remote users we didn't see in the cubit list any more final cancels = >[]; for (final deadUser in existing) { - currentRemoteUsersState = currentRemoteUsersState.remove(deadUser); + currentRemoteUsersState = + currentRemoteUsersState.remove(deadUser.toString()); cancels.add(_conversationSubscriptions.remove(deadUser)!.cancel()); } await cancels.wait; @@ -285,63 +265,76 @@ class ChatComponentCubit extends Cubit { emit(_updateTitle(state.copyWith(remoteUsers: currentRemoteUsersState))); } - (ChatComponentState, types.Message?) _messageStateToChatMessage( + (ChatComponentState, core.Message?) _messageStateToChatMessage( ChatComponentState currentState, MessageState message) { final authorIdentityPublicKey = message.content.author.toVeilid(); - late final types.User author; + final authorUserId = authorIdentityPublicKey.toString(); + + late final core.User author; if (authorIdentityPublicKey == _localUserIdentityKey && currentState.localUser != null) { author = currentState.localUser!; } else { - final remoteUser = currentState.remoteUsers[authorIdentityPublicKey]; + final remoteUser = currentState.remoteUsers[authorUserId]; if (remoteUser != null) { author = remoteUser; } else { final historicalRemoteUser = - currentState.historicalRemoteUsers[authorIdentityPublicKey]; + currentState.historicalRemoteUsers[authorUserId]; if (historicalRemoteUser != null) { author = historicalRemoteUser; } else { - final unknownRemoteUser = - currentState.unknownUsers[authorIdentityPublicKey]; + final unknownRemoteUser = currentState.unknownUsers[authorUserId]; if (unknownRemoteUser != null) { author = unknownRemoteUser; } else { final unknownUser = _convertUnknownUser(authorIdentityPublicKey); currentState = currentState.copyWith( - unknownUsers: currentState.unknownUsers - .add(authorIdentityPublicKey, unknownUser)); + unknownUsers: + currentState.unknownUsers.add(authorUserId, unknownUser)); author = unknownUser; } } } } - types.Status? status; - if (message.sendState != null) { - assert(author.id == _localUserIdentityKey.toString(), - 'send state should only be on sent messages'); - switch (message.sendState!) { - case MessageSendState.sending: - status = types.Status.sending; - case MessageSendState.sent: - status = types.Status.sent; - case MessageSendState.delivered: - status = types.Status.delivered; - } - } + // types.Status? status; + // if (message.sendState != null) { + // assert(author.id == _localUserIdentityKey.toString(), + // 'send state should only be on sent messages'); + // switch (message.sendState!) { + // case MessageSendState.sending: + // status = types.Status.sending; + // case MessageSendState.sent: + // status = types.Status.sent; + // case MessageSendState.delivered: + // status = types.Status.delivered; + // } + // } + + final reconciledAt = message.reconciledTimestamp == null + ? null + : DateTime.fromMicrosecondsSinceEpoch( + message.reconciledTimestamp!.value.toInt()); + + // print('message seqid: ${message.seqId}'); switch (message.content.whichKind()) { case proto.Message_Kind.text: - final contextText = message.content.text; - final textMessage = types.TextMessage( - author: author, - createdAt: - (message.sentTimestamp.value ~/ BigInt.from(1000)).toInt(), - id: message.content.authorUniqueIdString, - text: contextText.text, - showStatus: status != null, - status: status); + final reconciledId = message.content.authorUniqueIdString; + final contentText = message.content.text; + final textMessage = core.TextMessage( + authorId: author.id, + createdAt: DateTime.fromMicrosecondsSinceEpoch( + message.sentTimestamp.value.toInt()), + sentAt: reconciledAt, + id: reconciledId, + text: '${contentText.text} (${message.seqId})', + //text: contentText.text, + metadata: { + kSeqId: message.seqId, + if (core.isOnlyEmoji(contentText.text)) 'isOnlyEmoji': true, + }); return (currentState, textMessage); case proto.Message_Kind.secret: case proto.Message_Kind.delete: @@ -375,7 +368,7 @@ class ChatComponentCubit extends Cubit { final messagesState = avMessagesState.asData!.value; // Convert protobuf messages to chat messages - final chatMessages = []; + final chatMessages = []; final tsSet = {}; for (final message in messagesState.window) { final (newState, chatMessage) = @@ -390,11 +383,11 @@ class ChatComponentCubit extends Cubit { // '\nChatMessages:\n$chatMessages' ); } else { - chatMessages.insert(0, chatMessage); + chatMessages.add(chatMessage); } } return currentState.copyWith( - messageWindow: AsyncValue.data(WindowState( + messageWindow: AsyncValue.data(WindowState( window: chatMessages.toIList(), length: messagesState.length, windowTail: messagesState.windowTail, diff --git a/lib/chat/cubits/single_contact_messages_cubit.dart b/lib/chat/cubits/single_contact_messages_cubit.dart index 66032f6..d93c4be 100644 --- a/lib/chat/cubits/single_contact_messages_cubit.dart +++ b/lib/chat/cubits/single_contact_messages_cubit.dart @@ -3,6 +3,8 @@ import 'dart:async'; import 'package:async_tools/async_tools.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:uuid/uuid.dart'; +import 'package:uuid/v4.dart'; import 'package:veilid_support/veilid_support.dart'; import '../../account_manager/account_manager.dart'; @@ -11,9 +13,12 @@ import '../../tools/tools.dart'; import '../models/models.dart'; import 'reconciliation/reconciliation.dart'; +const _sfSendMessageTag = 'sfSendMessageTag'; + class RenderStateElement { RenderStateElement( - {required this.message, + {required this.seqId, + required this.message, required this.isLocal, this.reconciledTimestamp, this.sent = false, @@ -36,6 +41,7 @@ class RenderStateElement { return null; } + int seqId; proto.Message message; bool isLocal; Timestamp? reconciledTimestamp; @@ -71,6 +77,8 @@ class SingleContactMessagesCubit extends Cubit { Future close() async { await _initWait(); + await serialFutureClose((this, _sfSendMessageTag)); + await _commandController.close(); await _commandRunnerFut; await _unsentMessagesQueue.close(); @@ -309,9 +317,6 @@ class SingleContactMessagesCubit extends Cubit { // Async process to send messages in the background Future _processUnsentMessages(IList messages) async { - // _sendingMessages = messages; - - // _renderState(); try { await _sentMessagesDHTLog!.operateAppendEventual((writer) async { // Get the previous message if we have one @@ -337,8 +342,6 @@ class SingleContactMessagesCubit extends Cubit { } on Exception catch (e, st) { log.error('Exception appending unsent messages: $e:\n$st\n'); } - - // _sendingMessages = const IList.empty(); } // Produce a state for this cubit from the input cubits and queues @@ -349,8 +352,9 @@ class SingleContactMessagesCubit extends Cubit { // Get all sent messages that are still offline //final sentMessages = _sentMessagesDHTLog. - //Get all items in the unsent queue - //final unsentMessages = _unsentMessagesQueue.queue; + + // Get all items in the unsent queue + final unsentMessages = _unsentMessagesQueue.queue; // If we aren't ready to render a state, say we're loading if (reconciledMessages == null) { @@ -374,8 +378,19 @@ class SingleContactMessagesCubit extends Cubit { // values: unsentMessages, // ); + // List of all rendered state elements that we will turn into + // message states final renderedElements = []; + + // Keep track of the ids we have rendered + // because there can be an overlap between the 'unsent messages' + // and the reconciled messages as the async state catches up final renderedIds = {}; + + var seqId = (reconciledMessages.windowTail == 0 + ? reconciledMessages.length + : reconciledMessages.windowTail) - + reconciledMessages.windowElements.length; for (final m in reconciledMessages.windowElements) { final isLocal = m.content.author.toVeilid() == _accountInfo.identityTypedPublicKey; @@ -387,33 +402,44 @@ class SingleContactMessagesCubit extends Cubit { final sent = isLocal; final sentOffline = false; // + if (renderedIds.contains(m.content.authorUniqueIdString)) { + seqId++; + continue; + } renderedElements.add(RenderStateElement( + seqId: seqId, message: m.content, isLocal: isLocal, reconciledTimestamp: reconciledTimestamp, sent: sent, sentOffline: sentOffline, )); - renderedIds.add(m.content.authorUniqueIdString); + seqId++; } // Render in-flight messages at the bottom - // for (final m in _sendingMessages) { + // + // for (final m in unsentMessages) { // if (renderedIds.contains(m.authorUniqueIdString)) { + // seqId++; // continue; // } // renderedElements.add(RenderStateElement( + // seqId: seqId, // message: m, // isLocal: true, // sent: true, // sentOffline: true, // )); + // renderedIds.add(m.authorUniqueIdString); + // seqId++; // } // Render the state final messages = renderedElements .map((x) => MessageState( + seqId: x.seqId, content: x.message, sentTimestamp: Timestamp.fromInt64(x.message.timestamp), reconciledTimestamp: x.reconciledTimestamp, @@ -431,20 +457,26 @@ class SingleContactMessagesCubit extends Cubit { void _sendMessage({required proto.Message message}) { // Add common fields - // id and signature will get set by _processMessageToSend + // real id and signature will get set by _processMessageToSend + // temporary id set here is random and not 'valid' in the eyes + // of reconcilation, noting that reconciled timestamp is not yet set. message ..author = _accountInfo.identityTypedPublicKey.toProto() - ..timestamp = Veilid.instance.now().toInt64(); + ..timestamp = Veilid.instance.now().toInt64() + ..id = Uuid.parse(_uuidGen.generate()); if ((message.writeToBuffer().lengthInBytes + 256) > 4096) { throw const FormatException('message is too long'); } // Put in the queue - _unsentMessagesQueue.addSync(message); + serialFuture((this, _sfSendMessageTag), () async { + // Add the message to the persistent queue + await _unsentMessagesQueue.add(message); - // Update the view - _renderState(); + // Update the view + _renderState(); + }); } Future _commandRunner() async { @@ -487,7 +519,6 @@ class SingleContactMessagesCubit extends Cubit { late final MessageReconciliation _reconciliation; late final PersistentQueue _unsentMessagesQueue; - // IList _sendingMessages = const IList.empty(); StreamSubscription? _sentSubscription; StreamSubscription? _rcvdSubscription; StreamSubscription>? @@ -496,4 +527,5 @@ class SingleContactMessagesCubit extends Cubit { late final Future _commandRunnerFut; final _sspRemoteConversationRecordKey = SingleStateProcessor(); + final _uuidGen = const UuidV4(); } diff --git a/lib/chat/models/chat_component_state.dart b/lib/chat/models/chat_component_state.dart index 82e492d..1a52524 100644 --- a/lib/chat/models/chat_component_state.dart +++ b/lib/chat/models/chat_component_state.dart @@ -1,12 +1,7 @@ import 'package:async_tools/async_tools.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_chat_types/flutter_chat_types.dart' show Message, User; -import 'package:flutter_chat_ui/flutter_chat_ui.dart' - show ChatState, InputTextFieldController; +import 'package:flutter_chat_core/flutter_chat_core.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:scroll_to_index/scroll_to_index.dart'; -import 'package:veilid_support/veilid_support.dart'; import 'window_state.dart'; @@ -16,26 +11,16 @@ part 'chat_component_state.freezed.dart'; sealed class ChatComponentState with _$ChatComponentState { const factory ChatComponentState( { - // GlobalKey for the chat - required GlobalKey chatKey, - // ScrollController for the chat - required AutoScrollController scrollController, - // TextEditingController for the chat - required InputTextFieldController textEditingController, // Local user required User? localUser, // Active remote users - required IMap remoteUsers, + required IMap remoteUsers, // Historical remote users - required IMap historicalRemoteUsers, + required IMap historicalRemoteUsers, // Unknown users - required IMap unknownUsers, + required IMap unknownUsers, // Messages state required AsyncValue> messageWindow, // Title of the chat required String title}) = _ChatComponentState; } - -extension ChatComponentStateExt on ChatComponentState { - // -} diff --git a/lib/chat/models/chat_component_state.freezed.dart b/lib/chat/models/chat_component_state.freezed.dart index ae3acee..dd5e68e 100644 --- a/lib/chat/models/chat_component_state.freezed.dart +++ b/lib/chat/models/chat_component_state.freezed.dart @@ -15,15 +15,11 @@ T _$identity(T value) => value; /// @nodoc mixin _$ChatComponentState { -// GlobalKey for the chat - GlobalKey get chatKey; // ScrollController for the chat - AutoScrollController - get scrollController; // TextEditingController for the chat - InputTextFieldController get textEditingController; // Local user +// Local user User? get localUser; // Active remote users - IMap get remoteUsers; // Historical remote users - IMap get historicalRemoteUsers; // Unknown users - IMap get unknownUsers; // Messages state + IMap get remoteUsers; // Historical remote users + IMap get historicalRemoteUsers; // Unknown users + IMap get unknownUsers; // Messages state AsyncValue> get messageWindow; // Title of the chat String get title; @@ -40,11 +36,6 @@ mixin _$ChatComponentState { return identical(this, other) || (other.runtimeType == runtimeType && other is ChatComponentState && - (identical(other.chatKey, chatKey) || other.chatKey == chatKey) && - (identical(other.scrollController, scrollController) || - other.scrollController == scrollController) && - (identical(other.textEditingController, textEditingController) || - other.textEditingController == textEditingController) && (identical(other.localUser, localUser) || other.localUser == localUser) && (identical(other.remoteUsers, remoteUsers) || @@ -59,21 +50,12 @@ mixin _$ChatComponentState { } @override - int get hashCode => Object.hash( - runtimeType, - chatKey, - scrollController, - textEditingController, - localUser, - remoteUsers, - historicalRemoteUsers, - unknownUsers, - messageWindow, - title); + int get hashCode => Object.hash(runtimeType, localUser, remoteUsers, + historicalRemoteUsers, unknownUsers, messageWindow, title); @override String toString() { - return 'ChatComponentState(chatKey: $chatKey, scrollController: $scrollController, textEditingController: $textEditingController, localUser: $localUser, remoteUsers: $remoteUsers, historicalRemoteUsers: $historicalRemoteUsers, unknownUsers: $unknownUsers, messageWindow: $messageWindow, title: $title)'; + return 'ChatComponentState(localUser: $localUser, remoteUsers: $remoteUsers, historicalRemoteUsers: $historicalRemoteUsers, unknownUsers: $unknownUsers, messageWindow: $messageWindow, title: $title)'; } } @@ -84,16 +66,14 @@ abstract mixin class $ChatComponentStateCopyWith<$Res> { _$ChatComponentStateCopyWithImpl; @useResult $Res call( - {GlobalKey chatKey, - AutoScrollController scrollController, - InputTextFieldController textEditingController, - User? localUser, - IMap, User> remoteUsers, - IMap, User> historicalRemoteUsers, - IMap, User> unknownUsers, + {User? localUser, + IMap remoteUsers, + IMap historicalRemoteUsers, + IMap unknownUsers, AsyncValue> messageWindow, String title}); + $UserCopyWith<$Res>? get localUser; $AsyncValueCopyWith, $Res> get messageWindow; } @@ -110,9 +90,6 @@ class _$ChatComponentStateCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? chatKey = null, - Object? scrollController = null, - Object? textEditingController = null, Object? localUser = freezed, Object? remoteUsers = null, Object? historicalRemoteUsers = null, @@ -121,18 +98,6 @@ class _$ChatComponentStateCopyWithImpl<$Res> Object? title = null, }) { return _then(_self.copyWith( - chatKey: null == chatKey - ? _self.chatKey - : chatKey // ignore: cast_nullable_to_non_nullable - as GlobalKey, - scrollController: null == scrollController - ? _self.scrollController - : scrollController // ignore: cast_nullable_to_non_nullable - as AutoScrollController, - textEditingController: null == textEditingController - ? _self.textEditingController - : textEditingController // ignore: cast_nullable_to_non_nullable - as InputTextFieldController, localUser: freezed == localUser ? _self.localUser : localUser // ignore: cast_nullable_to_non_nullable @@ -140,15 +105,15 @@ class _$ChatComponentStateCopyWithImpl<$Res> remoteUsers: null == remoteUsers ? _self.remoteUsers! : remoteUsers // ignore: cast_nullable_to_non_nullable - as IMap, User>, + as IMap, historicalRemoteUsers: null == historicalRemoteUsers ? _self.historicalRemoteUsers! : historicalRemoteUsers // ignore: cast_nullable_to_non_nullable - as IMap, User>, + as IMap, unknownUsers: null == unknownUsers ? _self.unknownUsers! : unknownUsers // ignore: cast_nullable_to_non_nullable - as IMap, User>, + as IMap, messageWindow: null == messageWindow ? _self.messageWindow : messageWindow // ignore: cast_nullable_to_non_nullable @@ -160,6 +125,20 @@ class _$ChatComponentStateCopyWithImpl<$Res> )); } + /// Create a copy of ChatComponentState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $UserCopyWith<$Res>? get localUser { + if (_self.localUser == null) { + return null; + } + + return $UserCopyWith<$Res>(_self.localUser!, (value) { + return _then(_self.copyWith(localUser: value)); + }); + } + /// Create a copy of ChatComponentState /// with the given fields replaced by the non-null parameter values. @override @@ -176,37 +155,25 @@ class _$ChatComponentStateCopyWithImpl<$Res> class _ChatComponentState implements ChatComponentState { const _ChatComponentState( - {required this.chatKey, - required this.scrollController, - required this.textEditingController, - required this.localUser, + {required this.localUser, required this.remoteUsers, required this.historicalRemoteUsers, required this.unknownUsers, required this.messageWindow, required this.title}); -// GlobalKey for the chat - @override - final GlobalKey chatKey; -// ScrollController for the chat - @override - final AutoScrollController scrollController; -// TextEditingController for the chat - @override - final InputTextFieldController textEditingController; // Local user @override final User? localUser; // Active remote users @override - final IMap, User> remoteUsers; + final IMap remoteUsers; // Historical remote users @override - final IMap, User> historicalRemoteUsers; + final IMap historicalRemoteUsers; // Unknown users @override - final IMap, User> unknownUsers; + final IMap unknownUsers; // Messages state @override final AsyncValue> messageWindow; @@ -227,11 +194,6 @@ class _ChatComponentState implements ChatComponentState { return identical(this, other) || (other.runtimeType == runtimeType && other is _ChatComponentState && - (identical(other.chatKey, chatKey) || other.chatKey == chatKey) && - (identical(other.scrollController, scrollController) || - other.scrollController == scrollController) && - (identical(other.textEditingController, textEditingController) || - other.textEditingController == textEditingController) && (identical(other.localUser, localUser) || other.localUser == localUser) && (identical(other.remoteUsers, remoteUsers) || @@ -246,21 +208,12 @@ class _ChatComponentState implements ChatComponentState { } @override - int get hashCode => Object.hash( - runtimeType, - chatKey, - scrollController, - textEditingController, - localUser, - remoteUsers, - historicalRemoteUsers, - unknownUsers, - messageWindow, - title); + int get hashCode => Object.hash(runtimeType, localUser, remoteUsers, + historicalRemoteUsers, unknownUsers, messageWindow, title); @override String toString() { - return 'ChatComponentState(chatKey: $chatKey, scrollController: $scrollController, textEditingController: $textEditingController, localUser: $localUser, remoteUsers: $remoteUsers, historicalRemoteUsers: $historicalRemoteUsers, unknownUsers: $unknownUsers, messageWindow: $messageWindow, title: $title)'; + return 'ChatComponentState(localUser: $localUser, remoteUsers: $remoteUsers, historicalRemoteUsers: $historicalRemoteUsers, unknownUsers: $unknownUsers, messageWindow: $messageWindow, title: $title)'; } } @@ -273,16 +226,15 @@ abstract mixin class _$ChatComponentStateCopyWith<$Res> @override @useResult $Res call( - {GlobalKey chatKey, - AutoScrollController scrollController, - InputTextFieldController textEditingController, - User? localUser, - IMap, User> remoteUsers, - IMap, User> historicalRemoteUsers, - IMap, User> unknownUsers, + {User? localUser, + IMap remoteUsers, + IMap historicalRemoteUsers, + IMap unknownUsers, AsyncValue> messageWindow, String title}); + @override + $UserCopyWith<$Res>? get localUser; @override $AsyncValueCopyWith, $Res> get messageWindow; } @@ -300,9 +252,6 @@ class __$ChatComponentStateCopyWithImpl<$Res> @override @pragma('vm:prefer-inline') $Res call({ - Object? chatKey = null, - Object? scrollController = null, - Object? textEditingController = null, Object? localUser = freezed, Object? remoteUsers = null, Object? historicalRemoteUsers = null, @@ -311,18 +260,6 @@ class __$ChatComponentStateCopyWithImpl<$Res> Object? title = null, }) { return _then(_ChatComponentState( - chatKey: null == chatKey - ? _self.chatKey - : chatKey // ignore: cast_nullable_to_non_nullable - as GlobalKey, - scrollController: null == scrollController - ? _self.scrollController - : scrollController // ignore: cast_nullable_to_non_nullable - as AutoScrollController, - textEditingController: null == textEditingController - ? _self.textEditingController - : textEditingController // ignore: cast_nullable_to_non_nullable - as InputTextFieldController, localUser: freezed == localUser ? _self.localUser : localUser // ignore: cast_nullable_to_non_nullable @@ -330,15 +267,15 @@ class __$ChatComponentStateCopyWithImpl<$Res> remoteUsers: null == remoteUsers ? _self.remoteUsers : remoteUsers // ignore: cast_nullable_to_non_nullable - as IMap, User>, + as IMap, historicalRemoteUsers: null == historicalRemoteUsers ? _self.historicalRemoteUsers : historicalRemoteUsers // ignore: cast_nullable_to_non_nullable - as IMap, User>, + as IMap, unknownUsers: null == unknownUsers ? _self.unknownUsers : unknownUsers // ignore: cast_nullable_to_non_nullable - as IMap, User>, + as IMap, messageWindow: null == messageWindow ? _self.messageWindow : messageWindow // ignore: cast_nullable_to_non_nullable @@ -350,6 +287,20 @@ class __$ChatComponentStateCopyWithImpl<$Res> )); } + /// Create a copy of ChatComponentState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $UserCopyWith<$Res>? get localUser { + if (_self.localUser == null) { + return null; + } + + return $UserCopyWith<$Res>(_self.localUser!, (value) { + return _then(_self.copyWith(localUser: value)); + }); + } + /// Create a copy of ChatComponentState /// with the given fields replaced by the non-null parameter values. @override diff --git a/lib/chat/models/message_state.dart b/lib/chat/models/message_state.dart index cf82021..80852e6 100644 --- a/lib/chat/models/message_state.dart +++ b/lib/chat/models/message_state.dart @@ -25,7 +25,10 @@ enum MessageSendState { @freezed sealed class MessageState with _$MessageState { + @JsonSerializable() const factory MessageState({ + // Sequence number of the message for display purposes + required int seqId, // Content of the message @JsonKey(fromJson: messageFromJson, toJson: messageToJson) required proto.Message content, diff --git a/lib/chat/models/message_state.freezed.dart b/lib/chat/models/message_state.freezed.dart index 0900f8b..342b305 100644 --- a/lib/chat/models/message_state.freezed.dart +++ b/lib/chat/models/message_state.freezed.dart @@ -15,7 +15,8 @@ T _$identity(T value) => value; /// @nodoc mixin _$MessageState implements DiagnosticableTreeMixin { -// Content of the message +// Sequence number of the message for display purposes + int get seqId; // Content of the message @JsonKey(fromJson: messageFromJson, toJson: messageToJson) proto.Message get content; // Sent timestamp Timestamp get sentTimestamp; // Reconciled timestamp @@ -37,6 +38,7 @@ mixin _$MessageState implements DiagnosticableTreeMixin { void debugFillProperties(DiagnosticPropertiesBuilder properties) { properties ..add(DiagnosticsProperty('type', 'MessageState')) + ..add(DiagnosticsProperty('seqId', seqId)) ..add(DiagnosticsProperty('content', content)) ..add(DiagnosticsProperty('sentTimestamp', sentTimestamp)) ..add(DiagnosticsProperty('reconciledTimestamp', reconciledTimestamp)) @@ -48,6 +50,7 @@ mixin _$MessageState implements DiagnosticableTreeMixin { return identical(this, other) || (other.runtimeType == runtimeType && other is MessageState && + (identical(other.seqId, seqId) || other.seqId == seqId) && (identical(other.content, content) || other.content == content) && (identical(other.sentTimestamp, sentTimestamp) || other.sentTimestamp == sentTimestamp) && @@ -59,12 +62,12 @@ mixin _$MessageState implements DiagnosticableTreeMixin { @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, content, sentTimestamp, reconciledTimestamp, sendState); + int get hashCode => Object.hash(runtimeType, seqId, content, sentTimestamp, + reconciledTimestamp, sendState); @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'MessageState(content: $content, sentTimestamp: $sentTimestamp, reconciledTimestamp: $reconciledTimestamp, sendState: $sendState)'; + return 'MessageState(seqId: $seqId, content: $content, sentTimestamp: $sentTimestamp, reconciledTimestamp: $reconciledTimestamp, sendState: $sendState)'; } } @@ -75,7 +78,8 @@ abstract mixin class $MessageStateCopyWith<$Res> { _$MessageStateCopyWithImpl; @useResult $Res call( - {@JsonKey(fromJson: messageFromJson, toJson: messageToJson) + {int seqId, + @JsonKey(fromJson: messageFromJson, toJson: messageToJson) proto.Message content, Timestamp sentTimestamp, Timestamp? reconciledTimestamp, @@ -94,12 +98,17 @@ class _$MessageStateCopyWithImpl<$Res> implements $MessageStateCopyWith<$Res> { @pragma('vm:prefer-inline') @override $Res call({ + Object? seqId = null, Object? content = null, Object? sentTimestamp = null, Object? reconciledTimestamp = freezed, Object? sendState = freezed, }) { return _then(_self.copyWith( + seqId: null == seqId + ? _self.seqId + : seqId // ignore: cast_nullable_to_non_nullable + as int, content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable @@ -121,10 +130,12 @@ class _$MessageStateCopyWithImpl<$Res> implements $MessageStateCopyWith<$Res> { } /// @nodoc + @JsonSerializable() class _MessageState with DiagnosticableTreeMixin implements MessageState { const _MessageState( - {@JsonKey(fromJson: messageFromJson, toJson: messageToJson) + {required this.seqId, + @JsonKey(fromJson: messageFromJson, toJson: messageToJson) required this.content, required this.sentTimestamp, required this.reconciledTimestamp, @@ -132,6 +143,9 @@ class _MessageState with DiagnosticableTreeMixin implements MessageState { factory _MessageState.fromJson(Map json) => _$MessageStateFromJson(json); +// Sequence number of the message for display purposes + @override + final int seqId; // Content of the message @override @JsonKey(fromJson: messageFromJson, toJson: messageToJson) @@ -165,6 +179,7 @@ class _MessageState with DiagnosticableTreeMixin implements MessageState { void debugFillProperties(DiagnosticPropertiesBuilder properties) { properties ..add(DiagnosticsProperty('type', 'MessageState')) + ..add(DiagnosticsProperty('seqId', seqId)) ..add(DiagnosticsProperty('content', content)) ..add(DiagnosticsProperty('sentTimestamp', sentTimestamp)) ..add(DiagnosticsProperty('reconciledTimestamp', reconciledTimestamp)) @@ -176,6 +191,7 @@ class _MessageState with DiagnosticableTreeMixin implements MessageState { return identical(this, other) || (other.runtimeType == runtimeType && other is _MessageState && + (identical(other.seqId, seqId) || other.seqId == seqId) && (identical(other.content, content) || other.content == content) && (identical(other.sentTimestamp, sentTimestamp) || other.sentTimestamp == sentTimestamp) && @@ -187,12 +203,12 @@ class _MessageState with DiagnosticableTreeMixin implements MessageState { @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, content, sentTimestamp, reconciledTimestamp, sendState); + int get hashCode => Object.hash(runtimeType, seqId, content, sentTimestamp, + reconciledTimestamp, sendState); @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'MessageState(content: $content, sentTimestamp: $sentTimestamp, reconciledTimestamp: $reconciledTimestamp, sendState: $sendState)'; + return 'MessageState(seqId: $seqId, content: $content, sentTimestamp: $sentTimestamp, reconciledTimestamp: $reconciledTimestamp, sendState: $sendState)'; } } @@ -205,7 +221,8 @@ abstract mixin class _$MessageStateCopyWith<$Res> @override @useResult $Res call( - {@JsonKey(fromJson: messageFromJson, toJson: messageToJson) + {int seqId, + @JsonKey(fromJson: messageFromJson, toJson: messageToJson) proto.Message content, Timestamp sentTimestamp, Timestamp? reconciledTimestamp, @@ -225,12 +242,17 @@ class __$MessageStateCopyWithImpl<$Res> @override @pragma('vm:prefer-inline') $Res call({ + Object? seqId = null, Object? content = null, Object? sentTimestamp = null, Object? reconciledTimestamp = freezed, Object? sendState = freezed, }) { return _then(_MessageState( + seqId: null == seqId + ? _self.seqId + : seqId // ignore: cast_nullable_to_non_nullable + as int, content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable diff --git a/lib/chat/models/message_state.g.dart b/lib/chat/models/message_state.g.dart index daae37f..2eee78d 100644 --- a/lib/chat/models/message_state.g.dart +++ b/lib/chat/models/message_state.g.dart @@ -8,6 +8,7 @@ part of 'message_state.dart'; _MessageState _$MessageStateFromJson(Map json) => _MessageState( + seqId: (json['seq_id'] as num).toInt(), content: messageFromJson(json['content'] as Map), sentTimestamp: Timestamp.fromJson(json['sent_timestamp']), reconciledTimestamp: json['reconciled_timestamp'] == null @@ -20,6 +21,7 @@ _MessageState _$MessageStateFromJson(Map json) => Map _$MessageStateToJson(_MessageState instance) => { + 'seq_id': instance.seqId, 'content': messageToJson(instance.content), 'sent_timestamp': instance.sentTimestamp.toJson(), 'reconciled_timestamp': instance.reconciledTimestamp?.toJson(), diff --git a/lib/chat/views/chat_builders/chat_builders.dart b/lib/chat/views/chat_builders/chat_builders.dart new file mode 100644 index 0000000..529341f --- /dev/null +++ b/lib/chat/views/chat_builders/chat_builders.dart @@ -0,0 +1,2 @@ +export 'vc_composer_widget.dart'; +export 'vc_text_message_widget.dart'; diff --git a/lib/chat/views/chat_builders/vc_composer_widget.dart b/lib/chat/views/chat_builders/vc_composer_widget.dart new file mode 100644 index 0000000..b3eb1e5 --- /dev/null +++ b/lib/chat/views/chat_builders/vc_composer_widget.dart @@ -0,0 +1,431 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_chat_ui/flutter_chat_ui.dart'; +// Typedefs need to come out +// ignore: implementation_imports +import 'package:flutter_chat_ui/src/utils/typedefs.dart'; +import 'package:provider/provider.dart'; + +import '../../../theme/theme.dart'; +import '../../chat.dart'; + +enum ShiftEnterAction { newline, send } + +/// The message composer widget positioned at the bottom of the chat screen. +/// +/// Includes a text input field, an optional attachment button, +/// and a send button. +class VcComposerWidget extends StatefulWidget { + /// Creates a message composer widget. + const VcComposerWidget({ + super.key, + this.textEditingController, + this.left = 0, + this.right = 0, + this.top, + this.bottom = 0, + this.sigmaX = 20, + this.sigmaY = 20, + this.padding = const EdgeInsets.all(8), + this.attachmentIcon = const Icon(Icons.attachment), + this.sendIcon = const Icon(Icons.send), + this.gap = 8, + this.inputBorder, + this.filled, + this.topWidget, + this.handleSafeArea = true, + this.backgroundColor, + this.attachmentIconColor, + this.sendIconColor, + this.hintColor, + this.textColor, + this.inputFillColor, + this.hintText = 'Type a message', + this.keyboardAppearance, + this.autocorrect, + this.autofocus = false, + this.textCapitalization = TextCapitalization.sentences, + this.keyboardType, + this.textInputAction = TextInputAction.newline, + this.shiftEnterAction = ShiftEnterAction.send, + this.focusNode, + this.maxLength, + this.minLines = 1, + this.maxLines = 3, + }); + + /// Optional controller for the text input field. + final TextEditingController? textEditingController; + + /// Optional left position. + final double? left; + + /// Optional right position. + final double? right; + + /// Optional top position. + final double? top; + + /// Optional bottom position. + final double? bottom; + + /// Optional X blur value for the background (if using glassmorphism). + final double? sigmaX; + + /// Optional Y blur value for the background (if using glassmorphism). + final double? sigmaY; + + /// Padding around the composer content. + final EdgeInsetsGeometry? padding; + + /// Icon for the attachment button. Defaults to [Icons.attachment]. + final Widget? attachmentIcon; + + /// Icon for the send button. Defaults to [Icons.send]. + final Widget? sendIcon; + + /// Horizontal gap between elements (attachment icon, text field, send icon). + final double? gap; + + /// Border style for the text input field. + final InputBorder? inputBorder; + + /// Whether the text input field should be filled. + final bool? filled; + + /// Optional widget to display above the main composer row. + final Widget? topWidget; + + /// Whether to adjust padding for the bottom safe area. + final bool handleSafeArea; + + /// Background color of the composer container. + final Color? backgroundColor; + + /// Color of the attachment icon. + final Color? attachmentIconColor; + + /// Color of the send icon. + final Color? sendIconColor; + + /// Color of the hint text in the input field. + final Color? hintColor; + + /// Color of the text entered in the input field. + final Color? textColor; + + /// Fill color for the text input field when [filled] is true. + final Color? inputFillColor; + + /// Placeholder text for the input field. + final String? hintText; + + /// Appearance of the keyboard. + final Brightness? keyboardAppearance; + + /// Whether to enable autocorrect for the input field. + final bool? autocorrect; + + /// Whether the input field should autofocus. + final bool autofocus; + + /// Capitalization behavior for the input field. + final TextCapitalization textCapitalization; + + /// Type of keyboard to display. + final TextInputType? keyboardType; + + /// Action button type for the keyboard (e.g., newline, send). + final TextInputAction textInputAction; + + /// Action when shift-enter is pressed (e.g., newline, send). + final ShiftEnterAction shiftEnterAction; + + /// Focus node for the text input field. + final FocusNode? focusNode; + + /// Maximum character length for the input field. + final int? maxLength; + + /// Minimum number of lines for the input field. + final int? minLines; + + /// Maximum number of lines the input field can expand to. + final int? maxLines; + + @override + State createState() => _VcComposerState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty( + 'textEditingController', textEditingController)) + ..add(DoubleProperty('left', left)) + ..add(DoubleProperty('right', right)) + ..add(DoubleProperty('top', top)) + ..add(DoubleProperty('bottom', bottom)) + ..add(DoubleProperty('sigmaX', sigmaX)) + ..add(DoubleProperty('sigmaY', sigmaY)) + ..add(DiagnosticsProperty('padding', padding)) + ..add(DoubleProperty('gap', gap)) + ..add(DiagnosticsProperty('inputBorder', inputBorder)) + ..add(DiagnosticsProperty('filled', filled)) + ..add(DiagnosticsProperty('handleSafeArea', handleSafeArea)) + ..add(ColorProperty('backgroundColor', backgroundColor)) + ..add(ColorProperty('attachmentIconColor', attachmentIconColor)) + ..add(ColorProperty('sendIconColor', sendIconColor)) + ..add(ColorProperty('hintColor', hintColor)) + ..add(ColorProperty('textColor', textColor)) + ..add(ColorProperty('inputFillColor', inputFillColor)) + ..add(StringProperty('hintText', hintText)) + ..add(EnumProperty('keyboardAppearance', keyboardAppearance)) + ..add(DiagnosticsProperty('autocorrect', autocorrect)) + ..add(DiagnosticsProperty('autofocus', autofocus)) + ..add(EnumProperty( + 'textCapitalization', textCapitalization)) + ..add(DiagnosticsProperty('keyboardType', keyboardType)) + ..add(EnumProperty('textInputAction', textInputAction)) + ..add( + EnumProperty('shiftEnterAction', shiftEnterAction)) + ..add(DiagnosticsProperty('focusNode', focusNode)) + ..add(IntProperty('maxLength', maxLength)) + ..add(IntProperty('minLines', minLines)) + ..add(IntProperty('maxLines', maxLines)); + } +} + +class _VcComposerState extends State { + final _key = GlobalKey(); + late final TextEditingController _textController; + late final FocusNode _focusNode; + late String _suffixText; + + @override + void initState() { + super.initState(); + _textController = widget.textEditingController ?? TextEditingController(); + _focusNode = widget.focusNode ?? FocusNode(); + _focusNode.onKeyEvent = _handleKeyEvent; + _updateSuffixText(); + WidgetsBinding.instance.addPostFrameCallback((_) => _measure()); + } + + void _updateSuffixText() { + final utf8Length = utf8.encode(_textController.text).length; + _suffixText = '$utf8Length/${widget.maxLength}'; + } + + KeyEventResult _handleKeyEvent(FocusNode node, KeyEvent event) { + // Check for Shift+Enter + if (event is KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.enter && + HardwareKeyboard.instance.isShiftPressed) { + if (widget.shiftEnterAction == ShiftEnterAction.send) { + _handleSubmitted(_textController.text); + return KeyEventResult.handled; + } else if (widget.shiftEnterAction == ShiftEnterAction.newline) { + final val = _textController.value; + final insertOffset = val.selection.extent.offset; + final messageWithNewLine = + '${_textController.text.substring(0, insertOffset)}\n' + '${_textController.text.substring(insertOffset)}'; + _textController.value = TextEditingValue( + text: messageWithNewLine, + selection: TextSelection.fromPosition( + TextPosition(offset: insertOffset + 1), + ), + ); + return KeyEventResult.handled; + } + } + return KeyEventResult.ignored; + } + + @override + void didUpdateWidget(covariant VcComposerWidget oldWidget) { + super.didUpdateWidget(oldWidget); + WidgetsBinding.instance.addPostFrameCallback((_) => _measure()); + } + + @override + void dispose() { + // Only try to dispose text controller if it's not provided, let + // user handle disposing it how they want. + if (widget.textEditingController == null) { + _textController.dispose(); + } + if (widget.focusNode == null) { + _focusNode.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final bottomSafeArea = + widget.handleSafeArea ? MediaQuery.of(context).padding.bottom : 0.0; + final onAttachmentTap = context.read(); + final theme = Theme.of(context); + final scaleTheme = theme.extension()!; + final config = scaleTheme.config; + final scheme = scaleTheme.scheme; + final scale = scaleTheme.scheme.scale(ScaleKind.primary); + final textTheme = theme.textTheme; + final scaleChatTheme = scaleTheme.chatTheme(); + final chatTheme = scaleChatTheme.chatTheme; + + final suffixTextStyle = + textTheme.bodySmall!.copyWith(color: scale.subtleText); + + return Positioned( + left: widget.left, + right: widget.right, + top: widget.top, + bottom: widget.bottom, + child: ClipRect( + child: DecoratedBox( + key: _key, + decoration: BoxDecoration( + border: config.preferBorders + ? Border(top: BorderSide(color: scale.border, width: 2)) + : null, + color: config.preferBorders + ? scale.elementBackground + : scale.border), + child: Column( + children: [ + if (widget.topWidget != null) widget.topWidget!, + Padding( + padding: widget.handleSafeArea + ? (widget.padding?.add( + EdgeInsets.only(bottom: bottomSafeArea), + ) ?? + EdgeInsets.only(bottom: bottomSafeArea)) + : (widget.padding ?? EdgeInsets.zero), + child: Row( + children: [ + if (widget.attachmentIcon != null && + onAttachmentTap != null) + IconButton( + icon: widget.attachmentIcon!, + color: widget.attachmentIconColor ?? + chatTheme.colors.onSurface.withValues(alpha: 0.5), + onPressed: onAttachmentTap, + ) + else + const SizedBox.shrink(), + SizedBox(width: widget.gap), + Expanded( + child: TextField( + controller: _textController, + decoration: InputDecoration( + filled: widget.filled ?? !config.preferBorders, + fillColor: widget.inputFillColor ?? + scheme.primaryScale.subtleBackground, + isDense: true, + contentPadding: + const EdgeInsets.fromLTRB(8, 8, 8, 8), + disabledBorder: OutlineInputBorder( + borderSide: config.preferBorders + ? BorderSide( + color: scheme.grayScale.border, + width: 2) + : BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular( + 8 * config.borderRadiusScale))), + enabledBorder: OutlineInputBorder( + borderSide: config.preferBorders + ? BorderSide( + color: scheme.primaryScale.border, + width: 2) + : BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular( + 8 * config.borderRadiusScale))), + focusedBorder: OutlineInputBorder( + borderSide: config.preferBorders + ? BorderSide( + color: scheme.primaryScale.border, + width: 2) + : BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular( + 8 * config.borderRadiusScale))), + hintText: widget.hintText, + hintStyle: chatTheme.typography.bodyMedium.copyWith( + color: widget.hintColor ?? + chatTheme.colors.onSurface + .withValues(alpha: 0.5), + ), + border: widget.inputBorder, + hoverColor: Colors.transparent, + suffix: Text(_suffixText, style: suffixTextStyle)), + onSubmitted: _handleSubmitted, + onChanged: (value) { + setState(_updateSuffixText); + }, + textInputAction: widget.textInputAction, + keyboardAppearance: widget.keyboardAppearance, + autocorrect: widget.autocorrect ?? true, + autofocus: widget.autofocus, + textCapitalization: widget.textCapitalization, + keyboardType: widget.keyboardType, + focusNode: _focusNode, + //maxLength: widget.maxLength, + minLines: widget.minLines, + maxLines: widget.maxLines, + maxLengthEnforcement: MaxLengthEnforcement.none, + inputFormatters: [ + Utf8LengthLimitingTextInputFormatter( + maxLength: widget.maxLength), + ], + ), + ), + SizedBox(width: widget.gap), + if ((widget.sendIcon ?? scaleChatTheme.sendButtonIcon) != + null) + IconButton( + icon: + (widget.sendIcon ?? scaleChatTheme.sendButtonIcon)!, + color: widget.sendIconColor, + onPressed: () => _handleSubmitted(_textController.text), + ) + else + const SizedBox.shrink(), + ], + ), + ), + ], + ), + ), + ), + ); + } + + void _measure() { + if (!mounted) { + return; + } + + final renderBox = _key.currentContext?.findRenderObject() as RenderBox?; + if (renderBox != null) { + final height = renderBox.size.height; + final bottomSafeArea = MediaQuery.of(context).padding.bottom; + + context.read().setHeight( + // only set real height of the composer, ignoring safe area + widget.handleSafeArea ? height - bottomSafeArea : height, + ); + } + } + + void _handleSubmitted(String text) { + if (text.isNotEmpty) { + context.read()?.call(text); + _textController.clear(); + } + } +} diff --git a/lib/chat/views/chat_builders/vc_text_message_widget.dart b/lib/chat/views/chat_builders/vc_text_message_widget.dart new file mode 100644 index 0000000..fc1fe80 --- /dev/null +++ b/lib/chat/views/chat_builders/vc_text_message_widget.dart @@ -0,0 +1,269 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_chat_core/flutter_chat_core.dart'; +import 'package:provider/provider.dart'; + +import '../../../theme/theme.dart'; +import '../date_formatter.dart'; + +/// A widget that displays a text message. +class VcTextMessageWidget extends StatelessWidget { + /// Creates a widget to display a simple text message. + const VcTextMessageWidget({ + required this.message, + required this.index, + this.padding = const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + this.borderRadius, + this.onlyEmojiFontSize, + this.sentBackgroundColor, + this.receivedBackgroundColor, + this.sentTextStyle, + this.receivedTextStyle, + this.timeStyle, + this.showTime = true, + this.showStatus = true, + this.timeAndStatusPosition = TimeAndStatusPosition.end, + super.key, + }); + + /// The text message data model. + final TextMessage message; + + /// The index of the message in the list. + final int index; + + /// Padding around the message bubble content. + final EdgeInsetsGeometry? padding; + + /// Border radius of the message bubble. + final BorderRadiusGeometry? borderRadius; + + /// Font size for messages containing only emojis. + final double? onlyEmojiFontSize; + + /// Background color for messages sent by the current user. + final Color? sentBackgroundColor; + + /// Background color for messages received from other users. + final Color? receivedBackgroundColor; + + /// Text style for messages sent by the current user. + final TextStyle? sentTextStyle; + + /// Text style for messages received from other users. + final TextStyle? receivedTextStyle; + + /// Text style for the message timestamp and status. + final TextStyle? timeStyle; + + /// Whether to display the message timestamp. + final bool showTime; + + /// Whether to display the message status (sent, delivered, seen) + /// for sent messages. + final bool showStatus; + + /// Position of the timestamp and status indicator relative to the text. + final TimeAndStatusPosition timeAndStatusPosition; + + bool get _isOnlyEmoji => message.metadata?['isOnlyEmoji'] == true; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final scaleTheme = theme.extension()!; + final config = scaleTheme.config; + final scheme = scaleTheme.scheme; + final scale = scaleTheme.scheme.scale(ScaleKind.primary); + final textTheme = theme.textTheme; + final scaleChatTheme = scaleTheme.chatTheme(); + final chatTheme = scaleChatTheme.chatTheme; + + final isSentByMe = context.watch() == message.authorId; + final backgroundColor = _resolveBackgroundColor(isSentByMe, scaleChatTheme); + final textStyle = _resolveTextStyle(isSentByMe, scaleChatTheme); + final timeStyle = _resolveTimeStyle(isSentByMe, scaleChatTheme); + final emojiFontSize = onlyEmojiFontSize ?? scaleChatTheme.onlyEmojiFontSize; + + final timeAndStatus = showTime || (isSentByMe && showStatus) + ? TimeAndStatus( + time: message.time, + status: message.status, + showTime: showTime, + showStatus: isSentByMe && showStatus, + textStyle: timeStyle, + ) + : null; + + final textContent = Text( + message.text, + style: _isOnlyEmoji + ? textStyle.copyWith(fontSize: emojiFontSize) + : textStyle, + ); + + return Container( + padding: _isOnlyEmoji + ? EdgeInsets.symmetric( + horizontal: (padding?.horizontal ?? 0) / 2, + // vertical: 0, + ) + : padding, + decoration: _isOnlyEmoji + ? null + : BoxDecoration( + color: backgroundColor, + borderRadius: borderRadius ?? chatTheme.shape, + ), + child: _buildContentBasedOnPosition( + context: context, + textContent: textContent, + timeAndStatus: timeAndStatus, + textStyle: textStyle, + ), + ); + } + + Widget _buildContentBasedOnPosition({ + required BuildContext context, + required Widget textContent, + TimeAndStatus? timeAndStatus, + TextStyle? textStyle, + }) { + if (timeAndStatus == null) { + return textContent; + } + + switch (timeAndStatusPosition) { + case TimeAndStatusPosition.start: + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [textContent, timeAndStatus], + ); + case TimeAndStatusPosition.inline: + return Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Flexible(child: textContent), + const SizedBox(width: 4), + timeAndStatus, + ], + ); + case TimeAndStatusPosition.end: + return Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [textContent, timeAndStatus], + ); + } + } + + Color _resolveBackgroundColor(bool isSentByMe, ScaleChatTheme theme) { + if (isSentByMe) { + return sentBackgroundColor ?? theme.primaryColor; + } + return receivedBackgroundColor ?? theme.secondaryColor; + } + + TextStyle _resolveTextStyle(bool isSentByMe, ScaleChatTheme theme) { + if (isSentByMe) { + return sentTextStyle ?? theme.sentMessageBodyTextStyle; + } + return receivedTextStyle ?? theme.receivedMessageBodyTextStyle; + } + + TextStyle _resolveTimeStyle(bool isSentByMe, ScaleChatTheme theme) { + final ts = _resolveTextStyle(isSentByMe, theme); + + return theme.timeStyle.copyWith(color: ts.color); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('message', message)) + ..add(IntProperty('index', index)) + ..add(DiagnosticsProperty('padding', padding)) + ..add(DiagnosticsProperty( + 'borderRadius', borderRadius)) + ..add(DoubleProperty('onlyEmojiFontSize', onlyEmojiFontSize)) + ..add(ColorProperty('sentBackgroundColor', sentBackgroundColor)) + ..add(ColorProperty('receivedBackgroundColor', receivedBackgroundColor)) + ..add(DiagnosticsProperty('sentTextStyle', sentTextStyle)) + ..add(DiagnosticsProperty( + 'receivedTextStyle', receivedTextStyle)) + ..add(DiagnosticsProperty('timeStyle', timeStyle)) + ..add(DiagnosticsProperty('showTime', showTime)) + ..add(DiagnosticsProperty('showStatus', showStatus)) + ..add(EnumProperty( + 'timeAndStatusPosition', timeAndStatusPosition)); + } +} + +/// A widget to display the message timestamp and status indicator. +class TimeAndStatus extends StatelessWidget { + /// Creates a widget for displaying time and status. + const TimeAndStatus({ + required this.time, + this.status, + this.showTime = true, + this.showStatus = true, + this.textStyle, + super.key, + }); + + /// The time the message was created. + final DateTime? time; + + /// The status of the message. + final MessageStatus? status; + + /// Whether to display the timestamp. + final bool showTime; + + /// Whether to display the status indicator. + final bool showStatus; + + /// The text style for the time and status. + final TextStyle? textStyle; + + @override + Widget build(BuildContext context) { + final dformat = DateFormatter(); + + return Row( + spacing: 2, + mainAxisSize: MainAxisSize.min, + children: [ + if (showTime && time != null) + Text(dformat.chatDateTimeFormat(time!.toLocal()), style: textStyle), + if (showStatus && status != null) + if (status == MessageStatus.sending) + SizedBox( + width: 6, + height: 6, + child: CircularProgressIndicator( + color: textStyle?.color, + strokeWidth: 2, + ), + ) + else + Icon(getIconForStatus(status!), color: textStyle?.color, size: 12), + ], + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('time', time)) + ..add(EnumProperty('status', status)) + ..add(DiagnosticsProperty('showTime', showTime)) + ..add(DiagnosticsProperty('showStatus', showStatus)) + ..add(DiagnosticsProperty('textStyle', textStyle)); + } +} diff --git a/lib/chat/views/chat_component_widget.dart b/lib/chat/views/chat_component_widget.dart index 1646772..aed7356 100644 --- a/lib/chat/views/chat_component_widget.dart +++ b/lib/chat/views/chat_component_widget.dart @@ -1,11 +1,13 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:math'; import 'package:async_tools/async_tools.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_chat_types/flutter_chat_types.dart' as types; +import 'package:flutter_chat_core/flutter_chat_core.dart' as core; import 'package:flutter_chat_ui/flutter_chat_ui.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:veilid_support/veilid_support.dart'; @@ -16,11 +18,15 @@ import '../../conversation/conversation.dart'; import '../../notifications/notifications.dart'; import '../../theme/theme.dart'; import '../chat.dart'; +import 'chat_builders/chat_builders.dart'; const onEndReachedThreshold = 0.75; +const _kScrollTag = 'kScrollTag'; +const kSeqId = 'seqId'; +const maxMessageLength = 2048; -class ChatComponentWidget extends StatelessWidget { - const ChatComponentWidget({ +class ChatComponentWidget extends StatefulWidget { + const ChatComponentWidget._({ required super.key, required TypedKey localConversationRecordKey, required void Function() onCancel, @@ -29,10 +35,14 @@ class ChatComponentWidget extends StatelessWidget { _onCancel = onCancel, _onClose = onClose; - ///////////////////////////////////////////////////////////////////// - - @override - Widget build(BuildContext context) { + // Create a single-contact chat and its associated state + static Widget singleContact({ + required BuildContext context, + required TypedKey localConversationRecordKey, + required void Function() onCancel, + required void Function() onClose, + Key? key, + }) { // Get the account info final accountInfo = context.watch().state; @@ -45,19 +55,19 @@ class ChatComponentWidget extends StatelessWidget { // Get the active conversation cubit final activeConversationCubit = context .select( - (x) => x.tryOperateSync(_localConversationRecordKey, + (x) => x.tryOperateSync(localConversationRecordKey, closure: (cubit) => cubit)); if (activeConversationCubit == null) { - return waitingPage(onCancel: _onCancel); + return waitingPage(onCancel: onCancel); } // Get the messages cubit final messagesCubit = context.select( - (x) => x.tryOperateSync(_localConversationRecordKey, + (x) => x.tryOperateSync(localConversationRecordKey, closure: (cubit) => cubit)); if (messagesCubit == null) { - return waitingPage(onCancel: _onCancel); + return waitingPage(onCancel: onCancel); } // Make chat component state @@ -70,26 +80,65 @@ class ChatComponentWidget extends StatelessWidget { activeConversationCubit: activeConversationCubit, messagesCubit: messagesCubit, ), - child: Builder(builder: _buildChatComponent)); + child: ChatComponentWidget._( + key: ValueKey(localConversationRecordKey), + localConversationRecordKey: localConversationRecordKey, + onCancel: onCancel, + onClose: onClose)); } - ///////////////////////////////////////////////////////////////////// + @override + State createState() => _ChatComponentWidgetState(); - Widget _buildChatComponent(BuildContext context) { + //////////////////////////////////////////////////////////////////////////// + final TypedKey _localConversationRecordKey; + final void Function() _onCancel; + final void Function() _onClose; +} + +class _ChatComponentWidgetState extends State { + //////////////////////////////////////////////////////////////////// + + @override + void initState() { + _chatController = core.InMemoryChatController(); + _textEditingController = TextEditingController(); + _scrollController = ScrollController(); + _chatStateProcessor = SingleStateProcessor(); + + final _chatComponentCubit = context.read(); + _chatStateProcessor.follow(_chatComponentCubit.stream, + _chatComponentCubit.state, _updateChatState); + + super.initState(); + } + + @override + void dispose() { + unawaited(_chatStateProcessor.close()); + + _chatController.dispose(); + _scrollController.dispose(); + _textEditingController.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { final theme = Theme.of(context); - final scaleScheme = theme.extension()!; - final scaleConfig = theme.extension()!; - final scale = scaleScheme.scale(ScaleKind.primary); + final scaleTheme = theme.extension()!; + final scale = scaleTheme.scheme.scale(ScaleKind.primary); final textTheme = theme.textTheme; - final chatTheme = makeChatTheme(scaleScheme, scaleConfig, textTheme); - final errorChatTheme = (ChatThemeEditor(chatTheme) - ..inputTextColor = scaleScheme.errorScale.primary - ..sendButtonIcon = Image.asset( - 'assets/icon-send.png', - color: scaleScheme.errorScale.primary, - package: 'flutter_chat_ui', - )) - .commit(); + final scaleChatTheme = scaleTheme.chatTheme(); + // final errorChatTheme = chatTheme.copyWith(color:) + // ..inputTextColor = scaleScheme.errorScale.primary + // ..sendButtonIcon = Image.asset( + // 'assets/icon-send.png', + // color: scaleScheme.errorScale.primary, + // package: 'flutter_chat_ui', + // )) + // .commit(); // Get the enclosing chat component cubit that contains our state // (created by ChatComponentWidget.builder()) @@ -110,9 +159,8 @@ class ChatComponentWidget extends StatelessWidget { final title = chatComponentState.title; if (chatComponentCubit.scrollOffset != 0) { - chatComponentState.scrollController.position.correctPixels( - chatComponentState.scrollController.position.pixels + - chatComponentCubit.scrollOffset); + _scrollController.position.correctPixels( + _scrollController.position.pixels + chatComponentCubit.scrollOffset); chatComponentCubit.scrollOffset = 0; } @@ -138,7 +186,7 @@ class ChatComponentWidget extends StatelessWidget { IconButton( iconSize: 24, icon: Icon(Icons.close, color: scale.borderText), - onPressed: _onClose) + onPressed: widget._onClose) .paddingLTRB(0, 0, 8, 0) ]), ), @@ -164,7 +212,7 @@ class ChatComponentWidget extends StatelessWidget { chatComponentCubit.scrollOffset = scrollOffset; // - singleFuture(chatComponentState.chatKey, () async { + singleFuture((chatComponentCubit, _kScrollTag), () async { await _handlePageForward( chatComponentCubit, messageWindow, notification); }); @@ -182,7 +230,7 @@ class ChatComponentWidget extends StatelessWidget { chatComponentCubit.scrollOffset = scrollOffset; // - singleFuture(chatComponentState.chatKey, () async { + singleFuture((chatComponentCubit, _kScrollTag), () async { await _handlePageBackward( chatComponentCubit, messageWindow, notification); }); @@ -190,82 +238,181 @@ class ChatComponentWidget extends StatelessWidget { return false; }, child: ValueListenableBuilder( - valueListenable: chatComponentState.textEditingController, + valueListenable: _textEditingController, builder: (context, textEditingValue, __) { final messageIsValid = - utf8.encode(textEditingValue.text).lengthInBytes < - 2048; + _messageIsValid(textEditingValue.text); + var sendIconColor = scaleTheme.config.preferBorders + ? scale.border + : scale.borderText; + + if (!messageIsValid || + _textEditingController.text.isEmpty) { + sendIconColor = sendIconColor.withAlpha(128); + } return Chat( - key: chatComponentState.chatKey, - theme: messageIsValid ? chatTheme : errorChatTheme, - messages: messageWindow.window.toList(), - scrollToBottomOnSend: isFirstPage, - scrollController: chatComponentState.scrollController, - inputOptions: InputOptions( - inputClearMode: messageIsValid - ? InputClearMode.always - : InputClearMode.never, - textEditingController: - chatComponentState.textEditingController), - // isLastPage: isLastPage, - // onEndReached: () async { - // await _handlePageBackward( - // chatComponentCubit, messageWindow); - // }, - //onEndReachedThreshold: onEndReachedThreshold, - //onAttachmentPressed: _handleAttachmentPressed, - //onMessageTap: _handleMessageTap, - //onPreviewDataFetched: _handlePreviewDataFetched, - usePreviewData: false, // - onSendPressed: (pt) { - try { - if (!messageIsValid) { - context.read().error( - text: translate('chat.message_too_long')); - return; - } - _handleSendPressed(chatComponentCubit, pt); - } on FormatException { - context.read().error( - text: translate('chat.message_too_long')); - } - }, - listBottomWidget: messageIsValid - ? null - : Text(translate('chat.message_too_long'), - style: TextStyle( - color: - scaleScheme.errorScale.primary)) - .toCenter(), - //showUserAvatars: false, - //showUserNames: true, - user: localUser, - emptyState: const EmptyChatWidget()); + currentUserId: localUser.id, + resolveUser: (id) async { + if (id == localUser.id) { + return localUser; + } + return chatComponentState.remoteUsers.get(id); + }, + chatController: _chatController, + onMessageSend: (text) => + _handleSendPressed(chatComponentCubit, text), + theme: scaleChatTheme.chatTheme, + builders: core.Builders( + // Chat list builder + chatAnimatedListBuilder: (context, itemBuilder) => + ChatAnimatedListReversed( + scrollController: _scrollController, + itemBuilder: itemBuilder), + // Text message builder + textMessageBuilder: (context, message, index) => + VcTextMessageWidget( + message: message, + index: index, + // showTime: true, + // showStatus: true, + ), + // Composer builder + composerBuilder: (ctx) => VcComposerWidget( + autofocus: true, + textInputAction: isAnyMobile + ? TextInputAction.newline + : TextInputAction.send, + shiftEnterAction: isAnyMobile + ? ShiftEnterAction.send + : ShiftEnterAction.newline, + textEditingController: _textEditingController, + maxLength: maxMessageLength, + keyboardType: TextInputType.multiline, + sendIconColor: sendIconColor, + topWidget: messageIsValid + ? null + : Text(translate('chat.message_too_long'), + style: TextStyle( + color: scaleTheme + .scheme.errorScale.primary)) + .toCenter(), + ), + ), + timeFormat: core.DateFormat.jm(), + ); }))).expanded(), ], ); } - void _handleSendPressed( - ChatComponentCubit chatComponentCubit, types.PartialText message) { - final text = message.text; + ///////////////////////////////////////////////////////////////////// + bool _messageIsValid(String text) => + utf8.encode(text).lengthInBytes < maxMessageLength; + + Future _updateChatState(ChatComponentState chatComponentState) async { + // Update message window state + final data = chatComponentState.messageWindow.asData; + if (data == null) { + await _chatController.setMessages([]); + return; + } + + final windowState = data.value; + + await _chatController.setMessages(windowState.window.toList()); + + // final newMessagesSet = windowState.window.toSet(); + // final newMessagesById = + // Map.fromEntries(newMessagesSet.map((m) => MapEntry(m.id, m))); + // final newMessagesBySeqId = Map.fromEntries( + // newMessagesSet.map((m) => MapEntry(m.metadata![kSeqId], m))); + // final oldMessagesSet = _chatController.messages.toSet(); + + // if (oldMessagesSet.isEmpty) { + // await _chatController.setMessages(windowState.window.toList()); + // return; + // } + + // // See how many messages differ by equality (not identity) + // // If there are more than `replaceAllMessagesThreshold` differences + // // just replace the whole list of messages + // final diffs = newMessagesSet.diffAndIntersect(oldMessagesSet, + // diffThisMinusOther: true, diffOtherMinusThis: true); + // final addedMessages = diffs.diffThisMinusOther!; + // final removedMessages = diffs.diffOtherMinusThis!; + + // final replaceAllPaginationLimit = windowState.windowCount / 3; + + // if ((addedMessages.length >= replaceAllPaginationLimit) || + // removedMessages.length >= replaceAllPaginationLimit) { + // await _chatController.setMessages(windowState.window.toList()); + // return; + // } + + // // Remove messages that are gone, and replace the ones that have changed + // for (final m in removedMessages) { + // final newm = newMessagesById[m.id]; + // if (newm != null) { + // await _chatController.updateMessage(m, newm); + // } else { + // final newm = newMessagesBySeqId[m.metadata![kSeqId]]; + // if (newm != null) { + // await _chatController.updateMessage(m, newm); + // addedMessages.remove(newm); + // } else { + // await _chatController.removeMessage(m); + // } + // } + // } + + // // // Check for append + // if (addedMessages.isNotEmpty) { + // if (_chatController.messages.isNotEmpty && + // (addedMessages.first.metadata![kSeqId] as int) > + // (_chatController.messages.reversed.last.metadata![kSeqId] + // as int)) { + // await _chatController.insertAllMessages(addedMessages.reversedView, + // index: 0); + // } + // // Check for prepend + // else if (_chatController.messages.isNotEmpty && + // (addedMessages.last.metadata![kSeqId] as int) < + // (_chatController.messages.reversed.first.metadata![kSeqId] + // as int)) { + // await _chatController.insertAllMessages( + // addedMessages.reversedView, + // ); + // } + // // Otherwise just replace + // // xxx could use a better algorithm here to merge added messages in + // else { + // await _chatController.setMessages(windowState.window.toList()); + // } + // } + } + + void _handleSendPressed(ChatComponentCubit chatComponentCubit, String text) { if (text.startsWith('/')) { chatComponentCubit.runCommand(text); return; } - chatComponentCubit.sendMessage(message); + if (!_messageIsValid(text)) { + context + .read() + .error(text: translate('chat.message_too_long')); + return; + } + + chatComponentCubit.sendMessage(text: text); } // void _handleAttachmentPressed() async { - // // - // } - Future _handlePageForward( ChatComponentCubit chatComponentCubit, - WindowState messageWindow, + WindowState messageWindow, ScrollNotification notification) async { debugPrint( '_handlePageForward: messagesState.length=${messageWindow.length} ' @@ -299,7 +446,7 @@ class ChatComponentWidget extends StatelessWidget { Future _handlePageBackward( ChatComponentCubit chatComponentCubit, - WindowState messageWindow, + WindowState messageWindow, ScrollNotification notification, ) async { debugPrint( @@ -335,8 +482,8 @@ class ChatComponentWidget extends StatelessWidget { //chatComponentCubit.scrollOffset = 0; } - //////////////////////////////////////////////////////////////////////////// - final TypedKey _localConversationRecordKey; - final void Function() _onCancel; - final void Function() _onClose; + late final core.ChatController _chatController; + late final TextEditingController _textEditingController; + late final ScrollController _scrollController; + late final SingleStateProcessor _chatStateProcessor; } diff --git a/lib/chat/views/date_formatter.dart b/lib/chat/views/date_formatter.dart new file mode 100644 index 0000000..2835b70 --- /dev/null +++ b/lib/chat/views/date_formatter.dart @@ -0,0 +1,41 @@ +import 'package:flutter_translate/flutter_translate.dart'; +import 'package:intl/intl.dart'; + +class DateFormatter { + DateFormatter(); + + String chatDateTimeFormat(DateTime dateTime) { + final now = DateTime.now(); + + final justNow = now.subtract(const Duration(minutes: 1)); + + final localDateTime = dateTime.toLocal(); + + if (!localDateTime.difference(justNow).isNegative) { + return translate('date_formatter.just_now'); + } + + final roughTimeString = DateFormat.jm().format(dateTime); + + if (localDateTime.day == now.day && + localDateTime.month == now.month && + localDateTime.year == now.year) { + return roughTimeString; + } + + final yesterday = now.subtract(const Duration(days: 1)); + + if (localDateTime.day == yesterday.day && + localDateTime.month == now.month && + localDateTime.year == now.year) { + return translate('date_formatter.yesterday'); + } + + if (now.difference(localDateTime).inDays < 4) { + final weekday = DateFormat(DateFormat.WEEKDAY).format(localDateTime); + + return '$weekday, $roughTimeString'; + } + return '${DateFormat.yMd().format(dateTime)}, $roughTimeString'; + } +} diff --git a/lib/chat/views/utf8_length_limiting_text_input_formatter.dart b/lib/chat/views/utf8_length_limiting_text_input_formatter.dart new file mode 100644 index 0000000..f037ca8 --- /dev/null +++ b/lib/chat/views/utf8_length_limiting_text_input_formatter.dart @@ -0,0 +1,54 @@ +import 'dart:convert'; +import 'dart:math'; + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter { + Utf8LengthLimitingTextInputFormatter({this.maxLength}) + : assert(maxLength != null || maxLength! >= 0, 'maxLength is invalid'); + + final int? maxLength; + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, + TextEditingValue newValue, + ) { + if (maxLength != null && _bytesLength(newValue.text) > maxLength!) { + // If already at the maximum and tried to enter even more, + // keep the old value. + if (_bytesLength(oldValue.text) == maxLength) { + return oldValue; + } + return _truncate(newValue, maxLength!); + } + return newValue; + } + + static TextEditingValue _truncate(TextEditingValue value, int maxLength) { + var newValue = ''; + if (_bytesLength(value.text) > maxLength) { + var length = 0; + + value.text.characters.takeWhile((char) { + final nbBytes = _bytesLength(char); + if (length + nbBytes <= maxLength) { + newValue += char; + length += nbBytes; + return true; + } + return false; + }); + } + return TextEditingValue( + text: newValue, + selection: value.selection.copyWith( + baseOffset: min(value.selection.start, newValue.length), + extentOffset: min(value.selection.end, newValue.length), + ), + ); + } + + static int _bytesLength(String value) => utf8.encode(value).length; +} diff --git a/lib/chat/views/views.dart b/lib/chat/views/views.dart index 9703643..41b1936 100644 --- a/lib/chat/views/views.dart +++ b/lib/chat/views/views.dart @@ -1,3 +1,4 @@ export 'chat_component_widget.dart'; export 'empty_chat_widget.dart'; export 'no_conversation_widget.dart'; +export 'utf8_length_limiting_text_input_formatter.dart'; diff --git a/lib/init.dart b/lib/init.dart index 8c80bf9..958cd08 100644 --- a/lib/init.dart +++ b/lib/init.dart @@ -17,8 +17,12 @@ class VeilidChatGlobalInit { // Initialize Veilid Future _initializeVeilid() async { // Init Veilid - Veilid.instance.initializeVeilidCore( - await getDefaultVeilidPlatformConfig(kIsWeb, VeilidChatApp.name)); + try { + Veilid.instance.initializeVeilidCore( + await getDefaultVeilidPlatformConfig(kIsWeb, VeilidChatApp.name)); + } on VeilidAPIExceptionAlreadyInitialized { + log.debug('Already initialized, not reinitializing veilid-core'); + } // Veilid logging initVeilidLog(kIsDebugMode); diff --git a/lib/keyboard_shortcuts.dart b/lib/keyboard_shortcuts.dart index 0c531a9..1e25dac 100644 --- a/lib/keyboard_shortcuts.dart +++ b/lib/keyboard_shortcuts.dart @@ -16,6 +16,14 @@ class ReloadThemeIntent extends Intent { const ReloadThemeIntent(); } +class ChangeBrightnessIntent extends Intent { + const ChangeBrightnessIntent(); +} + +class ChangeColorIntent extends Intent { + const ChangeColorIntent(); +} + class AttachDetachIntent extends Intent { const AttachDetachIntent(); } @@ -49,6 +57,49 @@ class KeyboardShortcuts extends StatelessWidget { }); } + void changeBrightness(BuildContext context) { + singleFuture(this, () async { + final prefs = PreferencesRepository.instance.value; + + final oldBrightness = prefs.themePreference.brightnessPreference; + final newBrightness = BrightnessPreference.values[ + (oldBrightness.index + 1) % BrightnessPreference.values.length]; + + log.info('Changing brightness to $newBrightness'); + + final newPrefs = prefs.copyWith( + themePreference: prefs.themePreference + .copyWith(brightnessPreference: newBrightness)); + await PreferencesRepository.instance.set(newPrefs); + + if (context.mounted) { + ThemeSwitcher.of(context) + .changeTheme(theme: newPrefs.themePreference.themeData()); + } + }); + } + + void changeColor(BuildContext context) { + singleFuture(this, () async { + final prefs = PreferencesRepository.instance.value; + final oldColor = prefs.themePreference.colorPreference; + final newColor = ColorPreference + .values[(oldColor.index + 1) % ColorPreference.values.length]; + + log.info('Changing color to $newColor'); + + final newPrefs = prefs.copyWith( + themePreference: + prefs.themePreference.copyWith(colorPreference: newColor)); + await PreferencesRepository.instance.set(newPrefs); + + if (context.mounted) { + ThemeSwitcher.of(context) + .changeTheme(theme: newPrefs.themePreference.themeData()); + } + }); + } + void _attachDetach(BuildContext context) { singleFuture(this, () async { if (ProcessorRepository.instance.processorConnectionState.isAttached) { @@ -75,17 +126,34 @@ class KeyboardShortcuts extends StatelessWidget { Widget build(BuildContext context) => ThemeSwitcher( builder: (context) => Shortcuts( shortcuts: { - LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.keyR): - const ReloadThemeIntent(), - LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.keyD): - const AttachDetachIntent(), LogicalKeySet( - LogicalKeyboardKey.alt, LogicalKeyboardKey.backquote): - const DeveloperPageIntent(), + LogicalKeyboardKey.alt, + LogicalKeyboardKey.control, + LogicalKeyboardKey.keyR): const ReloadThemeIntent(), + LogicalKeySet( + LogicalKeyboardKey.alt, + LogicalKeyboardKey.control, + LogicalKeyboardKey.keyB): const ChangeBrightnessIntent(), + LogicalKeySet( + LogicalKeyboardKey.alt, + LogicalKeyboardKey.control, + LogicalKeyboardKey.keyC): const ChangeColorIntent(), + LogicalKeySet( + LogicalKeyboardKey.alt, + LogicalKeyboardKey.control, + LogicalKeyboardKey.keyD): const AttachDetachIntent(), + LogicalKeySet( + LogicalKeyboardKey.alt, + LogicalKeyboardKey.control, + LogicalKeyboardKey.backquote): const DeveloperPageIntent(), }, child: Actions(actions: >{ ReloadThemeIntent: CallbackAction( onInvoke: (intent) => reloadTheme(context)), + ChangeBrightnessIntent: CallbackAction( + onInvoke: (intent) => changeBrightness(context)), + ChangeColorIntent: CallbackAction( + onInvoke: (intent) => changeColor(context)), AttachDetachIntent: CallbackAction( onInvoke: (intent) => _attachDetach(context)), DeveloperPageIntent: CallbackAction( diff --git a/lib/layout/home/home_account_ready.dart b/lib/layout/home/home_account_ready.dart index 3674a08..a2966a6 100644 --- a/lib/layout/home/home_account_ready.dart +++ b/lib/layout/home/home_account_ready.dart @@ -130,15 +130,19 @@ class _HomeAccountReadyState extends State { if (activeChatLocalConversationKey == null) { return const NoConversationWidget(); } - return ChatComponentWidget( - localConversationRecordKey: activeChatLocalConversationKey, - onCancel: () { - activeChatCubit.setActiveChat(null); - }, - onClose: () { - activeChatCubit.setActiveChat(null); - }, - key: ValueKey(activeChatLocalConversationKey)); + return Material( + color: Colors.transparent, + child: Builder( + builder: (context) => ChatComponentWidget.singleContact( + context: context, + localConversationRecordKey: activeChatLocalConversationKey, + onCancel: () { + activeChatCubit.setActiveChat(null); + }, + onClose: () { + activeChatCubit.setActiveChat(null); + }, + key: ValueKey(activeChatLocalConversationKey)))); } @override diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index 3d1b14f..b4c3b58 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -5,6 +5,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart'; +import 'package:keyboard_avoider/keyboard_avoider.dart'; import 'package:provider/provider.dart'; import 'package:transitioned_indexed_stack/transitioned_indexed_stack.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -207,33 +208,32 @@ class HomeScreenState extends State return DefaultTextStyle( style: theme.textTheme.bodySmall!, - child: ZoomDrawer( - controller: _zoomDrawerController, - menuScreen: Builder(builder: (context) { - final zoomDrawer = ZoomDrawer.of(context); - zoomDrawer!.stateNotifier.addListener(() { - if (zoomDrawer.isOpen()) { - FocusManager.instance.primaryFocus?.unfocus(); - } - }); - return const DrawerMenu(); - }), - mainScreen: Provider.value( - value: _zoomDrawerController, - child: Builder(builder: _buildAccountPageView)), - borderRadius: 0, - angle: 0, - //mainScreenOverlayColor: theme.shadowColor.withAlpha(0x2F), - openCurve: Curves.fastEaseInToSlowEaseOut, - closeCurve: Curves.fastEaseInToSlowEaseOut, - // duration: const Duration(milliseconds: 250), - // reverseDuration: const Duration(milliseconds: 250), - menuScreenTapClose: canClose, - mainScreenTapClose: canClose, - disableDragGesture: !canClose, - mainScreenScale: .25, - slideWidth: min(360, MediaQuery.of(context).size.width * 0.9), - )); + child: KeyboardAvoider( + curve: Curves.ease, + child: ZoomDrawer( + controller: _zoomDrawerController, + menuScreen: Builder(builder: (context) { + final zoomDrawer = ZoomDrawer.of(context); + zoomDrawer!.stateNotifier.addListener(() { + if (zoomDrawer.isOpen()) { + FocusManager.instance.primaryFocus?.unfocus(); + } + }); + return const DrawerMenu(); + }), + mainScreen: Provider.value( + value: _zoomDrawerController, + child: Builder(builder: _buildAccountPageView)), + borderRadius: 0, + angle: 0, + openCurve: Curves.fastEaseInToSlowEaseOut, + closeCurve: Curves.fastEaseInToSlowEaseOut, + menuScreenTapClose: canClose, + mainScreenTapClose: canClose, + disableDragGesture: !canClose, + mainScreenScale: .25, + slideWidth: min(360, MediaQuery.of(context).size.width * 0.9), + ))); } //////////////////////////////////////////////////////////////////////////// diff --git a/lib/theme/models/chat_theme.dart b/lib/theme/models/chat_theme.dart deleted file mode 100644 index b650e17..0000000 --- a/lib/theme/models/chat_theme.dart +++ /dev/null @@ -1,488 +0,0 @@ -// ignore_for_file: always_put_required_named_parameters_first - -import 'package:flutter/material.dart'; -import 'package:flutter_chat_ui/flutter_chat_ui.dart'; - -import 'scale_theme/scale_scheme.dart'; - -ChatTheme makeChatTheme( - ScaleScheme scale, ScaleConfig scaleConfig, TextTheme textTheme) => - DefaultChatTheme( - primaryColor: scaleConfig.preferBorders - ? scale.primaryScale.calloutText - : scale.primaryScale.calloutBackground, - secondaryColor: scaleConfig.preferBorders - ? scale.secondaryScale.calloutText - : scale.secondaryScale.calloutBackground, - backgroundColor: - scale.grayScale.appBackground.withAlpha(scaleConfig.wallpaperAlpha), - messageBorderRadius: scaleConfig.borderRadiusScale * 12, - bubbleBorderSide: scaleConfig.preferBorders - ? BorderSide( - color: scale.primaryScale.calloutBackground, - width: 2, - ) - : BorderSide(width: 2, color: Colors.black.withAlpha(96)), - sendButtonIcon: Image.asset( - 'assets/icon-send.png', - color: scaleConfig.preferBorders - ? scale.primaryScale.border - : scale.primaryScale.borderText, - package: 'flutter_chat_ui', - ), - inputBackgroundColor: Colors.blue, - inputBorderRadius: BorderRadius.zero, - inputTextStyle: textTheme.bodyLarge!, - inputTextDecoration: InputDecoration( - filled: !scaleConfig.preferBorders, - fillColor: scale.primaryScale.subtleBackground, - isDense: true, - contentPadding: const EdgeInsets.fromLTRB(8, 8, 8, 8), - disabledBorder: OutlineInputBorder( - borderSide: scaleConfig.preferBorders - ? BorderSide(color: scale.grayScale.border, width: 2) - : BorderSide.none, - borderRadius: BorderRadius.all( - Radius.circular(8 * scaleConfig.borderRadiusScale))), - enabledBorder: OutlineInputBorder( - borderSide: scaleConfig.preferBorders - ? BorderSide(color: scale.primaryScale.border, width: 2) - : BorderSide.none, - borderRadius: BorderRadius.all( - Radius.circular(8 * scaleConfig.borderRadiusScale))), - focusedBorder: OutlineInputBorder( - borderSide: scaleConfig.preferBorders - ? BorderSide(color: scale.primaryScale.border, width: 2) - : BorderSide.none, - borderRadius: BorderRadius.all( - Radius.circular(8 * scaleConfig.borderRadiusScale))), - ), - inputContainerDecoration: BoxDecoration( - border: scaleConfig.preferBorders - ? Border( - top: BorderSide(color: scale.primaryScale.border, width: 2)) - : null, - color: scaleConfig.preferBorders - ? scale.primaryScale.elementBackground - : scale.primaryScale.border), - inputPadding: const EdgeInsets.all(6), - inputTextColor: !scaleConfig.preferBorders - ? scale.primaryScale.appText - : scale.primaryScale.border, - messageInsetsHorizontal: 12, - messageInsetsVertical: 8, - attachmentButtonIcon: const Icon(Icons.attach_file), - sentMessageBodyTextStyle: textTheme.bodyLarge!.copyWith( - color: scaleConfig.preferBorders - ? scale.primaryScale.calloutBackground - : scale.primaryScale.calloutText, - ), - sentEmojiMessageTextStyle: const TextStyle( - color: Colors.white, - fontSize: 64, - ), - receivedMessageBodyTextStyle: textTheme.bodyLarge!.copyWith( - color: scaleConfig.preferBorders - ? scale.secondaryScale.calloutBackground - : scale.secondaryScale.calloutText, - ), - receivedEmojiMessageTextStyle: const TextStyle( - color: Colors.white, - fontSize: 64, - ), - dateDividerTextStyle: textTheme.labelSmall!); - -class EditedChatTheme extends ChatTheme { - const EditedChatTheme({ - required super.attachmentButtonIcon, - required super.attachmentButtonMargin, - required super.backgroundColor, - super.bubbleMargin, - required super.dateDividerMargin, - required super.dateDividerTextStyle, - required super.deliveredIcon, - required super.documentIcon, - required super.emptyChatPlaceholderTextStyle, - required super.errorColor, - required super.errorIcon, - required super.inputBackgroundColor, - required super.inputSurfaceTintColor, - required super.inputElevation, - required super.inputBorderRadius, - super.inputContainerDecoration, - required super.inputMargin, - required super.inputPadding, - required super.inputTextColor, - super.inputTextCursorColor, - required super.inputTextDecoration, - required super.inputTextStyle, - required super.messageBorderRadius, - required super.messageInsetsHorizontal, - required super.messageInsetsVertical, - required super.messageMaxWidth, - required super.primaryColor, - required super.receivedEmojiMessageTextStyle, - super.receivedMessageBodyBoldTextStyle, - super.receivedMessageBodyCodeTextStyle, - super.receivedMessageBodyLinkTextStyle, - required super.receivedMessageBodyTextStyle, - required super.receivedMessageCaptionTextStyle, - required super.receivedMessageDocumentIconColor, - required super.receivedMessageLinkDescriptionTextStyle, - required super.receivedMessageLinkTitleTextStyle, - required super.secondaryColor, - required super.seenIcon, - required super.sendButtonIcon, - required super.sendButtonMargin, - required super.sendingIcon, - required super.sentEmojiMessageTextStyle, - super.sentMessageBodyBoldTextStyle, - super.sentMessageBodyCodeTextStyle, - super.sentMessageBodyLinkTextStyle, - required super.sentMessageBodyTextStyle, - required super.sentMessageCaptionTextStyle, - required super.sentMessageDocumentIconColor, - required super.sentMessageLinkDescriptionTextStyle, - required super.sentMessageLinkTitleTextStyle, - required super.statusIconPadding, - required super.systemMessageTheme, - required super.typingIndicatorTheme, - required super.unreadHeaderTheme, - required super.userAvatarImageBackgroundColor, - required super.userAvatarNameColors, - required super.userAvatarTextStyle, - required super.userNameTextStyle, - super.highlightMessageColor, - }); -} - -class ChatThemeEditor { - ChatThemeEditor(ChatTheme base) - : attachmentButtonIcon = base.attachmentButtonIcon, - attachmentButtonMargin = base.attachmentButtonMargin, - backgroundColor = base.backgroundColor, - bubbleMargin = base.bubbleMargin, - dateDividerMargin = base.dateDividerMargin, - dateDividerTextStyle = base.dateDividerTextStyle, - deliveredIcon = base.deliveredIcon, - documentIcon = base.documentIcon, - emptyChatPlaceholderTextStyle = base.emptyChatPlaceholderTextStyle, - errorColor = base.errorColor, - errorIcon = base.errorIcon, - inputBackgroundColor = base.inputBackgroundColor, - inputSurfaceTintColor = base.inputSurfaceTintColor, - inputElevation = base.inputElevation, - inputBorderRadius = base.inputBorderRadius, - inputContainerDecoration = base.inputContainerDecoration, - inputMargin = base.inputMargin, - inputPadding = base.inputPadding, - inputTextColor = base.inputTextColor, - inputTextCursorColor = base.inputTextCursorColor, - inputTextDecoration = base.inputTextDecoration, - inputTextStyle = base.inputTextStyle, - messageBorderRadius = base.messageBorderRadius, - messageInsetsHorizontal = base.messageInsetsHorizontal, - messageInsetsVertical = base.messageInsetsVertical, - messageMaxWidth = base.messageMaxWidth, - primaryColor = base.primaryColor, - receivedEmojiMessageTextStyle = base.receivedEmojiMessageTextStyle, - receivedMessageBodyBoldTextStyle = - base.receivedMessageBodyBoldTextStyle, - receivedMessageBodyCodeTextStyle = - base.receivedMessageBodyCodeTextStyle, - receivedMessageBodyLinkTextStyle = - base.receivedMessageBodyLinkTextStyle, - receivedMessageBodyTextStyle = base.receivedMessageBodyTextStyle, - receivedMessageCaptionTextStyle = base.receivedMessageCaptionTextStyle, - receivedMessageDocumentIconColor = - base.receivedMessageDocumentIconColor, - receivedMessageLinkDescriptionTextStyle = - base.receivedMessageLinkDescriptionTextStyle, - receivedMessageLinkTitleTextStyle = - base.receivedMessageLinkTitleTextStyle, - secondaryColor = base.secondaryColor, - seenIcon = base.seenIcon, - sendButtonIcon = base.sendButtonIcon, - sendButtonMargin = base.sendButtonMargin, - sendingIcon = base.sendingIcon, - sentEmojiMessageTextStyle = base.sentEmojiMessageTextStyle, - sentMessageBodyBoldTextStyle = base.sentMessageBodyBoldTextStyle, - sentMessageBodyCodeTextStyle = base.sentMessageBodyCodeTextStyle, - sentMessageBodyLinkTextStyle = base.sentMessageBodyLinkTextStyle, - sentMessageBodyTextStyle = base.sentMessageBodyTextStyle, - sentMessageCaptionTextStyle = base.sentMessageCaptionTextStyle, - sentMessageDocumentIconColor = base.sentMessageDocumentIconColor, - sentMessageLinkDescriptionTextStyle = - base.sentMessageLinkDescriptionTextStyle, - sentMessageLinkTitleTextStyle = base.sentMessageLinkTitleTextStyle, - statusIconPadding = base.statusIconPadding, - systemMessageTheme = base.systemMessageTheme, - typingIndicatorTheme = base.typingIndicatorTheme, - unreadHeaderTheme = base.unreadHeaderTheme, - userAvatarImageBackgroundColor = base.userAvatarImageBackgroundColor, - userAvatarNameColors = base.userAvatarNameColors, - userAvatarTextStyle = base.userAvatarTextStyle, - userNameTextStyle = base.userNameTextStyle, - highlightMessageColor = base.highlightMessageColor; - - EditedChatTheme commit() => EditedChatTheme( - attachmentButtonIcon: attachmentButtonIcon, - attachmentButtonMargin: attachmentButtonMargin, - backgroundColor: backgroundColor, - bubbleMargin: bubbleMargin, - dateDividerMargin: dateDividerMargin, - dateDividerTextStyle: dateDividerTextStyle, - deliveredIcon: deliveredIcon, - documentIcon: documentIcon, - emptyChatPlaceholderTextStyle: emptyChatPlaceholderTextStyle, - errorColor: errorColor, - errorIcon: errorIcon, - inputBackgroundColor: inputBackgroundColor, - inputSurfaceTintColor: inputSurfaceTintColor, - inputElevation: inputElevation, - inputBorderRadius: inputBorderRadius, - inputContainerDecoration: inputContainerDecoration, - inputMargin: inputMargin, - inputPadding: inputPadding, - inputTextColor: inputTextColor, - inputTextCursorColor: inputTextCursorColor, - inputTextDecoration: inputTextDecoration, - inputTextStyle: inputTextStyle, - messageBorderRadius: messageBorderRadius, - messageInsetsHorizontal: messageInsetsHorizontal, - messageInsetsVertical: messageInsetsVertical, - messageMaxWidth: messageMaxWidth, - primaryColor: primaryColor, - receivedEmojiMessageTextStyle: receivedEmojiMessageTextStyle, - receivedMessageBodyBoldTextStyle: receivedMessageBodyBoldTextStyle, - receivedMessageBodyCodeTextStyle: receivedMessageBodyCodeTextStyle, - receivedMessageBodyLinkTextStyle: receivedMessageBodyLinkTextStyle, - receivedMessageBodyTextStyle: receivedMessageBodyTextStyle, - receivedMessageCaptionTextStyle: receivedMessageCaptionTextStyle, - receivedMessageDocumentIconColor: receivedMessageDocumentIconColor, - receivedMessageLinkDescriptionTextStyle: - receivedMessageLinkDescriptionTextStyle, - receivedMessageLinkTitleTextStyle: receivedMessageLinkTitleTextStyle, - secondaryColor: secondaryColor, - seenIcon: seenIcon, - sendButtonIcon: sendButtonIcon, - sendButtonMargin: sendButtonMargin, - sendingIcon: sendingIcon, - sentEmojiMessageTextStyle: sentEmojiMessageTextStyle, - sentMessageBodyBoldTextStyle: sentMessageBodyBoldTextStyle, - sentMessageBodyCodeTextStyle: sentMessageBodyCodeTextStyle, - sentMessageBodyLinkTextStyle: sentMessageBodyLinkTextStyle, - sentMessageBodyTextStyle: sentMessageBodyTextStyle, - sentMessageCaptionTextStyle: sentMessageCaptionTextStyle, - sentMessageDocumentIconColor: sentMessageDocumentIconColor, - sentMessageLinkDescriptionTextStyle: - sentMessageLinkDescriptionTextStyle, - sentMessageLinkTitleTextStyle: sentMessageLinkTitleTextStyle, - statusIconPadding: statusIconPadding, - systemMessageTheme: systemMessageTheme, - typingIndicatorTheme: typingIndicatorTheme, - unreadHeaderTheme: unreadHeaderTheme, - userAvatarImageBackgroundColor: userAvatarImageBackgroundColor, - userAvatarNameColors: userAvatarNameColors, - userAvatarTextStyle: userAvatarTextStyle, - userNameTextStyle: userNameTextStyle, - highlightMessageColor: highlightMessageColor, - ); - - ///////////////////////////////////////////////////////////////////////////// - - /// Icon for select attachment button. - Widget? attachmentButtonIcon; - - /// Margin of attachment button. - EdgeInsets? attachmentButtonMargin; - - /// Used as a background color of a chat widget. - Color backgroundColor; - - // Margin around the message bubble. - EdgeInsetsGeometry? bubbleMargin; - - /// Margin around date dividers. - EdgeInsets dateDividerMargin; - - /// Text style of the date dividers. - TextStyle dateDividerTextStyle; - - /// Icon for message's `delivered` status. For the best look use size of 16. - Widget? deliveredIcon; - - /// Icon inside file message. - Widget? documentIcon; - - /// Text style of the empty chat placeholder. - TextStyle emptyChatPlaceholderTextStyle; - - /// Color to indicate something bad happened (usually - shades of red). - Color errorColor; - - /// Icon for message's `error` status. For the best look use size of 16. - Widget? errorIcon; - - /// Color of the bottom bar where text field is. - Color inputBackgroundColor; - - /// Surface Tint Color of the bottom bar where text field is. - Color inputSurfaceTintColor; - - double inputElevation; - - /// Top border radius of the bottom bar where text field is. - BorderRadius inputBorderRadius; - - /// Decoration of the container wrapping the text field. - Decoration? inputContainerDecoration; - - /// Outer insets of the bottom bar where text field is. - EdgeInsets inputMargin; - - /// Inner insets of the bottom bar where text field is. - EdgeInsets inputPadding; - - /// Color of the text field's text and attachment/send buttons. - Color inputTextColor; - - /// Color of the text field's cursor. - Color? inputTextCursorColor; - - /// Decoration of the input text field. - InputDecoration inputTextDecoration; - - /// Text style of the message input. To change the color use [inputTextColor]. - TextStyle inputTextStyle; - - /// Border radius of message container. - double messageBorderRadius; - - /// Horizontal message bubble insets. - double messageInsetsHorizontal; - - /// Vertical message bubble insets. - double messageInsetsVertical; - - /// Message bubble max width. set to [double.infinity] adaptive screen. - double messageMaxWidth; - - /// Primary color of the chat used as a background of sent messages - /// and statuses. - Color primaryColor; - - /// Text style used for displaying emojis on text messages. - TextStyle receivedEmojiMessageTextStyle; - - /// Body text style used for displaying bold text on received text messages. - /// Default to a bold version of [receivedMessageBodyTextStyle]. - TextStyle? receivedMessageBodyBoldTextStyle; - - /// Body text style used for displaying code text on received text messages. - /// Defaults to a mono version of [receivedMessageBodyTextStyle]. - TextStyle? receivedMessageBodyCodeTextStyle; - - /// Text style used for displaying link text on received text messages. - /// Defaults to [receivedMessageBodyTextStyle]. - TextStyle? receivedMessageBodyLinkTextStyle; - - /// Body text style used for displaying text on different types - /// of received messages. - TextStyle receivedMessageBodyTextStyle; - - /// Caption text style used for displaying secondary info (e.g. file size) on - /// different types of received messages. - TextStyle receivedMessageCaptionTextStyle; - - /// Color of the document icon on received messages. Has no effect when - /// [documentIcon] is used. - Color receivedMessageDocumentIconColor; - - /// Text style used for displaying link description on received messages. - TextStyle receivedMessageLinkDescriptionTextStyle; - - /// Text style used for displaying link title on received messages. - TextStyle receivedMessageLinkTitleTextStyle; - - /// Secondary color, used as a background of received messages. - Color secondaryColor; - - /// Icon for message's `seen` status. For the best look use size of 16. - Widget? seenIcon; - - /// Icon for send button. - Widget? sendButtonIcon; - - /// Margin of send button. - EdgeInsets? sendButtonMargin; - - /// Icon for message's `sending` status. For the best look use size of 10. - Widget? sendingIcon; - - /// Text style used for displaying emojis on text messages. - TextStyle sentEmojiMessageTextStyle; - - /// Body text style used for displaying bold text on sent text messages. - /// Defaults to a bold version of [sentMessageBodyTextStyle]. - TextStyle? sentMessageBodyBoldTextStyle; - - /// Body text style used for displaying code text on sent text messages. - /// Defaults to a mono version of [sentMessageBodyTextStyle]. - TextStyle? sentMessageBodyCodeTextStyle; - - /// Text style used for displaying link text on sent text messages. - /// Defaults to [sentMessageBodyTextStyle]. - TextStyle? sentMessageBodyLinkTextStyle; - - /// Body text style used for displaying text on different types - /// of sent messages. - TextStyle sentMessageBodyTextStyle; - - /// Caption text style used for displaying secondary info (e.g. file size) on - /// different types of sent messages. - TextStyle sentMessageCaptionTextStyle; - - /// Color of the document icon on sent messages. Has no effect when - /// [documentIcon] is used. - Color sentMessageDocumentIconColor; - - /// Text style used for displaying link description on sent messages. - TextStyle sentMessageLinkDescriptionTextStyle; - - /// Text style used for displaying link title on sent messages. - TextStyle sentMessageLinkTitleTextStyle; - - /// Padding around status icons. - EdgeInsets statusIconPadding; - - /// Theme for the system message. Will not have an effect if a custom builder - /// is provided. - SystemMessageTheme systemMessageTheme; - - /// Theme for typing indicator. See [TypingIndicator]. - TypingIndicatorTheme typingIndicatorTheme; - - /// Theme for the unread header. - UnreadHeaderTheme unreadHeaderTheme; - - /// Color used as a background for user avatar if an image is provided. - /// Visible if the image has some transparent parts. - Color userAvatarImageBackgroundColor; - - /// Colors used as backgrounds for user avatars with no image and so, - /// corresponding user names. - /// Calculated based on a user ID, so unique across the whole app. - List userAvatarNameColors; - - /// Text style used for displaying initials on user avatar if no - /// image is provided. - TextStyle userAvatarTextStyle; - - /// User names text style. Color will be overwritten - /// with [userAvatarNameColors]. - TextStyle userNameTextStyle; - - /// Color used as background of message row on highligth. - Color? highlightMessageColor; -} diff --git a/lib/theme/models/models.dart b/lib/theme/models/models.dart index be80542..7806ede 100644 --- a/lib/theme/models/models.dart +++ b/lib/theme/models/models.dart @@ -1,4 +1,3 @@ -export 'chat_theme.dart'; export 'radix_generator.dart'; export 'scale_theme/scale_theme.dart'; export 'theme_preference.dart'; diff --git a/lib/theme/models/scale_theme/scale_chat_theme.dart b/lib/theme/models/scale_theme/scale_chat_theme.dart new file mode 100644 index 0000000..5da0fe2 --- /dev/null +++ b/lib/theme/models/scale_theme/scale_chat_theme.dart @@ -0,0 +1,369 @@ +import 'package:awesome_extensions/awesome_extensions.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_chat_core/flutter_chat_core.dart' as core; + +import 'scale_theme.dart'; + +class ScaleChatTheme { + ScaleChatTheme({ + // Default chat theme + required this.chatTheme, + + // Customization fields (from v1 of flutter chat ui) + required this.attachmentButtonIcon, + // required this.attachmentButtonMargin, + required this.backgroundColor, + required this.bubbleBorderSide, + // required this.dateDividerMargin, + // required this.chatContentMargin, + required this.dateDividerTextStyle, + // required this.deliveredIcon, + // required this.documentIcon, + // required this.emptyChatPlaceholderTextStyle, + // required this.errorColor, + // required this.errorIcon, + required this.inputBackgroundColor, + // required this.inputSurfaceTintColor, + // required this.inputElevation, + required this.inputBorderRadius, + // required this.inputMargin, + required this.inputPadding, + required this.inputTextColor, + required this.inputTextStyle, + required this.messageBorderRadius, + required this.messageInsetsHorizontal, + required this.messageInsetsVertical, + // required this.messageMaxWidth, + required this.primaryColor, + required this.receivedEmojiMessageTextStyle, + required this.receivedMessageBodyTextStyle, + // required this.receivedMessageCaptionTextStyle, + // required this.receivedMessageDocumentIconColor, + // required this.receivedMessageLinkDescriptionTextStyle, + // required this.receivedMessageLinkTitleTextStyle, + required this.secondaryColor, + // required this.seenIcon, + required this.sendButtonIcon, + // required this.sendButtonMargin, + // required this.sendingIcon, + required this.onlyEmojiFontSize, + required this.timeStyle, + required this.sentMessageBodyTextStyle, + // required this.sentMessageCaptionTextStyle, + // required this.sentMessageDocumentIconColor, + // required this.sentMessageLinkDescriptionTextStyle, + // required this.sentMessageLinkTitleTextStyle, + // required this.statusIconPadding, + // required this.userAvatarImageBackgroundColor, + // required this.userAvatarNameColors, + // required this.userAvatarTextStyle, + // required this.userNameTextStyle, + // required this.bubbleMargin, + required this.inputContainerDecoration, + // required this.inputTextCursorColor, + // required this.receivedMessageBodyBoldTextStyle, + // required this.receivedMessageBodyCodeTextStyle, + // required this.receivedMessageBodyLinkTextStyle, + // required this.sentMessageBodyBoldTextStyle, + // required this.sentMessageBodyCodeTextStyle, + // required this.sentMessageBodyLinkTextStyle, + // required this.highlightMessageColor, + }); + + final core.ChatTheme chatTheme; + + /// Icon for select attachment button. + final Widget? attachmentButtonIcon; + + /// Margin of attachment button. + // final EdgeInsets? attachmentButtonMargin; + + /// Used as a background color of a chat widget. + final Color backgroundColor; + + // Margin around the message bubble. + // final EdgeInsetsGeometry? bubbleMargin; + + /// Border for chat bubbles + final BorderSide bubbleBorderSide; + + /// Margin around date dividers. + // final EdgeInsets dateDividerMargin; + + /// Margin inside chat area. + // final EdgeInsets chatContentMargin; + + /// Text style of the date dividers. + final TextStyle dateDividerTextStyle; + + /// Icon for message's `delivered` status. For the best look use size of 16. + // final Widget? deliveredIcon; + + /// Icon inside file message. + // final Widget? documentIcon; + + /// Text style of the empty chat placeholder. + // final TextStyle emptyChatPlaceholderTextStyle; + + /// Color to indicate something bad happened (usually - shades of red). + // final Color errorColor; + + /// Icon for message's `error` status. For the best look use size of 16. + // final Widget? errorIcon; + + /// Color of the bottom bar where text field is. + final Color inputBackgroundColor; + + /// Surface Tint Color of the bottom bar where text field is. + // final Color inputSurfaceTintColor; + + /// Elevation to use for input material + // final double inputElevation; + + /// Top border radius of the bottom bar where text field is. + final BorderRadius inputBorderRadius; + + /// Decoration of the container wrapping the text field. + final Decoration? inputContainerDecoration; + + /// Outer insets of the bottom bar where text field is. + // final EdgeInsets inputMargin; + + /// Inner insets of the bottom bar where text field is. + final EdgeInsets inputPadding; + + /// Color of the text field's text and attachment/send buttons. + final Color inputTextColor; + + /// Color of the text field's cursor. + // final Color? inputTextCursorColor; + + /// Text style of the message input. To change the color use [inputTextColor]. + final TextStyle inputTextStyle; + + /// Border radius of message container. + final double messageBorderRadius; + + /// Horizontal message bubble insets. + final double messageInsetsHorizontal; + + /// Vertical message bubble insets. + final double messageInsetsVertical; + + /// Message bubble max width. set to [double.infinity] adaptive screen. + // final double messageMaxWidth; + + /// Primary color of the chat used as a background of sent messages + /// and statuses. + final Color primaryColor; + + /// Text style used for displaying emojis on text messages. + final TextStyle receivedEmojiMessageTextStyle; + + /// Body text style used for displaying bold text on received text messages. + // Default to a bold version of [receivedMessageBodyTextStyle]. + // final TextStyle? receivedMessageBodyBoldTextStyle; + + /// Body text style used for displaying code text on received text messages. + // Defaults to a mono version of [receivedMessageBodyTextStyle]. + // final TextStyle? receivedMessageBodyCodeTextStyle; + + /// Text style used for displaying link text on received text messages. + // Defaults to [receivedMessageBodyTextStyle]. + // final TextStyle? receivedMessageBodyLinkTextStyle; + + /// Body text style used for displaying text on different types + /// of received messages. + final TextStyle receivedMessageBodyTextStyle; + + /// Caption text style used for displaying secondary info (e.g. file size) on + /// different types of received messages. + // final TextStyle receivedMessageCaptionTextStyle; + + /// Color of the document icon on received messages. Has no effect when + // [documentIcon] is used. + // final Color receivedMessageDocumentIconColor; + + /// Text style used for displaying link description on received messages. + // final TextStyle receivedMessageLinkDescriptionTextStyle; + + /// Text style used for displaying link title on received messages. + // final TextStyle receivedMessageLinkTitleTextStyle; + + /// Secondary color, used as a background of received messages. + final Color secondaryColor; + + /// Icon for message's `seen` status. For the best look use size of 16. + // final Widget? seenIcon; + + /// Icon for send button. + final Widget? sendButtonIcon; + + /// Margin of send button. + // final EdgeInsets? sendButtonMargin; + + /// Icon for message's `sending` status. For the best look use size of 10. + // final Widget? sendingIcon; + + /// Text size for displaying emojis on text messages. + final double onlyEmojiFontSize; + + /// Text style used for time and status + final TextStyle timeStyle; + + /// Body text style used for displaying bold text on sent text messages. + /// Defaults to a bold version of [sentMessageBodyTextStyle]. + // final TextStyle? sentMessageBodyBoldTextStyle; + + /// Body text style used for displaying code text on sent text messages. + /// Defaults to a mono version of [sentMessageBodyTextStyle]. + // final TextStyle? sentMessageBodyCodeTextStyle; + + /// Text style used for displaying link text on sent text messages. + /// Defaults to [sentMessageBodyTextStyle]. + // final TextStyle? sentMessageBodyLinkTextStyle; + + /// Body text style used for displaying text on different types + /// of sent messages. + final TextStyle sentMessageBodyTextStyle; + + /// Caption text style used for displaying secondary info (e.g. file size) on + /// different types of sent messages. + // final TextStyle sentMessageCaptionTextStyle; + + /// Color of the document icon on sent messages. Has no effect when + // [documentIcon] is used. + // final Color sentMessageDocumentIconColor; + + /// Text style used for displaying link description on sent messages. + // final TextStyle sentMessageLinkDescriptionTextStyle; + + /// Text style used for displaying link title on sent messages. + // final TextStyle sentMessageLinkTitleTextStyle; + + /// Padding around status icons. + // final EdgeInsets statusIconPadding; + + /// Color used as a background for user avatar if an image is provided. + /// Visible if the image has some transparent parts. + // final Color userAvatarImageBackgroundColor; + + /// Colors used as backgrounds for user avatars with no image and so, + /// corresponding user names. + /// Calculated based on a user ID, so unique across the whole app. + // final List userAvatarNameColors; + + /// Text style used for displaying initials on user avatar if no + /// image is provided. + // final TextStyle userAvatarTextStyle; + + /// User names text style. Color will be overwritten with + // [userAvatarNameColors]. + // final TextStyle userNameTextStyle; + + /// Color used as background of message row on highlight. + // final Color? highlightMessageColor; +} + +extension ScaleChatThemeExt on ScaleTheme { + ScaleChatTheme chatTheme() { + // 'brightness' is not actually used by ChatColors.fromThemeData, + // or ChatTypography.fromThemeData so just say 'light' here + final themeData = toThemeData(Brightness.light); + final typography = core.ChatTypography.fromThemeData(themeData); + + final surfaceContainer = config.preferBorders + ? scheme.secondaryScale.calloutText + : scheme.secondaryScale.calloutBackground; + + final colors = core.ChatColors( + // Primary color, often used for sent messages and accents. + primary: config.preferBorders + ? scheme.primaryScale.calloutText + : scheme.primaryScale.calloutBackground, + // Color for text and icons displayed on top of [primary]. + onPrimary: scheme.primaryScale.primaryText, + // The main background color of the chat screen. + surface: + scheme.grayScale.appBackground.withAlpha(config.wallpaperAlpha), + + // Color for text and icons displayed on top of [surface]. + onSurface: scheme.primaryScale.appText, + + // Background color for elements like received messages. + surfaceContainer: surfaceContainer, + + // A slightly lighter/darker variant of [surfaceContainer]. + surfaceContainerLow: surfaceContainer.darken(25), + + // A slightly lighter/darker variant of [surfaceContainer]. + surfaceContainerHigh: surfaceContainer.lighten(25)); + + final chatTheme = core.ChatTheme( + colors: colors, + typography: typography, + shape: + BorderRadius.all(Radius.circular(config.borderRadiusScale * 12))); + + return ScaleChatTheme( + chatTheme: chatTheme, + primaryColor: config.preferBorders + ? scheme.primaryScale.calloutText + : scheme.primaryScale.calloutBackground, + secondaryColor: config.preferBorders + ? scheme.secondaryScale.calloutText + : scheme.secondaryScale.calloutBackground, + backgroundColor: + scheme.grayScale.appBackground.withAlpha(config.wallpaperAlpha), + messageBorderRadius: config.borderRadiusScale * 12, + bubbleBorderSide: config.preferBorders + ? BorderSide( + color: scheme.primaryScale.calloutBackground, + width: 2, + ) + : BorderSide(width: 2, color: Colors.black.withAlpha(96)), + sendButtonIcon: Image.asset( + 'assets/icon-send.png', + color: config.preferBorders + ? scheme.primaryScale.border + : scheme.primaryScale.borderText, + package: 'flutter_chat_ui', + ), + inputBackgroundColor: Colors.blue, + inputBorderRadius: BorderRadius.zero, + inputTextStyle: textTheme.bodyLarge!, + inputContainerDecoration: BoxDecoration( + border: config.preferBorders + ? Border( + top: + BorderSide(color: scheme.primaryScale.border, width: 2)) + : null, + color: config.preferBorders + ? scheme.primaryScale.elementBackground + : scheme.primaryScale.border), + inputPadding: const EdgeInsets.all(6), + inputTextColor: !config.preferBorders + ? scheme.primaryScale.appText + : scheme.primaryScale.border, + messageInsetsHorizontal: 12, + messageInsetsVertical: 8, + attachmentButtonIcon: const Icon(Icons.attach_file), + sentMessageBodyTextStyle: textTheme.bodyLarge!.copyWith( + color: config.preferBorders + ? scheme.primaryScale.calloutBackground + : scheme.primaryScale.calloutText, + ), + onlyEmojiFontSize: 64, + timeStyle: textTheme.bodySmall!.copyWith(fontSize: 9), + receivedMessageBodyTextStyle: textTheme.bodyLarge!.copyWith( + color: config.preferBorders + ? scheme.secondaryScale.calloutBackground + : scheme.secondaryScale.calloutText, + ), + receivedEmojiMessageTextStyle: const TextStyle( + color: Colors.white, + fontSize: 64, + ), + dateDividerTextStyle: textTheme.labelSmall!); + } +} diff --git a/lib/theme/models/scale_theme/scale_color.dart b/lib/theme/models/scale_theme/scale_color.dart index d79d878..f3c884f 100644 --- a/lib/theme/models/scale_theme/scale_color.dart +++ b/lib/theme/models/scale_theme/scale_color.dart @@ -83,6 +83,7 @@ class ScaleColor { calloutBackground: calloutBackground ?? this.calloutBackground, calloutText: calloutText ?? this.calloutText); + // Use static method // ignore: prefer_constructors_over_static_methods static ScaleColor lerp(ScaleColor a, ScaleColor b, double t) => ScaleColor( appBackground: Color.lerp(a.appBackground, b.appBackground, t) ?? diff --git a/lib/theme/models/scale_theme/scale_theme.dart b/lib/theme/models/scale_theme/scale_theme.dart index ef430d2..c3217ea 100644 --- a/lib/theme/models/scale_theme/scale_theme.dart +++ b/lib/theme/models/scale_theme/scale_theme.dart @@ -4,6 +4,7 @@ import 'scale_input_decorator_theme.dart'; import 'scale_scheme.dart'; export 'scale_app_bar_theme.dart'; +export 'scale_chat_theme.dart'; export 'scale_color.dart'; export 'scale_input_decorator_theme.dart'; export 'scale_scheme.dart'; diff --git a/lib/theme/views/responsive.dart b/lib/theme/views/responsive.dart index 91af81d..0182c9f 100644 --- a/lib/theme/views/responsive.dart +++ b/lib/theme/views/responsive.dart @@ -1,13 +1,24 @@ -import 'dart:io'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -bool get isAndroid => !kIsWeb && Platform.isAndroid; -bool get isiOS => !kIsWeb && Platform.isIOS; -bool get isWeb => kIsWeb; -bool get isDesktop => - !isWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS); +final isAndroid = !kIsWeb && defaultTargetPlatform == TargetPlatform.android; +final isiOS = !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS; +final isMobile = !kIsWeb && + (defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.android); +final isDesktop = !kIsWeb && + !(defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.android); + +const isWeb = kIsWeb; +final isWebMobile = kIsWeb && + (defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.android); +final isWebDesktop = kIsWeb && + !(defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.android); + +final isAnyMobile = isMobile || isWebMobile; const kMobileWidthCutoff = 500.0; diff --git a/lib/theme/views/styled_alert.dart b/lib/theme/views/styled_alert.dart index 8602c22..1215c84 100644 --- a/lib/theme/views/styled_alert.dart +++ b/lib/theme/views/styled_alert.dart @@ -128,7 +128,7 @@ Future showErrorStacktraceModal( await showErrorModal( context: context, title: translate('toast.error'), - text: 'Error: {e}\n StackTrace: {st}', + text: 'Error: $error\n StackTrace: $stackTrace', ); } diff --git a/lib/tools/loggy.dart b/lib/tools/loggy.dart index 47a1ffd..adc62d1 100644 --- a/lib/tools/loggy.dart +++ b/lib/tools/loggy.dart @@ -165,5 +165,8 @@ void initLoggy() { registerVeilidProtoToDebug(); registerVeilidDHTProtoToDebug(); registerVeilidchatProtoToDebug(); - Bloc.observer = const StateLogger(); + + if (kIsDebugMode) { + Bloc.observer = const StateLogger(); + } } diff --git a/lib/tools/state_logger.dart b/lib/tools/state_logger.dart index 50dec46..a133346 100644 --- a/lib/tools/state_logger.dart +++ b/lib/tools/state_logger.dart @@ -6,14 +6,17 @@ import 'package:veilid_support/veilid_support.dart'; import 'loggy.dart'; const Map _blocChangeLogLevels = { - 'ConnectionStateCubit': LogLevel.off, - 'ActiveSingleContactChatBlocMapCubit': LogLevel.off, - 'ActiveConversationsBlocMapCubit': LogLevel.off, - 'PersistentQueueCubit': LogLevel.off, - 'TableDBArrayProtobufCubit': LogLevel.off, - 'DHTLogCubit': LogLevel.off, - 'SingleContactMessagesCubit': LogLevel.off, - 'ChatComponentCubit': LogLevel.off, + 'RouterCubit': LogLevel.debug, + 'PerAccountCollectionBlocMapCubit': LogLevel.debug, + 'PerAccountCollectionCubit': LogLevel.debug, + 'ActiveChatCubit': LogLevel.debug, + 'AccountRecordCubit': LogLevel.debug, + 'ContactListCubit': LogLevel.debug, + 'ContactInvitationListCubit': LogLevel.debug, + 'ChatListCubit': LogLevel.debug, + 'PreferencesCubit': LogLevel.debug, + 'ConversationCubit': LogLevel.debug, + 'DefaultDHTRecordCubit': LogLevel.debug, }; const Map _blocCreateCloseLogLevels = {}; @@ -40,7 +43,7 @@ class StateLogger extends BlocObserver { @override void onChange(BlocBase bloc, Change change) { super.onChange(bloc, change); - _checkLogLevel(_blocChangeLogLevels, LogLevel.debug, bloc, (logLevel) { + _checkLogLevel(_blocChangeLogLevels, LogLevel.off, bloc, (logLevel) { const encoder = JsonEncoder.withIndent(' ', DynamicDebug.toDebug); log.log( logLevel, diff --git a/packages/veilid_support/lib/identity_support/super_identity.dart b/packages/veilid_support/lib/identity_support/super_identity.dart index 5ee8c43..5dc0e90 100644 --- a/packages/veilid_support/lib/identity_support/super_identity.dart +++ b/packages/veilid_support/lib/identity_support/super_identity.dart @@ -23,6 +23,7 @@ part 'super_identity.g.dart'; /// Encryption: None @freezed sealed class SuperIdentity with _$SuperIdentity { + @JsonSerializable() const factory SuperIdentity({ /// Public DHT record storing this structure for account recovery /// changing this can migrate/forward the SuperIdentity to a new DHT record diff --git a/packages/veilid_support/lib/identity_support/super_identity.freezed.dart b/packages/veilid_support/lib/identity_support/super_identity.freezed.dart index b142373..3144205 100644 --- a/packages/veilid_support/lib/identity_support/super_identity.freezed.dart +++ b/packages/veilid_support/lib/identity_support/super_identity.freezed.dart @@ -169,6 +169,7 @@ class _$SuperIdentityCopyWithImpl<$Res> } /// @nodoc + @JsonSerializable() class _SuperIdentity extends SuperIdentity { const _SuperIdentity( diff --git a/packages/veilid_support/lib/src/config.dart b/packages/veilid_support/lib/src/config.dart index 47bd84e..6902479 100644 --- a/packages/veilid_support/lib/src/config.dart +++ b/packages/veilid_support/lib/src/config.dart @@ -4,8 +4,10 @@ import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:veilid/veilid.dart'; +// Allowed to pull sentinel value // ignore: do_not_use_environment const bool kIsReleaseMode = bool.fromEnvironment('dart.vm.product'); +// Allowed to pull sentinel value // ignore: do_not_use_environment const bool kIsProfileMode = bool.fromEnvironment('dart.vm.profile'); const bool kIsDebugMode = !kIsReleaseMode && !kIsProfileMode; @@ -13,18 +15,21 @@ const bool kIsDebugMode = !kIsReleaseMode && !kIsProfileMode; Future> getDefaultVeilidPlatformConfig( bool isWeb, String appName) async { final ignoreLogTargetsStr = + // Allowed to change settings // ignore: do_not_use_environment const String.fromEnvironment('IGNORE_LOG_TARGETS').trim(); final ignoreLogTargets = ignoreLogTargetsStr.isEmpty ? [] : ignoreLogTargetsStr.split(',').map((e) => e.trim()).toList(); + // Allowed to change settings // ignore: do_not_use_environment var flamePathStr = const String.fromEnvironment('FLAME').trim(); if (flamePathStr == '1') { flamePathStr = p.join( (await getApplicationSupportDirectory()).absolute.path, '$appName.folded'); + // Allowed for debugging // ignore: avoid_print print('Flame data logged to $flamePathStr'); } @@ -73,30 +78,37 @@ Future getVeilidConfig(bool isWeb, String programName) async { var config = await getDefaultVeilidConfig( isWeb: isWeb, programName: programName, + // Allowed to change settings // ignore: avoid_redundant_argument_values, do_not_use_environment namespace: const String.fromEnvironment('NAMESPACE'), + // Allowed to change settings // ignore: avoid_redundant_argument_values, do_not_use_environment bootstrap: const String.fromEnvironment('BOOTSTRAP'), + // Allowed to change settings // ignore: avoid_redundant_argument_values, do_not_use_environment networkKeyPassword: const String.fromEnvironment('NETWORK_KEY'), ); + // Allowed to change settings // ignore: do_not_use_environment if (const String.fromEnvironment('DELETE_TABLE_STORE') == '1') { config = config.copyWith(tableStore: config.tableStore.copyWith(delete: true)); } + // Allowed to change settings // ignore: do_not_use_environment if (const String.fromEnvironment('DELETE_PROTECTED_STORE') == '1') { config = config.copyWith( protectedStore: config.protectedStore.copyWith(delete: true)); } + // Allowed to change settings // ignore: do_not_use_environment if (const String.fromEnvironment('DELETE_BLOCK_STORE') == '1') { config = config.copyWith(blockStore: config.blockStore.copyWith(delete: true)); } + // Allowed to change settings // ignore: do_not_use_environment const envNetwork = String.fromEnvironment('NETWORK'); if (envNetwork.isNotEmpty) { @@ -111,7 +123,8 @@ Future getVeilidConfig(bool isWeb, String programName) async { return config.copyWith( capabilities: - // XXX: Remove DHTV and DHTW when we get background sync implemented + // XXX: Remove DHTV and DHTW after DHT widening (and maybe remote + // rehydration?) const VeilidConfigCapabilities(disable: ['DHTV', 'DHTW', 'TUNL']), protectedStore: // XXX: Linux often does not have a secret storage mechanism installed diff --git a/packages/veilid_support/lib/src/table_db_array_protobuf_cubit.dart b/packages/veilid_support/lib/src/table_db_array_protobuf_cubit.dart index 81d7f57..92b920f 100644 --- a/packages/veilid_support/lib/src/table_db_array_protobuf_cubit.dart +++ b/packages/veilid_support/lib/src/table_db_array_protobuf_cubit.dart @@ -24,7 +24,7 @@ class TableDBArrayProtobufStateData final IList windowElements; // The length of the entire array final int length; - // One past the end of the last element + // One past the end of the last element (modulo length, can be zero) final int windowTail; // The total number of elements to try to keep in 'elements' final int windowCount; diff --git a/pubspec.lock b/pubspec.lock index e7252a3..947b17e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -393,6 +393,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + cross_cache: + dependency: transitive + description: + name: cross_cache + sha256: "007d0340c19d4d201192a3335c4034f4b79eae5ea53f90b69eeb5d239d9fbd1d" + url: "https://pub.dev" + source: hosted + version: "1.0.2" cross_file: dependency: transitive description: @@ -542,23 +550,20 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.1" - flutter_chat_types: + flutter_chat_core: dependency: "direct main" description: - name: flutter_chat_types - sha256: e285b588f6d19d907feb1f6d912deaf22e223656769c34093b64e1c59b094fb9 - url: "https://pub.dev" - source: hosted - version: "3.6.2" + path: "../flutter_chat_ui/packages/flutter_chat_core" + relative: true + source: path + version: "2.1.2" flutter_chat_ui: dependency: "direct main" description: - path: "." - ref: main - resolved-ref: d4b9d507d10f5d640156cacfd754f661f8c0f4c1 - url: "https://gitlab.com/veilid/flutter-chat-ui.git" - source: git - version: "1.6.14" + path: "../flutter_chat_ui/packages/flutter_chat_ui" + relative: true + source: path + version: "2.1.3" flutter_form_builder: dependency: "direct main" description: @@ -575,22 +580,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.21.2" - flutter_link_previewer: - dependency: transitive - description: - name: flutter_link_previewer - sha256: "007069e60f42419fb59872beb7a3cc3ea21e9f1bdff5d40239f376fa62ca9f20" - url: "https://pub.dev" - source: hosted - version: "3.2.2" - flutter_linkify: - dependency: transitive - description: - name: flutter_linkify - sha256: "74669e06a8f358fee4512b4320c0b80e51cffc496607931de68d28f099254073" - url: "https://pub.dev" - source: hosted - version: "6.0.0" flutter_localizations: dependency: "direct main" description: flutter @@ -604,14 +593,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.5" - flutter_parsed_text: - dependency: transitive - description: - name: flutter_parsed_text - sha256: "529cf5793b7acdf16ee0f97b158d0d4ba0bf06e7121ef180abe1a5b59e32c1e2" - url: "https://pub.dev" - source: hosted - version: "2.2.1" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -801,6 +782,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + idb_shim: + dependency: transitive + description: + name: idb_shim + sha256: d3dae2085f2dcc9d05b851331fddb66d57d3447ff800de9676b396795436e135 + url: "https://pub.dev" + source: hosted + version: "2.6.5+1" image: dependency: "direct main" description: @@ -857,14 +846,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.9.4" - linkify: - dependency: transitive + keyboard_avoider: + dependency: "direct main" description: - name: linkify - sha256: "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832" + name: keyboard_avoider + sha256: d2917bd52c6612bf8d1ff97f74049ddf3592a81d44e814f0e7b07dcfd245b75c url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "0.2.0" lint_hard: dependency: "direct dev" description: @@ -1073,14 +1062,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" - photo_view: - dependency: transitive - description: - name: photo_view - sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e" - url: "https://pub.dev" - source: hosted - version: "0.15.0" pinput: dependency: "direct main" description: @@ -1157,10 +1138,10 @@ packages: dependency: "direct main" description: name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.5" pub_semver: dependency: transitive description: @@ -1305,6 +1286,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + scrollview_observer: + dependency: transitive + description: + name: scrollview_observer + sha256: "437c930927c5a3240ed2d40398f99d96eaca58f861817ff44f6d0c60113bcf9d" + url: "https://pub.dev" + source: hosted + version: "1.26.0" searchable_listview: dependency: "direct main" description: @@ -1314,6 +1303,14 @@ packages: url: "https://gitlab.com/veilid/Searchable-Listview.git" source: git version: "2.16.0" + sembast: + dependency: transitive + description: + name: sembast + sha256: d3f0d0ba501a5f1fd7d6c8532ee01385977c8a069c334635dae390d059ae3d6d + url: "https://pub.dev" + source: hosted + version: "3.8.5" share_plus: dependency: "direct main" description: @@ -1774,7 +1771,7 @@ packages: path: "../veilid/veilid-flutter" relative: true source: path - version: "0.4.4" + version: "0.4.6" veilid_support: dependency: "direct main" description: @@ -1782,14 +1779,6 @@ packages: relative: true source: path version: "1.0.2+0" - visibility_detector: - dependency: transitive - description: - name: visibility_detector - sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 - url: "https://pub.dev" - source: hosted - version: "0.4.0+2" watcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ac985bf..9cf61e8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,11 @@ name: veilidchat description: VeilidChat -publish_to: 'none' +publish_to: "none" version: 0.4.7+20 environment: - sdk: '>=3.2.0 <4.0.0' - flutter: '>=3.22.1' + sdk: ">=3.2.0 <4.0.0" + flutter: ">=3.22.1" dependencies: accordion: ^2.6.0 @@ -37,11 +37,16 @@ dependencies: sdk: flutter flutter_animate: ^4.5.2 flutter_bloc: ^9.1.0 - flutter_chat_types: ^3.6.2 + flutter_chat_core: + git: + url: https://gitlab.com/veilid/flutter-chat-ui.git + path: packages/flutter_chat_core + ref: veilidchat flutter_chat_ui: git: url: https://gitlab.com/veilid/flutter-chat-ui.git - ref: main + path: packages/flutter_chat_ui + ref: veilidchat flutter_form_builder: ^10.0.1 flutter_hooks: ^0.21.2 flutter_localizations: @@ -59,6 +64,7 @@ dependencies: image: ^4.5.3 intl: ^0.19.0 json_annotation: ^4.9.0 + keyboard_avoider: ^0.2.0 loggy: ^2.0.3 meta: ^1.16.0 mobile_scanner: ^6.0.7 @@ -110,15 +116,17 @@ dependencies: xterm: ^4.0.0 zxing2: ^0.2.3 -# dependency_overrides: -# async_tools: -# path: ../dart_async_tools -# bloc_advanced_tools: -# path: ../bloc_advanced_tools -# searchable_listview: -# path: ../Searchable-Listview -# flutter_chat_ui: -# path: ../flutter_chat_ui +dependency_overrides: + # async_tools: + # path: ../dart_async_tools + # bloc_advanced_tools: + # path: ../bloc_advanced_tools + # searchable_listview: + # path: ../Searchable-Listview + flutter_chat_core: + path: ../flutter_chat_ui/packages/flutter_chat_core + flutter_chat_ui: + path: ../flutter_chat_ui/packages/flutter_chat_ui dev_dependencies: build_runner: ^2.4.15 @@ -129,22 +137,22 @@ dev_dependencies: flutter_native_splash: color: "#8588D0" - + icons_launcher: - image_path: 'assets/launcher/icon.png' + image_path: "assets/launcher/icon.png" platforms: android: enable: true - adaptive_background_color: '#ffffff' - adaptive_foreground_image: 'assets/launcher/icon.png' - adaptive_round_image: 'assets/launcher/icon.png' + adaptive_background_color: "#ffffff" + adaptive_foreground_image: "assets/launcher/icon.png" + adaptive_round_image: "assets/launcher/icon.png" ios: enable: true web: enable: true macos: enable: true - image_path: 'assets/launcher/macos_icon.png' + image_path: "assets/launcher/macos_icon.png" windows: enable: true linux: @@ -192,7 +200,7 @@ flutter: - asset: assets/fonts/SourceCodePro-Regular.ttf - asset: assets/fonts/SourceCodePro-Bold.ttf weight: 700 - + # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware From 576c8e477a57ded4b10574601cf684b3dc8a56e5 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 17 May 2025 18:09:33 -0400 Subject: [PATCH 69/93] switch to hosted package --- lib/chat/cubits/chat_component_cubit.dart | 4 ++-- pubspec.lock | 18 ++++++++++-------- pubspec.yaml | 22 +++++++--------------- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/lib/chat/cubits/chat_component_cubit.dart b/lib/chat/cubits/chat_component_cubit.dart index 76ff660..a1c64bf 100644 --- a/lib/chat/cubits/chat_component_cubit.dart +++ b/lib/chat/cubits/chat_component_cubit.dart @@ -329,8 +329,8 @@ class ChatComponentCubit extends Cubit { message.sentTimestamp.value.toInt()), sentAt: reconciledAt, id: reconciledId, - text: '${contentText.text} (${message.seqId})', - //text: contentText.text, + //text: '${contentText.text} (${message.seqId})', + text: contentText.text, metadata: { kSeqId: message.seqId, if (core.isOnlyEmoji(contentText.text)) 'isOnlyEmoji': true, diff --git a/pubspec.lock b/pubspec.lock index 947b17e..47990cf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -553,17 +553,19 @@ packages: flutter_chat_core: dependency: "direct main" description: - path: "../flutter_chat_ui/packages/flutter_chat_core" - relative: true - source: path - version: "2.1.2" + name: flutter_chat_core + sha256: "529959634622e9df3b96a4a3764ecc61ec6f0dfa3258a52c139ae10a56ccad80" + url: "https://pub.dev" + source: hosted + version: "2.2.0" flutter_chat_ui: dependency: "direct main" description: - path: "../flutter_chat_ui/packages/flutter_chat_ui" - relative: true - source: path - version: "2.1.3" + name: flutter_chat_ui + sha256: c63df9cd05fe86a3588b4e47f184fbb9e9c3b86153b8a97f3a789e6edb03d28e + url: "https://pub.dev" + source: hosted + version: "2.2.0" flutter_form_builder: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 9cf61e8..73ec83a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,16 +37,8 @@ dependencies: sdk: flutter flutter_animate: ^4.5.2 flutter_bloc: ^9.1.0 - flutter_chat_core: - git: - url: https://gitlab.com/veilid/flutter-chat-ui.git - path: packages/flutter_chat_core - ref: veilidchat - flutter_chat_ui: - git: - url: https://gitlab.com/veilid/flutter-chat-ui.git - path: packages/flutter_chat_ui - ref: veilidchat + flutter_chat_core: ^2.2.0 + flutter_chat_ui: ^2.2.0 flutter_form_builder: ^10.0.1 flutter_hooks: ^0.21.2 flutter_localizations: @@ -116,17 +108,17 @@ dependencies: xterm: ^4.0.0 zxing2: ^0.2.3 -dependency_overrides: + # dependency_overrides: # async_tools: # path: ../dart_async_tools # bloc_advanced_tools: # path: ../bloc_advanced_tools # searchable_listview: # path: ../Searchable-Listview - flutter_chat_core: - path: ../flutter_chat_ui/packages/flutter_chat_core - flutter_chat_ui: - path: ../flutter_chat_ui/packages/flutter_chat_ui + # flutter_chat_core: + # path: ../flutter_chat_ui/packages/flutter_chat_core + # flutter_chat_ui: + # path: ../flutter_chat_ui/packages/flutter_chat_ui dev_dependencies: build_runner: ^2.4.15 From 34f9bea6eb4654bde345d18600717313624f7dbe Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 17 May 2025 19:30:45 -0400 Subject: [PATCH 70/93] add unsent messages, fix hotkeys so they work with chrome --- lib/chat/cubits/chat_component_cubit.dart | 1 + .../cubits/single_contact_messages_cubit.dart | 32 ++--- lib/chat/views/chat_component_widget.dart | 83 ++++++------ lib/keyboard_shortcuts.dart | 47 ++++--- pubspec.lock | 124 +++++++++--------- 5 files changed, 149 insertions(+), 138 deletions(-) diff --git a/lib/chat/cubits/chat_component_cubit.dart b/lib/chat/cubits/chat_component_cubit.dart index a1c64bf..737b1a4 100644 --- a/lib/chat/cubits/chat_component_cubit.dart +++ b/lib/chat/cubits/chat_component_cubit.dart @@ -333,6 +333,7 @@ class ChatComponentCubit extends Cubit { text: contentText.text, metadata: { kSeqId: message.seqId, + kSending: message.sendState == MessageSendState.sending, if (core.isOnlyEmoji(contentText.text)) 'isOnlyEmoji': true, }); return (currentState, textMessage); diff --git a/lib/chat/cubits/single_contact_messages_cubit.dart b/lib/chat/cubits/single_contact_messages_cubit.dart index d93c4be..77b1bb9 100644 --- a/lib/chat/cubits/single_contact_messages_cubit.dart +++ b/lib/chat/cubits/single_contact_messages_cubit.dart @@ -419,22 +419,22 @@ class SingleContactMessagesCubit extends Cubit { } // Render in-flight messages at the bottom - // - // for (final m in unsentMessages) { - // if (renderedIds.contains(m.authorUniqueIdString)) { - // seqId++; - // continue; - // } - // renderedElements.add(RenderStateElement( - // seqId: seqId, - // message: m, - // isLocal: true, - // sent: true, - // sentOffline: true, - // )); - // renderedIds.add(m.authorUniqueIdString); - // seqId++; - // } + + for (final m in unsentMessages) { + if (renderedIds.contains(m.authorUniqueIdString)) { + seqId++; + continue; + } + renderedElements.add(RenderStateElement( + seqId: seqId, + message: m, + isLocal: true, + sent: true, + sentOffline: true, + )); + renderedIds.add(m.authorUniqueIdString); + seqId++; + } // Render the state final messages = renderedElements diff --git a/lib/chat/views/chat_component_widget.dart b/lib/chat/views/chat_component_widget.dart index aed7356..5ecf6e9 100644 --- a/lib/chat/views/chat_component_widget.dart +++ b/lib/chat/views/chat_component_widget.dart @@ -23,6 +23,7 @@ import 'chat_builders/chat_builders.dart'; const onEndReachedThreshold = 0.75; const _kScrollTag = 'kScrollTag'; const kSeqId = 'seqId'; +const kSending = 'sending'; const maxMessageLength = 2048; class ChatComponentWidget extends StatefulWidget { @@ -321,51 +322,55 @@ class _ChatComponentWidgetState extends State { final windowState = data.value; - await _chatController.setMessages(windowState.window.toList()); + // await _chatController.setMessages(windowState.window.toList()); - // final newMessagesSet = windowState.window.toSet(); - // final newMessagesById = - // Map.fromEntries(newMessagesSet.map((m) => MapEntry(m.id, m))); - // final newMessagesBySeqId = Map.fromEntries( - // newMessagesSet.map((m) => MapEntry(m.metadata![kSeqId], m))); - // final oldMessagesSet = _chatController.messages.toSet(); + final newMessagesSet = windowState.window.toSet(); + final newMessagesById = + Map.fromEntries(newMessagesSet.map((m) => MapEntry(m.id, m))); + final newMessagesBySeqId = Map.fromEntries( + newMessagesSet.map((m) => MapEntry(m.metadata![kSeqId], m))); + final oldMessagesSet = _chatController.messages.toSet(); - // if (oldMessagesSet.isEmpty) { - // await _chatController.setMessages(windowState.window.toList()); - // return; - // } + if (oldMessagesSet.isEmpty) { + await _chatController.setMessages(windowState.window.toList()); + return; + } - // // See how many messages differ by equality (not identity) - // // If there are more than `replaceAllMessagesThreshold` differences - // // just replace the whole list of messages - // final diffs = newMessagesSet.diffAndIntersect(oldMessagesSet, - // diffThisMinusOther: true, diffOtherMinusThis: true); - // final addedMessages = diffs.diffThisMinusOther!; - // final removedMessages = diffs.diffOtherMinusThis!; + // See how many messages differ by equality (not identity) + // If there are more than `replaceAllMessagesThreshold` differences + // just replace the whole list of messages + final diffs = newMessagesSet.diffAndIntersect(oldMessagesSet, + diffThisMinusOther: true, diffOtherMinusThis: true); + final addedMessages = diffs.diffThisMinusOther!; + final removedMessages = diffs.diffOtherMinusThis!; - // final replaceAllPaginationLimit = windowState.windowCount / 3; + final replaceAllPaginationLimit = windowState.windowCount / 3; - // if ((addedMessages.length >= replaceAllPaginationLimit) || - // removedMessages.length >= replaceAllPaginationLimit) { - // await _chatController.setMessages(windowState.window.toList()); - // return; - // } + if ((addedMessages.length >= replaceAllPaginationLimit) || + removedMessages.length >= replaceAllPaginationLimit) { + await _chatController.setMessages(windowState.window.toList()); + return; + } - // // Remove messages that are gone, and replace the ones that have changed - // for (final m in removedMessages) { - // final newm = newMessagesById[m.id]; - // if (newm != null) { - // await _chatController.updateMessage(m, newm); - // } else { - // final newm = newMessagesBySeqId[m.metadata![kSeqId]]; - // if (newm != null) { - // await _chatController.updateMessage(m, newm); - // addedMessages.remove(newm); - // } else { - // await _chatController.removeMessage(m); - // } - // } - // } + // Remove messages that are gone, and replace the ones that have changed + for (final m in removedMessages) { + final newm = newMessagesById[m.id]; + if (newm != null) { + await _chatController.updateMessage(m, newm); + } else { + final newm = newMessagesBySeqId[m.metadata![kSeqId]]; + if (newm != null) { + await _chatController.updateMessage(m, newm); + addedMessages.remove(newm); + } else { + await _chatController.removeMessage(m); + } + } + } + + if (addedMessages.isNotEmpty) { + await _chatController.setMessages(windowState.window.toList()); + } // // // Check for append // if (addedMessages.isNotEmpty) { diff --git a/lib/keyboard_shortcuts.dart b/lib/keyboard_shortcuts.dart index 1e25dac..6708d72 100644 --- a/lib/keyboard_shortcuts.dart +++ b/lib/keyboard_shortcuts.dart @@ -125,27 +125,32 @@ class KeyboardShortcuts extends StatelessWidget { @override Widget build(BuildContext context) => ThemeSwitcher( builder: (context) => Shortcuts( - shortcuts: { - LogicalKeySet( - LogicalKeyboardKey.alt, - LogicalKeyboardKey.control, - LogicalKeyboardKey.keyR): const ReloadThemeIntent(), - LogicalKeySet( - LogicalKeyboardKey.alt, - LogicalKeyboardKey.control, - LogicalKeyboardKey.keyB): const ChangeBrightnessIntent(), - LogicalKeySet( - LogicalKeyboardKey.alt, - LogicalKeyboardKey.control, - LogicalKeyboardKey.keyC): const ChangeColorIntent(), - LogicalKeySet( - LogicalKeyboardKey.alt, - LogicalKeyboardKey.control, - LogicalKeyboardKey.keyD): const AttachDetachIntent(), - LogicalKeySet( - LogicalKeyboardKey.alt, - LogicalKeyboardKey.control, - LogicalKeyboardKey.backquote): const DeveloperPageIntent(), + shortcuts: const { + SingleActivator( + LogicalKeyboardKey.keyR, + control: true, + alt: true, + ): ReloadThemeIntent(), + SingleActivator( + LogicalKeyboardKey.keyB, + control: true, + alt: true, + ): ChangeBrightnessIntent(), + SingleActivator( + LogicalKeyboardKey.keyC, + control: true, + alt: true, + ): ChangeColorIntent(), + SingleActivator( + LogicalKeyboardKey.keyA, + control: true, + alt: true, + ): AttachDetachIntent(), + SingleActivator( + LogicalKeyboardKey.keyD, + control: true, + alt: true, + ): DeveloperPageIntent(), }, child: Actions(actions: >{ ReloadThemeIntent: CallbackAction( diff --git a/pubspec.lock b/pubspec.lock index 47990cf..cdec931 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 + sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f url: "https://pub.dev" source: hosted - version: "80.0.0" + version: "82.0.0" accordion: dependency: "direct main" description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: analyzer - sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" + sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.4.5" animated_bottom_navigation_bar: dependency: "direct main" description: @@ -69,10 +69,10 @@ packages: dependency: "direct main" description: name: archive - sha256: "0c64e928dcbefddecd234205422bcfc2b5e6d31be0b86fef0d0dd48d7b4c9742" + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" url: "https://pub.dev" source: hosted - version: "4.0.4" + version: "4.0.7" args: dependency: transitive description: @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: bidi - sha256: "9a712c7ddf708f7c41b1923aa83648a3ed44cfd75b04f72d598c45e5be287f9d" + sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d" url: "https://pub.dev" source: hosted - version: "2.0.12" + version: "2.0.13" bloc: dependency: "direct main" description: @@ -277,26 +277,26 @@ packages: dependency: transitive description: name: camera_android_camerax - sha256: "13784f539c7f104766bff84e4479a70f03b29d78b208278be45c939250d9d7f5" + sha256: ea7e40bd63afb8f55058e48ec529ce96562be9d08393f79631a06f781161fd0d url: "https://pub.dev" source: hosted - version: "0.6.14+1" + version: "0.6.16" camera_avfoundation: dependency: transitive description: name: camera_avfoundation - sha256: ba48b65a3a97004276ede882e6b838d9667642ff462c95a8bb57ca8a82b6bd25 + sha256: ca36181194f429eef3b09de3c96280f2400693f9735025f90d1f4a27465fdd72 url: "https://pub.dev" source: hosted - version: "0.9.18+11" + version: "0.9.19" camera_platform_interface: dependency: transitive description: name: camera_platform_interface - sha256: "953e7baed3a7c8fae92f7200afeb2be503ff1a17c3b4e4ed7b76f008c2810a31" + sha256: "2f757024a48696ff4814a789b0bd90f5660c0fb25f393ab4564fb483327930e2" url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.10.0" camera_web: dependency: transitive description: @@ -485,10 +485,10 @@ packages: dependency: "direct main" description: name: fast_immutable_collections - sha256: "95a69b9380483dff49ae2c12c9eb92e2b4e1aeff481a33c2a20883471771598a" + sha256: d1aa3d7788fab06cce7f303f4969c7a16a10c865e1bd2478291a8ebcbee084e5 url: "https://pub.dev" source: hosted - version: "11.0.3" + version: "11.0.4" ffi: dependency: transitive description: @@ -538,10 +538,10 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: "1046d719fbdf230330d3443187cc33cc11963d15c9089f6cc56faa42a4c5f0cc" + sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38 url: "https://pub.dev" source: hosted - version: "9.1.0" + version: "9.1.1" flutter_cache_manager: dependency: transitive description: @@ -591,18 +591,18 @@ packages: dependency: "direct main" description: name: flutter_native_splash - sha256: edb09c35ee9230c4b03f13dd45bb3a276d0801865f0a4650b7e2a3bba61a803a + sha256: "8321a6d11a8d13977fa780c89de8d257cce3d841eecfb7a4cadffcc4f12d82dc" url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "2.4.6" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "5a1e6fb2c0561958d7e4c33574674bda7b77caaca7a33b758876956f2902eea3" + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e url: "https://pub.dev" source: hosted - version: "2.0.27" + version: "2.0.28" flutter_shaders: dependency: transitive description: @@ -639,10 +639,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b + sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1 url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.1.0" flutter_translate: dependency: "direct main" description: @@ -676,10 +676,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "7ed2ddaa47524976d5f2aa91432a79da36a76969edd84170777ac5bea82d797c" + sha256: "6022db4c7bfa626841b2a10f34dd1e1b68e8f8f9650db6112dcdeeca45ca793c" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.0.6" freezed_annotation: dependency: "direct main" description: @@ -740,18 +740,18 @@ packages: dependency: transitive description: name: html - sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" url: "https://pub.dev" source: hosted - version: "0.15.5" + version: "0.15.6" http: dependency: transitive description: name: http - sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" http_multi_server: dependency: transitive description: @@ -796,10 +796,10 @@ packages: dependency: "direct main" description: name: image - sha256: "13d3349ace88f12f4a0d175eb5c12dcdd39d35c4c109a8a13dfeb6d0bd9e31c3" + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" url: "https://pub.dev" source: hosted - version: "4.5.3" + version: "4.5.4" indent: dependency: transitive description: @@ -844,10 +844,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a" + sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c url: "https://pub.dev" source: hosted - version: "6.9.4" + version: "6.9.5" keyboard_avoider: dependency: "direct main" description: @@ -860,10 +860,10 @@ packages: dependency: "direct dev" description: name: lint_hard - sha256: ffe7058cb49e021d244d67e650a63380445b56643c2849c6929e938246b99058 + sha256: "2073d4e83ac4e3f2b87cc615fff41abb5c2c5618e117edcd3d71f40f2186f4d5" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.1" logging: dependency: transitive description: @@ -916,10 +916,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: "9cb9e371ee9b5b548714f9ab5fd33b530d799745c83d5729ecd1e8ab2935dbd1" + sha256: f536c5b8cadcf73d764bdce09c94744f06aa832264730f8971b21a60c5666826 url: "https://pub.dev" source: hosted - version: "6.0.7" + version: "6.0.10" nested: dependency: transitive description: @@ -996,10 +996,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.dev" source: hosted - version: "2.2.16" + version: "2.2.17" path_provider_foundation: dependency: transitive description: @@ -1108,10 +1108,10 @@ packages: dependency: transitive description: name: posix - sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "6.0.2" preload_page_view: dependency: "direct main" description: @@ -1292,10 +1292,10 @@ packages: dependency: transitive description: name: scrollview_observer - sha256: "437c930927c5a3240ed2d40398f99d96eaca58f861817ff44f6d0c60113bcf9d" + sha256: "174d4efe7b79459a07662175c4db42c9862dcf78d3978e6e9c2d6c0d8137f4ca" url: "https://pub.dev" source: hosted - version: "1.26.0" + version: "1.26.1" searchable_listview: dependency: "direct main" description: @@ -1333,18 +1333,18 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad" + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.10" shared_preferences_foundation: dependency: transitive description: @@ -1603,10 +1603,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "6c7653816b1c938e121b69ff63a33c9dc68102b65a5fb0a5c0f9786256ed33e6" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.5" timing: dependency: transitive description: @@ -1667,18 +1667,18 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4" + sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" url: "https://pub.dev" source: hosted - version: "6.3.15" + version: "6.3.16" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.3" url_launcher_linux: dependency: transitive description: @@ -1707,10 +1707,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" url_launcher_windows: dependency: transitive description: @@ -1801,26 +1801,26 @@ packages: dependency: transitive description: name: web_socket - sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" win32: dependency: transitive description: name: win32 - sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" url: "https://pub.dev" source: hosted - version: "5.12.0" + version: "5.13.0" window_manager: dependency: "direct main" description: From 61855521dc078076234a73a118629a2d97a6fb4e Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 18 May 2025 11:33:08 -0400 Subject: [PATCH 71/93] eliminate race condition with listen/watch --- .../cubits/reconciliation/message_reconciliation.dart | 11 +++++------ lib/chat/cubits/single_contact_messages_cubit.dart | 9 ++++++--- .../lib/dht_support/src/dht_log/dht_log_spine.dart | 3 +-- .../src/dht_short_array/dht_short_array_head.dart | 4 ++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/chat/cubits/reconciliation/message_reconciliation.dart b/lib/chat/cubits/reconciliation/message_reconciliation.dart index 683b46d..f6d46c3 100644 --- a/lib/chat/cubits/reconciliation/message_reconciliation.dart +++ b/lib/chat/cubits/reconciliation/message_reconciliation.dart @@ -32,11 +32,10 @@ class MessageReconciliation { final activeInputQueues = await _updateAuthorInputQueues(); // Process all input queues together - await _outputCubit - .operate((reconciledArray) async => _reconcileInputQueues( - reconciledArray: reconciledArray, - activeInputQueues: activeInputQueues, - )); + await _outputCubit.operate((reconciledArray) => _reconcileInputQueues( + reconciledArray: reconciledArray, + activeInputQueues: activeInputQueues, + )); }); } @@ -273,5 +272,5 @@ class MessageReconciliation { final TableDBArrayProtobufCubit _outputCubit; final void Function(Object, StackTrace?) _onError; - static const int _maxReconcileChunk = 65536; + static const _maxReconcileChunk = 65536; } diff --git a/lib/chat/cubits/single_contact_messages_cubit.dart b/lib/chat/cubits/single_contact_messages_cubit.dart index 77b1bb9..0e20229 100644 --- a/lib/chat/cubits/single_contact_messages_cubit.dart +++ b/lib/chat/cubits/single_contact_messages_cubit.dart @@ -102,7 +102,7 @@ class SingleContactMessagesCubit extends Cubit { } // Initialize everything - Future _init(Completer _cancel) async { + Future _init(Completer _) async { _unsentMessagesQueue = PersistentQueue( table: 'SingleContactUnsentMessages', key: _remoteConversationRecordKey.toString(), @@ -126,6 +126,9 @@ class SingleContactMessagesCubit extends Cubit { // Command execution background process _commandRunnerFut = Future.delayed(Duration.zero, _commandRunner); + + // Run reconciliation once for all input queues + _reconciliation.reconcileMessages(null); } // Make crypto @@ -198,7 +201,7 @@ class SingleContactMessagesCubit extends Cubit { }); } - Future _makeLocalMessagesCrypto() async => + Future _makeLocalMessagesCrypto() => VeilidCryptoPrivate.fromTypedKey( _accountInfo.userLogin!.identitySecret, 'tabledb'); @@ -210,7 +213,7 @@ class SingleContactMessagesCubit extends Cubit { final crypto = await _makeLocalMessagesCrypto(); _reconciledMessagesCubit = TableDBArrayProtobufCubit( - open: () async => TableDBArrayProtobuf.make( + open: () => TableDBArrayProtobuf.make( table: tableName, crypto: crypto, fromBuffer: proto.ReconciledMessage.fromBuffer), diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart index d9f5df2..93cdcc4 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart @@ -608,14 +608,13 @@ class _DHTLogSpine { Future watch() async { // This will update any existing watches if necessary try { - await _spineRecord.watch(subkeys: [ValueSubkeyRange.single(0)]); - // Update changes to the head record // xxx: check if this localChanges can be false... // xxx: Don't watch for local changes because this class already handles // xxx: notifying listeners and knows when it makes local changes _subscription ??= await _spineRecord.listen(localChanges: true, _onSpineChanged); + await _spineRecord.watch(subkeys: [ValueSubkeyRange.single(0)]); } on Exception { // If anything fails, try to cancel the watches await cancelWatch(); diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart index 66b5baa..1785b28 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart @@ -482,13 +482,13 @@ class _DHTShortArrayHead { Future watch() async { // This will update any existing watches if necessary try { - await _headRecord.watch(subkeys: [ValueSubkeyRange.single(0)]); - // Update changes to the head record // Don't watch for local changes because this class already handles // notifying listeners and knows when it makes local changes _subscription ??= await _headRecord.listen(localChanges: false, _onHeadValueChanged); + + await _headRecord.watch(subkeys: [ValueSubkeyRange.single(0)]); } on Exception { // If anything fails, try to cancel the watches await cancelWatch(); From be8014c97aaf9d99b2496142319962942837d599 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Fri, 23 May 2025 11:30:26 -0400 Subject: [PATCH 72/93] State machine work --- assets/i18n/en.json | 4 +- ...per_account_collection_bloc_map_cubit.dart | 14 +- .../per_account_collection_state.freezed.dart | 12 +- .../models/user_login/user_login.freezed.dart | 24 +- .../views/edit_account_page.dart | 6 +- .../cubits/single_contact_messages_cubit.dart | 7 +- .../models/chat_component_state.freezed.dart | 36 +-- lib/chat/views/chat_component_widget.dart | 22 +- .../cubits/contact_request_inbox_cubit.dart | 3 + .../cubits/waiting_invitation_cubit.dart | 288 ++++++++++++------ .../waiting_invitations_bloc_map_cubit.dart | 120 +++++--- .../models/valid_contact_invitation.dart | 51 ++-- .../views/paste_invitation_dialog.dart | 21 +- .../views/scan_invitation_dialog.dart | 6 +- .../views/contact_details_widget.dart | 3 +- lib/contacts/views/contact_item_widget.dart | 1 - lib/contacts/views/contacts_browser.dart | 37 +-- lib/contacts/views/contacts_page.dart | 37 ++- .../active_conversations_bloc_map_cubit.dart | 13 +- ...ve_single_contact_chat_bloc_map_cubit.dart | 25 +- .../cubits/conversation_cubit.dart | 96 +++--- lib/tools/state_logger.dart | 1 + macos/Podfile.lock | 9 +- packages/veilid_support/example/pubspec.lock | 70 +++-- .../src/dht_log/dht_log_spine.dart | 2 +- .../dht_record/default_dht_record_cubit.dart | 6 +- .../src/dht_record/dht_record_cubit.dart | 6 +- .../identity_support/identity_support.dart | 1 + .../lib/identity_support/super_identity.dart | 67 ++-- .../super_identity_cubit.dart | 21 ++ packages/veilid_support/pubspec.lock | 82 ++--- packages/veilid_support/pubspec.yaml | 4 +- pubspec.lock | 74 ++--- pubspec.yaml | 39 +-- 34 files changed, 703 insertions(+), 505 deletions(-) create mode 100644 packages/veilid_support/lib/identity_support/super_identity_cubit.dart diff --git a/assets/i18n/en.json b/assets/i18n/en.json index f0b84a0..53bf461 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -213,7 +213,9 @@ }, "waiting_invitation": { "accepted": "Contact invitation accepted from {name}", - "rejected": "Contact invitation was rejected" + "rejected": "Contact invitation was rejected", + "invalid": "Contact invitation was not valid", + "init_failed": "Contact initialization failed" }, "paste_invitation_dialog": { "title": "Paste Contact Invite", diff --git a/lib/account_manager/cubits/per_account_collection_bloc_map_cubit.dart b/lib/account_manager/cubits/per_account_collection_bloc_map_cubit.dart index ded50e4..e63b53e 100644 --- a/lib/account_manager/cubits/per_account_collection_bloc_map_cubit.dart +++ b/lib/account_manager/cubits/per_account_collection_bloc_map_cubit.dart @@ -22,11 +22,11 @@ class PerAccountCollectionBlocMapCubit extends BlocMapCubit _addPerAccountCollectionCubit( - {required TypedKey superIdentityRecordKey}) async => + void _addPerAccountCollectionCubit( + {required TypedKey superIdentityRecordKey}) => add( superIdentityRecordKey, - () async => PerAccountCollectionCubit( + () => PerAccountCollectionCubit( locator: _locator, accountInfoCubit: AccountInfoCubit( accountRepository: _accountRepository, @@ -35,11 +35,11 @@ class PerAccountCollectionBlocMapCubit extends BlocMapCubit removeFromState(TypedKey key) => remove(key); + void removeFromState(TypedKey key) => remove(key); @override - Future updateState( - TypedKey key, LocalAccount? oldValue, LocalAccount newValue) async { + void updateState( + TypedKey key, LocalAccount? oldValue, LocalAccount newValue) { // Don't replace unless this is a totally different account // The sub-cubit's subscription will update our state later if (oldValue != null) { @@ -53,7 +53,7 @@ class PerAccountCollectionBlocMapCubit extends BlocMapCubit { @useResult $Res call( {AccountInfo accountInfo, - AsyncValue? avAccountRecordState, + AsyncValue? avAccountRecordState, AccountInfoCubit? accountInfoCubit, AccountRecordCubit? accountRecordCubit, ContactInvitationListCubit? contactInvitationListCubit, @@ -147,9 +147,9 @@ class _$PerAccountCollectionStateCopyWithImpl<$Res> : accountInfo // ignore: cast_nullable_to_non_nullable as AccountInfo, avAccountRecordState: freezed == avAccountRecordState - ? _self.avAccountRecordState! + ? _self.avAccountRecordState : avAccountRecordState // ignore: cast_nullable_to_non_nullable - as AsyncValue?, + as AsyncValue?, accountInfoCubit: freezed == accountInfoCubit ? _self.accountInfoCubit : accountInfoCubit // ignore: cast_nullable_to_non_nullable @@ -227,7 +227,7 @@ class _PerAccountCollectionState extends PerAccountCollectionState { @override final AccountInfo accountInfo; @override - final AsyncValue? avAccountRecordState; + final AsyncValue? avAccountRecordState; @override final AccountInfoCubit? accountInfoCubit; @override @@ -326,7 +326,7 @@ abstract mixin class _$PerAccountCollectionStateCopyWith<$Res> @useResult $Res call( {AccountInfo accountInfo, - AsyncValue? avAccountRecordState, + AsyncValue? avAccountRecordState, AccountInfoCubit? accountInfoCubit, AccountRecordCubit? accountRecordCubit, ContactInvitationListCubit? contactInvitationListCubit, @@ -375,7 +375,7 @@ class __$PerAccountCollectionStateCopyWithImpl<$Res> avAccountRecordState: freezed == avAccountRecordState ? _self.avAccountRecordState : avAccountRecordState // ignore: cast_nullable_to_non_nullable - as AsyncValue?, + as AsyncValue?, accountInfoCubit: freezed == accountInfoCubit ? _self.accountInfoCubit : accountInfoCubit // ignore: cast_nullable_to_non_nullable diff --git a/lib/account_manager/models/user_login/user_login.freezed.dart b/lib/account_manager/models/user_login/user_login.freezed.dart index c406812..914afb8 100644 --- a/lib/account_manager/models/user_login/user_login.freezed.dart +++ b/lib/account_manager/models/user_login/user_login.freezed.dart @@ -67,8 +67,8 @@ abstract mixin class $UserLoginCopyWith<$Res> { _$UserLoginCopyWithImpl; @useResult $Res call( - {Typed superIdentityRecordKey, - Typed identitySecret, + {TypedKey superIdentityRecordKey, + TypedSecret identitySecret, AccountRecordInfo accountRecordInfo, Timestamp lastActive}); @@ -94,13 +94,13 @@ class _$UserLoginCopyWithImpl<$Res> implements $UserLoginCopyWith<$Res> { }) { return _then(_self.copyWith( superIdentityRecordKey: null == superIdentityRecordKey - ? _self.superIdentityRecordKey! + ? _self.superIdentityRecordKey : superIdentityRecordKey // ignore: cast_nullable_to_non_nullable - as Typed, + as TypedKey, identitySecret: null == identitySecret - ? _self.identitySecret! + ? _self.identitySecret : identitySecret // ignore: cast_nullable_to_non_nullable - as Typed, + as TypedSecret, accountRecordInfo: null == accountRecordInfo ? _self.accountRecordInfo : accountRecordInfo // ignore: cast_nullable_to_non_nullable @@ -136,10 +136,10 @@ class _UserLogin implements UserLogin { // SuperIdentity record key for the user // used to index the local accounts table @override - final Typed superIdentityRecordKey; + final TypedKey superIdentityRecordKey; // The identity secret as unlocked from the local accounts table @override - final Typed identitySecret; + final TypedSecret identitySecret; // The account record key, owner key and secret pulled from the identity @override final AccountRecordInfo accountRecordInfo; @@ -197,8 +197,8 @@ abstract mixin class _$UserLoginCopyWith<$Res> @override @useResult $Res call( - {Typed superIdentityRecordKey, - Typed identitySecret, + {TypedKey superIdentityRecordKey, + TypedSecret identitySecret, AccountRecordInfo accountRecordInfo, Timestamp lastActive}); @@ -227,11 +227,11 @@ class __$UserLoginCopyWithImpl<$Res> implements _$UserLoginCopyWith<$Res> { superIdentityRecordKey: null == superIdentityRecordKey ? _self.superIdentityRecordKey : superIdentityRecordKey // ignore: cast_nullable_to_non_nullable - as Typed, + as TypedKey, identitySecret: null == identitySecret ? _self.identitySecret : identitySecret // ignore: cast_nullable_to_non_nullable - as Typed, + as TypedSecret, accountRecordInfo: null == accountRecordInfo ? _self.accountRecordInfo : accountRecordInfo // ignore: cast_nullable_to_non_nullable diff --git a/lib/account_manager/views/edit_account_page.dart b/lib/account_manager/views/edit_account_page.dart index 9af4719..bb14968 100644 --- a/lib/account_manager/views/edit_account_page.dart +++ b/lib/account_manager/views/edit_account_page.dart @@ -214,9 +214,9 @@ class _EditAccountPageState extends WindowSetupState { // Look up account cubit for this specific account final perAccountCollectionBlocMapCubit = context.read(); - final accountRecordCubit = await perAccountCollectionBlocMapCubit - .operate(widget.superIdentityRecordKey, - closure: (c) async => c.accountRecordCubit); + final accountRecordCubit = perAccountCollectionBlocMapCubit + .entry(widget.superIdentityRecordKey) + ?.accountRecordCubit; if (accountRecordCubit == null) { return false; } diff --git a/lib/chat/cubits/single_contact_messages_cubit.dart b/lib/chat/cubits/single_contact_messages_cubit.dart index 0e20229..878858b 100644 --- a/lib/chat/cubits/single_contact_messages_cubit.dart +++ b/lib/chat/cubits/single_contact_messages_cubit.dart @@ -176,12 +176,11 @@ class SingleContactMessagesCubit extends Cubit { _remoteIdentityPublicKey, rcvdMessagesDHTLog); } - Future updateRemoteMessagesRecordKey( - TypedKey? remoteMessagesRecordKey) async { - await _initWait(); - + void updateRemoteMessagesRecordKey(TypedKey? remoteMessagesRecordKey) { _sspRemoteConversationRecordKey.updateState(remoteMessagesRecordKey, (remoteMessagesRecordKey) async { + await _initWait(); + // Don't bother if nothing is changing if (_remoteMessagesRecordKey == remoteMessagesRecordKey) { return; diff --git a/lib/chat/models/chat_component_state.freezed.dart b/lib/chat/models/chat_component_state.freezed.dart index dd5e68e..bbecc7a 100644 --- a/lib/chat/models/chat_component_state.freezed.dart +++ b/lib/chat/models/chat_component_state.freezed.dart @@ -67,9 +67,9 @@ abstract mixin class $ChatComponentStateCopyWith<$Res> { @useResult $Res call( {User? localUser, - IMap remoteUsers, - IMap historicalRemoteUsers, - IMap unknownUsers, + IMap remoteUsers, + IMap historicalRemoteUsers, + IMap unknownUsers, AsyncValue> messageWindow, String title}); @@ -103,17 +103,17 @@ class _$ChatComponentStateCopyWithImpl<$Res> : localUser // ignore: cast_nullable_to_non_nullable as User?, remoteUsers: null == remoteUsers - ? _self.remoteUsers! + ? _self.remoteUsers : remoteUsers // ignore: cast_nullable_to_non_nullable - as IMap, + as IMap, historicalRemoteUsers: null == historicalRemoteUsers - ? _self.historicalRemoteUsers! + ? _self.historicalRemoteUsers : historicalRemoteUsers // ignore: cast_nullable_to_non_nullable - as IMap, + as IMap, unknownUsers: null == unknownUsers - ? _self.unknownUsers! + ? _self.unknownUsers : unknownUsers // ignore: cast_nullable_to_non_nullable - as IMap, + as IMap, messageWindow: null == messageWindow ? _self.messageWindow : messageWindow // ignore: cast_nullable_to_non_nullable @@ -167,13 +167,13 @@ class _ChatComponentState implements ChatComponentState { final User? localUser; // Active remote users @override - final IMap remoteUsers; + final IMap remoteUsers; // Historical remote users @override - final IMap historicalRemoteUsers; + final IMap historicalRemoteUsers; // Unknown users @override - final IMap unknownUsers; + final IMap unknownUsers; // Messages state @override final AsyncValue> messageWindow; @@ -227,9 +227,9 @@ abstract mixin class _$ChatComponentStateCopyWith<$Res> @useResult $Res call( {User? localUser, - IMap remoteUsers, - IMap historicalRemoteUsers, - IMap unknownUsers, + IMap remoteUsers, + IMap historicalRemoteUsers, + IMap unknownUsers, AsyncValue> messageWindow, String title}); @@ -267,15 +267,15 @@ class __$ChatComponentStateCopyWithImpl<$Res> remoteUsers: null == remoteUsers ? _self.remoteUsers : remoteUsers // ignore: cast_nullable_to_non_nullable - as IMap, + as IMap, historicalRemoteUsers: null == historicalRemoteUsers ? _self.historicalRemoteUsers : historicalRemoteUsers // ignore: cast_nullable_to_non_nullable - as IMap, + as IMap, unknownUsers: null == unknownUsers ? _self.unknownUsers : unknownUsers // ignore: cast_nullable_to_non_nullable - as IMap, + as IMap, messageWindow: null == messageWindow ? _self.messageWindow : messageWindow // ignore: cast_nullable_to_non_nullable diff --git a/lib/chat/views/chat_component_widget.dart b/lib/chat/views/chat_component_widget.dart index 5ecf6e9..7d90d89 100644 --- a/lib/chat/views/chat_component_widget.dart +++ b/lib/chat/views/chat_component_widget.dart @@ -54,10 +54,9 @@ class ChatComponentWidget extends StatefulWidget { final contactListCubit = context.watch(); // Get the active conversation cubit - final activeConversationCubit = context - .select( - (x) => x.tryOperateSync(localConversationRecordKey, - closure: (cubit) => cubit)); + final activeConversationCubit = context.select< + ActiveConversationsBlocMapCubit, + ActiveConversationCubit?>((x) => x.entry(localConversationRecordKey)); if (activeConversationCubit == null) { return waitingPage(onCancel: onCancel); } @@ -65,8 +64,7 @@ class ChatComponentWidget extends StatefulWidget { // Get the messages cubit final messagesCubit = context.select( - (x) => x.tryOperateSync(localConversationRecordKey, - closure: (cubit) => cubit)); + (x) => x.entry(localConversationRecordKey)); if (messagesCubit == null) { return waitingPage(onCancel: onCancel); } @@ -106,10 +104,11 @@ class _ChatComponentWidgetState extends State { _textEditingController = TextEditingController(); _scrollController = ScrollController(); _chatStateProcessor = SingleStateProcessor(); + _focusNode = FocusNode(); - final _chatComponentCubit = context.read(); - _chatStateProcessor.follow(_chatComponentCubit.stream, - _chatComponentCubit.state, _updateChatState); + final chatComponentCubit = context.read(); + _chatStateProcessor.follow( + chatComponentCubit.stream, chatComponentCubit.state, _updateChatState); super.initState(); } @@ -118,6 +117,7 @@ class _ChatComponentWidgetState extends State { void dispose() { unawaited(_chatStateProcessor.close()); + _focusNode.dispose(); _chatController.dispose(); _scrollController.dispose(); _textEditingController.dispose(); @@ -281,6 +281,7 @@ class _ChatComponentWidgetState extends State { // Composer builder composerBuilder: (ctx) => VcComposerWidget( autofocus: true, + focusNode: _focusNode, textInputAction: isAnyMobile ? TextInputAction.newline : TextInputAction.send, @@ -399,6 +400,8 @@ class _ChatComponentWidgetState extends State { } void _handleSendPressed(ChatComponentCubit chatComponentCubit, String text) { + _focusNode.requestFocus(); + if (text.startsWith('/')) { chatComponentCubit.runCommand(text); return; @@ -491,4 +494,5 @@ class _ChatComponentWidgetState extends State { late final TextEditingController _textEditingController; late final ScrollController _scrollController; late final SingleStateProcessor _chatStateProcessor; + late final FocusNode _focusNode; } diff --git a/lib/contact_invitation/cubits/contact_request_inbox_cubit.dart b/lib/contact_invitation/cubits/contact_request_inbox_cubit.dart index 714201b..198ae85 100644 --- a/lib/contact_invitation/cubits/contact_request_inbox_cubit.dart +++ b/lib/contact_invitation/cubits/contact_request_inbox_cubit.dart @@ -1,9 +1,12 @@ +import 'package:async_tools/async_tools.dart'; import 'package:veilid_support/veilid_support.dart'; import '../../account_manager/account_manager.dart'; import '../../proto/proto.dart' as proto; // Watch subkey #1 of the ContactRequest record for accept/reject +typedef ContactRequestInboxState = AsyncValue; + class ContactRequestInboxCubit extends DefaultDHTRecordCubit { ContactRequestInboxCubit( diff --git a/lib/contact_invitation/cubits/waiting_invitation_cubit.dart b/lib/contact_invitation/cubits/waiting_invitation_cubit.dart index 47addc2..b712546 100644 --- a/lib/contact_invitation/cubits/waiting_invitation_cubit.dart +++ b/lib/contact_invitation/cubits/waiting_invitation_cubit.dart @@ -9,10 +9,62 @@ import 'package:veilid_support/veilid_support.dart'; import '../../account_manager/account_manager.dart'; import '../../conversation/conversation.dart'; import '../../proto/proto.dart' as proto; -import '../../tools/tools.dart'; import '../models/accepted_contact.dart'; import 'contact_request_inbox_cubit.dart'; +/// State of WaitingInvitationCubit +sealed class WaitingInvitationState + implements StateMachineState { + WaitingInvitationState({required this.global}); + final WaitingInvitationStateGlobal global; +} + +class WaitingInvitationStateGlobal { + WaitingInvitationStateGlobal( + {required this.accountInfo, + required this.accountRecordCubit, + required this.contactInvitationRecord}); + final AccountInfo accountInfo; + final AccountRecordCubit accountRecordCubit; + final proto.ContactInvitationRecord contactInvitationRecord; +} + +/// State of WaitingInvitationCubit: +/// Signature was invalid +class WaitingInvitationStateInvalidSignature + with StateMachineEndState + implements WaitingInvitationState { + const WaitingInvitationStateInvalidSignature({required this.global}); + + @override + final WaitingInvitationStateGlobal global; +} + +/// State of WaitingInvitationCubit: +/// Failed to initialize +class WaitingInvitationStateInitFailed + with StateMachineEndState + implements WaitingInvitationState { + const WaitingInvitationStateInitFailed( + {required this.global, required this.exception}); + + @override + final WaitingInvitationStateGlobal global; + final Exception exception; +} + +/// State of WaitingInvitationCubit: +/// Finished normally with an invitation status +class WaitingInvitationStateInvitationStatus + with StateMachineEndState + implements WaitingInvitationState { + const WaitingInvitationStateInvitationStatus( + {required this.global, required this.status}); + @override + final WaitingInvitationStateGlobal global; + final InvitationStatus status; +} + @immutable class InvitationStatus extends Equatable { const InvitationStatus({required this.acceptedContact}); @@ -22,94 +74,160 @@ class InvitationStatus extends Equatable { List get props => [acceptedContact]; } -class WaitingInvitationCubit extends AsyncTransformerCubit { - WaitingInvitationCubit( - ContactRequestInboxCubit super.input, { +/// State of WaitingInvitationCubit: +/// Waiting for the invited contact to accept/reject the invitation +class WaitingInvitationStateWaitForContactResponse + extends AsyncCubitReactorState< + WaitingInvitationState, + ContactRequestInboxState, + ContactRequestInboxCubit> implements WaitingInvitationState { + WaitingInvitationStateWaitForContactResponse(super.create, + {required this.global}) + : super(onState: (ctx) async { + final signedContactResponse = ctx.state.asData?.value; + if (signedContactResponse == null) { + return null; + } + + final contactResponse = proto.ContactResponse.fromBuffer( + signedContactResponse.contactResponse); + final contactSuperRecordKey = + contactResponse.superIdentityRecordKey.toVeilid(); + + // Fetch the remote contact's account superidentity + return WaitingInvitationStateWaitForContactSuperIdentity( + () => SuperIdentityCubit(superRecordKey: contactSuperRecordKey), + global: global, + signedContactResponse: signedContactResponse); + }); + + @override + final WaitingInvitationStateGlobal global; +} + +/// State of WaitingInvitationCubit: +/// Once an accept/reject happens, get the SuperIdentity of the recipient +class WaitingInvitationStateWaitForContactSuperIdentity + extends AsyncCubitReactorState implements WaitingInvitationState { + WaitingInvitationStateWaitForContactSuperIdentity(super.create, + {required this.global, + required proto.SignedContactResponse signedContactResponse}) + : super(onState: (ctx) async { + final contactSuperIdentity = ctx.state.asData?.value; + if (contactSuperIdentity == null) { + return null; + } + + final contactResponseBytes = + Uint8List.fromList(signedContactResponse.contactResponse); + final contactResponse = + proto.ContactResponse.fromBuffer(contactResponseBytes); + + // Verify + final idcs = await contactSuperIdentity.currentInstance.cryptoSystem; + final signature = signedContactResponse.identitySignature.toVeilid(); + if (!await idcs.verify(contactSuperIdentity.currentInstance.publicKey, + contactResponseBytes, signature)) { + // Could not verify signature of contact response + return WaitingInvitationStateInvalidSignature( + global: global, + ); + } + + // Check for rejection + if (!contactResponse.accept) { + // Rejection + return WaitingInvitationStateInvitationStatus( + global: global, + status: const InvitationStatus(acceptedContact: null), + ); + } + + // Pull profile from remote conversation key + final remoteConversationRecordKey = + contactResponse.remoteConversationRecordKey.toVeilid(); + + return WaitingInvitationStateWaitForConversation( + () => ConversationCubit( + accountInfo: global.accountInfo, + remoteIdentityPublicKey: + contactSuperIdentity.currentInstance.typedPublicKey, + remoteConversationRecordKey: remoteConversationRecordKey), + global: global, + remoteConversationRecordKey: remoteConversationRecordKey, + contactSuperIdentity: contactSuperIdentity, + ); + }); + + @override + final WaitingInvitationStateGlobal global; +} + +/// State of WaitingInvitationCubit: +/// Wait for the conversation cubit to initialize so we can return the +/// accepted invitation +class WaitingInvitationStateWaitForConversation extends AsyncCubitReactorState< + WaitingInvitationState, + AsyncValue, + ConversationCubit> implements WaitingInvitationState { + WaitingInvitationStateWaitForConversation(super.create, + {required this.global, + required TypedKey remoteConversationRecordKey, + required SuperIdentity contactSuperIdentity}) + : super(onState: (ctx) async { + final remoteConversation = ctx.state.asData?.value.remoteConversation; + final localConversation = ctx.state.asData?.value.localConversation; + if (remoteConversation == null || localConversation != null) { + return null; + } + + // Stop reacting to the conversation cubit + ctx.stop(); + + // Complete the local conversation now that we have the remote profile + final remoteProfile = remoteConversation.profile; + final localConversationRecordKey = global + .contactInvitationRecord.localConversationRecordKey + .toVeilid(); + + try { + await ctx.cubit.initLocalConversation( + profile: global.accountRecordCubit.state.asData!.value.profile, + existingConversationRecordKey: localConversationRecordKey); + } on Exception catch (e) { + return WaitingInvitationStateInitFailed( + global: global, exception: e); + } + + return WaitingInvitationStateInvitationStatus( + global: global, + status: InvitationStatus( + acceptedContact: AcceptedContact( + remoteProfile: remoteProfile, + remoteIdentity: contactSuperIdentity, + remoteConversationRecordKey: remoteConversationRecordKey, + localConversationRecordKey: localConversationRecordKey))); + }); + + @override + final WaitingInvitationStateGlobal global; +} + +/// Invitation state processor for sent invitations +class WaitingInvitationCubit extends StateMachineCubit { + WaitingInvitationCubit({ + required ContactRequestInboxCubit Function() initialStateCreate, required AccountInfo accountInfo, required AccountRecordCubit accountRecordCubit, required proto.ContactInvitationRecord contactInvitationRecord, }) : super( - transform: (signedContactResponse) => _transform( - signedContactResponse, + WaitingInvitationStateWaitForContactResponse( + initialStateCreate, + global: WaitingInvitationStateGlobal( accountInfo: accountInfo, accountRecordCubit: accountRecordCubit, - contactInvitationRecord: contactInvitationRecord)); - - static Future> _transform( - proto.SignedContactResponse? signedContactResponse, - {required AccountInfo accountInfo, - required AccountRecordCubit accountRecordCubit, - required proto.ContactInvitationRecord contactInvitationRecord}) async { - if (signedContactResponse == null) { - return const AsyncValue.loading(); - } - - final contactResponseBytes = - Uint8List.fromList(signedContactResponse.contactResponse); - final contactResponse = - proto.ContactResponse.fromBuffer(contactResponseBytes); - final contactIdentityMasterRecordKey = - contactResponse.superIdentityRecordKey.toVeilid(); - - // Fetch the remote contact's account master - final contactSuperIdentity = await SuperIdentity.open( - superRecordKey: contactIdentityMasterRecordKey); - - // Verify - final idcs = await contactSuperIdentity.currentInstance.cryptoSystem; - final signature = signedContactResponse.identitySignature.toVeilid(); - if (!await idcs.verify(contactSuperIdentity.currentInstance.publicKey, - contactResponseBytes, signature)) { - // Could not verify signature of contact response - return AsyncValue.error('Invalid signature on contact response.'); - } - - // Check for rejection - if (!contactResponse.accept) { - // Rejection - return const AsyncValue.data(InvitationStatus(acceptedContact: null)); - } - - // Pull profile from remote conversation key - final remoteConversationRecordKey = - contactResponse.remoteConversationRecordKey.toVeilid(); - - final conversation = ConversationCubit( - accountInfo: accountInfo, - remoteIdentityPublicKey: - contactSuperIdentity.currentInstance.typedPublicKey, - remoteConversationRecordKey: remoteConversationRecordKey); - - // wait for remote conversation for up to 20 seconds - proto.Conversation? remoteConversation; - var retryCount = 20; - do { - await conversation.refresh(); - remoteConversation = conversation.state.asData?.value.remoteConversation; - if (remoteConversation != null) { - break; - } - log.info('Remote conversation could not be read. Waiting...'); - await Future.delayed(const Duration(seconds: 1)); - retryCount--; - } while (retryCount > 0); - if (remoteConversation == null) { - return AsyncValue.error('Invitation accept timed out.'); - } - - // Complete the local conversation now that we have the remote profile - final remoteProfile = remoteConversation.profile; - final localConversationRecordKey = - contactInvitationRecord.localConversationRecordKey.toVeilid(); - return conversation.initLocalConversation( - profile: accountRecordCubit.state.asData!.value.profile, - existingConversationRecordKey: localConversationRecordKey, - callback: (localConversation) async => AsyncValue.data(InvitationStatus( - acceptedContact: AcceptedContact( - remoteProfile: remoteProfile, - remoteIdentity: contactSuperIdentity, - remoteConversationRecordKey: remoteConversationRecordKey, - localConversationRecordKey: localConversationRecordKey)))); - } + contactInvitationRecord: contactInvitationRecord), + ), + ); } diff --git a/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart b/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart index 197ba8c..f125e71 100644 --- a/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart +++ b/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart @@ -10,13 +10,13 @@ import '../../proto/proto.dart' as proto; import 'cubits.dart'; typedef WaitingInvitationsBlocMapState - = BlocMapState>; + = BlocMapState; // Map of contactRequestInboxRecordKey to WaitingInvitationCubit // Wraps a contact invitation cubit to watch for accept/reject // Automatically follows the state of a ContactInvitationListCubit. class WaitingInvitationsBlocMapCubit extends BlocMapCubit, WaitingInvitationCubit> + WaitingInvitationState, WaitingInvitationCubit> with StateMapFollower, TypedKey, proto.ContactInvitationRecord> { @@ -45,13 +45,12 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit _addWaitingInvitation( - {required proto.ContactInvitationRecord - contactInvitationRecord}) async => + void _addWaitingInvitation( + {required proto.ContactInvitationRecord contactInvitationRecord}) => add( contactInvitationRecord.contactRequestInbox.recordKey.toVeilid(), - () async => WaitingInvitationCubit( - ContactRequestInboxCubit( + () => WaitingInvitationCubit( + initialStateCreate: () => ContactRequestInboxCubit( accountInfo: _accountInfo, contactInvitationRecord: contactInvitationRecord), accountInfo: _accountInfo, @@ -63,44 +62,73 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit removeFromState(TypedKey key) => remove(key); + void removeFromState(TypedKey key) => remove(key); @override - Future updateState( - TypedKey key, - proto.ContactInvitationRecord? oldValue, - proto.ContactInvitationRecord newValue) async { - await _addWaitingInvitation(contactInvitationRecord: newValue); + void updateState(TypedKey key, proto.ContactInvitationRecord? oldValue, + proto.ContactInvitationRecord newValue) { + _addWaitingInvitation(contactInvitationRecord: newValue); } //// diff --git a/lib/contact_invitation/models/valid_contact_invitation.dart b/lib/contact_invitation/models/valid_contact_invitation.dart index f19e951..c39692c 100644 --- a/lib/contact_invitation/models/valid_contact_invitation.dart +++ b/lib/contact_invitation/models/valid_contact_invitation.dart @@ -47,36 +47,35 @@ class ValidContactInvitation { accountInfo: _accountInfo, remoteIdentityPublicKey: _contactSuperIdentity.currentInstance.typedPublicKey); - return conversation.initLocalConversation( - profile: profile, - callback: (localConversation) async { - final contactResponse = proto.ContactResponse() - ..accept = true - ..remoteConversationRecordKey = localConversation.key.toProto() - ..superIdentityRecordKey = - _accountInfo.superIdentityRecordKey.toProto(); - final contactResponseBytes = contactResponse.writeToBuffer(); + final localConversationRecordKey = + await conversation.initLocalConversation(profile: profile); - final cs = await _accountInfo.identityCryptoSystem; - final identitySignature = await cs.signWithKeyPair( - _accountInfo.identityWriter, contactResponseBytes); + final contactResponse = proto.ContactResponse() + ..accept = true + ..remoteConversationRecordKey = localConversationRecordKey.toProto() + ..superIdentityRecordKey = + _accountInfo.superIdentityRecordKey.toProto(); + final contactResponseBytes = contactResponse.writeToBuffer(); - final signedContactResponse = proto.SignedContactResponse() - ..contactResponse = contactResponseBytes - ..identitySignature = identitySignature.toProto(); + final cs = await _accountInfo.identityCryptoSystem; + final identitySignature = await cs.signWithKeyPair( + _accountInfo.identityWriter, contactResponseBytes); - // Write the acceptance to the inbox - await contactRequestInbox - .eventualWriteProtobuf(signedContactResponse, subkey: 1); + final signedContactResponse = proto.SignedContactResponse() + ..contactResponse = contactResponseBytes + ..identitySignature = identitySignature.toProto(); - return AcceptedContact( - remoteProfile: _contactRequestPrivate.profile, - remoteIdentity: _contactSuperIdentity, - remoteConversationRecordKey: - _contactRequestPrivate.chatRecordKey.toVeilid(), - localConversationRecordKey: localConversation.key, - ); - }); + // Write the acceptance to the inbox + await contactRequestInbox.eventualWriteProtobuf(signedContactResponse, + subkey: 1); + + return AcceptedContact( + remoteProfile: _contactRequestPrivate.profile, + remoteIdentity: _contactSuperIdentity, + remoteConversationRecordKey: + _contactRequestPrivate.chatRecordKey.toVeilid(), + localConversationRecordKey: localConversationRecordKey, + ); }); } on Exception catch (e) { log.debug('exception: $e', e); diff --git a/lib/contact_invitation/views/paste_invitation_dialog.dart b/lib/contact_invitation/views/paste_invitation_dialog.dart index 9cd9efc..b014fc2 100644 --- a/lib/contact_invitation/views/paste_invitation_dialog.dart +++ b/lib/contact_invitation/views/paste_invitation_dialog.dart @@ -112,8 +112,8 @@ class PasteInvitationDialogState extends State { constraints: const BoxConstraints(maxHeight: 200), child: TextField( enabled: !dialogState.isValidating, - onChanged: (text) async => - _onPasteChanged(text, validateInviteData), + autofocus: true, + onChanged: (text) => _onPasteChanged(text, validateInviteData), style: monoStyle, keyboardType: TextInputType.multiline, maxLines: null, @@ -129,14 +129,11 @@ class PasteInvitationDialogState extends State { } @override - // ignore: prefer_expression_function_bodies - Widget build(BuildContext context) { - return InvitationDialog( - locator: widget._locator, - onValidationCancelled: onValidationCancelled, - onValidationSuccess: onValidationSuccess, - onValidationFailed: onValidationFailed, - inviteControlIsValid: inviteControlIsValid, - buildInviteControl: buildInviteControl); - } + Widget build(BuildContext context) => InvitationDialog( + locator: widget._locator, + onValidationCancelled: onValidationCancelled, + onValidationSuccess: onValidationSuccess, + onValidationFailed: onValidationFailed, + inviteControlIsValid: inviteControlIsValid, + buildInviteControl: buildInviteControl); } diff --git a/lib/contact_invitation/views/scan_invitation_dialog.dart b/lib/contact_invitation/views/scan_invitation_dialog.dart index fa8bba9..8fbdf5c 100644 --- a/lib/contact_invitation/views/scan_invitation_dialog.dart +++ b/lib/contact_invitation/views/scan_invitation_dialog.dart @@ -169,7 +169,7 @@ class ScanInvitationDialogState extends State { fit: BoxFit.contain, scanWindow: scanWindow, controller: cameraController, - errorBuilder: (context, error, child) => + errorBuilder: (context, error) => ScannerErrorWidget(error: error), onDetect: (c) { final barcode = c.barcodes.firstOrNull; @@ -242,6 +242,10 @@ class ScanInvitationDialogState extends State { return const Icon(Icons.camera_front); case CameraFacing.back: return const Icon(Icons.camera_rear); + case CameraFacing.external: + return const Icon(Icons.camera_alt); + case CameraFacing.unknown: + return const Icon(Icons.question_mark); } }, ), diff --git a/lib/contacts/views/contact_details_widget.dart b/lib/contacts/views/contact_details_widget.dart index 7b5416e..707dc1e 100644 --- a/lib/contacts/views/contact_details_widget.dart +++ b/lib/contacts/views/contact_details_widget.dart @@ -27,8 +27,7 @@ class ContactDetailsWidget extends StatefulWidget { final void Function(bool)? onModifiedState; } -class _ContactDetailsWidgetState extends State - with SingleTickerProviderStateMixin { +class _ContactDetailsWidgetState extends State { @override Widget build(BuildContext context) => SingleChildScrollView( child: EditContactForm( diff --git a/lib/contacts/views/contact_item_widget.dart b/lib/contacts/views/contact_item_widget.dart index 4614f27..e206570 100644 --- a/lib/contacts/views/contact_item_widget.dart +++ b/lib/contacts/views/contact_item_widget.dart @@ -23,7 +23,6 @@ class ContactItemWidget extends StatelessWidget { _onDelete = onDelete; @override - // ignore: prefer_expression_function_bodies Widget build( BuildContext context, ) { diff --git a/lib/contacts/views/contacts_browser.dart b/lib/contacts/views/contacts_browser.dart index 74cd0b5..84a2601 100644 --- a/lib/contacts/views/contacts_browser.dart +++ b/lib/contacts/views/contacts_browser.dart @@ -8,7 +8,6 @@ import 'package:searchable_listview/searchable_listview.dart'; import 'package:star_menu/star_menu.dart'; import 'package:veilid_support/veilid_support.dart'; -import '../../chat_list/chat_list.dart'; import '../../contact_invitation/contact_invitation.dart'; import '../../proto/proto.dart' as proto; import '../../theme/theme.dart'; @@ -45,6 +44,7 @@ class ContactsBrowserElement { class ContactsBrowser extends StatefulWidget { const ContactsBrowser( {required this.onContactSelected, + required this.onContactDeleted, required this.onStartChat, this.selectedContactRecordKey, super.key}); @@ -52,6 +52,7 @@ class ContactsBrowser extends StatefulWidget { State createState() => _ContactsBrowserState(); final Future Function(proto.Contact? contact) onContactSelected; + final Future Function(proto.Contact contact) onContactDeleted; final Future Function(proto.Contact contact) onStartChat; final TypedKey? selectedContactRecordKey; @@ -66,7 +67,10 @@ class ContactsBrowser extends StatefulWidget { 'onContactSelected', onContactSelected)) ..add( ObjectFlagProperty Function(proto.Contact contact)>.has( - 'onStartChat', onStartChat)); + 'onStartChat', onStartChat)) + ..add( + ObjectFlagProperty Function(proto.Contact contact)>.has( + 'onContactDeleted', onContactDeleted)); } } @@ -89,8 +93,6 @@ class _ContactsBrowserState extends State final menuParams = StarMenuParameters( shape: MenuShape.linear, centerOffset: const Offset(0, 64), - // backgroundParams: - // BackgroundParams(backgroundColor: theme.shadowColor.withAlpha(128)), boundaryBackground: BoundaryBackground( color: menuBackgroundColor, decoration: ShapeDecoration( @@ -145,7 +147,7 @@ class _ContactsBrowserState extends State return StarMenu( items: inviteMenuItems, - onItemTapped: (_index, controller) { + onItemTapped: (_, controller) { controller.closeMenu!(); }, controller: _invitationMenuController, @@ -162,16 +164,13 @@ class _ContactsBrowserState extends State Widget build(BuildContext context) { final theme = Theme.of(context); final scale = theme.extension()!; - //final scaleConfig = theme.extension()!; final cilState = context.watch().state; - //final cilBusy = cilState.busy; final contactInvitationRecordList = cilState.state.asData?.value.map((x) => x.value).toIList() ?? const IListConst([]); final ciState = context.watch().state; - //final ciBusy = ciState.busy; final contactList = ciState.state.asData?.value.map((x) => x.value).toIList(); @@ -201,8 +200,8 @@ class _ContactsBrowserState extends State contact.localConversationRecordKey.toVeilid(), disabled: false, onDoubleTap: _onStartChat, - onTap: _onSelectContact, - onDelete: _onDeleteContact) + onTap: onContactSelected, + onDelete: _onContactDeleted) .paddingLTRB(0, 4, 0, 0); case ContactsBrowserElementKind.invitation: final invitation = element.invitation!; @@ -261,7 +260,7 @@ class _ContactsBrowserState extends State ]); } - Future _onSelectContact(proto.Contact contact) async { + Future onContactSelected(proto.Contact contact) async { await widget.onContactSelected(contact); } @@ -269,20 +268,8 @@ class _ContactsBrowserState extends State await widget.onStartChat(contact); } - Future _onDeleteContact(proto.Contact contact) async { - final localConversationRecordKey = - contact.localConversationRecordKey.toVeilid(); - - final contactListCubit = context.read(); - final chatListCubit = context.read(); - - // Delete the contact itself - await contactListCubit.deleteContact( - localConversationRecordKey: localConversationRecordKey); - - // Remove any chats for this contact - await chatListCubit.deleteChat( - localConversationRecordKey: localConversationRecordKey); + Future _onContactDeleted(proto.Contact contact) async { + await widget.onContactDeleted(contact); } //////////////////////////////////////////////////////////////////////////// diff --git a/lib/contacts/views/contacts_page.dart b/lib/contacts/views/contacts_page.dart index 0f1731d..26a6f0d 100644 --- a/lib/contacts/views/contacts_page.dart +++ b/lib/contacts/views/contacts_page.dart @@ -103,6 +103,7 @@ class _ContactsPageState extends State { .toVeilid(), onContactSelected: _onContactSelected, onStartChat: _onChatStarted, + onContactDeleted: _onContactDeleted, ).paddingLTRB(4, 0, 4, 8)))), if (enableRight && enableLeft) Container( @@ -157,6 +158,40 @@ class _ContactsPageState extends State { } } + Future _onContactDeleted(proto.Contact contact) async { + if (contact == _selectedContact && _isModified) { + final ok = await showConfirmModal( + context: context, + title: translate('confirmation.discard_changes'), + text: translate('confirmation.are_you_sure_discard')); + if (!ok) { + return false; + } + } + setState(() { + _selectedContact = null; + _isModified = false; + }); + + if (mounted) { + final localConversationRecordKey = + contact.localConversationRecordKey.toVeilid(); + + final contactListCubit = context.read(); + final chatListCubit = context.read(); + + // Delete the contact itself + await contactListCubit.deleteContact( + localConversationRecordKey: localConversationRecordKey); + + // Remove any chats for this contact + await chatListCubit.deleteChat( + localConversationRecordKey: localConversationRecordKey); + } + + return true; + } + proto.Contact? _selectedContact; - bool _isModified = false; + var _isModified = false; } diff --git a/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart b/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart index 08a249f..3c00eba 100644 --- a/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart +++ b/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart @@ -74,11 +74,11 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit _addDirectConversation( + void _addDirectConversation( {required TypedKey remoteIdentityPublicKey, required TypedKey localConversationRecordKey, - required TypedKey remoteConversationRecordKey}) async => - add(localConversationRecordKey, () async { + required TypedKey remoteConversationRecordKey}) => + add(localConversationRecordKey, () { // Conversation cubit the tracks the state between the local // and remote halves of a contact's relationship with this account final conversationCubit = ConversationCubit( @@ -129,11 +129,10 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit removeFromState(TypedKey key) => remove(key); + void removeFromState(TypedKey key) => remove(key); @override - Future updateState( - TypedKey key, proto.Chat? oldValue, proto.Chat newValue) async { + void updateState(TypedKey key, proto.Chat? oldValue, proto.Chat newValue) { switch (newValue.whichKind()) { case proto.Chat_Kind.notSet: throw StateError('unknown chat kind'); @@ -161,7 +160,7 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit _addConversationMessages(_SingleContactChatState state) async { - // xxx could use atomic update() function - await update(state.localConversationRecordKey, - onUpdate: (cubit) async => + void _addConversationMessages(_SingleContactChatState state) { + update(state.localConversationRecordKey, + onUpdate: (cubit) => cubit.updateRemoteMessagesRecordKey(state.remoteMessagesRecordKey), - onCreate: () async => SingleContactMessagesCubit( + onCreate: () => SingleContactMessagesCubit( accountInfo: _accountInfo, remoteIdentityPublicKey: state.remoteIdentityPublicKey, localConversationRecordKey: state.localConversationRecordKey, @@ -87,13 +84,11 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit removeFromState(TypedKey key) => remove(key); + void removeFromState(TypedKey key) => remove(key); @override - Future updateState( - TypedKey key, - AsyncValue? oldValue, - AsyncValue newValue) async { + void updateState(TypedKey key, AsyncValue? oldValue, + AsyncValue newValue) { final newState = _mapStateValue(newValue); if (oldValue != null) { final oldState = _mapStateValue(oldValue); @@ -102,14 +97,14 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit get props => [localConversation, remoteConversation]; + + @override + String toString() => 'ConversationState(' + 'localConversation: ${DynamicDebug.toDebug(localConversation)}, ' + 'remoteConversation: ${DynamicDebug.toDebug(remoteConversation)}' + ')'; } /// Represents the control channel between two contacts @@ -39,13 +45,11 @@ class ConversationCubit extends Cubit> { TypedKey? localConversationRecordKey, TypedKey? remoteConversationRecordKey}) : _accountInfo = accountInfo, - _localConversationRecordKey = localConversationRecordKey, _remoteIdentityPublicKey = remoteIdentityPublicKey, - _remoteConversationRecordKey = remoteConversationRecordKey, super(const AsyncValue.loading()) { _identityWriter = _accountInfo.identityWriter; - if (_localConversationRecordKey != null) { + if (localConversationRecordKey != null) { _initWait.add((_) async { await _setLocalConversation(() async { // Open local record key if it is specified @@ -54,7 +58,7 @@ class ConversationCubit extends Cubit> { final writer = _identityWriter; final record = await pool.openRecordWrite( - _localConversationRecordKey!, writer, + localConversationRecordKey, writer, debugName: 'ConversationCubit::LocalConversation', parent: accountInfo.accountRecordKey, crypto: crypto); @@ -64,17 +68,17 @@ class ConversationCubit extends Cubit> { }); } - if (_remoteConversationRecordKey != null) { + if (remoteConversationRecordKey != null) { _initWait.add((cancel) async { await _setRemoteConversation(() async { // Open remote record key if it is specified final pool = DHTRecordPool.instance; final crypto = await _cachedConversationCrypto(); - final record = await pool.openRecordRead(_remoteConversationRecordKey, + final record = await pool.openRecordRead(remoteConversationRecordKey, debugName: 'ConversationCubit::RemoteConversation', parent: - await pool.getParentRecordKey(_remoteConversationRecordKey) ?? + await pool.getParentRecordKey(remoteConversationRecordKey) ?? accountInfo.accountRecordKey, crypto: crypto); @@ -104,13 +108,11 @@ class ConversationCubit extends Cubit> { /// incomplete 'existingConversationRecord' that we need to fill /// in now that we have the remote identity key /// The ConversationCubit must not already have a local conversation - /// The callback allows for more initialization to occur and for - /// cleanup to delete records upon failure of the callback - Future initLocalConversation( + /// Returns the local conversation record key that was initialized + Future initLocalConversation( {required proto.Profile profile, - required FutureOr Function(DHTRecord) callback, TypedKey? existingConversationRecordKey}) async { - assert(_localConversationRecordKey == null, + assert(_localConversationCubit == null, 'must not have a local conversation yet'); final pool = DHTRecordPool.instance; @@ -138,11 +140,8 @@ class ConversationCubit extends Cubit> { schema: DHTSchema.smpl( oCnt: 0, members: [DHTSchemaMember(mKey: writer.key, mCnt: 1)])); } - final out = localConversationRecord - // ignore: prefer_expression_function_bodies - .deleteScope((localConversation) async { - // Make messages log - return _initLocalMessages( + await localConversationRecord.deleteScope((localConversation) async { + await _initLocalMessages( localConversationKey: localConversation.key, callback: (messages) async { // Create initial local conversation key contents @@ -158,36 +157,31 @@ class ConversationCubit extends Cubit> { if (update != null) { throw Exception('Failed to write local conversation'); } - final out = await callback(localConversation); - // Upon success emit the local conversation record to the state - _updateLocalConversationState(AsyncValue.data(conversation)); - - return out; + // If success, save the new local conversation + // record key in this object + localConversation.ref(); + await _setLocalConversation(() async => localConversation); }); }); - // If success, save the new local conversation record key in this object - _localConversationRecordKey = localConversationRecord.key; - await _setLocalConversation(() async => localConversationRecord); - - return out; + return localConversationRecord.key; } /// Force refresh of conversation keys - Future refresh() async { - await _initWait(); + // Future refresh() async { + // await _initWait(); - final lcc = _localConversationCubit; - final rcc = _remoteConversationCubit; + // final lcc = _localConversationCubit; + // final rcc = _remoteConversationCubit; - if (lcc != null) { - await lcc.refreshDefault(); - } - if (rcc != null) { - await rcc.refreshDefault(); - } - } + // if (lcc != null) { + // await lcc.refreshDefault(); + // } + // if (rcc != null) { + // await rcc.refreshDefault(); + // } + // } /// Watch for account record changes and update the conversation void watchAccountChanges(Stream> accountStream, @@ -226,12 +220,6 @@ class ConversationCubit extends Cubit> { _incrementalState = ConversationState( localConversation: conv, remoteConversation: _incrementalState.remoteConversation); - // return loading still if state isn't complete - if (_localConversationRecordKey != null && - _incrementalState.localConversation == null) { - return const AsyncValue.loading(); - } - // local state is complete, all remote state is emitted incrementally return AsyncValue.data(_incrementalState); }, loading: AsyncValue.loading, @@ -246,12 +234,6 @@ class ConversationCubit extends Cubit> { _incrementalState = ConversationState( localConversation: _incrementalState.localConversation, remoteConversation: conv); - // return loading still if the local state isn't complete - if (_localConversationRecordKey != null && - _incrementalState.localConversation == null) { - return const AsyncValue.loading(); - } - // local state is complete, all remote state is emitted incrementally return AsyncValue.data(_incrementalState); }, loading: AsyncValue.loading, @@ -263,9 +245,12 @@ class ConversationCubit extends Cubit> { // Open local converation key Future _setLocalConversation(Future Function() open) async { assert(_localConversationCubit == null, - 'shoud not set local conversation twice'); + 'should not set local conversation twice'); _localConversationCubit = DefaultDHTRecordCubit( open: open, decodeState: proto.Conversation.fromBuffer); + + await _localConversationCubit!.ready(); + _localSubscription = _localConversationCubit!.stream.listen(_updateLocalConversationState); _updateLocalConversationState(_localConversationCubit!.state); @@ -274,9 +259,12 @@ class ConversationCubit extends Cubit> { // Open remote converation key Future _setRemoteConversation(Future Function() open) async { assert(_remoteConversationCubit == null, - 'shoud not set remote conversation twice'); + 'should not set remote conversation twice'); _remoteConversationCubit = DefaultDHTRecordCubit( open: open, decodeState: proto.Conversation.fromBuffer); + + await _remoteConversationCubit!.ready(); + _remoteSubscription = _remoteConversationCubit!.stream.listen(_updateRemoteConversationState); _updateRemoteConversationState(_remoteConversationCubit!.state); @@ -316,14 +304,12 @@ class ConversationCubit extends Cubit> { final AccountInfo _accountInfo; late final KeyPair _identityWriter; final TypedKey _remoteIdentityPublicKey; - TypedKey? _localConversationRecordKey; - final TypedKey? _remoteConversationRecordKey; DefaultDHTRecordCubit? _localConversationCubit; DefaultDHTRecordCubit? _remoteConversationCubit; StreamSubscription>? _localSubscription; StreamSubscription>? _remoteSubscription; StreamSubscription>? _accountSubscription; - ConversationState _incrementalState = const ConversationState( + var _incrementalState = const ConversationState( localConversation: null, remoteConversation: null); VeilidCrypto? _conversationCrypto; final WaitSet _initWait = WaitSet(); diff --git a/lib/tools/state_logger.dart b/lib/tools/state_logger.dart index a133346..8782662 100644 --- a/lib/tools/state_logger.dart +++ b/lib/tools/state_logger.dart @@ -17,6 +17,7 @@ const Map _blocChangeLogLevels = { 'PreferencesCubit': LogLevel.debug, 'ConversationCubit': LogLevel.debug, 'DefaultDHTRecordCubit': LogLevel.debug, + 'WaitingInvitationCubit': LogLevel.debug, }; const Map _blocCreateCloseLogLevels = {}; diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 2d40a21..beb7d0e 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -2,7 +2,8 @@ PODS: - file_saver (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) - - mobile_scanner (6.0.2): + - mobile_scanner (7.0.0): + - Flutter - FlutterMacOS - package_info_plus (0.0.1): - FlutterMacOS @@ -33,7 +34,7 @@ PODS: DEPENDENCIES: - file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - - mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos`) + - mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) @@ -52,7 +53,7 @@ EXTERNAL SOURCES: FlutterMacOS: :path: Flutter/ephemeral mobile_scanner: - :path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos + :path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin package_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos pasteboard: @@ -79,7 +80,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - mobile_scanner: 0e365ed56cad24f28c0fd858ca04edefb40dfac3 + mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 package_info_plus: f0052d280d17aa382b932f399edf32507174e870 pasteboard: 278d8100149f940fb795d6b3a74f0720c890ecb7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 diff --git a/packages/veilid_support/example/pubspec.lock b/packages/veilid_support/example/pubspec.lock index c40540e..9af9773 100644 --- a/packages/veilid_support/example/pubspec.lock +++ b/packages/veilid_support/example/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 + sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f url: "https://pub.dev" source: hosted - version: "80.0.0" + version: "82.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" + sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.4.5" args: dependency: transitive description: @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" async_tools: dependency: "direct dev" description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: bloc_advanced_tools - sha256: "7c7f294b425552c2d4831b01ad0d3e1f33f2bdf9acfb7b639caa072781d228cf" + sha256: dfb142569814952af8d93e7fe045972d847e29382471687db59913e253202f6e url: "https://pub.dev" source: hosted - version: "0.1.10" + version: "0.1.12" boolean_selector: dependency: transitive description: @@ -89,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" clock: dependency: transitive description: @@ -117,10 +125,10 @@ packages: dependency: transitive description: name: coverage - sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 + sha256: "802bd084fb82e55df091ec8ad1553a7331b61c08251eef19a508b6f3f3a9858d" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.13.1" crypto: dependency: transitive description: @@ -149,18 +157,18 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" fast_immutable_collections: dependency: transitive description: name: fast_immutable_collections - sha256: "95a69b9380483dff49ae2c12c9eb92e2b4e1aeff481a33c2a20883471771598a" + sha256: d1aa3d7788fab06cce7f303f4969c7a16a10c865e1bd2478291a8ebcbee084e5 url: "https://pub.dev" source: hosted - version: "11.0.3" + version: "11.0.4" ffi: dependency: transitive description: @@ -299,10 +307,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -323,10 +331,10 @@ packages: dependency: "direct dev" description: name: lint_hard - sha256: ffe7058cb49e021d244d67e650a63380445b56643c2849c6929e938246b99058 + sha256: "2073d4e83ac4e3f2b87cc615fff41abb5c2c5618e117edcd3d71f40f2186f4d5" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.1" logging: dependency: transitive description: @@ -411,10 +419,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.dev" source: hosted - version: "2.2.16" + version: "2.2.17" path_provider_foundation: dependency: transitive description: @@ -483,10 +491,10 @@ packages: dependency: transitive description: name: protobuf - sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + sha256: "579fe5557eae58e3adca2e999e38f02441d8aa908703854a9e0a0f47fa857731" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.1.0" pub_semver: dependency: transitive description: @@ -658,7 +666,7 @@ packages: path: "../../../../veilid/veilid-flutter" relative: true source: path - version: "0.4.4" + version: "0.4.6" veilid_support: dependency: "direct main" description: @@ -677,10 +685,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" watcher: dependency: transitive description: @@ -701,26 +709,26 @@ packages: dependency: transitive description: name: web_socket - sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" webdriver: dependency: transitive description: name: webdriver - sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.1.0" webkit_inspection_protocol: dependency: transitive description: diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart index 93cdcc4..6323692 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart @@ -613,7 +613,7 @@ class _DHTLogSpine { // xxx: Don't watch for local changes because this class already handles // xxx: notifying listeners and knows when it makes local changes _subscription ??= - await _spineRecord.listen(localChanges: true, _onSpineChanged); + await _spineRecord.listen(localChanges: false, _onSpineChanged); await _spineRecord.watch(subkeys: [ValueSubkeyRange.single(0)]); } on Exception { // If anything fails, try to cancel the watches diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/default_dht_record_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_record/default_dht_record_cubit.dart index e5fb513..3d396d2 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/default_dht_record_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/default_dht_record_cubit.dart @@ -6,14 +6,14 @@ import '../../../veilid_support.dart'; class DefaultDHTRecordCubit extends DHTRecordCubit { DefaultDHTRecordCubit({ required super.open, - required T Function(List data) decodeState, + required T Function(Uint8List data) decodeState, }) : super( initialStateFunction: _makeInitialStateFunction(decodeState), stateFunction: _makeStateFunction(decodeState), watchFunction: _makeWatchFunction()); static InitialStateFunction _makeInitialStateFunction( - T Function(List data) decodeState) => + T Function(Uint8List data) decodeState) => (record) async { final initialData = await record.get(); if (initialData == null) { @@ -23,7 +23,7 @@ class DefaultDHTRecordCubit extends DHTRecordCubit { }; static StateFunction _makeStateFunction( - T Function(List data) decodeState) => + T Function(Uint8List data) decodeState) => (record, subkeys, updatedata) async { final defaultSubkey = record.subkeyOrDefault(-1); if (subkeys.containsSubkey(defaultSubkey)) { diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart index cab5a77..eb56f7c 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart @@ -87,6 +87,10 @@ abstract class DHTRecordCubit extends Cubit> { await super.close(); } + Future ready() async { + await initWait(); + } + Future refresh(List subkeys) async { await initWait(); @@ -111,8 +115,6 @@ abstract class DHTRecordCubit extends Cubit> { } } - // DHTRecord get record => _record; - @protected final WaitSet initWait = WaitSet(); diff --git a/packages/veilid_support/lib/identity_support/identity_support.dart b/packages/veilid_support/lib/identity_support/identity_support.dart index 463be9a..68723bf 100644 --- a/packages/veilid_support/lib/identity_support/identity_support.dart +++ b/packages/veilid_support/lib/identity_support/identity_support.dart @@ -3,4 +3,5 @@ export 'exceptions.dart'; export 'identity.dart'; export 'identity_instance.dart'; export 'super_identity.dart'; +export 'super_identity_cubit.dart'; export 'writable_super_identity.dart'; diff --git a/packages/veilid_support/lib/identity_support/super_identity.dart b/packages/veilid_support/lib/identity_support/super_identity.dart index 5dc0e90..c8fd59d 100644 --- a/packages/veilid_support/lib/identity_support/super_identity.dart +++ b/packages/veilid_support/lib/identity_support/super_identity.dart @@ -64,7 +64,40 @@ sealed class SuperIdentity with _$SuperIdentity { const SuperIdentity._(); - /// Opens an existing super identity and validates it + /// Ensure a SuperIdentity is valid + Future validate({required TypedKey superRecordKey}) async { + // Validate current IdentityInstance + if (!await currentInstance.validateIdentityInstance( + superRecordKey: superRecordKey, superPublicKey: publicKey)) { + // Invalid current IdentityInstance signature(s) + throw IdentityException.invalid; + } + + // Validate deprecated IdentityInstances + for (final deprecatedInstance in deprecatedInstances) { + if (!await deprecatedInstance.validateIdentityInstance( + superRecordKey: superRecordKey, superPublicKey: publicKey)) { + // Invalid deprecated IdentityInstance signature(s) + throw IdentityException.invalid; + } + } + + // Validate SuperIdentity + final deprecatedInstancesSignatures = + deprecatedInstances.map((x) => x.signature).toList(); + if (!await _validateSuperIdentitySignature( + recordKey: recordKey, + currentInstanceSignature: currentInstance.signature, + deprecatedInstancesSignatures: deprecatedInstancesSignatures, + deprecatedSuperRecordKeys: deprecatedSuperRecordKeys, + publicKey: publicKey, + signature: signature)) { + // Invalid SuperIdentity signature + throw IdentityException.invalid; + } + } + + /// Opens an existing super identity, validates it, and returns it static Future open({required TypedKey superRecordKey}) async { final pool = DHTRecordPool.instance; @@ -75,37 +108,7 @@ sealed class SuperIdentity with _$SuperIdentity { final superIdentity = (await superRec.getJson(SuperIdentity.fromJson, refreshMode: DHTRecordRefreshMode.network))!; - // Validate current IdentityInstance - if (!await superIdentity.currentInstance.validateIdentityInstance( - superRecordKey: superRecordKey, - superPublicKey: superIdentity.publicKey)) { - // Invalid current IdentityInstance signature(s) - throw IdentityException.invalid; - } - - // Validate deprecated IdentityInstances - for (final deprecatedInstance in superIdentity.deprecatedInstances) { - if (!await deprecatedInstance.validateIdentityInstance( - superRecordKey: superRecordKey, - superPublicKey: superIdentity.publicKey)) { - // Invalid deprecated IdentityInstance signature(s) - throw IdentityException.invalid; - } - } - - // Validate SuperIdentity - final deprecatedInstancesSignatures = - superIdentity.deprecatedInstances.map((x) => x.signature).toList(); - if (!await _validateSuperIdentitySignature( - recordKey: superIdentity.recordKey, - currentInstanceSignature: superIdentity.currentInstance.signature, - deprecatedInstancesSignatures: deprecatedInstancesSignatures, - deprecatedSuperRecordKeys: superIdentity.deprecatedSuperRecordKeys, - publicKey: superIdentity.publicKey, - signature: superIdentity.signature)) { - // Invalid SuperIdentity signature - throw IdentityException.invalid; - } + await superIdentity.validate(superRecordKey: superRecordKey); return superIdentity; }); diff --git a/packages/veilid_support/lib/identity_support/super_identity_cubit.dart b/packages/veilid_support/lib/identity_support/super_identity_cubit.dart new file mode 100644 index 0000000..9de55ad --- /dev/null +++ b/packages/veilid_support/lib/identity_support/super_identity_cubit.dart @@ -0,0 +1,21 @@ +import 'package:async_tools/async_tools.dart'; + +import '../veilid_support.dart'; + +typedef SuperIdentityState = AsyncValue; + +class SuperIdentityCubit extends DefaultDHTRecordCubit { + SuperIdentityCubit({required TypedKey superRecordKey}) + : super( + open: () => _open(superRecordKey: superRecordKey), + decodeState: (buf) => jsonDecodeBytes(SuperIdentity.fromJson, buf)); + + static Future _open({required TypedKey superRecordKey}) async { + final pool = DHTRecordPool.instance; + + return pool.openRecordRead( + superRecordKey, + debugName: 'SuperIdentityCubit::_open::SuperIdentityRecord', + ); + } +} diff --git a/packages/veilid_support/pubspec.lock b/packages/veilid_support/pubspec.lock index d86402b..b4c7eef 100644 --- a/packages/veilid_support/pubspec.lock +++ b/packages/veilid_support/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 + sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f url: "https://pub.dev" source: hosted - version: "80.0.0" + version: "82.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" + sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.4.5" args: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: bloc_advanced_tools - sha256: "7c7f294b425552c2d4831b01ad0d3e1f33f2bdf9acfb7b639caa072781d228cf" + sha256: dfb142569814952af8d93e7fe045972d847e29382471687db59913e253202f6e url: "https://pub.dev" source: hosted - version: "0.1.10" + version: "0.1.12" boolean_selector: dependency: transitive description: @@ -161,6 +161,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" code_builder: dependency: transitive description: @@ -189,10 +197,10 @@ packages: dependency: transitive description: name: coverage - sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 + sha256: "802bd084fb82e55df091ec8ad1553a7331b61c08251eef19a508b6f3f3a9858d" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.13.1" crypto: dependency: transitive description: @@ -205,10 +213,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.0" equatable: dependency: "direct main" description: @@ -221,10 +229,10 @@ packages: dependency: "direct main" description: name: fast_immutable_collections - sha256: "95a69b9380483dff49ae2c12c9eb92e2b4e1aeff481a33c2a20883471771598a" + sha256: d1aa3d7788fab06cce7f303f4969c7a16a10c865e1bd2478291a8ebcbee084e5 url: "https://pub.dev" source: hosted - version: "11.0.3" + version: "11.0.4" ffi: dependency: transitive description: @@ -263,10 +271,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "7ed2ddaa47524976d5f2aa91432a79da36a76969edd84170777ac5bea82d797c" + sha256: "6022db4c7bfa626841b2a10f34dd1e1b68e8f8f9650db6112dcdeeca45ca793c" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.0.6" freezed_annotation: dependency: "direct main" description: @@ -311,10 +319,10 @@ packages: dependency: transitive description: name: http - sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" http_multi_server: dependency: transitive description: @@ -367,18 +375,18 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a" + sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c url: "https://pub.dev" source: hosted - version: "6.9.4" + version: "6.9.5" lint_hard: dependency: "direct dev" description: name: lint_hard - sha256: ffe7058cb49e021d244d67e650a63380445b56643c2849c6929e938246b99058 + sha256: "2073d4e83ac4e3f2b87cc615fff41abb5c2c5618e117edcd3d71f40f2186f4d5" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.1" logging: dependency: transitive description: @@ -463,10 +471,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.dev" source: hosted - version: "2.2.16" + version: "2.2.17" path_provider_foundation: dependency: transitive description: @@ -527,10 +535,10 @@ packages: dependency: "direct main" description: name: protobuf - sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + sha256: "579fe5557eae58e3adca2e999e38f02441d8aa908703854a9e0a0f47fa857731" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.1.0" pub_semver: dependency: transitive description: @@ -684,26 +692,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" + sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" url: "https://pub.dev" source: hosted - version: "1.25.15" + version: "1.26.2" test_api: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" test_core: dependency: transitive description: name: test_core - sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" + sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" url: "https://pub.dev" source: hosted - version: "0.6.8" + version: "0.6.11" timing: dependency: transitive description: @@ -734,15 +742,15 @@ packages: path: "../../../veilid/veilid-flutter" relative: true source: path - version: "0.4.4" + version: "0.4.6" vm_service: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "6f82e9ee8e7339f5d8b699317f6f3afc17c80a68ebef1bc0d6f52a678c14b1e6" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.0.1" watcher: dependency: transitive description: @@ -763,18 +771,18 @@ packages: dependency: transitive description: name: web_socket - sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" webkit_inspection_protocol: dependency: transitive description: diff --git a/packages/veilid_support/pubspec.yaml b/packages/veilid_support/pubspec.yaml index 65ba78d..8864fa6 100644 --- a/packages/veilid_support/pubspec.yaml +++ b/packages/veilid_support/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: async_tools: ^0.1.9 bloc: ^9.0.0 - bloc_advanced_tools: ^0.1.10 + bloc_advanced_tools: ^0.1.12 charcode: ^1.4.0 collection: ^1.19.1 convert: ^3.1.2 @@ -23,7 +23,7 @@ dependencies: path: ^1.9.1 path_provider: ^2.1.5 - protobuf: ^3.1.0 + protobuf: ^4.1.0 veilid: # veilid: ^0.0.1 path: ../../../veilid/veilid-flutter diff --git a/pubspec.lock b/pubspec.lock index cdec931..aa97499 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -157,10 +157,10 @@ packages: dependency: "direct main" description: name: bloc_advanced_tools - sha256: "7c7f294b425552c2d4831b01ad0d3e1f33f2bdf9acfb7b639caa072781d228cf" + sha256: dfb142569814952af8d93e7fe045972d847e29382471687db59913e253202f6e url: "https://pub.dev" source: hosted - version: "0.1.10" + version: "0.1.12" blurry_modal_progress_hud: dependency: "direct main" description: @@ -277,10 +277,10 @@ packages: dependency: transitive description: name: camera_android_camerax - sha256: ea7e40bd63afb8f55058e48ec529ce96562be9d08393f79631a06f781161fd0d + sha256: "9fb44e73e0fea3647a904dc26d38db24055e5b74fc68fd2b6d3abfa1bd20f536" url: "https://pub.dev" source: hosted - version: "0.6.16" + version: "0.6.17" camera_avfoundation: dependency: transitive description: @@ -437,10 +437,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.0" diffutil_dart: dependency: transitive description: @@ -477,10 +477,10 @@ packages: dependency: "direct main" description: name: expansion_tile_group - sha256: "3be10b81d6d99d1213fe76a285993be0ea6092565ac100152deb6cdf9f5521dc" + sha256: "894c5088d94dda5d1ddde50463881935ff41b15850fe674605b9003d09716c8e" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" fast_immutable_collections: dependency: "direct main" description: @@ -554,18 +554,18 @@ packages: dependency: "direct main" description: name: flutter_chat_core - sha256: "529959634622e9df3b96a4a3764ecc61ec6f0dfa3258a52c139ae10a56ccad80" + sha256: "7875785bc4aa0b1dce56a76d2a8bd65841c130a3deb2c527878ebfdf8c54f971" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" flutter_chat_ui: dependency: "direct main" description: name: flutter_chat_ui - sha256: c63df9cd05fe86a3588b4e47f184fbb9e9c3b86153b8a97f3a789e6edb03d28e + sha256: "012aa0d9cc2898b8f89b48f66adb106de9547e466ba21ad54ccef25515f68dcc" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" flutter_form_builder: dependency: "direct main" description: @@ -631,10 +631,10 @@ packages: dependency: "direct main" description: name: flutter_sticky_header - sha256: "7f76d24d119424ca0c95c146b8627a457e8de8169b0d584f766c2c545db8f8be" + sha256: fb4fda6164ef3e5fc7ab73aba34aad253c17b7c6ecf738fa26f1a905b7d2d1e2 url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.8.0" flutter_svg: dependency: "direct main" description: @@ -724,10 +724,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 + sha256: "0b1e06223bee260dee31a171fb1153e306907563a0b0225e8c1733211911429a" url: "https://pub.dev" source: hosted - version: "14.8.1" + version: "15.1.2" graphs: dependency: transitive description: @@ -788,10 +788,10 @@ packages: dependency: transitive description: name: idb_shim - sha256: d3dae2085f2dcc9d05b851331fddb66d57d3447ff800de9676b396795436e135 + sha256: "40e872276d79a1a97cc2c1ea0ecf046b8e34d788f16a8ea8f0da3e9b337d42da" url: "https://pub.dev" source: hosted - version: "2.6.5+1" + version: "2.6.6+1" image: dependency: "direct main" description: @@ -812,10 +812,10 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.20.2" io: dependency: transitive description: @@ -916,10 +916,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: f536c5b8cadcf73d764bdce09c94744f06aa832264730f8971b21a60c5666826 + sha256: "72f06a071aa8b14acea3ab43ea7949eefe4a2469731ae210e006ba330a033a8c" url: "https://pub.dev" source: hosted - version: "6.0.10" + version: "7.0.0" nested: dependency: transitive description: @@ -964,10 +964,10 @@ packages: dependency: "direct main" description: name: pasteboard - sha256: "7bf733f3a00c7188ec1f2c6f0612854248b302cf91ef3611a2b7bb141c0f9d55" + sha256: "9ff73ada33f79a59ff91f6c01881fd4ed0a0031cfc4ae2d86c0384471525fca1" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.4.0" path: dependency: "direct main" description: @@ -1132,10 +1132,10 @@ packages: dependency: "direct main" description: name: protobuf - sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + sha256: "579fe5557eae58e3adca2e999e38f02441d8aa908703854a9e0a0f47fa857731" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.1.0" provider: dependency: "direct main" description: @@ -1317,18 +1317,18 @@ packages: dependency: "direct main" description: name: share_plus - sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da + sha256: b2961506569e28948d75ec346c28775bb111986bb69dc6a20754a457e3d97fa0 url: "https://pub.dev" source: hosted - version: "10.1.4" + version: "11.0.0" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b + sha256: "1032d392bc5d2095a77447a805aa3f804d2ae6a4d5eef5e6ebb3bd94c1bc19ef" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "6.0.0" shared_preferences: dependency: "direct main" description: @@ -1603,10 +1603,10 @@ packages: dependency: transitive description: name: test_api - sha256: "6c7653816b1c938e121b69ff63a33c9dc68102b65a5fb0a5c0f9786256ed33e6" + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.5" + version: "0.7.6" timing: dependency: transitive description: @@ -1731,10 +1731,10 @@ packages: dependency: transitive description: name: value_layout_builder - sha256: c02511ea91ca5c643b514a33a38fa52536f74aa939ec367d02938b5ede6807fa + sha256: ab4b7d98bac8cefeb9713154d43ee0477490183f5aa23bb4ffa5103d9bbf6275 url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.5.0" vector_graphics: dependency: transitive description: @@ -1755,10 +1755,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" + sha256: "557a315b7d2a6dbb0aaaff84d857967ce6bdc96a63dc6ee2a57ce5a6ee5d3331" url: "https://pub.dev" source: hosted - version: "1.1.16" + version: "1.1.17" vector_math: dependency: transitive description: @@ -1887,4 +1887,4 @@ packages: version: "1.1.2" sdks: dart: ">=3.7.0 <4.0.0" - flutter: ">=3.29.0" + flutter: ">=3.32.0" diff --git a/pubspec.yaml b/pubspec.yaml index 73ec83a..519714b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ version: 0.4.7+20 environment: sdk: ">=3.2.0 <4.0.0" - flutter: ">=3.22.1" + flutter: ">=3.32.0" dependencies: accordion: ^2.6.0 @@ -21,7 +21,7 @@ dependencies: badges: ^3.1.2 basic_utils: ^5.8.2 bloc: ^9.0.0 - bloc_advanced_tools: ^0.1.10 + bloc_advanced_tools: ^0.1.12 blurry_modal_progress_hud: ^1.1.1 change_case: ^2.2.0 charcode: ^1.4.0 @@ -46,29 +46,29 @@ dependencies: flutter_native_splash: ^2.4.5 flutter_slidable: ^4.0.0 flutter_spinkit: ^5.2.1 - flutter_sticky_header: ^0.7.0 + flutter_sticky_header: ^0.8.0 flutter_svg: ^2.0.17 flutter_translate: ^4.1.0 flutter_zoom_drawer: ^3.2.0 form_builder_validators: ^11.1.2 freezed_annotation: ^3.0.0 - go_router: ^14.8.1 + go_router: ^15.1.2 image: ^4.5.3 intl: ^0.19.0 json_annotation: ^4.9.0 keyboard_avoider: ^0.2.0 loggy: ^2.0.3 meta: ^1.16.0 - mobile_scanner: ^6.0.7 + mobile_scanner: ^7.0.0 package_info_plus: ^8.3.0 - pasteboard: ^0.3.0 + pasteboard: ^0.4.0 path: ^1.9.1 path_provider: ^2.1.5 pdf: ^3.11.3 pinput: ^5.0.1 preload_page_view: ^0.2.0 printing: ^5.14.2 - protobuf: ^3.1.0 + protobuf: ^4.1.0 provider: ^6.1.2 qr_code_dart_scan: ^0.10.0 qr_flutter: ^4.1.0 @@ -81,7 +81,7 @@ dependencies: git: url: https://gitlab.com/veilid/Searchable-Listview.git ref: main - share_plus: ^10.1.4 + share_plus: ^11.0.0 shared_preferences: ^2.5.2 signal_strength_indicator: ^0.4.1 sliver_expandable: ^1.1.2 @@ -108,17 +108,18 @@ dependencies: xterm: ^4.0.0 zxing2: ^0.2.3 - # dependency_overrides: - # async_tools: - # path: ../dart_async_tools - # bloc_advanced_tools: - # path: ../bloc_advanced_tools - # searchable_listview: - # path: ../Searchable-Listview - # flutter_chat_core: - # path: ../flutter_chat_ui/packages/flutter_chat_core - # flutter_chat_ui: - # path: ../flutter_chat_ui/packages/flutter_chat_ui +dependency_overrides: + intl: ^0.20.2 # Until flutter_translate updates intl +# async_tools: +# path: ../dart_async_tools +# bloc_advanced_tools: +# path: ../bloc_advanced_tools +# searchable_listview: +# path: ../Searchable-Listview +# flutter_chat_core: +# path: ../flutter_chat_ui/packages/flutter_chat_core +# flutter_chat_ui: +# path: ../flutter_chat_ui/packages/flutter_chat_ui dev_dependencies: build_runner: ^2.4.15 From 3b1cb53b8afb912acdf4aabe19d9e57db2571f73 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 25 May 2025 23:40:52 -0400 Subject: [PATCH 73/93] Accessibility update --- CHANGELOG.md | 3 + assets/i18n/en.json | 1 + ios/Podfile.lock | 83 +------ ios/Runner.xcodeproj/project.pbxproj | 18 -- .../xcshareddata/xcschemes/Runner.xcscheme | 2 + .../views/edit_account_page.dart | 47 ++-- .../views/edit_profile_form.dart | 78 +++--- .../views/new_account_page.dart | 2 + .../views/show_recovery_key_page.dart | 7 +- lib/app.dart | 4 +- .../chat_builders/vc_composer_widget.dart | 1 + .../chat_builders/vc_text_message_widget.dart | 13 +- lib/chat/views/chat_component_widget.dart | 25 +- .../chat_single_contact_item_widget.dart | 23 +- .../views/contact_invitation_item_widget.dart | 4 +- lib/contacts/views/availability_widget.dart | 45 ++-- lib/contacts/views/contact_item_widget.dart | 26 +- lib/contacts/views/contacts_browser.dart | 31 +-- lib/contacts/views/contacts_page.dart | 10 +- lib/contacts/views/edit_contact_form.dart | 35 +-- lib/keyboard_shortcuts.dart | 136 +++++++++-- lib/layout/default_app_bar.dart | 16 +- lib/layout/home/drawer_menu/drawer_menu.dart | 31 +-- .../home/drawer_menu/menu_item_widget.dart | 4 +- lib/layout/home/home_account_ready.dart | 147 ++++++------ lib/layout/home/home_screen.dart | 59 ++--- .../views/notifications_preferences.dart | 226 +++++++----------- lib/router/views/router_shell.dart | 10 +- lib/settings/models/preferences.dart | 4 +- lib/settings/settings_page.dart | 76 +++--- lib/theme/models/contrast_generator.dart | 8 + lib/theme/models/scale_theme/scale_theme.dart | 8 + lib/theme/models/theme_preference.dart | 2 +- lib/theme/views/avatar_widget.dart | 74 ------ lib/theme/views/enter_password.dart | 3 +- .../brightness_preferences.dart | 28 +-- .../{ => preferences}/color_preferences.dart | 43 ++-- .../display_scale_preferences.dart | 109 +++++++++ lib/theme/views/preferences/preferences.dart | 4 + .../preferences/wallpaper_preferences.dart | 25 ++ lib/theme/views/responsive.dart | 4 + .../{ => styled_widgets}/styled_alert.dart | 3 +- .../views/styled_widgets/styled_avatar.dart | 77 ++++++ .../styled_button_box.dart} | 19 +- .../views/styled_widgets/styled_checkbox.dart | 63 +++++ .../{ => styled_widgets}/styled_dialog.dart | 2 +- .../views/styled_widgets/styled_dropdown.dart | 59 +++++ .../{ => styled_widgets}/styled_scaffold.dart | 2 +- .../styled_slide_tile.dart} | 50 ++-- .../views/styled_widgets/styled_slider.dart | 79 ++++++ .../views/styled_widgets/styled_widgets.dart | 8 + lib/theme/views/views.dart | 12 +- lib/theme/views/wallpaper_preferences.dart | 37 --- lib/veilid_processor/views/developer.dart | 8 +- .../views/signal_strength_meter.dart | 2 +- 55 files changed, 1089 insertions(+), 807 deletions(-) delete mode 100644 lib/theme/views/avatar_widget.dart rename lib/theme/views/{ => preferences}/brightness_preferences.dart (59%) rename lib/theme/views/{ => preferences}/color_preferences.dart (54%) create mode 100644 lib/theme/views/preferences/display_scale_preferences.dart create mode 100644 lib/theme/views/preferences/preferences.dart create mode 100644 lib/theme/views/preferences/wallpaper_preferences.dart rename lib/theme/views/{ => styled_widgets}/styled_alert.dart (99%) create mode 100644 lib/theme/views/styled_widgets/styled_avatar.dart rename lib/theme/views/{option_box.dart => styled_widgets/styled_button_box.dart} (75%) create mode 100644 lib/theme/views/styled_widgets/styled_checkbox.dart rename lib/theme/views/{ => styled_widgets}/styled_dialog.dart (98%) create mode 100644 lib/theme/views/styled_widgets/styled_dropdown.dart rename lib/theme/views/{ => styled_widgets}/styled_scaffold.dart (97%) rename lib/theme/views/{slider_tile.dart => styled_widgets/styled_slide_tile.dart} (75%) create mode 100644 lib/theme/views/styled_widgets/styled_slider.dart create mode 100644 lib/theme/views/styled_widgets/styled_widgets.dart delete mode 100644 lib/theme/views/wallpaper_preferences.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index a7b1930..8f7eefa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ - Deprecated accounts no longer crash application at startup - Simplify SingleContactMessagesCubit and MessageReconciliation - Update flutter_chat_ui to 2.0.0 +- Accessibility improvements + - Text scaling + - Keyboard shortcuts Ctrl + / Ctrl - to change font size ## v0.4.7 ## - *Community Contributions* diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 53bf461..50b0904 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -276,6 +276,7 @@ "titlebar": "Settings", "color_theme": "Color Theme", "brightness_mode": "Brightness Mode", + "display_scale": "Display Scale", "display_beta_warning": "Display beta warning on startup", "none": "None", "in_app": "In-app", diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2528d2d..add7488 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -6,54 +6,9 @@ PODS: - Flutter (1.0.0) - flutter_native_splash (2.4.3): - Flutter - - GoogleDataTransport (10.1.0): - - nanopb (~> 3.30910.0) - - PromisesObjC (~> 2.4) - - GoogleMLKit/BarcodeScanning (7.0.0): - - GoogleMLKit/MLKitCore - - MLKitBarcodeScanning (~> 6.0.0) - - GoogleMLKit/MLKitCore (7.0.0): - - MLKitCommon (~> 12.0.0) - - GoogleToolboxForMac/Defines (4.2.1) - - GoogleToolboxForMac/Logger (4.2.1): - - GoogleToolboxForMac/Defines (= 4.2.1) - - "GoogleToolboxForMac/NSData+zlib (4.2.1)": - - GoogleToolboxForMac/Defines (= 4.2.1) - - GoogleUtilities/Environment (8.0.2): - - GoogleUtilities/Privacy - - GoogleUtilities/Logger (8.0.2): - - GoogleUtilities/Environment - - GoogleUtilities/Privacy - - GoogleUtilities/Privacy (8.0.2) - - GoogleUtilities/UserDefaults (8.0.2): - - GoogleUtilities/Logger - - GoogleUtilities/Privacy - - GTMSessionFetcher/Core (3.5.0) - - MLImage (1.0.0-beta6) - - MLKitBarcodeScanning (6.0.0): - - MLKitCommon (~> 12.0) - - MLKitVision (~> 8.0) - - MLKitCommon (12.0.0): - - GoogleDataTransport (~> 10.0) - - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1) - - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)" - - GoogleUtilities/Logger (~> 8.0) - - GoogleUtilities/UserDefaults (~> 8.0) - - GTMSessionFetcher/Core (< 4.0, >= 3.3.2) - - MLKitVision (8.0.0): - - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1) - - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)" - - GTMSessionFetcher/Core (< 4.0, >= 3.3.2) - - MLImage (= 1.0.0-beta6) - - MLKitCommon (~> 12.0) - - mobile_scanner (6.0.2): + - mobile_scanner (7.0.0): - Flutter - - GoogleMLKit/BarcodeScanning (~> 7.0.0) - - nanopb (3.30910.0): - - nanopb/decode (= 3.30910.0) - - nanopb/encode (= 3.30910.0) - - nanopb/decode (3.30910.0) - - nanopb/encode (3.30910.0) + - FlutterMacOS - package_info_plus (0.4.5): - Flutter - pasteboard (0.0.1): @@ -63,7 +18,6 @@ PODS: - FlutterMacOS - printing (1.0.0): - Flutter - - PromisesObjC (2.4.0) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): @@ -84,7 +38,7 @@ DEPENDENCIES: - file_saver (from `.symlinks/plugins/file_saver/ios`) - Flutter (from `Flutter`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - - mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`) + - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - pasteboard (from `.symlinks/plugins/pasteboard/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) @@ -96,20 +50,6 @@ DEPENDENCIES: - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - veilid (from `.symlinks/plugins/veilid/ios`) -SPEC REPOS: - trunk: - - GoogleDataTransport - - GoogleMLKit - - GoogleToolboxForMac - - GoogleUtilities - - GTMSessionFetcher - - MLImage - - MLKitBarcodeScanning - - MLKitCommon - - MLKitVision - - nanopb - - PromisesObjC - EXTERNAL SOURCES: camera_avfoundation: :path: ".symlinks/plugins/camera_avfoundation/ios" @@ -120,7 +60,7 @@ EXTERNAL SOURCES: flutter_native_splash: :path: ".symlinks/plugins/flutter_native_splash/ios" mobile_scanner: - :path: ".symlinks/plugins/mobile_scanner/ios" + :path: ".symlinks/plugins/mobile_scanner/darwin" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" pasteboard: @@ -143,26 +83,15 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/veilid/ios" SPEC CHECKSUMS: - camera_avfoundation: 04b44aeb14070126c6529e5ab82cc7c9fca107cf + camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436 file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf - GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 - GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318 - GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8 - GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d - GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 - MLImage: 0ad1c5f50edd027672d8b26b0fee78a8b4a0fc56 - MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2 - MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d - MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e - mobile_scanner: af8f71879eaba2bbcb4d86c6a462c3c0e7f23036 - nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 printing: 54ff03f28fe9ba3aa93358afb80a8595a071dd07 - PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e612191..3a96d3e 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -139,7 +139,6 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 02C44F9283ADDE9FAAA73512 /* [CP] Embed Pods Frameworks */, - 61BE8A90522682C17620991D /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -232,23 +231,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 61BE8A90522682C17620991D /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3e31b44..a44fb7f 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> { title: translate('edit_account_page.remove_account_confirm'), child: Column(mainAxisSize: MainAxisSize.min, children: [ Text(translate('edit_account_page.remove_account_confirm_message')) - .paddingLTRB(24, 24, 24, 0), - Text(translate('confirmation.are_you_sure')).paddingAll(8), + .paddingLTRB(24.scaled(context), 24.scaled(context), + 24.scaled(context), 0), + Text(translate('confirmation.are_you_sure')) + .paddingAll(8.scaled(context)), Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( onPressed: () { Navigator.of(context).pop(false); }, child: Row(mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.cancel, size: 16).paddingLTRB(0, 0, 4, 0), + Icon(Icons.cancel, size: 16.scaled(context)) + .paddingLTRB(0, 0, 4.scaled(context), 0), Text(translate('button.no')).paddingLTRB(0, 0, 4, 0) ])), ElevatedButton( @@ -89,10 +92,12 @@ class _EditAccountPageState extends WindowSetupState { Navigator.of(context).pop(true); }, child: Row(mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), - Text(translate('button.yes')).paddingLTRB(0, 0, 4, 0) + Icon(Icons.check, size: 16.scaled(context)) + .paddingLTRB(0, 0, 4.scaled(context), 0), + Text(translate('button.yes')) + .paddingLTRB(0, 0, 4.scaled(context), 0) ])) - ]).paddingAll(24) + ]).paddingAll(24.scaled(context)) ])); if (confirmed != null && confirmed) { try { @@ -141,29 +146,36 @@ class _EditAccountPageState extends WindowSetupState { title: translate('edit_account_page.destroy_account_confirm'), child: Column(mainAxisSize: MainAxisSize.min, children: [ Text(translate('edit_account_page.destroy_account_confirm_message')) - .paddingLTRB(24, 24, 24, 0), + .paddingLTRB(24.scaled(context), 24.scaled(context), + 24.scaled(context), 0), Text(translate( 'edit_account_page.destroy_account_confirm_message_details')) - .paddingLTRB(24, 24, 24, 0), - Text(translate('confirmation.are_you_sure')).paddingAll(8), + .paddingLTRB(24.scaled(context), 24.scaled(context), + 24.scaled(context), 0), + Text(translate('confirmation.are_you_sure')) + .paddingAll(24.scaled(context)), Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( onPressed: () { Navigator.of(context).pop(false); }, child: Row(mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.cancel, size: 16).paddingLTRB(0, 0, 4, 0), - Text(translate('button.no')).paddingLTRB(0, 0, 4, 0) + Icon(Icons.cancel, size: 16.scaled(context)) + .paddingLTRB(0, 0, 4.scaled(context), 0), + Text(translate('button.no')) + .paddingLTRB(0, 0, 4.scaled(context), 0) ])), ElevatedButton( onPressed: () { Navigator.of(context).pop(true); }, child: Row(mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), - Text(translate('button.yes')).paddingLTRB(0, 0, 4, 0) + Icon(Icons.check, size: 16.scaled(context)) + .paddingLTRB(0, 0, 4.scaled(context), 0), + Text(translate('button.yes')) + .paddingLTRB(0, 0, 4.scaled(context), 0) ])) - ]).paddingAll(24) + ]).paddingAll(24.scaled(context)) ])); if (confirmed != null && confirmed) { try { @@ -250,10 +262,12 @@ class _EditAccountPageState extends WindowSetupState { return StyledScaffold( appBar: DefaultAppBar( + context: context, title: Text(translate('edit_account_page.titlebar')), leading: Navigator.canPop(context) ? IconButton( icon: const Icon(Icons.arrow_back), + iconSize: 24.scaled(context), onPressed: () { singleFuture((this, _kDoBackArrow), () async { if (_isModified) { @@ -277,6 +291,7 @@ class _EditAccountPageState extends WindowSetupState { const SignalStrengthMeterWidget(), IconButton( icon: const Icon(Icons.settings), + iconSize: 24.scaled(context), tooltip: translate('menu.settings_tooltip'), onPressed: () async { await GoRouterHelper(context).push('/settings'); @@ -285,14 +300,14 @@ class _EditAccountPageState extends WindowSetupState { body: SingleChildScrollView( child: Column(children: [ _editAccountForm(context).paddingLTRB(0, 0, 0, 32), - OptionBox( + StyledButtonBox( instructions: translate('edit_account_page.remove_account_description'), buttonIcon: Icons.person_remove_alt_1, buttonText: translate('edit_account_page.remove_account'), onClick: _onRemoveAccount, ), - OptionBox( + StyledButtonBox( instructions: translate('edit_account_page.destroy_account_description'), buttonIcon: Icons.person_off, diff --git a/lib/account_manager/views/edit_profile_form.dart b/lib/account_manager/views/edit_profile_form.dart index 4977dc7..114c7b8 100644 --- a/lib/account_manager/views/edit_profile_form.dart +++ b/lib/account_manager/views/edit_profile_form.dart @@ -53,16 +53,16 @@ class EditProfileForm extends StatefulWidget { ..add(DiagnosticsProperty('initialValue', initialValue)); } - static const String formFieldName = 'name'; - static const String formFieldPronouns = 'pronouns'; - static const String formFieldAbout = 'about'; - static const String formFieldAvailability = 'availability'; - static const String formFieldFreeMessage = 'free_message'; - static const String formFieldAwayMessage = 'away_message'; - static const String formFieldBusyMessage = 'busy_message'; - static const String formFieldAvatar = 'avatar'; - static const String formFieldAutoAway = 'auto_away'; - static const String formFieldAutoAwayTimeout = 'auto_away_timeout'; + static const formFieldName = 'name'; + static const formFieldPronouns = 'pronouns'; + static const formFieldAbout = 'about'; + static const formFieldAvailability = 'availability'; + static const formFieldFreeMessage = 'free_message'; + static const formFieldAwayMessage = 'away_message'; + static const formFieldBusyMessage = 'busy_message'; + static const formFieldAvatar = 'avatar'; + static const formFieldAutoAway = 'auto_away'; + static const formFieldAutoAwayTimeout = 'auto_away_timeout'; } class _EditProfileFormState extends State { @@ -98,6 +98,7 @@ class _EditProfileFormState extends State { name: EditProfileForm.formFieldAvailability, initialValue: initialValue, decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_availability'), hintText: translate('account.empty_busy_message')), @@ -110,7 +111,7 @@ class _EditProfileFormState extends State { Text(x == proto.Availability.AVAILABILITY_OFFLINE ? translate('availability.always_show_offline') : AvailabilityWidget.availabilityName(x)) - .paddingLTRB(8, 0, 0, 0), + .paddingLTRB(8.scaled(context), 0, 0, 0), ]))) .toList(), ); @@ -175,17 +176,8 @@ class _EditProfileFormState extends State { BuildContext context, ) { final theme = Theme.of(context); - final scale = theme.extension()!; - final scaleConfig = theme.extension()!; final textTheme = theme.textTheme; - late final Color border; - if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) { - border = scale.primaryScale.elementBackground; - } else { - border = scale.primaryScale.border; - } - return FormBuilder( key: _formKey, autovalidateMode: AutovalidateMode.onUserInteraction, @@ -197,14 +189,9 @@ class _EditProfileFormState extends State { children: [ Row(children: [ const Spacer(), - AvatarWidget( + StyledAvatar( name: _currentValueName, - size: 128, - borderColor: border, - foregroundColor: scale.primaryScale.primaryText, - backgroundColor: scale.primaryScale.primary, - scaleConfig: scaleConfig, - textStyle: theme.textTheme.titleLarge!.copyWith(fontSize: 64), + size: 128.scaled(context), ).paddingLTRB(0, 0, 0, 16), const Spacer() ]), @@ -218,6 +205,7 @@ class _EditProfileFormState extends State { }); }, decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_name'), hintText: translate('account.empty_name')), @@ -233,6 +221,7 @@ class _EditProfileFormState extends State { initialValue: _savedValue.pronouns, maxLength: 64, decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_pronouns'), hintText: translate('account.empty_pronouns')), @@ -245,6 +234,7 @@ class _EditProfileFormState extends State { maxLines: 8, minLines: 1, decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_about'), hintText: translate('account.empty_about')), @@ -256,6 +246,7 @@ class _EditProfileFormState extends State { initialValue: _savedValue.freeMessage, maxLength: 128, decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_free_message'), hintText: translate('account.empty_free_message')), @@ -266,6 +257,7 @@ class _EditProfileFormState extends State { initialValue: _savedValue.awayMessage, maxLength: 128, decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_away_message'), hintText: translate('account.empty_away_message')), @@ -276,6 +268,7 @@ class _EditProfileFormState extends State { initialValue: _savedValue.busyMessage, maxLength: 128, decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: translate('account.form_busy_message'), hintText: translate('account.empty_busy_message')), @@ -291,12 +284,13 @@ class _EditProfileFormState extends State { _currentValueAutoAway = v ?? false; }); }, - ).paddingLTRB(0, 0, 0, 16), + ).paddingLTRB(0, 0, 0, 16.scaled(context)), FormBuilderTextField( name: EditProfileForm.formFieldAutoAwayTimeout, enabled: _currentValueAutoAway, initialValue: _savedValue.autoAwayTimeout.toString(), decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), labelText: translate('account.form_auto_away_timeout'), ), validator: FormBuilderValidators.positiveNumber(), @@ -306,7 +300,7 @@ class _EditProfileFormState extends State { const Spacer(), Text(widget.instructions).toCenter().flexible(flex: 6), const Spacer(), - ]).paddingSymmetric(vertical: 16), + ]).paddingSymmetric(vertical: 16.scaled(context)), Row(children: [ const Spacer(), Builder(builder: (context) { @@ -319,17 +313,19 @@ class _EditProfileFormState extends State { false; return ElevatedButton( - onPressed: (networkReady && _isModified) ? _doSubmit : null, - child: Row(mainAxisSize: MainAxisSize.min, children: [ - Icon(networkReady ? Icons.check : Icons.hourglass_empty, - size: 16) - .paddingLTRB(0, 0, 4, 0), - Text(networkReady - ? widget.submitText - : widget.submitDisabledText) - .paddingLTRB(0, 0, 4, 0) - ]), - ); + onPressed: (networkReady && _isModified) ? _doSubmit : null, + child: Padding( + padding: EdgeInsetsGeometry.all(4.scaled(context)), + child: Row(mainAxisSize: MainAxisSize.min, children: [ + Icon(networkReady ? Icons.check : Icons.hourglass_empty, + size: 16.scaled(context)) + .paddingLTRB(0, 0, 4.scaled(context), 0), + Text(networkReady + ? widget.submitText + : widget.submitDisabledText) + .paddingLTRB(0, 0, 4.scaled(context), 0) + ]), + )); }), const Spacer() ]) @@ -363,5 +359,5 @@ class _EditProfileFormState extends State { late AccountSpec _savedValue; late bool _currentValueAutoAway; late String _currentValueName; - bool _isModified = false; + var _isModified = false; } diff --git a/lib/account_manager/views/new_account_page.dart b/lib/account_manager/views/new_account_page.dart index 07034df..5012527 100644 --- a/lib/account_manager/views/new_account_page.dart +++ b/lib/account_manager/views/new_account_page.dart @@ -94,6 +94,7 @@ class _NewAccountPageState extends WindowSetupState { return StyledScaffold( appBar: DefaultAppBar( + context: context, title: Text(translate('new_account_page.titlebar')), leading: GoRouterHelper(context).canPop() ? IconButton( @@ -111,6 +112,7 @@ class _NewAccountPageState extends WindowSetupState { const SignalStrengthMeterWidget(), IconButton( icon: const Icon(Icons.settings), + iconSize: 24.scaled(context), tooltip: translate('menu.settings_tooltip'), onPressed: () async { await GoRouterHelper(context).push('/settings'); diff --git a/lib/account_manager/views/show_recovery_key_page.dart b/lib/account_manager/views/show_recovery_key_page.dart index 7c971e0..5423543 100644 --- a/lib/account_manager/views/show_recovery_key_page.dart +++ b/lib/account_manager/views/show_recovery_key_page.dart @@ -164,6 +164,7 @@ class _ShowRecoveryKeyPageState extends WindowSetupState { return StyledScaffold( appBar: DefaultAppBar( + context: context, title: Text(translate('show_recovery_key_page.titlebar')), actions: [ const SignalStrengthMeterWidget(), @@ -193,7 +194,7 @@ class _ShowRecoveryKeyPageState extends WindowSetupState { textAlign: TextAlign.center, translate('show_recovery_key_page.instructions_options')) .paddingLTRB(12, 0, 12, 24), - OptionBox( + StyledButtonBox( instructions: translate('show_recovery_key_page.instructions_print'), buttonIcon: Icons.print, @@ -209,7 +210,7 @@ class _ShowRecoveryKeyPageState extends WindowSetupState { _codeHandled = true; }); }), - OptionBox( + StyledButtonBox( instructions: translate('show_recovery_key_page.instructions_view'), buttonIcon: Icons.edit_document, @@ -229,7 +230,7 @@ class _ShowRecoveryKeyPageState extends WindowSetupState { _codeHandled = true; }); }), - OptionBox( + StyledButtonBox( instructions: translate('show_recovery_key_page.instructions_share'), buttonIcon: Icons.ios_share, diff --git a/lib/app.dart b/lib/app.dart index 802b0d7..5f4d6dc 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -31,7 +31,7 @@ class VeilidChatApp extends StatelessWidget { super.key, }); - static const String name = 'VeilidChat'; + static const name = 'VeilidChat'; final ThemeData initialThemeData; @@ -125,7 +125,7 @@ class VeilidChatApp extends StatelessWidget { @override Widget build(BuildContext context) => FutureProvider( initialData: null, - create: (context) async => VeilidChatGlobalInit.initialize(), + create: (context) => VeilidChatGlobalInit.initialize(), builder: (context, __) { final globalInit = context.watch(); if (globalInit == null) { diff --git a/lib/chat/views/chat_builders/vc_composer_widget.dart b/lib/chat/views/chat_builders/vc_composer_widget.dart index b3eb1e5..f470e9b 100644 --- a/lib/chat/views/chat_builders/vc_composer_widget.dart +++ b/lib/chat/views/chat_builders/vc_composer_widget.dart @@ -355,6 +355,7 @@ class _VcComposerState extends State { borderRadius: BorderRadius.all(Radius.circular( 8 * config.borderRadiusScale))), hintText: widget.hintText, + hintMaxLines: 1, hintStyle: chatTheme.typography.bodyMedium.copyWith( color: widget.hintColor ?? chatTheme.colors.onSurface diff --git a/lib/chat/views/chat_builders/vc_text_message_widget.dart b/lib/chat/views/chat_builders/vc_text_message_widget.dart index fc1fe80..52235f6 100644 --- a/lib/chat/views/chat_builders/vc_text_message_widget.dart +++ b/lib/chat/views/chat_builders/vc_text_message_widget.dart @@ -12,7 +12,7 @@ class VcTextMessageWidget extends StatelessWidget { const VcTextMessageWidget({ required this.message, required this.index, - this.padding = const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + this.padding, this.borderRadius, this.onlyEmojiFontSize, this.sentBackgroundColor, @@ -72,10 +72,6 @@ class VcTextMessageWidget extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); final scaleTheme = theme.extension()!; - final config = scaleTheme.config; - final scheme = scaleTheme.scheme; - final scale = scaleTheme.scheme.scale(ScaleKind.primary); - final textTheme = theme.textTheme; final scaleChatTheme = scaleTheme.chatTheme(); final chatTheme = scaleChatTheme.chatTheme; @@ -243,15 +239,16 @@ class TimeAndStatus extends StatelessWidget { if (showStatus && status != null) if (status == MessageStatus.sending) SizedBox( - width: 6, - height: 6, + width: 6.scaled(context), + height: 6.scaled(context), child: CircularProgressIndicator( color: textStyle?.color, strokeWidth: 2, ), ) else - Icon(getIconForStatus(status!), color: textStyle?.color, size: 12), + Icon(getIconForStatus(status!), + color: textStyle?.color, size: 12.scaled(context)), ], ); } diff --git a/lib/chat/views/chat_component_widget.dart b/lib/chat/views/chat_component_widget.dart index 7d90d89..8106a48 100644 --- a/lib/chat/views/chat_component_widget.dart +++ b/lib/chat/views/chat_component_widget.dart @@ -132,17 +132,9 @@ class _ChatComponentWidgetState extends State { final scale = scaleTheme.scheme.scale(ScaleKind.primary); final textTheme = theme.textTheme; final scaleChatTheme = scaleTheme.chatTheme(); - // final errorChatTheme = chatTheme.copyWith(color:) - // ..inputTextColor = scaleScheme.errorScale.primary - // ..sendButtonIcon = Image.asset( - // 'assets/icon-send.png', - // color: scaleScheme.errorScale.primary, - // package: 'flutter_chat_ui', - // )) - // .commit(); // Get the enclosing chat component cubit that contains our state - // (created by ChatComponentWidget.builder()) + // (created by ChatComponentWidget.singleContact()) final chatComponentCubit = context.watch(); final chatComponentState = chatComponentCubit.state; @@ -273,14 +265,19 @@ class _ChatComponentWidgetState extends State { // Text message builder textMessageBuilder: (context, message, index) => VcTextMessageWidget( - message: message, - index: index, - // showTime: true, - // showStatus: true, - ), + message: message, + index: index, + padding: const EdgeInsets.symmetric( + vertical: 12, horizontal: 16) + .scaled(context) + // showTime: true, + // showStatus: true, + ), // Composer builder composerBuilder: (ctx) => VcComposerWidget( autofocus: true, + padding: const EdgeInsets.all(4).scaled(context), + gap: 8.scaled(context), focusNode: _focusNode, textInputAction: isAnyMobile ? TextInputAction.newline diff --git a/lib/chat_list/views/chat_single_contact_item_widget.dart b/lib/chat_list/views/chat_single_contact_item_widget.dart index 5191fdd..d2594c5 100644 --- a/lib/chat_list/views/chat_single_contact_item_widget.dart +++ b/lib/chat_list/views/chat_single_contact_item_widget.dart @@ -24,11 +24,9 @@ class ChatSingleContactItemWidget extends StatelessWidget { final bool _disabled; @override - // ignore: prefer_expression_function_bodies Widget build( BuildContext context, ) { - final theme = Theme.of(context); final scaleTheme = Theme.of(context).extension()!; final activeChatCubit = context.watch(); @@ -48,23 +46,12 @@ class ChatSingleContactItemWidget extends StatelessWidget { selected: selected, ); - final avatar = AvatarWidget( + final avatar = StyledAvatar( name: name, - size: 32, - borderColor: scaleTheme.config.useVisualIndicators - ? scaleTheme.scheme.primaryScale.primaryText - : scaleTheme.scheme.primaryScale.subtleBorder, - foregroundColor: _disabled - ? scaleTheme.scheme.grayScale.primaryText - : scaleTheme.scheme.primaryScale.primaryText, - backgroundColor: _disabled - ? scaleTheme.scheme.grayScale.primary - : scaleTheme.scheme.primaryScale.primary, - scaleConfig: scaleTheme.config, - textStyle: theme.textTheme.titleLarge!, + size: 32.scaled(context), ); - return SliderTile( + return StyledSlideTile( key: ValueKey(_localConversationRecordKey), disabled: _disabled, selected: selected, @@ -75,14 +62,14 @@ class ChatSingleContactItemWidget extends StatelessWidget { trailing: AvailabilityWidget( availability: availability, color: scaleTileTheme.textColor, - ).fit(fit: BoxFit.scaleDown), + ).fit(fit: BoxFit.fill), onTap: () { singleFuture(activeChatCubit, () async { activeChatCubit.setActiveChat(_localConversationRecordKey); }); }, endActions: [ - SliderTileAction( + SlideTileAction( //icon: Icons.delete, label: translate('button.delete'), actionScale: ScaleKind.tertiary, diff --git a/lib/contact_invitation/views/contact_invitation_item_widget.dart b/lib/contact_invitation/views/contact_invitation_item_widget.dart index 544e5db..b86a833 100644 --- a/lib/contact_invitation/views/contact_invitation_item_widget.dart +++ b/lib/contact_invitation/views/contact_invitation_item_widget.dart @@ -44,7 +44,7 @@ class ContactInvitationItemWidget extends StatelessWidget { title = contactInvitationRecord.message; } - return SliderTile( + return StyledSlideTile( key: ObjectKey(contactInvitationRecord), disabled: tileDisabled, selected: selected, @@ -67,7 +67,7 @@ class ContactInvitationItemWidget extends StatelessWidget { ))); }, endActions: [ - SliderTileAction( + SlideTileAction( // icon: Icons.delete, label: translate('button.delete'), actionScale: ScaleKind.tertiary, diff --git a/lib/contacts/views/availability_widget.dart b/lib/contacts/views/availability_widget.dart index 75ef0a8..55fac39 100644 --- a/lib/contacts/views/availability_widget.dart +++ b/lib/contacts/views/availability_widget.dart @@ -1,37 +1,34 @@ -import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_translate/flutter_translate.dart'; import '../../proto/proto.dart' as proto; -import '../../theme/theme.dart'; class AvailabilityWidget extends StatelessWidget { const AvailabilityWidget( {required this.availability, required this.color, this.vertical = true, - this.size = 32, super.key}); - static Widget availabilityIcon(proto.Availability availability, Color color, - {double size = 24}) { + static Widget availabilityIcon( + proto.Availability availability, + Color color, + ) { late final Widget icon; switch (availability) { case proto.Availability.AVAILABILITY_AWAY: icon = SvgPicture.asset('assets/images/toilet.svg', - width: size, - height: size, colorFilter: ColorFilter.mode(color, BlendMode.srcATop)); case proto.Availability.AVAILABILITY_BUSY: - icon = Icon(Icons.event_busy, size: size); + icon = const Icon(Icons.event_busy, applyTextScaling: true); case proto.Availability.AVAILABILITY_FREE: - icon = Icon(Icons.event_available, size: size); + icon = const Icon(Icons.event_available, applyTextScaling: true); case proto.Availability.AVAILABILITY_OFFLINE: - icon = Icon(Icons.cloud_off, size: size); + icon = const Icon(Icons.cloud_off, applyTextScaling: true); case proto.Availability.AVAILABILITY_UNSPECIFIED: - icon = Icon(Icons.question_mark, size: size); + icon = const Icon(Icons.question_mark, applyTextScaling: true); } return icon; } @@ -59,23 +56,17 @@ class AvailabilityWidget extends StatelessWidget { final textTheme = theme.textTheme; final name = availabilityName(availability); - final icon = availabilityIcon(availability, color, size: size * 2 / 3); + final icon = availabilityIcon(availability, color); return vertical - ? ConstrainedBox( - constraints: BoxConstraints.tightFor(width: size), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - icon, - Text(name, style: textTheme.labelSmall!.copyWith(color: color)) - .fit(fit: BoxFit.scaleDown) - ])) - : ConstrainedBox( - constraints: BoxConstraints.tightFor(height: size), - child: Row(mainAxisSize: MainAxisSize.min, children: [ - icon, - Text(name, style: textTheme.labelLarge!.copyWith(color: color)) - .paddingLTRB(size / 4, 0, 0, 0) - ])); + ? Column(mainAxisSize: MainAxisSize.min, children: [ + icon, + Text(name, style: textTheme.labelSmall!.copyWith(color: color)) + ]) + : Row(mainAxisSize: MainAxisSize.min, children: [ + icon, + Text(' $name', style: textTheme.labelLarge!.copyWith(color: color)) + ]); } //////////////////////////////////////////////////////////////////////////// @@ -83,7 +74,6 @@ class AvailabilityWidget extends StatelessWidget { final proto.Availability availability; final Color color; final bool vertical; - final double size; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -92,7 +82,6 @@ class AvailabilityWidget extends StatelessWidget { ..add( DiagnosticsProperty('availability', availability)) ..add(DiagnosticsProperty('vertical', vertical)) - ..add(DoubleProperty('size', size)) ..add(ColorProperty('color', color)); } } diff --git a/lib/contacts/views/contact_item_widget.dart b/lib/contacts/views/contact_item_widget.dart index e206570..9a76be5 100644 --- a/lib/contacts/views/contact_item_widget.dart +++ b/lib/contacts/views/contact_item_widget.dart @@ -26,30 +26,16 @@ class ContactItemWidget extends StatelessWidget { Widget build( BuildContext context, ) { - final theme = Theme.of(context); - final scale = theme.extension()!; - final scaleConfig = theme.extension()!; - final name = _contact.nameOrNickname; final title = _contact.displayName; final subtitle = _contact.profile.status; - final avatar = AvatarWidget( + final avatar = StyledAvatar( name: name, - size: 34, - borderColor: _disabled - ? scale.grayScale.primaryText - : scale.primaryScale.subtleBorder, - foregroundColor: _disabled - ? scale.grayScale.primaryText - : scale.primaryScale.primaryText, - backgroundColor: - _disabled ? scale.grayScale.primary : scale.primaryScale.primary, - scaleConfig: scaleConfig, - textStyle: theme.textTheme.titleLarge!, + size: 34.scaled(context), ); - return SliderTile( + return StyledSlideTile( key: ObjectKey(_contact), disabled: _disabled, selected: _selected, @@ -69,7 +55,7 @@ class ContactItemWidget extends StatelessWidget { }), startActions: [ if (_onDoubleTap != null) - SliderTileAction( + SlideTileAction( //icon: Icons.edit, label: translate('button.chat'), actionScale: ScaleKind.secondary, @@ -81,7 +67,7 @@ class ContactItemWidget extends StatelessWidget { ], endActions: [ if (_onTap != null) - SliderTileAction( + SlideTileAction( //icon: Icons.edit, label: translate('button.edit'), actionScale: ScaleKind.secondary, @@ -91,7 +77,7 @@ class ContactItemWidget extends StatelessWidget { }), ), if (_onDelete != null) - SliderTileAction( + SlideTileAction( //icon: Icons.delete, label: translate('button.delete'), actionScale: ScaleKind.tertiary, diff --git a/lib/contacts/views/contacts_browser.dart b/lib/contacts/views/contacts_browser.dart index 84a2601..0504a7a 100644 --- a/lib/contacts/views/contacts_browser.dart +++ b/lib/contacts/views/contacts_browser.dart @@ -92,7 +92,7 @@ class _ContactsBrowserState extends State final menuParams = StarMenuParameters( shape: MenuShape.linear, - centerOffset: const Offset(0, 64), + centerOffset: Offset(0, 64.scaled(context)), boundaryBackground: BoundaryBackground( color: menuBackgroundColor, decoration: ShapeDecoration( @@ -113,13 +113,14 @@ class _ContactsBrowserState extends State onPressed: onPressed, icon: Icon( iconData, - size: 32, - ).paddingSTEB(0, 8, 0, 8), + size: 32.scaled(context), + ).paddingSTEB(0, 8.scaled(context), 0, 8.scaled(context)), label: Text( text, + textScaler: MediaQuery.of(context).textScaler, maxLines: 2, textAlign: TextAlign.center, - ).paddingSTEB(0, 8, 0, 8)); + ).paddingSTEB(0, 8.scaled(context), 0, 8.scaled(context))); final inviteMenuItems = [ makeMenuButton( @@ -135,14 +136,14 @@ class _ContactsBrowserState extends State onPressed: () async { _invitationMenuController.closeMenu!(); await ScanInvitationDialog.show(context); - }).paddingLTRB(0, 0, 0, 8), + }).paddingLTRB(0, 0, 0, 8.scaled(context)), makeMenuButton( iconData: Icons.contact_page, text: translate('add_contact_sheet.create_invite'), onPressed: () async { _invitationMenuController.closeMenu!(); await CreateInvitationDialog.show(context); - }).paddingLTRB(0, 0, 0, 8), + }).paddingLTRB(0, 0, 0, 8.scaled(context)), ]; return StarMenu( @@ -154,7 +155,7 @@ class _ContactsBrowserState extends State params: menuParams, child: IconButton( onPressed: () {}, - iconSize: 24, + iconSize: 24.scaled(context), icon: Icon(Icons.person_add, color: menuIconColor), tooltip: translate('add_contact_sheet.add_contact')), ); @@ -202,13 +203,13 @@ class _ContactsBrowserState extends State onDoubleTap: _onStartChat, onTap: onContactSelected, onDelete: _onContactDeleted) - .paddingLTRB(0, 4, 0, 0); + .paddingLTRB(0, 4.scaled(context), 0, 0); case ContactsBrowserElementKind.invitation: final invitation = element.invitation!; return ContactInvitationItemWidget( contactInvitationRecord: invitation, disabled: false) - .paddingLTRB(0, 4, 0, 0); + .paddingLTRB(0, 4.scaled(context), 0, 0); } }, filter: (value) { @@ -242,9 +243,11 @@ class _ContactsBrowserState extends State } return filtered; }, - searchFieldHeight: 40, - listViewPadding: const EdgeInsets.fromLTRB(4, 0, 4, 4), - searchFieldPadding: const EdgeInsets.fromLTRB(4, 8, 4, 4), + searchFieldHeight: 40.scaled(context), + listViewPadding: + const EdgeInsets.fromLTRB(4, 0, 4, 4).scaled(context), + searchFieldPadding: + const EdgeInsets.fromLTRB(4, 8, 4, 4).scaled(context), emptyWidget: contactList == null ? waitingPage( text: translate('contact_list.loading_contacts')) @@ -254,8 +257,8 @@ class _ContactsBrowserState extends State searchFieldEnabled: contactList != null, inputDecoration: InputDecoration(labelText: translate('contact_list.search')), - secondaryWidget: - buildInvitationButton(context).paddingLTRB(4, 0, 0, 0)) + secondaryWidget: buildInvitationButton(context) + .paddingLTRB(4.scaled(context), 0, 0, 0)) .expanded() ]); } diff --git a/lib/contacts/views/contacts_page.dart b/lib/contacts/views/contacts_page.dart index 26a6f0d..c984b57 100644 --- a/lib/contacts/views/contacts_page.dart +++ b/lib/contacts/views/contacts_page.dart @@ -40,6 +40,7 @@ class _ContactsPageState extends State { return StyledScaffold( appBar: DefaultAppBar( + context: context, title: Text( !enableSplit && enableRight ? translate('contacts_dialog.edit_contact') @@ -47,6 +48,7 @@ class _ContactsPageState extends State { ), leading: IconButton( icon: const Icon(Icons.arrow_back), + iconSize: 24.scaled(context), onPressed: () { singleFuture((this, _kDoBackArrow), () async { final confirmed = await _onContactSelected(null); @@ -65,21 +67,21 @@ class _ContactsPageState extends State { if (_selectedContact != null) IconButton( icon: const Icon(Icons.chat_bubble), - iconSize: 24, + iconSize: 24.scaled(context), color: appBarTheme.iconColor, tooltip: translate('contacts_dialog.new_chat'), onPressed: () async { await _onChatStarted(_selectedContact!); - }).paddingLTRB(8, 0, 8, 0), + }), if (enableSplit && _selectedContact != null) IconButton( icon: const Icon(Icons.close), - iconSize: 24, + iconSize: 24.scaled(context), color: appBarTheme.iconColor, tooltip: translate('contacts_dialog.close_contact'), onPressed: () async { await _onContactSelected(null); - }).paddingLTRB(8, 0, 8, 0), + }), ]), body: LayoutBuilder(builder: (context, constraint) { final maxWidth = constraint.maxWidth; diff --git a/lib/contacts/views/edit_contact_form.dart b/lib/contacts/views/edit_contact_form.dart index 514f019..7ab6019 100644 --- a/lib/contacts/views/edit_contact_form.dart +++ b/lib/contacts/views/edit_contact_form.dart @@ -92,16 +92,8 @@ class _EditContactFormState extends State { Widget _editContactForm(BuildContext context) { final theme = Theme.of(context); final scale = theme.extension()!; - final scaleConfig = theme.extension()!; final textTheme = theme.textTheme; - late final Color border; - if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) { - border = scale.primaryScale.elementBackground; - } else { - border = scale.primaryScale.subtleBorder; - } - return FormBuilder( key: _formKey, autovalidateMode: AutovalidateMode.onUserInteraction, @@ -116,18 +108,12 @@ class _EditContactFormState extends State { children: [ Row(children: [ const Spacer(), - AvatarWidget( - name: _currentValueNickname.isNotEmpty - ? _currentValueNickname - : widget.contact.profile.name, - size: 128, - borderColor: border, - foregroundColor: scale.primaryScale.primaryText, - backgroundColor: scale.primaryScale.primary, - scaleConfig: scaleConfig, - textStyle: theme.textTheme.titleLarge! - .copyWith(fontSize: 64), - ).paddingLTRB(0, 0, 0, 16), + StyledAvatar( + name: _currentValueNickname.isNotEmpty + ? _currentValueNickname + : widget.contact.profile.name, + size: 128) + .paddingLTRB(0, 0, 0, 16), const Spacer() ]), SelectableText(widget.contact.profile.name, @@ -211,10 +197,11 @@ class _EditContactFormState extends State { ElevatedButton( onPressed: _isModified ? _doSubmit : null, child: Row(mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), - Text(widget.submitText).paddingLTRB(0, 0, 4, 0) - ]), - ).paddingSymmetric(vertical: 4).alignAtCenter(), + Icon(Icons.check, size: 24.scaled(context)) + .paddingLTRB(0, 0, 4, 0), + Text(widget.submitText).paddingLTRB(0, 0, 4.scaled(context), 0) + ]).paddingAll(4.scaled(context)), + ).paddingSymmetric(vertical: 4.scaled(context)).alignAtCenter(), ], ), ); diff --git a/lib/keyboard_shortcuts.dart b/lib/keyboard_shortcuts.dart index 6708d72..7b952c8 100644 --- a/lib/keyboard_shortcuts.dart +++ b/lib/keyboard_shortcuts.dart @@ -32,6 +32,14 @@ class DeveloperPageIntent extends Intent { const DeveloperPageIntent(); } +class DisplayScaleUpIntent extends Intent { + const DisplayScaleUpIntent(); +} + +class DisplayScaleDownIntent extends Intent { + const DisplayScaleDownIntent(); +} + class KeyboardShortcuts extends StatelessWidget { const KeyboardShortcuts({required this.child, super.key}); @@ -57,7 +65,7 @@ class KeyboardShortcuts extends StatelessWidget { }); } - void changeBrightness(BuildContext context) { + void _changeBrightness(BuildContext context) { singleFuture(this, () async { final prefs = PreferencesRepository.instance.value; @@ -79,7 +87,7 @@ class KeyboardShortcuts extends StatelessWidget { }); } - void changeColor(BuildContext context) { + void _changeColor(BuildContext context) { singleFuture(this, () async { final prefs = PreferencesRepository.instance.value; final oldColor = prefs.themePreference.colorPreference; @@ -100,6 +108,54 @@ class KeyboardShortcuts extends StatelessWidget { }); } + void _displayScaleUp(BuildContext context) { + singleFuture(this, () async { + final prefs = PreferencesRepository.instance.value; + final oldIndex = displayScaleToIndex(prefs.themePreference.displayScale); + if (oldIndex == maxDisplayScaleIndex) { + return; + } + final newIndex = oldIndex + 1; + final newDisplayScaleName = indexToDisplayScaleName(newIndex); + + log.info('Changing display scale to $newDisplayScaleName'); + + final newPrefs = prefs.copyWith( + themePreference: prefs.themePreference + .copyWith(displayScale: indexToDisplayScale(newIndex))); + await PreferencesRepository.instance.set(newPrefs); + + if (context.mounted) { + ThemeSwitcher.of(context) + .changeTheme(theme: newPrefs.themePreference.themeData()); + } + }); + } + + void _displayScaleDown(BuildContext context) { + singleFuture(this, () async { + final prefs = PreferencesRepository.instance.value; + final oldIndex = displayScaleToIndex(prefs.themePreference.displayScale); + if (oldIndex == 0) { + return; + } + final newIndex = oldIndex - 1; + final newDisplayScaleName = indexToDisplayScaleName(newIndex); + + log.info('Changing display scale to $newDisplayScaleName'); + + final newPrefs = prefs.copyWith( + themePreference: prefs.themePreference + .copyWith(displayScale: indexToDisplayScale(newIndex))); + await PreferencesRepository.instance.set(newPrefs); + + if (context.mounted) { + ThemeSwitcher.of(context) + .changeTheme(theme: newPrefs.themePreference.themeData()); + } + }); + } + void _attachDetach(BuildContext context) { singleFuture(this, () async { if (ProcessorRepository.instance.processorConnectionState.isAttached) { @@ -125,44 +181,88 @@ class KeyboardShortcuts extends StatelessWidget { @override Widget build(BuildContext context) => ThemeSwitcher( builder: (context) => Shortcuts( - shortcuts: const { - SingleActivator( + shortcuts: { + ////////////////////////// Reload Theme + const SingleActivator( LogicalKeyboardKey.keyR, control: true, alt: true, - ): ReloadThemeIntent(), - SingleActivator( + ): const ReloadThemeIntent(), + ////////////////////////// Switch Brightness + const SingleActivator( LogicalKeyboardKey.keyB, control: true, alt: true, - ): ChangeBrightnessIntent(), - SingleActivator( + ): const ChangeBrightnessIntent(), + ////////////////////////// Change Color + const SingleActivator( LogicalKeyboardKey.keyC, control: true, alt: true, - ): ChangeColorIntent(), - SingleActivator( - LogicalKeyboardKey.keyA, - control: true, - alt: true, - ): AttachDetachIntent(), - SingleActivator( + ): const ChangeColorIntent(), + ////////////////////////// Attach/Detach + if (kIsDebugMode) + const SingleActivator( + LogicalKeyboardKey.keyA, + control: true, + alt: true, + ): const AttachDetachIntent(), + ////////////////////////// Show Developer Page + const SingleActivator( LogicalKeyboardKey.keyD, control: true, alt: true, - ): DeveloperPageIntent(), + ): const DeveloperPageIntent(), + ////////////////////////// Display Scale Up + SingleActivator( + LogicalKeyboardKey.equal, + meta: isMac || isiOS, + control: !(isMac || isiOS), + ): const DisplayScaleUpIntent(), + SingleActivator( + LogicalKeyboardKey.equal, + shift: true, + meta: isMac || isiOS, + control: !(isMac || isiOS), + ): const DisplayScaleUpIntent(), + SingleActivator( + LogicalKeyboardKey.add, + shift: true, + meta: isMac || isiOS, + control: !(isMac || isiOS), + ): const DisplayScaleUpIntent(), + SingleActivator( + LogicalKeyboardKey.numpadAdd, + meta: isMac || isiOS, + control: !(isMac || isiOS), + ): const DisplayScaleUpIntent(), + ////////////////////////// Display Scale Down + SingleActivator( + LogicalKeyboardKey.minus, + meta: isMac || isiOS, + control: !(isMac || isiOS), + ): const DisplayScaleDownIntent(), + SingleActivator( + LogicalKeyboardKey.numpadSubtract, + meta: isMac || isiOS, + control: !(isMac || isiOS), + ): const DisplayScaleDownIntent(), }, child: Actions(actions: >{ ReloadThemeIntent: CallbackAction( onInvoke: (intent) => reloadTheme(context)), ChangeBrightnessIntent: CallbackAction( - onInvoke: (intent) => changeBrightness(context)), + onInvoke: (intent) => _changeBrightness(context)), ChangeColorIntent: CallbackAction( - onInvoke: (intent) => changeColor(context)), + onInvoke: (intent) => _changeColor(context)), AttachDetachIntent: CallbackAction( onInvoke: (intent) => _attachDetach(context)), DeveloperPageIntent: CallbackAction( onInvoke: (intent) => _developerPage(context)), + DisplayScaleUpIntent: CallbackAction( + onInvoke: (intent) => _displayScaleUp(context)), + DisplayScaleDownIntent: CallbackAction( + onInvoke: (intent) => _displayScaleDown(context)), }, child: Focus(autofocus: true, child: child)))); ///////////////////////////////////////////////////////// diff --git a/lib/layout/default_app_bar.dart b/lib/layout/default_app_bar.dart index b9c0b41..7742dad 100644 --- a/lib/layout/default_app_bar.dart +++ b/lib/layout/default_app_bar.dart @@ -2,21 +2,27 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import '../theme/theme.dart'; + class DefaultAppBar extends AppBar { DefaultAppBar( - {super.title, + {required BuildContext context, + super.title, super.flexibleSpace, super.key, Widget? leading, super.actions}) : super( + toolbarHeight: 40.scaled(context), + leadingWidth: 40.scaled(context), leading: leading ?? Container( - margin: const EdgeInsets.all(4), + margin: const EdgeInsets.all(4).scaled(context), decoration: BoxDecoration( color: Colors.black.withAlpha(32), shape: BoxShape.circle), - child: - SvgPicture.asset('assets/images/vlogo.svg', height: 24) - .paddingAll(4))); + child: SvgPicture.asset('assets/images/vlogo.svg', + width: 24.scaled(context), + height: 24.scaled(context)) + .paddingAll(4.scaled(context)))); } diff --git a/lib/layout/home/drawer_menu/drawer_menu.dart b/lib/layout/home/drawer_menu/drawer_menu.dart index 0863b1f..33f2bc2 100644 --- a/lib/layout/home/drawer_menu/drawer_menu.dart +++ b/lib/layout/home/drawer_menu/drawer_menu.dart @@ -95,19 +95,15 @@ class _DrawerMenuState extends State { activeBorder = scale.primary; } - final avatar = AvatarWidget( + final avatar = StyledAvatar( name: name, - size: 34, - borderColor: border, - foregroundColor: loggedIn ? scale.primaryText : scale.subtleText, - backgroundColor: loggedIn ? scale.primary : scale.elementBackground, - scaleConfig: scaleConfig, - textStyle: theme.textTheme.titleLarge!, + size: 34.scaled(context), ); return AnimatedPadding( padding: EdgeInsets.fromLTRB(selected ? 0 : 8, selected ? 0 : 2, - selected ? 0 : 8, selected ? 0 : 2), + selected ? 0 : 8, selected ? 0 : 2) + .scaled(context), duration: const Duration(milliseconds: 50), child: MenuItemWidget( title: name, @@ -144,7 +140,7 @@ class _DrawerMenuState extends State { (scaleConfig.preferBorders || scaleConfig.useVisualIndicators) ? null : activeBorder, - minHeight: 48, + minHeight: 48.scaled(context), )); } @@ -196,7 +192,8 @@ class _DrawerMenuState extends State { color: scaleScheme.errorScale.subtleBorder, borderRadius: 12 * scaleConfig.borderRadiusScale), ); - loggedInAccounts.add(loggedInAccount.paddingLTRB(0, 0, 0, 8)); + loggedInAccounts + .add(loggedInAccount.paddingLTRB(0, 0, 0, 8.scaled(context))); } else { // Account is not logged in final scale = theme.extension()!.grayScale; @@ -246,8 +243,8 @@ class _DrawerMenuState extends State { } return IconButton( icon: icon, + padding: const EdgeInsets.all(12), color: border, - constraints: const BoxConstraints.expand(height: 48, width: 48), style: ButtonStyle( backgroundColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.hovered)) { @@ -286,7 +283,10 @@ class _DrawerMenuState extends State { final scale = scaleScheme.scale(_scaleKind); final settingsButton = _getButton( - icon: const Icon(Icons.settings), + icon: const Icon( + Icons.settings, + applyTextScaling: true, + ), tooltip: translate('menu.settings_tooltip'), scale: scale, scaleConfig: scaleConfig, @@ -295,7 +295,10 @@ class _DrawerMenuState extends State { }).paddingLTRB(0, 0, 16, 0); final addButton = _getButton( - icon: const Icon(Icons.add), + icon: const Icon( + Icons.add, + applyTextScaling: true, + ), tooltip: translate('menu.add_account_tooltip'), scale: scale, scaleConfig: scaleConfig, @@ -364,7 +367,7 @@ class _DrawerMenuState extends State { // : null) // .paddingLTRB(0, 0, 16, 0), GestureDetector( - onLongPress: () async { + onLongPress: () { context .findAncestorWidgetOfExactType()! .reloadTheme(context); diff --git a/lib/layout/home/drawer_menu/menu_item_widget.dart b/lib/layout/home/drawer_menu/menu_item_widget.dart index 1255458..80e466b 100644 --- a/lib/layout/home/drawer_menu/menu_item_widget.dart +++ b/lib/layout/home/drawer_menu/menu_item_widget.dart @@ -2,6 +2,8 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import '../../../theme/views/preferences/preferences.dart'; + class MenuItemWidget extends StatelessWidget { const MenuItemWidget({ required this.title, @@ -81,7 +83,7 @@ class MenuItemWidget extends StatelessWidget { hoverColor: footerButtonIconHoverColor, icon: Icon( footerButtonIcon, - size: 24, + size: 24.scaled(context), ), onPressed: footerCallback), ], diff --git a/lib/layout/home/home_account_ready.dart b/lib/layout/home/home_account_ready.dart index a2966a6..5ae1180 100644 --- a/lib/layout/home/home_account_ready.dart +++ b/lib/layout/home/home_account_ready.dart @@ -28,73 +28,81 @@ class _HomeAccountReadyState extends State { final theme = Theme.of(context); final scale = theme.extension()!; final scaleConfig = theme.extension()!; - return IconButton( - icon: const Icon(Icons.menu), - color: scaleConfig.preferBorders - ? scale.primaryScale.border - : scale.primaryScale.borderText, - constraints: const BoxConstraints.expand(height: 40, width: 40), - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - scaleConfig.preferBorders - ? scale.primaryScale.hoverElementBackground - : scale.primaryScale.hoverBorder), - shape: WidgetStateProperty.all( - RoundedRectangleBorder( - side: !scaleConfig.useVisualIndicators - ? BorderSide.none - : BorderSide( - strokeAlign: BorderSide.strokeAlignCenter, - color: scaleConfig.preferBorders - ? scale.primaryScale.border - : scale.primaryScale.borderText, - width: 2), - borderRadius: BorderRadius.all( - Radius.circular(8 * scaleConfig.borderRadiusScale))), - )), - tooltip: translate('menu.accounts_menu_tooltip'), - onPressed: () async { - final ctrl = context.read(); - await ctrl.toggle?.call(); - }); + return AspectRatio( + aspectRatio: 1, + child: IconButton( + icon: const Icon( + Icons.menu, + applyTextScaling: true, + ), + color: scaleConfig.preferBorders + ? scale.primaryScale.border + : scale.primaryScale.borderText, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + scaleConfig.preferBorders + ? scale.primaryScale.hoverElementBackground + : scale.primaryScale.hoverBorder), + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + side: !scaleConfig.useVisualIndicators + ? BorderSide.none + : BorderSide( + strokeAlign: BorderSide.strokeAlignCenter, + color: scaleConfig.preferBorders + ? scale.primaryScale.border + : scale.primaryScale.borderText, + width: 2), + borderRadius: BorderRadius.all(Radius.circular( + 8 * scaleConfig.borderRadiusScale))), + )), + tooltip: translate('menu.accounts_menu_tooltip'), + onPressed: () async { + final ctrl = context.read(); + await ctrl.toggle?.call(); + })); }); Widget buildContactsButton() => Builder(builder: (context) { final theme = Theme.of(context); final scale = theme.extension()!; final scaleConfig = theme.extension()!; - return IconButton( - icon: const Icon(Icons.contacts), - color: scaleConfig.preferBorders - ? scale.primaryScale.border - : scale.primaryScale.borderText, - constraints: const BoxConstraints.expand(height: 40, width: 40), - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - scaleConfig.preferBorders - ? scale.primaryScale.hoverElementBackground - : scale.primaryScale.hoverBorder), - shape: WidgetStateProperty.all( - RoundedRectangleBorder( - side: !scaleConfig.useVisualIndicators - ? BorderSide.none - : BorderSide( - strokeAlign: BorderSide.strokeAlignCenter, - color: scaleConfig.preferBorders - ? scale.primaryScale.border - : scale.primaryScale.borderText, - width: 2), - borderRadius: BorderRadius.all( - Radius.circular(8 * scaleConfig.borderRadiusScale))), - )), - tooltip: translate('menu.contacts_tooltip'), - onPressed: () async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => const ContactsPage(), + return AspectRatio( + aspectRatio: 1, + child: IconButton( + icon: const Icon( + Icons.contacts, + applyTextScaling: true, ), - ); - }); + color: scaleConfig.preferBorders + ? scale.primaryScale.border + : scale.primaryScale.borderText, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + scaleConfig.preferBorders + ? scale.primaryScale.hoverElementBackground + : scale.primaryScale.hoverBorder), + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + side: !scaleConfig.useVisualIndicators + ? BorderSide.none + : BorderSide( + strokeAlign: BorderSide.strokeAlignCenter, + color: scaleConfig.preferBorders + ? scale.primaryScale.border + : scale.primaryScale.borderText, + width: 2), + borderRadius: BorderRadius.all(Radius.circular( + 8 * scaleConfig.borderRadiusScale))), + )), + tooltip: translate('menu.contacts_tooltip'), + onPressed: () async { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const ContactsPage(), + ), + ); + })); }); Widget buildLeftPane(BuildContext context) => Builder( @@ -112,14 +120,17 @@ class _HomeAccountReadyState extends State { ? scale.primaryScale.subtleBackground : scale.primaryScale.subtleBorder, child: Column(children: [ - Row(children: [ - buildMenuButton().paddingLTRB(0, 0, 8, 0), - ProfileWidget( - profile: profile, - showPronouns: false, - ).expanded(), - buildContactsButton().paddingLTRB(8, 0, 0, 0), - ]).paddingAll(8), + IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + buildMenuButton().paddingLTRB(0, 0, 8, 0), + ProfileWidget( + profile: profile, + showPronouns: false, + ).expanded(), + buildContactsButton().paddingLTRB(8, 0, 0, 0), + ])).paddingAll(8), const ChatListWidget().expanded() ])); }))); diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index b4c3b58..e774790 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -71,8 +71,9 @@ class HomeScreenState extends State context: context, title: translate('splash.beta_title'), child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.warning, size: 64), + Icon(Icons.warning, size: 64.scaled(context)), RichText( + textScaler: MediaQuery.of(context).textScaler, textAlign: TextAlign.center, text: TextSpan( children: [ @@ -206,34 +207,36 @@ class HomeScreenState extends State .indexWhere((x) => x.superIdentity.recordKey == activeLocalAccount); final canClose = activeIndex != -1; + final drawer = ZoomDrawer( + controller: _zoomDrawerController, + menuScreen: Builder(builder: (context) { + final zoomDrawer = ZoomDrawer.of(context); + zoomDrawer!.stateNotifier.addListener(() { + if (zoomDrawer.isOpen()) { + FocusManager.instance.primaryFocus?.unfocus(); + } + }); + return const DrawerMenu(); + }), + mainScreen: Provider.value( + value: _zoomDrawerController, + child: Builder(builder: _buildAccountPageView)), + borderRadius: 0, + angle: 0, + openCurve: Curves.fastEaseInToSlowEaseOut, + closeCurve: Curves.fastEaseInToSlowEaseOut, + menuScreenTapClose: canClose, + mainScreenTapClose: canClose, + disableDragGesture: !canClose, + mainScreenScale: .25, + slideWidth: min(360, MediaQuery.of(context).size.width * 0.9), + ); + + final drawerWithAvoider = + isWeb ? drawer : KeyboardAvoider(curve: Curves.ease, child: drawer); + return DefaultTextStyle( - style: theme.textTheme.bodySmall!, - child: KeyboardAvoider( - curve: Curves.ease, - child: ZoomDrawer( - controller: _zoomDrawerController, - menuScreen: Builder(builder: (context) { - final zoomDrawer = ZoomDrawer.of(context); - zoomDrawer!.stateNotifier.addListener(() { - if (zoomDrawer.isOpen()) { - FocusManager.instance.primaryFocus?.unfocus(); - } - }); - return const DrawerMenu(); - }), - mainScreen: Provider.value( - value: _zoomDrawerController, - child: Builder(builder: _buildAccountPageView)), - borderRadius: 0, - angle: 0, - openCurve: Curves.fastEaseInToSlowEaseOut, - closeCurve: Curves.fastEaseInToSlowEaseOut, - menuScreenTapClose: canClose, - mainScreenTapClose: canClose, - disableDragGesture: !canClose, - mainScreenScale: .25, - slideWidth: min(360, MediaQuery.of(context).size.width * 0.9), - ))); + style: theme.textTheme.bodySmall!, child: drawerWithAvoider); } //////////////////////////////////////////////////////////////////////////// diff --git a/lib/notifications/views/notifications_preferences.dart b/lib/notifications/views/notifications_preferences.dart index 82f3555..8918fa9 100644 --- a/lib/notifications/views/notifications_preferences.dart +++ b/lib/notifications/views/notifications_preferences.dart @@ -1,24 +1,13 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_translate/flutter_translate.dart'; import '../../settings/settings.dart'; import '../../theme/theme.dart'; import '../notifications.dart'; -const String formFieldDisplayBetaWarning = 'displayBetaWarning'; -const String formFieldEnableBadge = 'enableBadge'; -const String formFieldEnableNotifications = 'enableNotifications'; -const String formFieldMessageNotificationContent = 'messageNotificationContent'; -const String formFieldInvitationAcceptMode = 'invitationAcceptMode'; -const String formFieldInvitationAcceptSound = 'invitationAcceptSound'; -const String formFieldMessageReceivedMode = 'messageReceivedMode'; -const String formFieldMessageReceivedSound = 'messageReceivedSound'; -const String formFieldMessageSentSound = 'messageSentSound'; - Widget buildSettingsPageNotificationPreferences( - {required BuildContext context, required void Function() onChanged}) { + {required BuildContext context}) { final theme = Theme.of(context); final scale = theme.extension()!; final scaleConfig = theme.extension()!; @@ -33,7 +22,6 @@ Widget buildSettingsPageNotificationPreferences( final newPrefs = preferencesRepository.value .copyWith(notificationsPreference: newNotificationsPreference); await preferencesRepository.set(newPrefs); - onChanged(); } List> notificationModeItems() { @@ -54,9 +42,10 @@ Widget buildSettingsPageNotificationPreferences( enabled: x.$2, child: Text( x.$3, - style: textTheme.labelSmall, + softWrap: false, + style: textTheme.labelMedium, textAlign: TextAlign.center, - ))); + ).fit(fit: BoxFit.scaleDown))); } return out; } @@ -77,7 +66,8 @@ Widget buildSettingsPageNotificationPreferences( enabled: x.$2, child: Text( x.$3, - style: textTheme.labelSmall, + softWrap: false, + style: textTheme.labelMedium, textAlign: TextAlign.center, ))); } @@ -110,7 +100,8 @@ Widget buildSettingsPageNotificationPreferences( enabled: x.$2, child: Text( x.$3, - style: textTheme.labelSmall, + softWrap: false, + style: textTheme.labelMedium, textAlign: TextAlign.center, ))); } @@ -127,66 +118,45 @@ Widget buildSettingsPageNotificationPreferences( ), child: Column(mainAxisSize: MainAxisSize.min, children: [ // Display Beta Warning - FormBuilderCheckbox( - name: formFieldDisplayBetaWarning, - title: Text(translate('settings_page.display_beta_warning'), - style: textTheme.labelMedium), - initialValue: notificationsPreference.displayBetaWarning, + StyledCheckbox( + label: translate('settings_page.display_beta_warning'), + value: notificationsPreference.displayBetaWarning, onChanged: (value) async { - if (value == null) { - return; - } final newNotificationsPreference = notificationsPreference.copyWith(displayBetaWarning: value); await updatePreferences(newNotificationsPreference); }), // Enable Badge - FormBuilderCheckbox( - name: formFieldEnableBadge, - title: Text(translate('settings_page.enable_badge'), - style: textTheme.labelMedium), - initialValue: notificationsPreference.enableBadge, + StyledCheckbox( + label: translate('settings_page.enable_badge'), + value: notificationsPreference.enableBadge, onChanged: (value) async { - if (value == null) { - return; - } final newNotificationsPreference = notificationsPreference.copyWith(enableBadge: value); await updatePreferences(newNotificationsPreference); }), // Enable Notifications - FormBuilderCheckbox( - name: formFieldEnableNotifications, - title: Text(translate('settings_page.enable_notifications'), - style: textTheme.labelMedium), - initialValue: notificationsPreference.enableNotifications, + StyledCheckbox( + label: translate('settings_page.enable_notifications'), + value: notificationsPreference.enableNotifications, onChanged: (value) async { - if (value == null) { - return; - } final newNotificationsPreference = notificationsPreference.copyWith(enableNotifications: value); await updatePreferences(newNotificationsPreference); }), - - FormBuilderDropdown( - name: formFieldMessageNotificationContent, - isDense: false, - decoration: InputDecoration( - labelText: translate('settings_page.message_notification_content')), - enabled: notificationsPreference.enableNotifications, - initialValue: notificationsPreference.messageNotificationContent, - onChanged: (value) async { - if (value == null) { - return; - } - final newNotificationsPreference = notificationsPreference.copyWith( - messageNotificationContent: value); - await updatePreferences(newNotificationsPreference); - }, + StyledDropdown( items: messageNotificationContentItems(), - ).paddingLTRB(0, 4, 0, 4), + value: notificationsPreference.messageNotificationContent, + decoratorLabel: translate('settings_page.message_notification_content'), + onChanged: !notificationsPreference.enableNotifications + ? null + : (value) async { + final newNotificationsPreference = notificationsPreference + .copyWith(messageNotificationContent: value); + await updatePreferences(newNotificationsPreference); + }, + ).paddingLTRB(0, 4.scaled(context), 0, 4.scaled(context)), // Notifications Table( @@ -199,95 +169,85 @@ Widget buildSettingsPageNotificationPreferences( color: scale.primaryScale.border, decorationColor: scale.primaryScale.border, decoration: TextDecoration.underline)) - .paddingAll(8), + .paddingAll(8.scaled(context)), Text(translate('settings_page.delivery'), textAlign: TextAlign.center, style: textTheme.titleMedium!.copyWith( color: scale.primaryScale.border, decorationColor: scale.primaryScale.border, decoration: TextDecoration.underline)) - .paddingAll(8), + .paddingAll(8.scaled(context)), Text(translate('settings_page.sound'), textAlign: TextAlign.center, style: textTheme.titleMedium!.copyWith( color: scale.primaryScale.border, decorationColor: scale.primaryScale.border, decoration: TextDecoration.underline)) - .paddingAll(8), + .paddingAll(8.scaled(context)), ]), TableRow(children: [ // Invitation accepted Text( textAlign: TextAlign.right, translate('settings_page.invitation_accepted')) - .paddingAll(8), - FormBuilderDropdown( - name: formFieldInvitationAcceptMode, - isDense: false, - enabled: notificationsPreference.enableNotifications, - initialValue: notificationsPreference.onInvitationAcceptedMode, - onChanged: (value) async { - if (value == null) { - return; - } - final newNotificationsPreference = notificationsPreference - .copyWith(onInvitationAcceptedMode: value); - await updatePreferences(newNotificationsPreference); - }, + .paddingAll(4.scaled(context)), + StyledDropdown( items: notificationModeItems(), - ).paddingAll(4), - FormBuilderDropdown( - name: formFieldInvitationAcceptSound, - isDense: false, - enabled: notificationsPreference.enableNotifications, - initialValue: notificationsPreference.onInvitationAcceptedSound, - onChanged: (value) async { - if (value == null) { - return; - } - final newNotificationsPreference = notificationsPreference - .copyWith(onInvitationAcceptedSound: value); - await updatePreferences(newNotificationsPreference); - }, + value: notificationsPreference.onInvitationAcceptedMode, + onChanged: !notificationsPreference.enableNotifications + ? null + : (value) async { + final newNotificationsPreference = + notificationsPreference.copyWith( + onInvitationAcceptedMode: value); + await updatePreferences(newNotificationsPreference); + }, + ).paddingAll(4.scaled(context)), + StyledDropdown( items: soundEffectItems(), - ).paddingLTRB(4, 4, 0, 4) + value: notificationsPreference.onInvitationAcceptedSound, + onChanged: !notificationsPreference.enableNotifications + ? null + : (value) async { + final newNotificationsPreference = + notificationsPreference.copyWith( + onInvitationAcceptedSound: value); + await updatePreferences(newNotificationsPreference); + }, + ).paddingLTRB( + 4.scaled(context), 4.scaled(context), 0, 4.scaled(context)) ]), // Message received TableRow(children: [ Text( textAlign: TextAlign.right, translate('settings_page.message_received')) - .paddingAll(8), - FormBuilderDropdown( - name: formFieldMessageReceivedMode, - isDense: false, - enabled: notificationsPreference.enableNotifications, - initialValue: notificationsPreference.onMessageReceivedMode, - onChanged: (value) async { - if (value == null) { - return; - } - final newNotificationsPreference = notificationsPreference - .copyWith(onMessageReceivedMode: value); - await updatePreferences(newNotificationsPreference); - }, + .paddingAll(4.scaled(context)), + StyledDropdown( items: notificationModeItems(), + value: notificationsPreference.onMessageReceivedMode, + onChanged: !notificationsPreference.enableNotifications + ? null + : (value) async { + final newNotificationsPreference = + notificationsPreference.copyWith( + onMessageReceivedMode: value); + await updatePreferences(newNotificationsPreference); + }, ).paddingAll(4), - FormBuilderDropdown( - name: formFieldMessageReceivedSound, - isDense: false, - enabled: notificationsPreference.enableNotifications, - initialValue: notificationsPreference.onMessageReceivedSound, - onChanged: (value) async { - if (value == null) { - return; - } - final newNotificationsPreference = notificationsPreference - .copyWith(onMessageReceivedSound: value); - await updatePreferences(newNotificationsPreference); - }, + StyledDropdown( items: soundEffectItems(), - ).paddingLTRB(4, 4, 0, 4) + value: notificationsPreference.onMessageReceivedSound, + onChanged: !notificationsPreference.enableNotifications + ? null + : (value) async { + final newNotificationsPreference = + notificationsPreference.copyWith( + onMessageReceivedSound: value); + await updatePreferences(newNotificationsPreference); + }, + ).paddingLTRB( + 4.scaled(context), 4.scaled(context), 0, 4.scaled(context)) ]), // Message sent @@ -295,25 +255,23 @@ Widget buildSettingsPageNotificationPreferences( Text( textAlign: TextAlign.right, translate('settings_page.message_sent')) - .paddingAll(8), + .paddingAll(4.scaled(context)), const SizedBox.shrink(), - FormBuilderDropdown( - name: formFieldMessageSentSound, - isDense: false, - enabled: notificationsPreference.enableNotifications, - initialValue: notificationsPreference.onMessageSentSound, - onChanged: (value) async { - if (value == null) { - return; - } - final newNotificationsPreference = notificationsPreference - .copyWith(onMessageSentSound: value); - await updatePreferences(newNotificationsPreference); - }, + StyledDropdown( items: soundEffectItems(), - ).paddingLTRB(4, 4, 0, 4) + value: notificationsPreference.onMessageSentSound, + onChanged: !notificationsPreference.enableNotifications + ? null + : (value) async { + final newNotificationsPreference = + notificationsPreference.copyWith( + onMessageSentSound: value); + await updatePreferences(newNotificationsPreference); + }, + ).paddingLTRB( + 4.scaled(context), 4.scaled(context), 0, 4.scaled(context)) ]), ]) - ]).paddingAll(8), + ]).paddingAll(8.scaled(context)), ); } diff --git a/lib/router/views/router_shell.dart b/lib/router/views/router_shell.dart index 164c452..d22129f 100644 --- a/lib/router/views/router_shell.dart +++ b/lib/router/views/router_shell.dart @@ -1,7 +1,9 @@ +import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import '../../keyboard_shortcuts.dart'; import '../../notifications/notifications.dart'; +import '../../settings/settings.dart'; import '../../theme/theme.dart'; class RouterShell extends StatelessWidget { @@ -10,7 +12,13 @@ class RouterShell extends StatelessWidget { @override Widget build(BuildContext context) => PopControl( dismissible: false, - child: NotificationsWidget(child: KeyboardShortcuts(child: _child))); + child: AsyncBlocBuilder( + builder: (context, state) => MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: + TextScaler.linear(state.themePreference.displayScale)), + child: NotificationsWidget( + child: KeyboardShortcuts(child: _child))))); final Widget _child; } diff --git a/lib/settings/models/preferences.dart b/lib/settings/models/preferences.dart index 3ef683e..a7432d6 100644 --- a/lib/settings/models/preferences.dart +++ b/lib/settings/models/preferences.dart @@ -20,7 +20,7 @@ sealed class LockPreference with _$LockPreference { factory LockPreference.fromJson(dynamic json) => _$LockPreferenceFromJson(json as Map); - static const LockPreference defaults = LockPreference(); + static const defaults = LockPreference(); } // Theme supports multiple translations @@ -49,5 +49,5 @@ sealed class Preferences with _$Preferences { factory Preferences.fromJson(dynamic json) => _$PreferencesFromJson(json as Map); - static const Preferences defaults = Preferences(); + static const defaults = Preferences(); } diff --git a/lib/settings/settings_page.dart b/lib/settings/settings_page.dart index 2a05f08..810b20e 100644 --- a/lib/settings/settings_page.dart +++ b/lib/settings/settings_page.dart @@ -1,7 +1,6 @@ import 'package:animated_theme_switcher/animated_theme_switcher.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:go_router/go_router.dart'; @@ -11,62 +10,53 @@ import '../theme/theme.dart'; import '../veilid_processor/veilid_processor.dart'; import 'settings.dart'; -class SettingsPage extends StatefulWidget { +class SettingsPage extends StatelessWidget { const SettingsPage({super.key}); - @override - SettingsPageState createState() => SettingsPageState(); -} - -class SettingsPageState extends State { - final _formKey = GlobalKey(); - static const String formFieldTheme = 'theme'; - static const String formFieldBrightness = 'brightness'; - - @override - void initState() { - super.initState(); - } - @override Widget build(BuildContext context) => AsyncBlocBuilder( builder: (context, state) => ThemeSwitcher.withTheme( - builder: (_, switcher, theme) => StyledScaffold( + builder: (_, switcher, theme) => StyledScaffold( appBar: DefaultAppBar( + context: context, title: Text(translate('settings_page.titlebar')), leading: IconButton( - icon: const Icon(Icons.arrow_back), + iconSize: 24.scaled(context), + icon: Icon(Icons.arrow_back), onPressed: () => GoRouterHelper(context).pop(), ), actions: [ const SignalStrengthMeterWidget() .paddingLTRB(16, 0, 16, 0), ]), - body: ThemeSwitchingArea( - child: FormBuilder( - key: _formKey, - child: ListView( - padding: const EdgeInsets.all(8), - children: [ - buildSettingsPageColorPreferences( - context: context, - switcher: switcher, - onChanged: () => setState(() {})) - .paddingLTRB(0, 8, 0, 0), - buildSettingsPageBrightnessPreferences( - context: context, - switcher: switcher, - onChanged: () => setState(() {})), - buildSettingsPageWallpaperPreferences( - context: context, - switcher: switcher, - onChanged: () => setState(() {})), - buildSettingsPageNotificationPreferences( - context: context, - onChanged: () => setState(() {})), - ].map((x) => x.paddingLTRB(0, 0, 0, 8)).toList(), + body: ListView( + padding: const EdgeInsets.all(8).scaled(context), + children: [ + buildSettingsPageColorPreferences( + context: context, + switcher: switcher, ), - ).paddingSymmetric(horizontal: 8, vertical: 8), - )))); + buildSettingsPageBrightnessPreferences( + context: context, + switcher: switcher, + ), + buildSettingsPageDisplayScalePreferences( + context: context, + switcher: switcher, + ), + buildSettingsPageWallpaperPreferences( + context: context, + switcher: switcher, + ), + buildSettingsPageNotificationPreferences( + context: context, + ), + ] + .map((x) => x.paddingLTRB(0, 0, 0, 8.scaled(context))) + .toList(), + ).paddingSymmetric(vertical: 4.scaled(context)), + ).paddingSymmetric( + horizontal: 8.scaled(context), vertical: 8.scaled(context)), + )); } diff --git a/lib/theme/models/contrast_generator.dart b/lib/theme/models/contrast_generator.dart index 314e28a..05c5f55 100644 --- a/lib/theme/models/contrast_generator.dart +++ b/lib/theme/models/contrast_generator.dart @@ -308,6 +308,13 @@ ThemeData contrastGenerator({ side: elementBorderWidgetStateProperty(), backgroundColor: elementBackgroundWidgetStateProperty())); + final sliderTheme = SliderThemeData.fromPrimaryColors( + primaryColor: scheme.primaryScale.borderText, + primaryColorDark: scheme.primaryScale.border, + primaryColorLight: scheme.primaryScale.border, + valueIndicatorTextStyle: textTheme.labelMedium! + .copyWith(color: scheme.primaryScale.borderText)); + final themeData = baseThemeData.copyWith( // chipTheme: baseThemeData.chipTheme.copyWith( // backgroundColor: scaleScheme.primaryScale.elementBackground, @@ -316,6 +323,7 @@ ThemeData contrastGenerator({ // checkmarkColor: scaleScheme.primaryScale.border, // side: BorderSide(color: scaleScheme.primaryScale.border)), elevatedButtonTheme: elevatedButtonTheme, + sliderTheme: sliderTheme, textSelectionTheme: TextSelectionThemeData( cursorColor: scheme.primaryScale.appText, selectionColor: scheme.primaryScale.appText.withAlpha(0x7F), diff --git a/lib/theme/models/scale_theme/scale_theme.dart b/lib/theme/models/scale_theme/scale_theme.dart index c3217ea..755bd54 100644 --- a/lib/theme/models/scale_theme/scale_theme.dart +++ b/lib/theme/models/scale_theme/scale_theme.dart @@ -132,6 +132,13 @@ class ScaleTheme extends ThemeExtension { iconColor: elementColorWidgetStateProperty(), )); + final sliderTheme = SliderThemeData.fromPrimaryColors( + primaryColor: scheme.primaryScale.hoverBorder, + primaryColorDark: scheme.primaryScale.border, + primaryColorLight: scheme.primaryScale.border, + valueIndicatorTextStyle: textTheme.labelMedium! + .copyWith(color: scheme.primaryScale.borderText)); + final themeData = baseThemeData.copyWith( scrollbarTheme: baseThemeData.scrollbarTheme.copyWith( thumbColor: WidgetStateProperty.resolveWith((states) { @@ -183,6 +190,7 @@ class ScaleTheme extends ThemeExtension { elevatedButtonTheme: elevatedButtonTheme, inputDecorationTheme: ScaleInputDecoratorTheme(scheme, config, textTheme), + sliderTheme: sliderTheme, extensions: >[scheme, config, this]); return themeData; diff --git a/lib/theme/models/theme_preference.dart b/lib/theme/models/theme_preference.dart index aaad52d..44d06d8 100644 --- a/lib/theme/models/theme_preference.dart +++ b/lib/theme/models/theme_preference.dart @@ -61,7 +61,7 @@ sealed class ThemePreferences with _$ThemePreferences { factory ThemePreferences.fromJson(dynamic json) => _$ThemePreferencesFromJson(json as Map); - static const ThemePreferences defaults = ThemePreferences(); + static const defaults = ThemePreferences(); } extension ThemePreferencesExt on ThemePreferences { diff --git a/lib/theme/views/avatar_widget.dart b/lib/theme/views/avatar_widget.dart deleted file mode 100644 index 42bea11..0000000 --- a/lib/theme/views/avatar_widget.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:awesome_extensions/awesome_extensions.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; - -import '../theme.dart'; - -class AvatarWidget extends StatelessWidget { - const AvatarWidget({ - required String name, - required double size, - required Color borderColor, - required Color foregroundColor, - required Color backgroundColor, - required ScaleConfig scaleConfig, - required TextStyle textStyle, - super.key, - ImageProvider? imageProvider, - }) : _name = name, - _size = size, - _borderColor = borderColor, - _foregroundColor = foregroundColor, - _backgroundColor = backgroundColor, - _scaleConfig = scaleConfig, - _textStyle = textStyle, - _imageProvider = imageProvider; - - @override - Widget build(BuildContext context) { - final abbrev = _name.split(' ').map((s) => s.isEmpty ? '' : s[0]).join(); - late final String shortname; - if (abbrev.length >= 3) { - shortname = abbrev[0] + abbrev[1] + abbrev[abbrev.length - 1]; - } else { - shortname = abbrev; - } - - return Container( - height: _size, - width: _size, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: _borderColor, - width: 1 * (_size ~/ 32 + 1), - strokeAlign: BorderSide.strokeAlignOutside)), - child: AvatarImage( - //size: 32, - backgroundImage: _imageProvider, - backgroundColor: - _scaleConfig.useVisualIndicators && !_scaleConfig.preferBorders - ? _foregroundColor - : _backgroundColor, - child: Text( - shortname.isNotEmpty ? shortname : '?', - softWrap: false, - style: _textStyle.copyWith( - color: _scaleConfig.useVisualIndicators && - !_scaleConfig.preferBorders - ? _backgroundColor - : _foregroundColor, - ), - ).fit().paddingAll(_size / 16))); - } - - //////////////////////////////////////////////////////////////////////////// - final String _name; - final double _size; - final Color _borderColor; - final Color _foregroundColor; - final Color _backgroundColor; - final ScaleConfig _scaleConfig; - final TextStyle _textStyle; - final ImageProvider? _imageProvider; -} diff --git a/lib/theme/views/enter_password.dart b/lib/theme/views/enter_password.dart index fc876da..f28b69e 100644 --- a/lib/theme/views/enter_password.dart +++ b/lib/theme/views/enter_password.dart @@ -32,7 +32,7 @@ class _EnterPasswordDialogState extends State { final passwordController = TextEditingController(); final focusNode = FocusNode(); final formKey = GlobalKey(); - bool _passwordVisible = false; + var _passwordVisible = false; @override void initState() { @@ -47,7 +47,6 @@ class _EnterPasswordDialogState extends State { } @override - // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { final theme = Theme.of(context); final scale = theme.extension()!; diff --git a/lib/theme/views/brightness_preferences.dart b/lib/theme/views/preferences/brightness_preferences.dart similarity index 59% rename from lib/theme/views/brightness_preferences.dart rename to lib/theme/views/preferences/brightness_preferences.dart index 7a1bb1d..a149483 100644 --- a/lib/theme/views/brightness_preferences.dart +++ b/lib/theme/views/preferences/brightness_preferences.dart @@ -1,14 +1,12 @@ import 'package:animated_theme_switcher/animated_theme_switcher.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import '../../settings/settings.dart'; -import '../models/models.dart'; +import '../../../settings/settings.dart'; +import '../../models/models.dart'; +import '../views.dart'; -const String formFieldBrightness = 'brightness'; - -List> _getBrightnessDropdownItems() { +List> _getBrightnessDropdownItems() { const brightnessPrefs = BrightnessPreference.values; final brightnessNames = { BrightnessPreference.system: translate('brightness.system'), @@ -22,25 +20,21 @@ List> _getBrightnessDropdownItems() { } Widget buildSettingsPageBrightnessPreferences( - {required BuildContext context, - required void Function() onChanged, - required ThemeSwitcherState switcher}) { + {required BuildContext context, required ThemeSwitcherState switcher}) { final preferencesRepository = PreferencesRepository.instance; final themePreferences = preferencesRepository.value.themePreference; - return FormBuilderDropdown( - name: formFieldBrightness, - decoration: InputDecoration( - label: Text(translate('settings_page.brightness_mode'))), + + return StyledDropdown( items: _getBrightnessDropdownItems(), - initialValue: themePreferences.brightnessPreference, + value: themePreferences.brightnessPreference, + decoratorLabel: translate('settings_page.brightness_mode'), onChanged: (value) async { - final newThemePrefs = themePreferences.copyWith( - brightnessPreference: value as BrightnessPreference); + final newThemePrefs = + themePreferences.copyWith(brightnessPreference: value); final newPrefs = preferencesRepository.value .copyWith(themePreference: newThemePrefs); await preferencesRepository.set(newPrefs); switcher.changeTheme(theme: newThemePrefs.themeData()); - onChanged(); }); } diff --git a/lib/theme/views/color_preferences.dart b/lib/theme/views/preferences/color_preferences.dart similarity index 54% rename from lib/theme/views/color_preferences.dart rename to lib/theme/views/preferences/color_preferences.dart index a9a8841..2c14a93 100644 --- a/lib/theme/views/color_preferences.dart +++ b/lib/theme/views/preferences/color_preferences.dart @@ -1,16 +1,12 @@ import 'package:animated_theme_switcher/animated_theme_switcher.dart'; -import 'package:async_tools/async_tools.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_translate/flutter_translate.dart'; -import '../../settings/settings.dart'; -import '../models/models.dart'; +import '../../../settings/settings.dart'; +import '../../models/models.dart'; +import '../views.dart'; -const String formFieldTheme = 'theme'; -const String _kSwitchTheme = 'switchTheme'; - -List> _getThemeDropdownItems() { +List> _getThemeDropdownItems() { const colorPrefs = ColorPreference.values; final colorNames = { ColorPreference.scarlet: translate('themes.scarlet'), @@ -34,27 +30,20 @@ List> _getThemeDropdownItems() { } Widget buildSettingsPageColorPreferences( - {required BuildContext context, - required void Function() onChanged, - required ThemeSwitcherState switcher}) { + {required BuildContext context, required ThemeSwitcherState switcher}) { final preferencesRepository = PreferencesRepository.instance; final themePreferences = preferencesRepository.value.themePreference; - return FormBuilderDropdown( - name: formFieldTheme, - decoration: - InputDecoration(label: Text(translate('settings_page.color_theme'))), - items: _getThemeDropdownItems(), - initialValue: themePreferences.colorPreference, - onChanged: (value) { - singleFuture(_kSwitchTheme, () async { - final newThemePrefs = themePreferences.copyWith( - colorPreference: value as ColorPreference); - final newPrefs = preferencesRepository.value - .copyWith(themePreference: newThemePrefs); - await preferencesRepository.set(newPrefs); - switcher.changeTheme(theme: newThemePrefs.themeData()); - onChanged(); - }); + return StyledDropdown( + items: _getThemeDropdownItems(), + value: themePreferences.colorPreference, + decoratorLabel: translate('settings_page.color_theme'), + onChanged: (value) async { + final newThemePrefs = themePreferences.copyWith(colorPreference: value); + final newPrefs = preferencesRepository.value + .copyWith(themePreference: newThemePrefs); + + await preferencesRepository.set(newPrefs); + switcher.changeTheme(theme: newThemePrefs.themeData()); }); } diff --git a/lib/theme/views/preferences/display_scale_preferences.dart b/lib/theme/views/preferences/display_scale_preferences.dart new file mode 100644 index 0000000..84bf97d --- /dev/null +++ b/lib/theme/views/preferences/display_scale_preferences.dart @@ -0,0 +1,109 @@ +import 'dart:math'; + +import 'package:animated_theme_switcher/animated_theme_switcher.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_translate/flutter_translate.dart'; + +import '../../../settings/settings.dart'; +import '../../models/models.dart'; +import '../views.dart'; + +const _scales = [ + 1 / (1 + 1 / 2), + 1 / (1 + 1 / 3), + 1 / (1 + 1 / 4), + 1, + 1 + (1 / 4), + 1 + (1 / 2), + 1 + (1 / 1), +]; +const _scaleNames = [ + '-3', + '-2', + '-1', + '0', + '1', + '2', + '3', +]; + +const _scaleNumMult = [ + 1 / (1 + 1 / 2), + 1 / (1 + 1 / 3), + 1 / (1 + 1 / 4), + 1, + 1 + 1 / 4, + 1 + 1 / 2, + 1 + 1 / 1, +]; + +int displayScaleToIndex(double displayScale) { + final idx = _scales.indexWhere((elem) => elem > displayScale); + final currentScaleIdx = idx == -1 ? _scales.length - 1 : max(0, idx - 1); + return currentScaleIdx; +} + +double indexToDisplayScale(int scaleIdx) { + final displayScale = + _scales[max(min(scaleIdx, _scales.length - 1), 0)].toDouble(); + return displayScale; +} + +String indexToDisplayScaleName(int scaleIdx) => + _scaleNames[max(min(scaleIdx, _scales.length - 1), 0)]; + +final maxDisplayScaleIndex = _scales.length - 1; + +Widget buildSettingsPageDisplayScalePreferences( + {required BuildContext context, required ThemeSwitcherState switcher}) { + final preferencesRepository = PreferencesRepository.instance; + final themePreferences = preferencesRepository.value.themePreference; + + final currentScaleIdx = displayScaleToIndex(themePreferences.displayScale); + final currentScaleName = indexToDisplayScaleName(currentScaleIdx); + + return StyledSlider( + value: currentScaleIdx.toDouble(), + label: currentScaleName, + decoratorLabel: translate('settings_page.display_scale'), + max: _scales.length - 1.toDouble(), + divisions: _scales.length - 1, + leftWidget: const Icon(Icons.text_decrease), + rightWidget: const Icon(Icons.text_increase), + onChanged: (value) async { + final scaleIdx = value.toInt(); + final displayScale = indexToDisplayScale(scaleIdx); + final newThemePrefs = + themePreferences.copyWith(displayScale: displayScale); + final newPrefs = preferencesRepository.value + .copyWith(themePreference: newThemePrefs); + + await preferencesRepository.set(newPrefs); + switcher.changeTheme(theme: newThemePrefs.themeData()); + }); +} + +extension DisplayScaledNum on num { + double scaled(BuildContext context) { + final prefs = context.watch().state.asData?.value ?? + PreferencesRepository.instance.value; + final currentScaleIdx = + displayScaleToIndex(prefs.themePreference.displayScale); + return this * _scaleNumMult[currentScaleIdx]; + } +} + +extension DisplayScaledEdgeInsets on EdgeInsets { + EdgeInsets scaled(BuildContext context) { + final prefs = context.watch().state.asData?.value ?? + PreferencesRepository.instance.value; + final currentScaleIdx = + displayScaleToIndex(prefs.themePreference.displayScale); + return EdgeInsets.fromLTRB( + left * _scaleNumMult[currentScaleIdx], + top * _scaleNumMult[currentScaleIdx], + right * _scaleNumMult[currentScaleIdx], + bottom * _scaleNumMult[currentScaleIdx]); + } +} diff --git a/lib/theme/views/preferences/preferences.dart b/lib/theme/views/preferences/preferences.dart new file mode 100644 index 0000000..ddac4c1 --- /dev/null +++ b/lib/theme/views/preferences/preferences.dart @@ -0,0 +1,4 @@ +export 'brightness_preferences.dart'; +export 'color_preferences.dart'; +export 'display_scale_preferences.dart'; +export 'wallpaper_preferences.dart'; diff --git a/lib/theme/views/preferences/wallpaper_preferences.dart b/lib/theme/views/preferences/wallpaper_preferences.dart new file mode 100644 index 0000000..050e294 --- /dev/null +++ b/lib/theme/views/preferences/wallpaper_preferences.dart @@ -0,0 +1,25 @@ +import 'package:animated_theme_switcher/animated_theme_switcher.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; + +import '../../../settings/settings.dart'; +import '../../models/models.dart'; +import '../views.dart'; + +Widget buildSettingsPageWallpaperPreferences( + {required BuildContext context, required ThemeSwitcherState switcher}) { + final preferencesRepository = PreferencesRepository.instance; + final themePreferences = preferencesRepository.value.themePreference; + + return StyledCheckbox( + value: themePreferences.enableWallpaper, + label: translate('settings_page.enable_wallpaper'), + onChanged: (value) async { + final newThemePrefs = themePreferences.copyWith(enableWallpaper: value); + final newPrefs = preferencesRepository.value + .copyWith(themePreference: newThemePrefs); + + await preferencesRepository.set(newPrefs); + switcher.changeTheme(theme: newThemePrefs.themeData()); + }); +} diff --git a/lib/theme/views/responsive.dart b/lib/theme/views/responsive.dart index 0182c9f..4ce42d8 100644 --- a/lib/theme/views/responsive.dart +++ b/lib/theme/views/responsive.dart @@ -3,6 +3,10 @@ import 'package:flutter/material.dart'; final isAndroid = !kIsWeb && defaultTargetPlatform == TargetPlatform.android; final isiOS = !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS; +final isMac = !kIsWeb && defaultTargetPlatform == TargetPlatform.macOS; +final isWindows = !kIsWeb && defaultTargetPlatform == TargetPlatform.windows; +final isLinux = !kIsWeb && defaultTargetPlatform == TargetPlatform.linux; + final isMobile = !kIsWeb && (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.android); diff --git a/lib/theme/views/styled_alert.dart b/lib/theme/views/styled_widgets/styled_alert.dart similarity index 99% rename from lib/theme/views/styled_alert.dart rename to lib/theme/views/styled_widgets/styled_alert.dart index 1215c84..4dec616 100644 --- a/lib/theme/views/styled_alert.dart +++ b/lib/theme/views/styled_widgets/styled_alert.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:rflutter_alert/rflutter_alert.dart'; -import '../theme.dart'; +import '../../theme.dart'; AlertStyle _alertStyle(BuildContext context) { final theme = Theme.of(context); @@ -186,6 +186,7 @@ Future showAlertWidgetModal( child: Text( translate('button.ok'), style: _buttonTextStyle(context), + softWrap: true, ), ) ], diff --git a/lib/theme/views/styled_widgets/styled_avatar.dart b/lib/theme/views/styled_widgets/styled_avatar.dart new file mode 100644 index 0000000..dde39f2 --- /dev/null +++ b/lib/theme/views/styled_widgets/styled_avatar.dart @@ -0,0 +1,77 @@ +import 'package:awesome_extensions/awesome_extensions.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +import '../../theme.dart'; + +class StyledAvatar extends StatelessWidget { + const StyledAvatar({ + required String name, + required double size, + bool enabled = true, + super.key, + ImageProvider? imageProvider, + }) : _name = name, + _size = size, + _imageProvider = imageProvider, + _enabled = enabled; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final scaleTheme = Theme.of(context).extension()!; + + final borderColor = scaleTheme.config.useVisualIndicators + ? scaleTheme.scheme.primaryScale.primaryText + : scaleTheme.scheme.primaryScale.subtleBorder; + final foregroundColor = !_enabled + ? scaleTheme.scheme.grayScale.primaryText + : scaleTheme.scheme.primaryScale.calloutText; + final backgroundColor = !_enabled + ? scaleTheme.scheme.grayScale.primary + : scaleTheme.scheme.primaryScale.calloutBackground; + final scaleConfig = scaleTheme.config; + final textStyle = theme.textTheme.titleLarge!.copyWith(fontSize: _size / 2); + + final abbrev = _name.split(' ').map((s) => s.isEmpty ? '' : s[0]).join(); + late final String shortname; + if (abbrev.length >= 3) { + shortname = abbrev[0] + abbrev[1] + abbrev[abbrev.length - 1]; + } else { + shortname = abbrev; + } + + return Container( + height: _size, + width: _size, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: !scaleConfig.useVisualIndicators + ? null + : Border.all( + color: borderColor, + width: 1 * (_size ~/ 16 + 1), + strokeAlign: BorderSide.strokeAlignOutside)), + child: AvatarImage( + backgroundImage: _imageProvider, + backgroundColor: scaleConfig.useVisualIndicators + ? foregroundColor + : backgroundColor, + child: Text( + shortname.isNotEmpty ? shortname : '?', + softWrap: false, + textScaler: MediaQuery.of(context).textScaler, + style: textStyle.copyWith( + color: scaleConfig.useVisualIndicators + ? backgroundColor + : foregroundColor, + ), + ).paddingAll(4.scaled(context)).fit(fit: BoxFit.scaleDown))); + } + + //////////////////////////////////////////////////////////////////////////// + final String _name; + final double _size; + final ImageProvider? _imageProvider; + final bool _enabled; +} diff --git a/lib/theme/views/option_box.dart b/lib/theme/views/styled_widgets/styled_button_box.dart similarity index 75% rename from lib/theme/views/option_box.dart rename to lib/theme/views/styled_widgets/styled_button_box.dart index 06a3293..811e01c 100644 --- a/lib/theme/views/option_box.dart +++ b/lib/theme/views/styled_widgets/styled_button_box.dart @@ -1,10 +1,10 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/material.dart'; -import '../theme.dart'; +import '../../theme.dart'; -class OptionBox extends StatelessWidget { - const OptionBox( +class StyledButtonBox extends StatelessWidget { + const StyledButtonBox( {required String instructions, required IconData buttonIcon, required String buttonText, @@ -41,12 +41,15 @@ class OptionBox extends StatelessWidget { onPressed: _onClick, child: Row(mainAxisSize: MainAxisSize.min, children: [ Icon(_buttonIcon, - size: 24, color: scale.primaryScale.appText) - .paddingLTRB(0, 8, 8, 8), + size: 24.scaled(context), + color: scale.primaryScale.appText) + .paddingLTRB(0, 8.scaled(context), + 8.scaled(context), 8.scaled(context)), Text(textAlign: TextAlign.center, _buttonText) - ])).paddingLTRB(0, 12, 0, 0).toCenter() - ]).paddingAll(12)) - .paddingLTRB(24, 0, 24, 12); + ])).paddingLTRB(0, 12.scaled(context), 0, 0).toCenter() + ]).paddingAll(12.scaled(context))) + .paddingLTRB( + 24.scaled(context), 0, 24.scaled(context), 12.scaled(context)); } final String _instructions; diff --git a/lib/theme/views/styled_widgets/styled_checkbox.dart b/lib/theme/views/styled_widgets/styled_checkbox.dart new file mode 100644 index 0000000..7eb3649 --- /dev/null +++ b/lib/theme/views/styled_widgets/styled_checkbox.dart @@ -0,0 +1,63 @@ +import 'package:async_tools/async_tools.dart'; +import 'package:awesome_extensions/awesome_extensions_flutter.dart'; +import 'package:flutter/material.dart'; + +import '../views.dart'; + +const _kStyledCheckboxChanged = 'kStyledCheckboxChanged'; + +class StyledCheckbox extends StatelessWidget { + const StyledCheckbox( + {required bool value, + required String label, + String? decoratorLabel, + Future Function(bool)? onChanged, + super.key}) + : _value = value, + _onChanged = onChanged, + _label = label, + _decoratorLabel = decoratorLabel; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final textTheme = theme.textTheme; + + var textStyle = textTheme.labelLarge!; + if (_onChanged == null) { + textStyle = textStyle.copyWith(color: textStyle.color!.withAlpha(127)); + } + + Widget ctrl = Row(children: [ + Transform.scale( + scale: 1.scaled(context), + child: Checkbox( + value: _value, + onChanged: _onChanged == null + ? null + : (value) { + if (value == null) { + return; + } + singleFuture((this, _kStyledCheckboxChanged), () async { + await _onChanged(value); + }); + })), + Text(_label, style: textStyle).paddingAll(4.scaled(context)), + ]); + + if (_decoratorLabel != null) { + ctrl = ctrl + .paddingLTRB(4.scaled(context), 4.scaled(context), 4.scaled(context), + 4.scaled(context)) + .decoratorLabel(context, _decoratorLabel); + } + + return ctrl; + } + + final String _label; + final String? _decoratorLabel; + final Future Function(bool)? _onChanged; + final bool _value; +} diff --git a/lib/theme/views/styled_dialog.dart b/lib/theme/views/styled_widgets/styled_dialog.dart similarity index 98% rename from lib/theme/views/styled_dialog.dart rename to lib/theme/views/styled_widgets/styled_dialog.dart index 75a0f6b..4106f1d 100644 --- a/lib/theme/views/styled_dialog.dart +++ b/lib/theme/views/styled_widgets/styled_dialog.dart @@ -2,7 +2,7 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import '../theme.dart'; +import '../../theme.dart'; class StyledDialog extends StatelessWidget { const StyledDialog({required this.title, required this.child, super.key}); diff --git a/lib/theme/views/styled_widgets/styled_dropdown.dart b/lib/theme/views/styled_widgets/styled_dropdown.dart new file mode 100644 index 0000000..3af6424 --- /dev/null +++ b/lib/theme/views/styled_widgets/styled_dropdown.dart @@ -0,0 +1,59 @@ +import 'package:async_tools/async_tools.dart'; +import 'package:awesome_extensions/awesome_extensions_flutter.dart'; +import 'package:flutter/material.dart'; + +import '../../models/models.dart'; +import '../views.dart'; + +const _kStyledDropdownChanged = 'kStyledDropdownChanged'; + +class StyledDropdown extends StatelessWidget { + const StyledDropdown( + {required List> items, + required T value, + String? decoratorLabel, + Future Function(T)? onChanged, + super.key}) + : _items = items, + _onChanged = onChanged, + _decoratorLabel = decoratorLabel, + _value = value; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final scheme = theme.extension()!; + + Widget ctrl = DropdownButton( + isExpanded: true, + padding: const EdgeInsets.fromLTRB(4, 0, 4, 0).scaled(context), + focusColor: theme.focusColor, + dropdownColor: scheme.primaryScale.elementBackground, + iconEnabledColor: scheme.primaryScale.appText, + iconDisabledColor: scheme.primaryScale.appText.withAlpha(127), + items: _items, + value: _value, + style: theme.textTheme.labelLarge, + onChanged: _onChanged == null + ? null + : (value) { + if (value == null) { + return; + } + singleFuture((this, _kStyledDropdownChanged), () async { + await _onChanged(value); + }); + }); + if (_decoratorLabel != null) { + ctrl = ctrl + .paddingLTRB(0, 4.scaled(context), 0, 4.scaled(context)) + .decoratorLabel(context, _decoratorLabel); + } + return ctrl; + } + + final List> _items; + final String? _decoratorLabel; + final Future Function(T)? _onChanged; + final T _value; +} diff --git a/lib/theme/views/styled_scaffold.dart b/lib/theme/views/styled_widgets/styled_scaffold.dart similarity index 97% rename from lib/theme/views/styled_scaffold.dart rename to lib/theme/views/styled_widgets/styled_scaffold.dart index 4fc803f..82f27f5 100644 --- a/lib/theme/views/styled_scaffold.dart +++ b/lib/theme/views/styled_widgets/styled_scaffold.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../theme.dart'; +import '../../theme.dart'; class StyledScaffold extends StatelessWidget { const StyledScaffold({required this.appBar, required this.body, super.key}); diff --git a/lib/theme/views/slider_tile.dart b/lib/theme/views/styled_widgets/styled_slide_tile.dart similarity index 75% rename from lib/theme/views/slider_tile.dart rename to lib/theme/views/styled_widgets/styled_slide_tile.dart index 8e5f178..43b3fd8 100644 --- a/lib/theme/views/slider_tile.dart +++ b/lib/theme/views/styled_widgets/styled_slide_tile.dart @@ -2,10 +2,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; -import '../theme.dart'; +import '../../theme.dart'; -class SliderTileAction { - const SliderTileAction({ +class SlideTileAction { + const SlideTileAction({ required this.actionScale, required this.onPressed, this.key, @@ -20,8 +20,8 @@ class SliderTileAction { final SlidableActionCallback? onPressed; } -class SliderTile extends StatelessWidget { - const SliderTile( +class StyledSlideTile extends StatelessWidget { + const StyledSlideTile( {required this.disabled, required this.selected, required this.tileScale, @@ -38,8 +38,8 @@ class SliderTile extends StatelessWidget { final bool disabled; final bool selected; final ScaleKind tileScale; - final List endActions; - final List startActions; + final List endActions; + final List startActions; final GestureTapCallback? onTap; final GestureTapCallback? onDoubleTap; final Widget? leading; @@ -54,8 +54,8 @@ class SliderTile extends StatelessWidget { ..add(DiagnosticsProperty('disabled', disabled)) ..add(DiagnosticsProperty('selected', selected)) ..add(DiagnosticsProperty('tileScale', tileScale)) - ..add(IterableProperty('endActions', endActions)) - ..add(IterableProperty('startActions', startActions)) + ..add(IterableProperty('endActions', endActions)) + ..add(IterableProperty('startActions', startActions)) ..add(ObjectFlagProperty.has('onTap', onTap)) ..add(DiagnosticsProperty('leading', leading)) ..add(StringProperty('title', title)) @@ -66,7 +66,6 @@ class SliderTile extends StatelessWidget { } @override - // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { final theme = Theme.of(context); final scaleTheme = theme.extension()!; @@ -91,12 +90,13 @@ class SliderTile extends StatelessWidget { selected: true, scaleKind: a.actionScale); return SlidableAction( - onPressed: disabled ? null : a.onPressed, - backgroundColor: scaleActionTheme.backgroundColor, - foregroundColor: scaleActionTheme.textColor, - icon: subtitle.isEmpty ? a.icon : null, - label: a.label, - padding: const EdgeInsets.all(2)); + onPressed: disabled ? null : a.onPressed, + backgroundColor: scaleActionTheme.backgroundColor, + foregroundColor: scaleActionTheme.textColor, + icon: subtitle.isEmpty ? a.icon : null, + label: a.label, + padding: const EdgeInsets.all(2).scaled(context), + ); }).toList()), startActionPane: startActions.isEmpty ? null @@ -109,17 +109,18 @@ class SliderTile extends StatelessWidget { scaleKind: a.actionScale); return SlidableAction( - onPressed: disabled ? null : a.onPressed, - backgroundColor: scaleActionTheme.backgroundColor, - foregroundColor: scaleActionTheme.textColor, - icon: subtitle.isEmpty ? a.icon : null, - label: a.label, - padding: const EdgeInsets.all(2)); + onPressed: disabled ? null : a.onPressed, + backgroundColor: scaleActionTheme.backgroundColor, + foregroundColor: scaleActionTheme.textColor, + icon: subtitle.isEmpty ? a.icon : null, + label: a.label, + padding: const EdgeInsets.all(2).scaled(context), + ); }).toList()), child: Padding( padding: scaleTheme.config.useVisualIndicators ? EdgeInsets.zero - : const EdgeInsets.fromLTRB(0, 2, 0, 2), + : const EdgeInsets.fromLTRB(0, 2, 0, 2).scaled(context), child: GestureDetector( onDoubleTap: onDoubleTap, child: ListTile( @@ -131,7 +132,8 @@ class SliderTile extends StatelessWidget { softWrap: false, ), subtitle: subtitle.isNotEmpty ? Text(subtitle) : null, - minTileHeight: 52, + contentPadding: const EdgeInsets.fromLTRB(8, 4, 8, 4) + .scaled(context), iconColor: scaleTileTheme.textColor, textColor: scaleTileTheme.textColor, leading: diff --git a/lib/theme/views/styled_widgets/styled_slider.dart b/lib/theme/views/styled_widgets/styled_slider.dart new file mode 100644 index 0000000..a0c7259 --- /dev/null +++ b/lib/theme/views/styled_widgets/styled_slider.dart @@ -0,0 +1,79 @@ +import 'package:async_tools/async_tools.dart'; +import 'package:awesome_extensions/awesome_extensions_flutter.dart'; +import 'package:flutter/material.dart'; + +import '../../models/models.dart'; +import '../views.dart'; + +const _kStyledSliderChanged = 'kStyledSliderChanged'; + +class StyledSlider extends StatelessWidget { + const StyledSlider( + {required double value, + String? label, + String? decoratorLabel, + Future Function(double)? onChanged, + Widget? leftWidget, + Widget? rightWidget, + double min = 0, + double max = 1, + int? divisions, + super.key}) + : _value = value, + _onChanged = onChanged, + _leftWidget = leftWidget, + _rightWidget = rightWidget, + _min = min, + _max = max, + _divisions = divisions, + _label = label, + _decoratorLabel = decoratorLabel; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final scale = theme.extension()!; + + Widget ctrl = Row(children: [ + if (_leftWidget != null) _leftWidget, + Slider( + activeColor: scale.scheme.primaryScale.border, + inactiveColor: scale.scheme.primaryScale.subtleBorder, + secondaryActiveColor: scale.scheme.secondaryScale.border, + value: _value, + min: _min, + max: _max, + divisions: _divisions, + label: _label, + thumbColor: scale.scheme.primaryScale.appText, + overlayColor: + WidgetStateColor.resolveWith((ws) => theme.focusColor), + onChanged: _onChanged == null + ? null + : (value) { + singleFuture((this, _kStyledSliderChanged), () async { + await _onChanged(value); + }); + }) + .expanded(), + if (_rightWidget != null) _rightWidget, + ]); + if (_decoratorLabel != null) { + ctrl = ctrl + .paddingLTRB(4.scaled(context), 4.scaled(context), 4.scaled(context), + 4.scaled(context)) + .decoratorLabel(context, _decoratorLabel); + } + return ctrl; + } + + final String? _label; + final String? _decoratorLabel; + final Future Function(double)? _onChanged; + final double _value; + final Widget? _leftWidget; + final Widget? _rightWidget; + final double _min; + final double _max; + final int? _divisions; +} diff --git a/lib/theme/views/styled_widgets/styled_widgets.dart b/lib/theme/views/styled_widgets/styled_widgets.dart new file mode 100644 index 0000000..ae45d59 --- /dev/null +++ b/lib/theme/views/styled_widgets/styled_widgets.dart @@ -0,0 +1,8 @@ +export 'styled_alert.dart'; +export 'styled_avatar.dart'; +export 'styled_checkbox.dart'; +export 'styled_dialog.dart'; +export 'styled_dropdown.dart'; +export 'styled_scaffold.dart'; +export 'styled_slide_tile.dart'; +export 'styled_slider.dart'; diff --git a/lib/theme/views/views.dart b/lib/theme/views/views.dart index 88f4a4a..1144440 100644 --- a/lib/theme/views/views.dart +++ b/lib/theme/views/views.dart @@ -1,16 +1,10 @@ -export 'avatar_widget.dart'; -export 'brightness_preferences.dart'; -export 'color_preferences.dart'; export 'enter_password.dart'; export 'enter_pin.dart'; -export 'option_box.dart'; export 'pop_control.dart'; +export 'preferences/preferences.dart'; export 'recovery_key_widget.dart'; export 'responsive.dart'; export 'scanner_error_widget.dart'; -export 'slider_tile.dart'; -export 'styled_alert.dart'; -export 'styled_dialog.dart'; -export 'styled_scaffold.dart'; -export 'wallpaper_preferences.dart'; +export 'styled_widgets/styled_button_box.dart'; +export 'styled_widgets/styled_widgets.dart'; export 'widget_helpers.dart'; diff --git a/lib/theme/views/wallpaper_preferences.dart b/lib/theme/views/wallpaper_preferences.dart deleted file mode 100644 index f9ae94c..0000000 --- a/lib/theme/views/wallpaper_preferences.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:animated_theme_switcher/animated_theme_switcher.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_form_builder/flutter_form_builder.dart'; -import 'package:flutter_translate/flutter_translate.dart'; - -import '../../settings/settings.dart'; -import '../models/models.dart'; - -const String formFieldEnableWallpaper = 'enable_wallpaper'; - -Widget buildSettingsPageWallpaperPreferences( - {required BuildContext context, - required void Function() onChanged, - required ThemeSwitcherState switcher}) { - final preferencesRepository = PreferencesRepository.instance; - final themePreferences = preferencesRepository.value.themePreference; - final theme = Theme.of(context); - final textTheme = theme.textTheme; - - return FormBuilderCheckbox( - name: formFieldEnableWallpaper, - title: Text(translate('settings_page.enable_wallpaper'), - style: textTheme.labelMedium), - initialValue: themePreferences.enableWallpaper, - onChanged: (value) async { - if (value != null) { - final newThemePrefs = - themePreferences.copyWith(enableWallpaper: value); - final newPrefs = preferencesRepository.value - .copyWith(themePreference: newThemePrefs); - - await preferencesRepository.set(newPrefs); - switcher.changeTheme(theme: newThemePrefs.themeData()); - onChanged(); - } - }); -} diff --git a/lib/veilid_processor/views/developer.dart b/lib/veilid_processor/views/developer.dart index d749a9c..c03b6bf 100644 --- a/lib/veilid_processor/views/developer.dart +++ b/lib/veilid_processor/views/developer.dart @@ -222,13 +222,16 @@ class _DeveloperPageState extends State { return Scaffold( backgroundColor: scale.primaryScale.border, appBar: DefaultAppBar( + context: context, title: Text(translate('developer.title')), leading: IconButton( + iconSize: 24.scaled(context), icon: Icon(Icons.arrow_back, color: scale.primaryScale.borderText), onPressed: () => GoRouterHelper(context).pop(), ), actions: [ IconButton( + iconSize: 24.scaled(context), icon: const Icon(Icons.copy), color: scale.primaryScale.borderText, disabledColor: scale.primaryScale.borderText.withAlpha(0x3F), @@ -238,6 +241,7 @@ class _DeveloperPageState extends State { await copySelection(context); }), IconButton( + iconSize: 24.scaled(context), icon: const Icon(Icons.copy_all), color: scale.primaryScale.borderText, disabledColor: scale.primaryScale.borderText.withAlpha(0x3F), @@ -245,6 +249,7 @@ class _DeveloperPageState extends State { await copyAll(context); }), IconButton( + iconSize: 24.scaled(context), icon: const Icon(Icons.clear_all), color: scale.primaryScale.borderText, disabledColor: scale.primaryScale.borderText.withAlpha(0x3F), @@ -259,7 +264,7 @@ class _DeveloperPageState extends State { } }), SizedBox.fromSize( - size: const Size(140, 48), + size: Size(140.scaled(context), 48), child: CustomDropdown( items: _logLevelDropdownItems, initialItem: _logLevelDropdownItems @@ -300,6 +305,7 @@ class _DeveloperPageState extends State { Image.asset('assets/images/ellet.png'), TerminalView(globalDebugTerminal, textStyle: kDefaultTerminalStyle, + textScaler: TextScaler.noScaling, controller: _terminalController, keyboardType: TextInputType.none, backgroundOpacity: _showEllet ? 0.75 : 1.0, diff --git a/lib/veilid_processor/views/signal_strength_meter.dart b/lib/veilid_processor/views/signal_strength_meter.dart index 5385bb1..44e3cb6 100644 --- a/lib/veilid_processor/views/signal_strength_meter.dart +++ b/lib/veilid_processor/views/signal_strength_meter.dart @@ -19,7 +19,7 @@ class SignalStrengthMeterWidget extends StatelessWidget { final theme = Theme.of(context); final scale = theme.extension()!; - const iconSize = 16.0; + final iconSize = 16.0.scaled(context); return BlocBuilder>(builder: (context, state) { From 68e8d7fd390b4b0ed32ce886250a67a072e64f42 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 27 May 2025 16:43:38 -0400 Subject: [PATCH 74/93] More UI Cleanup --- lib/account_manager/views/profile_widget.dart | 5 +- lib/chat/views/chat_component_widget.dart | 4 +- lib/chat_list/views/chat_list_widget.dart | 50 +-- lib/contacts/views/contacts_browser.dart | 104 +++--- lib/layout/default_app_bar.dart | 2 +- lib/layout/home/home_account_ready.dart | 8 +- lib/layout/home/home_screen.dart | 57 ++-- .../views/notifications_preferences.dart | 303 ++++++++---------- lib/settings/settings_page.dart | 5 +- lib/theme/models/scale_theme/scale_theme.dart | 6 + .../display_scale_preferences.dart | 18 ++ .../views/styled_widgets/styled_checkbox.dart | 6 +- .../views/styled_widgets/styled_scaffold.dart | 2 +- .../styled_widgets/styled_slide_tile.dart | 2 +- lib/theme/views/widget_helpers.dart | 1 + pubspec.lock | 8 - pubspec.yaml | 1 - 17 files changed, 281 insertions(+), 301 deletions(-) diff --git a/lib/account_manager/views/profile_widget.dart b/lib/account_manager/views/profile_widget.dart index c026856..af7cf30 100644 --- a/lib/account_manager/views/profile_widget.dart +++ b/lib/account_manager/views/profile_widget.dart @@ -20,7 +20,6 @@ class ProfileWidget extends StatelessWidget { // @override - // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { final theme = Theme.of(context); final scale = theme.extension()!; @@ -54,7 +53,7 @@ class ProfileWidget extends StatelessWidget { ? scale.primaryScale.border : scale.primaryScale.borderText), textAlign: TextAlign.left, - ).paddingAll(8), + ).paddingAll(8.scaled(context)), if (_profile.pronouns.isNotEmpty && _showPronouns) Text('(${_profile.pronouns})', textAlign: TextAlign.right, @@ -62,7 +61,7 @@ class ProfileWidget extends StatelessWidget { color: scaleConfig.preferBorders ? scale.primaryScale.border : scale.primaryScale.primary)) - .paddingAll(8), + .paddingAll(8.scaled(context)), const Spacer() ]), ); diff --git a/lib/chat/views/chat_component_widget.dart b/lib/chat/views/chat_component_widget.dart index 8106a48..8cb4edc 100644 --- a/lib/chat/views/chat_component_widget.dart +++ b/lib/chat/views/chat_component_widget.dart @@ -161,7 +161,7 @@ class _ChatComponentWidgetState extends State { return Column( children: [ Container( - height: 48, + height: 40.scaledNoShrink(context), decoration: BoxDecoration( color: scale.border, ), @@ -177,7 +177,7 @@ class _ChatComponentWidgetState extends State { )), const Spacer(), IconButton( - iconSize: 24, + iconSize: 24.scaledNoShrink(context), icon: Icon(Icons.close, color: scale.borderText), onPressed: widget._onClose) .paddingLTRB(0, 0, 8, 0) diff --git a/lib/chat_list/views/chat_list_widget.dart b/lib/chat_list/views/chat_list_widget.dart index cce416c..04720e8 100644 --- a/lib/chat_list/views/chat_list_widget.dart +++ b/lib/chat_list/views/chat_list_widget.dart @@ -63,30 +63,34 @@ class ChatListWidget extends StatelessWidget { title: translate('chat_list.chats'), child: (chatList.isEmpty) ? const SizedBox.expand(child: EmptyChatListWidget()) - : SearchableList( - initialList: chatList.map((x) => x.value).toList(), - itemBuilder: (c) { - switch (c.whichKind()) { - case proto.Chat_Kind.direct: - return _itemBuilderDirect( - c.direct, - contactMap, - ); - case proto.Chat_Kind.group: - return const Text( - 'group chats not yet supported!'); - case proto.Chat_Kind.notSet: - throw StateError('unknown chat kind'); - } + : TapRegion( + onTapOutside: (_) { + FocusScope.of(context).unfocus(); }, - filter: (value) => - _itemFilter(contactMap, chatList, value), - searchFieldPadding: - const EdgeInsets.fromLTRB(0, 0, 0, 4), - inputDecoration: InputDecoration( - labelText: translate('chat_list.search'), - ), - ).paddingAll(8), + child: SearchableList( + initialList: chatList.map((x) => x.value).toList(), + itemBuilder: (c) { + switch (c.whichKind()) { + case proto.Chat_Kind.direct: + return _itemBuilderDirect( + c.direct, + contactMap, + ); + case proto.Chat_Kind.group: + return const Text( + 'group chats not yet supported!'); + case proto.Chat_Kind.notSet: + throw StateError('unknown chat kind'); + } + }, + filter: (value) => + _itemFilter(contactMap, chatList, value), + searchFieldPadding: + const EdgeInsets.fromLTRB(0, 0, 0, 4), + inputDecoration: InputDecoration( + labelText: translate('chat_list.search'), + ), + )).paddingAll(8), ))) .paddingLTRB(8, 0, 8, 8); }); diff --git a/lib/contacts/views/contacts_browser.dart b/lib/contacts/views/contacts_browser.dart index 0504a7a..10b207f 100644 --- a/lib/contacts/views/contacts_browser.dart +++ b/lib/contacts/views/contacts_browser.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:searchable_listview/searchable_listview.dart'; -import 'package:star_menu/star_menu.dart'; import 'package:veilid_support/veilid_support.dart'; import '../../contact_invitation/contact_invitation.dart'; @@ -90,75 +89,62 @@ class _ContactsBrowserState extends State final menuBorderColor = scaleScheme.primaryScale.hoverBorder; - final menuParams = StarMenuParameters( - shape: MenuShape.linear, - centerOffset: Offset(0, 64.scaled(context)), - boundaryBackground: BoundaryBackground( - color: menuBackgroundColor, - decoration: ShapeDecoration( - color: menuBackgroundColor, - shape: RoundedRectangleBorder( - side: scaleConfig.useVisualIndicators - ? BorderSide( - width: 2, color: menuBorderColor, strokeAlign: 0) - : BorderSide.none, - borderRadius: BorderRadius.circular( - 8 * scaleConfig.borderRadiusScale))))); - - ElevatedButton makeMenuButton( + PopupMenuEntry makeMenuButton( {required IconData iconData, required String text, - required void Function()? onPressed}) => - ElevatedButton.icon( - onPressed: onPressed, - icon: Icon( - iconData, - size: 32.scaled(context), - ).paddingSTEB(0, 8.scaled(context), 0, 8.scaled(context)), - label: Text( - text, - textScaler: MediaQuery.of(context).textScaler, - maxLines: 2, - textAlign: TextAlign.center, - ).paddingSTEB(0, 8.scaled(context), 0, 8.scaled(context))); + void Function()? onTap}) => + PopupMenuItem( + onTap: onTap, + child: DecoratedBox( + decoration: ShapeDecoration( + color: menuBackgroundColor, + shape: RoundedRectangleBorder( + side: scaleConfig.useVisualIndicators + ? BorderSide( + width: 2, + color: menuBorderColor, + strokeAlign: 0) + : BorderSide.none, + borderRadius: BorderRadius.circular( + 8 * scaleConfig.borderRadiusScale))), + child: Row(spacing: 4.scaled(context), children: [ + Icon(iconData, size: 32.scaled(context)), + Text( + text, + textScaler: MediaQuery.of(context).textScaler, + maxLines: 2, + textAlign: TextAlign.center, + ) + ]).paddingAll(4.scaled(context))) + .paddingLTRB(0, 2.scaled(context), 0, 2.scaled(context))); final inviteMenuItems = [ makeMenuButton( - iconData: Icons.paste, - text: translate('add_contact_sheet.paste_invite'), - onPressed: () async { - _invitationMenuController.closeMenu!(); - await PasteInvitationDialog.show(context); + iconData: Icons.contact_page, + text: translate('add_contact_sheet.create_invite'), + onTap: () async { + await CreateInvitationDialog.show(context); }), makeMenuButton( iconData: Icons.qr_code_scanner, text: translate('add_contact_sheet.scan_invite'), - onPressed: () async { - _invitationMenuController.closeMenu!(); + onTap: () async { await ScanInvitationDialog.show(context); - }).paddingLTRB(0, 0, 0, 8.scaled(context)), + }), makeMenuButton( - iconData: Icons.contact_page, - text: translate('add_contact_sheet.create_invite'), - onPressed: () async { - _invitationMenuController.closeMenu!(); - await CreateInvitationDialog.show(context); - }).paddingLTRB(0, 0, 0, 8.scaled(context)), + iconData: Icons.paste, + text: translate('add_contact_sheet.paste_invite'), + onTap: () async { + await PasteInvitationDialog.show(context); + }), ]; - return StarMenu( - items: inviteMenuItems, - onItemTapped: (_, controller) { - controller.closeMenu!(); - }, - controller: _invitationMenuController, - params: menuParams, - child: IconButton( - onPressed: () {}, - iconSize: 24.scaled(context), - icon: Icon(Icons.person_add, color: menuIconColor), - tooltip: translate('add_contact_sheet.add_contact')), - ); + return PopupMenuButton( + itemBuilder: (_) => inviteMenuItems, + menuPadding: const EdgeInsets.symmetric(vertical: 4).scaled(context), + tooltip: translate('add_contact_sheet.add_contact'), + child: Icon( + size: 32.scaled(context), Icons.person_add, color: menuIconColor)); } @override @@ -253,12 +239,11 @@ class _ContactsBrowserState extends State text: translate('contact_list.loading_contacts')) : const EmptyContactListWidget(), defaultSuffixIconColor: scale.primaryScale.border, - closeKeyboardWhenScrolling: true, searchFieldEnabled: contactList != null, inputDecoration: InputDecoration(labelText: translate('contact_list.search')), secondaryWidget: buildInvitationButton(context) - .paddingLTRB(4.scaled(context), 0, 0, 0)) + .paddingLTRB(8.scaled(context), 0, 0, 0)) .expanded() ]); } @@ -276,5 +261,4 @@ class _ContactsBrowserState extends State } //////////////////////////////////////////////////////////////////////////// - final _invitationMenuController = StarMenuController(); } diff --git a/lib/layout/default_app_bar.dart b/lib/layout/default_app_bar.dart index 7742dad..d13fec9 100644 --- a/lib/layout/default_app_bar.dart +++ b/lib/layout/default_app_bar.dart @@ -13,7 +13,7 @@ class DefaultAppBar extends AppBar { Widget? leading, super.actions}) : super( - toolbarHeight: 40.scaled(context), + toolbarHeight: 48.scaled(context), leadingWidth: 40.scaled(context), leading: leading ?? Container( diff --git a/lib/layout/home/home_account_ready.dart b/lib/layout/home/home_account_ready.dart index 5ae1180..50a43c8 100644 --- a/lib/layout/home/home_account_ready.dart +++ b/lib/layout/home/home_account_ready.dart @@ -31,9 +31,9 @@ class _HomeAccountReadyState extends State { return AspectRatio( aspectRatio: 1, child: IconButton( - icon: const Icon( + icon: Icon( + size: 32.scaled(context), Icons.menu, - applyTextScaling: true, ), color: scaleConfig.preferBorders ? scale.primaryScale.border @@ -70,9 +70,9 @@ class _HomeAccountReadyState extends State { return AspectRatio( aspectRatio: 1, child: IconButton( - icon: const Icon( + icon: Icon( + size: 32.scaled(context), Icons.contacts, - applyTextScaling: true, ), color: scaleConfig.preferBorders ? scale.primaryScale.border diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index e774790..c9f1ecb 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -5,7 +5,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart'; -import 'package:keyboard_avoider/keyboard_avoider.dart'; import 'package:provider/provider.dart'; import 'package:transitioned_indexed_stack/transitioned_indexed_stack.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -207,36 +206,34 @@ class HomeScreenState extends State .indexWhere((x) => x.superIdentity.recordKey == activeLocalAccount); final canClose = activeIndex != -1; - final drawer = ZoomDrawer( - controller: _zoomDrawerController, - menuScreen: Builder(builder: (context) { - final zoomDrawer = ZoomDrawer.of(context); - zoomDrawer!.stateNotifier.addListener(() { - if (zoomDrawer.isOpen()) { - FocusManager.instance.primaryFocus?.unfocus(); - } - }); - return const DrawerMenu(); - }), - mainScreen: Provider.value( - value: _zoomDrawerController, - child: Builder(builder: _buildAccountPageView)), - borderRadius: 0, - angle: 0, - openCurve: Curves.fastEaseInToSlowEaseOut, - closeCurve: Curves.fastEaseInToSlowEaseOut, - menuScreenTapClose: canClose, - mainScreenTapClose: canClose, - disableDragGesture: !canClose, - mainScreenScale: .25, - slideWidth: min(360, MediaQuery.of(context).size.width * 0.9), - ); + final drawer = Scaffold( + backgroundColor: Colors.transparent, + body: ZoomDrawer( + controller: _zoomDrawerController, + menuScreen: Builder(builder: (context) { + final zoomDrawer = ZoomDrawer.of(context); + zoomDrawer!.stateNotifier.addListener(() { + if (zoomDrawer.isOpen()) { + FocusManager.instance.primaryFocus?.unfocus(); + } + }); + return const DrawerMenu(); + }), + mainScreen: Provider.value( + value: _zoomDrawerController, + child: Builder(builder: _buildAccountPageView)), + borderRadius: 0, + angle: 0, + openCurve: Curves.fastEaseInToSlowEaseOut, + closeCurve: Curves.fastEaseInToSlowEaseOut, + menuScreenTapClose: canClose, + mainScreenTapClose: canClose, + disableDragGesture: !canClose, + mainScreenScale: .25, + slideWidth: min(360, MediaQuery.of(context).size.width * 0.9), + )); - final drawerWithAvoider = - isWeb ? drawer : KeyboardAvoider(curve: Curves.ease, child: drawer); - - return DefaultTextStyle( - style: theme.textTheme.bodySmall!, child: drawerWithAvoider); + return DefaultTextStyle(style: theme.textTheme.bodySmall!, child: drawer); } //////////////////////////////////////////////////////////////////////////// diff --git a/lib/notifications/views/notifications_preferences.dart b/lib/notifications/views/notifications_preferences.dart index 8918fa9..ba06699 100644 --- a/lib/notifications/views/notifications_preferences.dart +++ b/lib/notifications/views/notifications_preferences.dart @@ -69,7 +69,7 @@ Widget buildSettingsPageNotificationPreferences( softWrap: false, style: textTheme.labelMedium, textAlign: TextAlign.center, - ))); + ).fit(fit: BoxFit.scaleDown))); } return out; } @@ -108,170 +108,147 @@ Widget buildSettingsPageNotificationPreferences( return out; } - return InputDecorator( - decoration: InputDecoration( - labelText: translate('settings_page.notifications'), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8 * scaleConfig.borderRadiusScale), - borderSide: BorderSide(width: 2, color: scale.primaryScale.border), - ), - ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - // Display Beta Warning - StyledCheckbox( - label: translate('settings_page.display_beta_warning'), - value: notificationsPreference.displayBetaWarning, - onChanged: (value) async { - final newNotificationsPreference = - notificationsPreference.copyWith(displayBetaWarning: value); - - await updatePreferences(newNotificationsPreference); - }), - // Enable Badge - StyledCheckbox( - label: translate('settings_page.enable_badge'), - value: notificationsPreference.enableBadge, - onChanged: (value) async { - final newNotificationsPreference = - notificationsPreference.copyWith(enableBadge: value); - await updatePreferences(newNotificationsPreference); - }), - // Enable Notifications - StyledCheckbox( - label: translate('settings_page.enable_notifications'), - value: notificationsPreference.enableNotifications, - onChanged: (value) async { - final newNotificationsPreference = - notificationsPreference.copyWith(enableNotifications: value); - await updatePreferences(newNotificationsPreference); - }), - StyledDropdown( - items: messageNotificationContentItems(), - value: notificationsPreference.messageNotificationContent, - decoratorLabel: translate('settings_page.message_notification_content'), - onChanged: !notificationsPreference.enableNotifications - ? null - : (value) async { - final newNotificationsPreference = notificationsPreference - .copyWith(messageNotificationContent: value); - await updatePreferences(newNotificationsPreference); - }, - ).paddingLTRB(0, 4.scaled(context), 0, 4.scaled(context)), - - // Notifications - Table( - defaultVerticalAlignment: TableCellVerticalAlignment.middle, + // Invitation accepted + Widget notificationSettingsItem( + {required String title, + required bool notificationsEnabled, + NotificationMode? deliveryValue, + SoundEffect? soundValue, + Future Function(NotificationMode)? onNotificationModeChanged, + Future Function(SoundEffect)? onSoundChanged}) => + Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.scaled(context), children: [ - TableRow(children: [ - Text(translate('settings_page.event'), - textAlign: TextAlign.center, - style: textTheme.titleMedium!.copyWith( - color: scale.primaryScale.border, - decorationColor: scale.primaryScale.border, - decoration: TextDecoration.underline)) - .paddingAll(8.scaled(context)), - Text(translate('settings_page.delivery'), - textAlign: TextAlign.center, - style: textTheme.titleMedium!.copyWith( - color: scale.primaryScale.border, - decorationColor: scale.primaryScale.border, - decoration: TextDecoration.underline)) - .paddingAll(8.scaled(context)), - Text(translate('settings_page.sound'), - textAlign: TextAlign.center, - style: textTheme.titleMedium!.copyWith( - color: scale.primaryScale.border, - decorationColor: scale.primaryScale.border, - decoration: TextDecoration.underline)) - .paddingAll(8.scaled(context)), - ]), - TableRow(children: [ - // Invitation accepted - Text( - textAlign: TextAlign.right, - translate('settings_page.invitation_accepted')) - .paddingAll(4.scaled(context)), - StyledDropdown( - items: notificationModeItems(), - value: notificationsPreference.onInvitationAcceptedMode, - onChanged: !notificationsPreference.enableNotifications - ? null - : (value) async { - final newNotificationsPreference = - notificationsPreference.copyWith( - onInvitationAcceptedMode: value); - await updatePreferences(newNotificationsPreference); - }, - ).paddingAll(4.scaled(context)), - StyledDropdown( - items: soundEffectItems(), - value: notificationsPreference.onInvitationAcceptedSound, - onChanged: !notificationsPreference.enableNotifications - ? null - : (value) async { - final newNotificationsPreference = - notificationsPreference.copyWith( - onInvitationAcceptedSound: value); - await updatePreferences(newNotificationsPreference); - }, - ).paddingLTRB( - 4.scaled(context), 4.scaled(context), 0, 4.scaled(context)) - ]), + Text('$title:', style: textTheme.titleMedium), + Wrap( + spacing: 8.scaled(context), // gap between adjacent chips + runSpacing: 8.scaled(context), // gap between lines + children: [ + if (deliveryValue != null) + IntrinsicWidth( + child: StyledDropdown( + decoratorLabel: translate('settings_page.delivery'), + items: notificationModeItems(), + value: deliveryValue, + onChanged: !notificationsEnabled + ? null + : onNotificationModeChanged, + )), + if (soundValue != null) + IntrinsicWidth( + child: StyledDropdown( + decoratorLabel: translate('settings_page.sound'), + items: soundEffectItems(), + value: soundValue, + onChanged: !notificationsEnabled ? null : onSoundChanged, + )) + ]) + ]).paddingAll(4.scaled(context)); + + return InputDecorator( + decoration: InputDecoration( + labelText: translate('settings_page.notifications'), + border: OutlineInputBorder( + borderRadius: + BorderRadius.circular(8 * scaleConfig.borderRadiusScale), + borderSide: BorderSide(width: 2, color: scale.primaryScale.border), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.scaled(context), + children: [ + // Display Beta Warning + StyledCheckbox( + label: translate('settings_page.display_beta_warning'), + value: notificationsPreference.displayBetaWarning, + onChanged: (value) async { + final newNotificationsPreference = notificationsPreference + .copyWith(displayBetaWarning: value); + + await updatePreferences(newNotificationsPreference); + }), + // Enable Badge + StyledCheckbox( + label: translate('settings_page.enable_badge'), + value: notificationsPreference.enableBadge, + onChanged: (value) async { + final newNotificationsPreference = + notificationsPreference.copyWith(enableBadge: value); + await updatePreferences(newNotificationsPreference); + }), + // Enable Notifications + StyledCheckbox( + label: translate('settings_page.enable_notifications'), + value: notificationsPreference.enableNotifications, + onChanged: (value) async { + final newNotificationsPreference = notificationsPreference + .copyWith(enableNotifications: value); + await updatePreferences(newNotificationsPreference); + }), + StyledDropdown( + items: messageNotificationContentItems(), + value: notificationsPreference.messageNotificationContent, + decoratorLabel: + translate('settings_page.message_notification_content'), + onChanged: !notificationsPreference.enableNotifications + ? null + : (value) async { + final newNotificationsPreference = notificationsPreference + .copyWith(messageNotificationContent: value); + await updatePreferences(newNotificationsPreference); + }, + ).paddingAll(4.scaled(context)), + + // Notifications + + // Invitation accepted + notificationSettingsItem( + title: translate('settings_page.invitation_accepted'), + notificationsEnabled: + notificationsPreference.enableNotifications, + deliveryValue: notificationsPreference.onInvitationAcceptedMode, + soundValue: notificationsPreference.onInvitationAcceptedSound, + onNotificationModeChanged: (value) async { + final newNotificationsPreference = notificationsPreference + .copyWith(onInvitationAcceptedMode: value); + await updatePreferences(newNotificationsPreference); + }, + onSoundChanged: (value) async { + final newNotificationsPreference = notificationsPreference + .copyWith(onInvitationAcceptedSound: value); + await updatePreferences(newNotificationsPreference); + }), + // Message received - TableRow(children: [ - Text( - textAlign: TextAlign.right, - translate('settings_page.message_received')) - .paddingAll(4.scaled(context)), - StyledDropdown( - items: notificationModeItems(), - value: notificationsPreference.onMessageReceivedMode, - onChanged: !notificationsPreference.enableNotifications - ? null - : (value) async { - final newNotificationsPreference = - notificationsPreference.copyWith( - onMessageReceivedMode: value); - await updatePreferences(newNotificationsPreference); - }, - ).paddingAll(4), - StyledDropdown( - items: soundEffectItems(), - value: notificationsPreference.onMessageReceivedSound, - onChanged: !notificationsPreference.enableNotifications - ? null - : (value) async { - final newNotificationsPreference = - notificationsPreference.copyWith( - onMessageReceivedSound: value); - await updatePreferences(newNotificationsPreference); - }, - ).paddingLTRB( - 4.scaled(context), 4.scaled(context), 0, 4.scaled(context)) - ]), + notificationSettingsItem( + title: translate('settings_page.message_received'), + notificationsEnabled: + notificationsPreference.enableNotifications, + deliveryValue: notificationsPreference.onMessageReceivedMode, + soundValue: notificationsPreference.onMessageReceivedSound, + onNotificationModeChanged: (value) async { + final newNotificationsPreference = notificationsPreference + .copyWith(onMessageReceivedMode: value); + await updatePreferences(newNotificationsPreference); + }, + onSoundChanged: (value) async { + final newNotificationsPreference = notificationsPreference + .copyWith(onMessageReceivedSound: value); + await updatePreferences(newNotificationsPreference); + }), // Message sent - TableRow(children: [ - Text( - textAlign: TextAlign.right, - translate('settings_page.message_sent')) - .paddingAll(4.scaled(context)), - const SizedBox.shrink(), - StyledDropdown( - items: soundEffectItems(), - value: notificationsPreference.onMessageSentSound, - onChanged: !notificationsPreference.enableNotifications - ? null - : (value) async { - final newNotificationsPreference = - notificationsPreference.copyWith( - onMessageSentSound: value); - await updatePreferences(newNotificationsPreference); - }, - ).paddingLTRB( - 4.scaled(context), 4.scaled(context), 0, 4.scaled(context)) - ]), - ]) - ]).paddingAll(8.scaled(context)), - ); + notificationSettingsItem( + title: translate('settings_page.message_sent'), + notificationsEnabled: + notificationsPreference.enableNotifications, + soundValue: notificationsPreference.onMessageSentSound, + onSoundChanged: (value) async { + final newNotificationsPreference = notificationsPreference + .copyWith(onMessageSentSound: value); + await updatePreferences(newNotificationsPreference); + }), + ]).paddingAll(4.scaled(context))); } diff --git a/lib/settings/settings_page.dart b/lib/settings/settings_page.dart index 810b20e..bb6ee3e 100644 --- a/lib/settings/settings_page.dart +++ b/lib/settings/settings_page.dart @@ -23,7 +23,7 @@ class SettingsPage extends StatelessWidget { title: Text(translate('settings_page.titlebar')), leading: IconButton( iconSize: 24.scaled(context), - icon: Icon(Icons.arrow_back), + icon: const Icon(Icons.arrow_back), onPressed: () => GoRouterHelper(context).pop(), ), actions: [ @@ -56,7 +56,6 @@ class SettingsPage extends StatelessWidget { .map((x) => x.paddingLTRB(0, 0, 0, 8.scaled(context))) .toList(), ).paddingSymmetric(vertical: 4.scaled(context)), - ).paddingSymmetric( - horizontal: 8.scaled(context), vertical: 8.scaled(context)), + ), )); } diff --git a/lib/theme/models/scale_theme/scale_theme.dart b/lib/theme/models/scale_theme/scale_theme.dart index 755bd54..a428c2c 100644 --- a/lib/theme/models/scale_theme/scale_theme.dart +++ b/lib/theme/models/scale_theme/scale_theme.dart @@ -191,6 +191,12 @@ class ScaleTheme extends ThemeExtension { inputDecorationTheme: ScaleInputDecoratorTheme(scheme, config, textTheme), sliderTheme: sliderTheme, + popupMenuTheme: PopupMenuThemeData( + color: scheme.primaryScale.subtleBackground, + shadowColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(8 * config.borderRadiusScale))), extensions: >[scheme, config, this]); return themeData; diff --git a/lib/theme/views/preferences/display_scale_preferences.dart b/lib/theme/views/preferences/display_scale_preferences.dart index 84bf97d..c056280 100644 --- a/lib/theme/views/preferences/display_scale_preferences.dart +++ b/lib/theme/views/preferences/display_scale_preferences.dart @@ -38,6 +38,16 @@ const _scaleNumMult = [ 1 + 1 / 1, ]; +const _scaleNumMultNoShrink = [ + 1, + 1, + 1, + 1, + 1 + 1 / 4, + 1 + 1 / 2, + 1 + 1 / 1, +]; + int displayScaleToIndex(double displayScale) { final idx = _scales.indexWhere((elem) => elem > displayScale); final currentScaleIdx = idx == -1 ? _scales.length - 1 : max(0, idx - 1); @@ -92,6 +102,14 @@ extension DisplayScaledNum on num { displayScaleToIndex(prefs.themePreference.displayScale); return this * _scaleNumMult[currentScaleIdx]; } + + double scaledNoShrink(BuildContext context) { + final prefs = context.watch().state.asData?.value ?? + PreferencesRepository.instance.value; + final currentScaleIdx = + displayScaleToIndex(prefs.themePreference.displayScale); + return this * _scaleNumMultNoShrink[currentScaleIdx]; + } } extension DisplayScaledEdgeInsets on EdgeInsets { diff --git a/lib/theme/views/styled_widgets/styled_checkbox.dart b/lib/theme/views/styled_widgets/styled_checkbox.dart index 7eb3649..0213db2 100644 --- a/lib/theme/views/styled_widgets/styled_checkbox.dart +++ b/lib/theme/views/styled_widgets/styled_checkbox.dart @@ -43,7 +43,11 @@ class StyledCheckbox extends StatelessWidget { await _onChanged(value); }); })), - Text(_label, style: textStyle).paddingAll(4.scaled(context)), + Text( + _label, + style: textStyle, + overflow: TextOverflow.clip, + ).paddingLTRB(4.scaled(context), 0, 0, 0).flexible(), ]); if (_decoratorLabel != null) { diff --git a/lib/theme/views/styled_widgets/styled_scaffold.dart b/lib/theme/views/styled_widgets/styled_scaffold.dart index 82f27f5..9a32640 100644 --- a/lib/theme/views/styled_widgets/styled_scaffold.dart +++ b/lib/theme/views/styled_widgets/styled_scaffold.dart @@ -27,7 +27,7 @@ class StyledScaffold extends StatelessWidget { return GestureDetector( onTap: () => FocusManager.instance.primaryFocus?.unfocus(), - child: scaffold /*.paddingAll(enableBorder ? 32 : 0) */); + child: scaffold); } //////////////////////////////////////////////////////////////////////////// diff --git a/lib/theme/views/styled_widgets/styled_slide_tile.dart b/lib/theme/views/styled_widgets/styled_slide_tile.dart index 43b3fd8..59f8a7b 100644 --- a/lib/theme/views/styled_widgets/styled_slide_tile.dart +++ b/lib/theme/views/styled_widgets/styled_slide_tile.dart @@ -132,7 +132,7 @@ class StyledSlideTile extends StatelessWidget { softWrap: false, ), subtitle: subtitle.isNotEmpty ? Text(subtitle) : null, - contentPadding: const EdgeInsets.fromLTRB(8, 4, 8, 4) + contentPadding: const EdgeInsets.fromLTRB(8, 2, 8, 2) .scaled(context), iconColor: scaleTileTheme.textColor, textColor: scaleTileTheme.textColor, diff --git a/lib/theme/views/widget_helpers.dart b/lib/theme/views/widget_helpers.dart index 910074e..a02f207 100644 --- a/lib/theme/views/widget_helpers.dart +++ b/lib/theme/views/widget_helpers.dart @@ -571,6 +571,7 @@ Container clipBorder({ required Color borderColor, required Widget child, }) => + // We want to return a container here // ignore: avoid_unnecessary_containers, use_decorated_box Container( decoration: ShapeDecoration( diff --git a/pubspec.lock b/pubspec.lock index aa97499..ad7857c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -848,14 +848,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.9.5" - keyboard_avoider: - dependency: "direct main" - description: - name: keyboard_avoider - sha256: d2917bd52c6612bf8d1ff97f74049ddf3592a81d44e814f0e7b07dcfd245b75c - url: "https://pub.dev" - source: hosted - version: "0.2.0" lint_hard: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 519714b..e933661 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,7 +56,6 @@ dependencies: image: ^4.5.3 intl: ^0.19.0 json_annotation: ^4.9.0 - keyboard_avoider: ^0.2.0 loggy: ^2.0.3 meta: ^1.16.0 mobile_scanner: ^7.0.0 From 5a6b57e8ec0983524471f834db5f54e7701ee499 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 27 May 2025 19:04:17 -0500 Subject: [PATCH 75/93] textscale dialogs --- lib/account_manager/views/profile_widget.dart | 62 ++++++++++--------- .../views/contact_invitation_display.dart | 2 +- .../views/create_invitation_dialog.dart | 28 ++++----- .../views/invitation_dialog.dart | 11 ++-- lib/layout/home/home_account_ready.dart | 3 +- .../views/styled_widgets/styled_dialog.dart | 13 ++-- .../styled_widgets/styled_slide_tile.dart | 2 +- 7 files changed, 62 insertions(+), 59 deletions(-) diff --git a/lib/account_manager/views/profile_widget.dart b/lib/account_manager/views/profile_widget.dart index af7cf30..8217414 100644 --- a/lib/account_manager/views/profile_widget.dart +++ b/lib/account_manager/views/profile_widget.dart @@ -7,17 +7,10 @@ import '../../theme/theme.dart'; class ProfileWidget extends StatelessWidget { const ProfileWidget({ required proto.Profile profile, - required bool showPronouns, + String? byline, super.key, }) : _profile = profile, - _showPronouns = showPronouns; - - // - - final proto.Profile _profile; - final bool _showPronouns; - - // + _byline = byline; @override Widget build(BuildContext context) { @@ -44,26 +37,37 @@ class ProfileWidget extends StatelessWidget { borderRadius: BorderRadius.all( Radius.circular(8 * scaleConfig.borderRadiusScale))), ), - child: Row(children: [ - const Spacer(), - Text( - _profile.name, - style: textTheme.titleMedium!.copyWith( - color: scaleConfig.preferBorders - ? scale.primaryScale.border - : scale.primaryScale.borderText), - textAlign: TextAlign.left, - ).paddingAll(8.scaled(context)), - if (_profile.pronouns.isNotEmpty && _showPronouns) - Text('(${_profile.pronouns})', - textAlign: TextAlign.right, - style: textTheme.bodySmall!.copyWith( - color: scaleConfig.preferBorders - ? scale.primaryScale.border - : scale.primaryScale.primary)) - .paddingAll(8.scaled(context)), - const Spacer() - ]), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 8.scaled(context), + children: [ + Text( + _profile.name, + style: textTheme.titleMedium!.copyWith( + color: scaleConfig.preferBorders + ? scale.primaryScale.border + : scale.primaryScale.borderText), + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + maxLines: 1, + ), + if (_byline != null) + Text( + _byline, + style: textTheme.bodySmall!.copyWith( + color: scaleConfig.preferBorders + ? scale.primaryScale.border + : scale.primaryScale.primary), + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + maxLines: 1, + ), + ]).paddingAll(8.scaled(context)), ); } + + //////////////////////////////////////////////////////////////////////////// + + final proto.Profile _profile; + final String? _byline; } diff --git a/lib/contact_invitation/views/contact_invitation_display.dart b/lib/contact_invitation/views/contact_invitation_display.dart index b3f048a..4ab840d 100644 --- a/lib/contact_invitation/views/contact_invitation_display.dart +++ b/lib/contact_invitation/views/contact_invitation_display.dart @@ -58,7 +58,6 @@ class ContactInvitationDisplayDialog extends StatelessWidget { } @override - // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { final theme = Theme.of(context); final textTheme = theme.textTheme; @@ -131,6 +130,7 @@ class ContactInvitationDisplayDialog extends StatelessWidget { if (message.isNotEmpty) Text(message, softWrap: true, + textAlign: TextAlign.center, maxLines: 2, style: textTheme.labelMedium! .copyWith(color: Colors.black)) diff --git a/lib/contact_invitation/views/create_invitation_dialog.dart b/lib/contact_invitation/views/create_invitation_dialog.dart index 581e8d6..41e6162 100644 --- a/lib/contact_invitation/views/create_invitation_dialog.dart +++ b/lib/contact_invitation/views/create_invitation_dialog.dart @@ -41,7 +41,7 @@ class _CreateInvitationDialogState extends State { late final TextEditingController _recipientTextController; EncryptionKeyType _encryptionKeyType = EncryptionKeyType.none; - String _encryptionKey = ''; + var _encryptionKey = ''; Timestamp? _expiration; @override @@ -171,24 +171,23 @@ class _CreateInvitationDialogState extends State { } @override - // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { final windowSize = MediaQuery.of(context).size; final maxDialogWidth = min(windowSize.width - 64.0, 800.0 - 64.0); final maxDialogHeight = windowSize.height - 64.0; final theme = Theme.of(context); - //final scale = theme.extension()!; final textTheme = theme.textTheme; return ConstrainedBox( constraints: BoxConstraints(maxHeight: maxDialogHeight, maxWidth: maxDialogWidth), child: SingleChildScrollView( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.all(8).scaled(context), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, + spacing: 16.scaled(context), children: [ TextField( autofocus: true, @@ -200,30 +199,29 @@ class _CreateInvitationDialogState extends State { LengthLimitingTextInputFormatter(128), ], decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), hintText: translate('create_invitation_dialog.recipient_hint'), labelText: translate('create_invitation_dialog.recipient_name'), helperText: translate('create_invitation_dialog.recipient_helper')), - ).paddingAll(8), - const SizedBox(height: 10), + ), TextField( controller: _messageTextController, inputFormatters: [ LengthLimitingTextInputFormatter(128), ], decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8).scaled(context), hintText: translate('create_invitation_dialog.message_hint'), labelText: translate('create_invitation_dialog.message_label'), helperText: translate('create_invitation_dialog.message_helper')), - ).paddingAll(8), - const SizedBox(height: 10), + ), Text(translate('create_invitation_dialog.protect_this_invitation'), - style: textTheme.labelLarge) - .paddingAll(8), + style: textTheme.labelLarge), Wrap( alignment: WrapAlignment.center, runAlignment: WrapAlignment.center, @@ -245,23 +243,23 @@ class _CreateInvitationDialogState extends State { selected: _encryptionKeyType == EncryptionKeyType.password, onSelected: _onPasswordEncryptionSelected, ) - ]).paddingAll(8).toCenter(), + ]).toCenter(), Container( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.all(8).scaled(context), child: ElevatedButton( onPressed: _recipientTextController.text.isNotEmpty ? _onGenerateButtonPressed : null, child: Text( translate('create_invitation_dialog.generate'), - ).paddingAll(16), + ).paddingAll(16.scaled(context)), ), ).toCenter(), - Text(translate('create_invitation_dialog.note')).paddingAll(8), + Text(translate('create_invitation_dialog.note')), Text( translate('create_invitation_dialog.note_text'), style: Theme.of(context).textTheme.bodySmall, - ).paddingAll(8), + ), ], ), ), diff --git a/lib/contact_invitation/views/invitation_dialog.dart b/lib/contact_invitation/views/invitation_dialog.dart index 385cbcb..3cd0bfb 100644 --- a/lib/contact_invitation/views/invitation_dialog.dart +++ b/lib/contact_invitation/views/invitation_dialog.dart @@ -292,13 +292,10 @@ class InvitationDialogState extends State { ]).toCenter(), if (_validInvitation != null && !_isValidating) Column(children: [ - Container( - constraints: const BoxConstraints(maxHeight: 64), - width: double.infinity, - child: ProfileWidget( - profile: _validInvitation!.remoteProfile, - showPronouns: true, - )).paddingLTRB(0, 0, 0, 16), + ProfileWidget( + profile: _validInvitation!.remoteProfile, + byline: _validInvitation!.remoteProfile.pronouns, + ).paddingLTRB(0, 0, 0, 16), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ diff --git a/lib/layout/home/home_account_ready.dart b/lib/layout/home/home_account_ready.dart index 50a43c8..a829248 100644 --- a/lib/layout/home/home_account_ready.dart +++ b/lib/layout/home/home_account_ready.dart @@ -127,7 +127,6 @@ class _HomeAccountReadyState extends State { buildMenuButton().paddingLTRB(0, 0, 8, 0), ProfileWidget( profile: profile, - showPronouns: false, ).expanded(), buildContactsButton().paddingLTRB(8, 0, 0, 0), ])).paddingAll(8), @@ -169,7 +168,7 @@ class _HomeAccountReadyState extends State { final hasActiveChat = activeChat != null; return LayoutBuilder(builder: (context, constraints) { - const leftColumnSize = 300.0; + const leftColumnSize = 320.0; late final bool visibleLeft; late final bool visibleRight; diff --git a/lib/theme/views/styled_widgets/styled_dialog.dart b/lib/theme/views/styled_widgets/styled_dialog.dart index 4106f1d..54431b2 100644 --- a/lib/theme/views/styled_widgets/styled_dialog.dart +++ b/lib/theme/views/styled_widgets/styled_dialog.dart @@ -1,7 +1,7 @@ -import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import '../../../settings/settings.dart'; import '../../theme.dart'; class StyledDialog extends StatelessWidget { @@ -41,17 +41,22 @@ class StyledDialog extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 12 * scaleConfig.borderRadiusScale))), - child: child.paddingAll(0)))); + child: child))); } static Future show( {required BuildContext context, required String title, - required Widget child}) async => + required Widget child}) => showDialog( context: context, useRootNavigator: false, - builder: (context) => StyledDialog(title: title, child: child)); + builder: (context) => AsyncBlocBuilder( + builder: (context, state) => MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.linear( + state.themePreference.displayScale)), + child: StyledDialog(title: title, child: child)))); final String title; final Widget child; diff --git a/lib/theme/views/styled_widgets/styled_slide_tile.dart b/lib/theme/views/styled_widgets/styled_slide_tile.dart index 59f8a7b..e4a0e27 100644 --- a/lib/theme/views/styled_widgets/styled_slide_tile.dart +++ b/lib/theme/views/styled_widgets/styled_slide_tile.dart @@ -120,7 +120,7 @@ class StyledSlideTile extends StatelessWidget { child: Padding( padding: scaleTheme.config.useVisualIndicators ? EdgeInsets.zero - : const EdgeInsets.fromLTRB(0, 2, 0, 2).scaled(context), + : const EdgeInsets.fromLTRB(0, 4, 0, 4).scaled(context), child: GestureDetector( onDoubleTap: onDoubleTap, child: ListTile( From b8eca1161edb4cf87093206d1613a7da23722c7a Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 27 May 2025 19:35:36 -0500 Subject: [PATCH 76/93] fix giant toilet --- lib/account_manager/views/edit_profile_form.dart | 10 +++++----- lib/contacts/views/availability_widget.dart | 14 +++++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/account_manager/views/edit_profile_form.dart b/lib/account_manager/views/edit_profile_form.dart index 114c7b8..c4f84da 100644 --- a/lib/account_manager/views/edit_profile_form.dart +++ b/lib/account_manager/views/edit_profile_form.dart @@ -103,14 +103,14 @@ class _EditProfileFormState extends State { labelText: translate('account.form_availability'), hintText: translate('account.empty_busy_message')), items: availabilities - .map((x) => DropdownMenuItem( - value: x, + .map((availability) => DropdownMenuItem( + value: availability, child: Row(mainAxisSize: MainAxisSize.min, children: [ AvailabilityWidget.availabilityIcon( - x, scale.primaryScale.appText), - Text(x == proto.Availability.AVAILABILITY_OFFLINE + context, availability, scale.primaryScale.appText), + Text(availability == proto.Availability.AVAILABILITY_OFFLINE ? translate('availability.always_show_offline') - : AvailabilityWidget.availabilityName(x)) + : AvailabilityWidget.availabilityName(availability)) .paddingLTRB(8.scaled(context), 0, 0, 0), ]))) .toList(), diff --git a/lib/contacts/views/availability_widget.dart b/lib/contacts/views/availability_widget.dart index 55fac39..5ef6080 100644 --- a/lib/contacts/views/availability_widget.dart +++ b/lib/contacts/views/availability_widget.dart @@ -4,6 +4,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_translate/flutter_translate.dart'; import '../../proto/proto.dart' as proto; +import '../../theme/theme.dart'; class AvailabilityWidget extends StatelessWidget { const AvailabilityWidget( @@ -13,6 +14,7 @@ class AvailabilityWidget extends StatelessWidget { super.key}); static Widget availabilityIcon( + BuildContext context, proto.Availability availability, Color color, ) { @@ -20,15 +22,17 @@ class AvailabilityWidget extends StatelessWidget { switch (availability) { case proto.Availability.AVAILABILITY_AWAY: icon = SvgPicture.asset('assets/images/toilet.svg', + width: 24.scaled(context), + height: 24.scaled(context), colorFilter: ColorFilter.mode(color, BlendMode.srcATop)); case proto.Availability.AVAILABILITY_BUSY: - icon = const Icon(Icons.event_busy, applyTextScaling: true); + icon = Icon(size: 24.scaled(context), Icons.event_busy); case proto.Availability.AVAILABILITY_FREE: - icon = const Icon(Icons.event_available, applyTextScaling: true); + icon = Icon(size: 24.scaled(context), Icons.event_available); case proto.Availability.AVAILABILITY_OFFLINE: - icon = const Icon(Icons.cloud_off, applyTextScaling: true); + icon = Icon(size: 24.scaled(context), Icons.cloud_off); case proto.Availability.AVAILABILITY_UNSPECIFIED: - icon = const Icon(Icons.question_mark, applyTextScaling: true); + icon = Icon(size: 24.scaled(context), Icons.question_mark); } return icon; } @@ -56,7 +60,7 @@ class AvailabilityWidget extends StatelessWidget { final textTheme = theme.textTheme; final name = availabilityName(availability); - final icon = availabilityIcon(availability, color); + final icon = availabilityIcon(context, availability, color); return vertical ? Column(mainAxisSize: MainAxisSize.min, children: [ From 8aaca62ea75444787702d481a2001b94ab78e231 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 28 May 2025 13:34:57 -0500 Subject: [PATCH 77/93] better time and status position --- .../chat_builders/vc_text_message_widget.dart | 91 +++++-------------- lib/chat/views/chat_component_widget.dart | 38 ++++++-- lib/chat_list/views/chat_list_widget.dart | 69 +++++++------- .../models/scale_theme/scale_chat_theme.dart | 5 +- lib/theme/views/widget_helpers.dart | 32 +++++++ 5 files changed, 119 insertions(+), 116 deletions(-) diff --git a/lib/chat/views/chat_builders/vc_text_message_widget.dart b/lib/chat/views/chat_builders/vc_text_message_widget.dart index 52235f6..7ea2957 100644 --- a/lib/chat/views/chat_builders/vc_text_message_widget.dart +++ b/lib/chat/views/chat_builders/vc_text_message_widget.dart @@ -22,7 +22,6 @@ class VcTextMessageWidget extends StatelessWidget { this.timeStyle, this.showTime = true, this.showStatus = true, - this.timeAndStatusPosition = TimeAndStatusPosition.end, super.key, }); @@ -63,9 +62,6 @@ class VcTextMessageWidget extends StatelessWidget { /// for sent messages. final bool showStatus; - /// Position of the timestamp and status indicator relative to the text. - final TimeAndStatusPosition timeAndStatusPosition; - bool get _isOnlyEmoji => message.metadata?['isOnlyEmoji'] == true; @override @@ -98,62 +94,26 @@ class VcTextMessageWidget extends StatelessWidget { : textStyle, ); - return Container( - padding: _isOnlyEmoji - ? EdgeInsets.symmetric( - horizontal: (padding?.horizontal ?? 0) / 2, - // vertical: 0, - ) - : padding, - decoration: _isOnlyEmoji - ? null - : BoxDecoration( - color: backgroundColor, - borderRadius: borderRadius ?? chatTheme.shape, - ), - child: _buildContentBasedOnPosition( - context: context, - textContent: textContent, - timeAndStatus: timeAndStatus, - textStyle: textStyle, - ), - ); - } - - Widget _buildContentBasedOnPosition({ - required BuildContext context, - required Widget textContent, - TimeAndStatus? timeAndStatus, - TextStyle? textStyle, - }) { - if (timeAndStatus == null) { - return textContent; - } - - switch (timeAndStatusPosition) { - case TimeAndStatusPosition.start: - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [textContent, timeAndStatus], - ); - case TimeAndStatusPosition.inline: - return Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Flexible(child: textContent), - const SizedBox(width: 4), - timeAndStatus, - ], - ); - case TimeAndStatusPosition.end: - return Column( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [textContent, timeAndStatus], - ); - } + return Column( + crossAxisAlignment: + isSentByMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, + children: [ + Container( + padding: _isOnlyEmoji + ? EdgeInsets.symmetric( + horizontal: (padding?.horizontal ?? 0) / 2, + // vertical: 0, + ) + : padding, + decoration: _isOnlyEmoji + ? null + : BoxDecoration( + color: backgroundColor, + borderRadius: borderRadius ?? chatTheme.shape, + ), + child: textContent), + if (timeAndStatus != null) timeAndStatus, + ]); } Color _resolveBackgroundColor(bool isSentByMe, ScaleChatTheme theme) { @@ -170,11 +130,8 @@ class VcTextMessageWidget extends StatelessWidget { return receivedTextStyle ?? theme.receivedMessageBodyTextStyle; } - TextStyle _resolveTimeStyle(bool isSentByMe, ScaleChatTheme theme) { - final ts = _resolveTextStyle(isSentByMe, theme); - - return theme.timeStyle.copyWith(color: ts.color); - } + TextStyle _resolveTimeStyle(bool isSentByMe, ScaleChatTheme theme) => + theme.timeStyle; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -193,9 +150,7 @@ class VcTextMessageWidget extends StatelessWidget { 'receivedTextStyle', receivedTextStyle)) ..add(DiagnosticsProperty('timeStyle', timeStyle)) ..add(DiagnosticsProperty('showTime', showTime)) - ..add(DiagnosticsProperty('showStatus', showStatus)) - ..add(EnumProperty( - 'timeAndStatusPosition', timeAndStatusPosition)); + ..add(DiagnosticsProperty('showStatus', showStatus)); } } diff --git a/lib/chat/views/chat_component_widget.dart b/lib/chat/views/chat_component_widget.dart index 8cb4edc..aecf531 100644 --- a/lib/chat/views/chat_component_widget.dart +++ b/lib/chat/views/chat_component_widget.dart @@ -261,18 +261,36 @@ class _ChatComponentWidgetState extends State { chatAnimatedListBuilder: (context, itemBuilder) => ChatAnimatedListReversed( scrollController: _scrollController, + messageGroupingTimeoutInSeconds: 60, itemBuilder: itemBuilder), // Text message builder - textMessageBuilder: (context, message, index) => - VcTextMessageWidget( - message: message, - index: index, - padding: const EdgeInsets.symmetric( - vertical: 12, horizontal: 16) - .scaled(context) - // showTime: true, - // showStatus: true, - ), + textMessageBuilder: (context, message, index) { + var showTime = true; + if (_chatController.messages.length > 1 && + index < _chatController.messages.length - 1 && + message.time != null) { + final nextMessage = + _chatController.messages[index + 1]; + if (nextMessage.time != null) { + if (nextMessage.time! + .difference(message.time!) + .inSeconds < + 60 && + nextMessage.authorId == message.authorId) { + showTime = false; + } + } + } + return VcTextMessageWidget( + message: message, + index: index, + padding: const EdgeInsets.symmetric( + vertical: 12, horizontal: 16) + .scaled(context), + showTime: showTime, + showStatus: showTime, + ); + }, // Composer builder composerBuilder: (ctx) => VcComposerWidget( autofocus: true, diff --git a/lib/chat_list/views/chat_list_widget.dart b/lib/chat_list/views/chat_list_widget.dart index 04720e8..33ddaab 100644 --- a/lib/chat_list/views/chat_list_widget.dart +++ b/lib/chat_list/views/chat_list_widget.dart @@ -56,43 +56,38 @@ class ChatListWidget extends StatelessWidget { valueMapper: (c) => c.value); final chatListV = context.watch().state; - return chatListV - .builder((context, chatList) => SizedBox.expand( - child: styledTitleContainer( - context: context, - title: translate('chat_list.chats'), - child: (chatList.isEmpty) - ? const SizedBox.expand(child: EmptyChatListWidget()) - : TapRegion( - onTapOutside: (_) { - FocusScope.of(context).unfocus(); - }, - child: SearchableList( - initialList: chatList.map((x) => x.value).toList(), - itemBuilder: (c) { - switch (c.whichKind()) { - case proto.Chat_Kind.direct: - return _itemBuilderDirect( - c.direct, - contactMap, - ); - case proto.Chat_Kind.group: - return const Text( - 'group chats not yet supported!'); - case proto.Chat_Kind.notSet: - throw StateError('unknown chat kind'); - } - }, - filter: (value) => - _itemFilter(contactMap, chatList, value), - searchFieldPadding: - const EdgeInsets.fromLTRB(0, 0, 0, 4), - inputDecoration: InputDecoration( - labelText: translate('chat_list.search'), - ), - )).paddingAll(8), - ))) - .paddingLTRB(8, 0, 8, 8); + return chatListV.builder((context, chatList) => SizedBox.expand( + child: styledContainer( + context: context, + child: (chatList.isEmpty) + ? const SizedBox.expand(child: EmptyChatListWidget()) + : TapRegion( + onTapOutside: (_) { + FocusScope.of(context).unfocus(); + }, + child: SearchableList( + initialList: chatList.map((x) => x.value).toList(), + itemBuilder: (c) { + switch (c.whichKind()) { + case proto.Chat_Kind.direct: + return _itemBuilderDirect( + c.direct, + contactMap, + ); + case proto.Chat_Kind.group: + return const Text('group chats not yet supported!'); + case proto.Chat_Kind.notSet: + throw StateError('unknown chat kind'); + } + }, + filter: (value) => + _itemFilter(contactMap, chatList, value), + searchFieldPadding: const EdgeInsets.fromLTRB(0, 0, 0, 4), + inputDecoration: InputDecoration( + labelText: translate('chat_list.search'), + ), + )).paddingAll(8), + ))); }); } } diff --git a/lib/theme/models/scale_theme/scale_chat_theme.dart b/lib/theme/models/scale_theme/scale_chat_theme.dart index 5da0fe2..d1145cf 100644 --- a/lib/theme/models/scale_theme/scale_chat_theme.dart +++ b/lib/theme/models/scale_theme/scale_chat_theme.dart @@ -354,7 +354,10 @@ extension ScaleChatThemeExt on ScaleTheme { : scheme.primaryScale.calloutText, ), onlyEmojiFontSize: 64, - timeStyle: textTheme.bodySmall!.copyWith(fontSize: 9), + timeStyle: textTheme.bodySmall!.copyWith(fontSize: 9).copyWith( + color: config.preferBorders || config.useVisualIndicators + ? scheme.primaryScale.calloutBackground + : scheme.primaryScale.borderText), receivedMessageBodyTextStyle: textTheme.bodyLarge!.copyWith( color: config.preferBorders ? scheme.secondaryScale.calloutBackground diff --git a/lib/theme/views/widget_helpers.dart b/lib/theme/views/widget_helpers.dart index a02f207..2d8d626 100644 --- a/lib/theme/views/widget_helpers.dart +++ b/lib/theme/views/widget_helpers.dart @@ -464,6 +464,38 @@ Widget styledTitleContainer({ ])); } +Widget styledContainer({ + required BuildContext context, + required Widget child, + Color? borderColor, + Color? backgroundColor, +}) { + final theme = Theme.of(context); + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + + return DecoratedBox( + decoration: ShapeDecoration( + color: borderColor ?? scale.primaryScale.border, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(8 * scaleConfig.borderRadiusScale), + )), + child: Column(children: [ + DecoratedBox( + decoration: ShapeDecoration( + color: + backgroundColor ?? scale.primaryScale.subtleBackground, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 8 * scaleConfig.borderRadiusScale), + )), + child: child) + .paddingAll(4) + .expanded() + ])); +} + Widget styledCard({ required BuildContext context, required Widget child, From 6421a775721715d6ec9926a308bd518b5d56db4e Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 28 May 2025 14:38:42 -0500 Subject: [PATCH 78/93] fix popup menu --- lib/contacts/views/contacts_browser.dart | 37 +++++++------------ lib/theme/models/scale_theme/scale_theme.dart | 3 +- pubspec.lock | 8 ---- pubspec.yaml | 1 - 4 files changed, 15 insertions(+), 34 deletions(-) diff --git a/lib/contacts/views/contacts_browser.dart b/lib/contacts/views/contacts_browser.dart index 10b207f..22c64c7 100644 --- a/lib/contacts/views/contacts_browser.dart +++ b/lib/contacts/views/contacts_browser.dart @@ -95,29 +95,18 @@ class _ContactsBrowserState extends State void Function()? onTap}) => PopupMenuItem( onTap: onTap, - child: DecoratedBox( - decoration: ShapeDecoration( - color: menuBackgroundColor, - shape: RoundedRectangleBorder( - side: scaleConfig.useVisualIndicators - ? BorderSide( - width: 2, - color: menuBorderColor, - strokeAlign: 0) - : BorderSide.none, - borderRadius: BorderRadius.circular( - 8 * scaleConfig.borderRadiusScale))), - child: Row(spacing: 4.scaled(context), children: [ - Icon(iconData, size: 32.scaled(context)), - Text( - text, - textScaler: MediaQuery.of(context).textScaler, - maxLines: 2, - textAlign: TextAlign.center, - ) - ]).paddingAll(4.scaled(context))) - .paddingLTRB(0, 2.scaled(context), 0, 2.scaled(context))); - + child: Row( + spacing: 8.scaled(context), + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Icon(iconData, size: 32.scaled(context)), + Text( + text, + textScaler: MediaQuery.of(context).textScaler, + maxLines: 2, + textAlign: TextAlign.center, + ) + ])); final inviteMenuItems = [ makeMenuButton( iconData: Icons.contact_page, @@ -141,7 +130,7 @@ class _ContactsBrowserState extends State return PopupMenuButton( itemBuilder: (_) => inviteMenuItems, - menuPadding: const EdgeInsets.symmetric(vertical: 4).scaled(context), + menuPadding: const EdgeInsets.symmetric(vertical: 8).scaled(context), tooltip: translate('add_contact_sheet.add_contact'), child: Icon( size: 32.scaled(context), Icons.person_add, color: menuIconColor)); diff --git a/lib/theme/models/scale_theme/scale_theme.dart b/lib/theme/models/scale_theme/scale_theme.dart index a428c2c..c1d41b2 100644 --- a/lib/theme/models/scale_theme/scale_theme.dart +++ b/lib/theme/models/scale_theme/scale_theme.dart @@ -192,9 +192,10 @@ class ScaleTheme extends ThemeExtension { ScaleInputDecoratorTheme(scheme, config, textTheme), sliderTheme: sliderTheme, popupMenuTheme: PopupMenuThemeData( - color: scheme.primaryScale.subtleBackground, + color: scheme.primaryScale.elementBackground, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( + side: BorderSide(color: scheme.primaryScale.border, width: 2), borderRadius: BorderRadius.circular(8 * config.borderRadiusScale))), extensions: >[scheme, config, this]); diff --git a/pubspec.lock b/pubspec.lock index ad7857c..3b7f823 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1527,14 +1527,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.1" - star_menu: - dependency: "direct main" - description: - name: star_menu - sha256: f29c7d255677c49ec2412ec2d17220d967f54b72b9e6afc5688fe122ea4d1d78 - url: "https://pub.dev" - source: hosted - version: "4.0.1" stream_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e933661..fccff99 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -92,7 +92,6 @@ dependencies: ref: main split_view: ^3.2.1 stack_trace: ^1.12.1 - star_menu: ^4.0.1 stream_transform: ^2.1.1 toastification: ^3.0.2 transitioned_indexed_stack: ^1.0.2 From b7752a7e95e62075f3eacc568144991dc687166a Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Fri, 30 May 2025 22:06:20 -0400 Subject: [PATCH 79/93] checkpoint on persistent queue update --- .../cubits/single_contact_messages_cubit.dart | 1 + .../example/integration_test/app_test.dart | 57 ++++++++++++------- packages/veilid_support/example/pubspec.lock | 2 +- packages/veilid_support/example/pubspec.yaml | 1 + .../lib/src/persistent_queue.dart | 55 +++++++++++++----- 5 files changed, 80 insertions(+), 36 deletions(-) diff --git a/lib/chat/cubits/single_contact_messages_cubit.dart b/lib/chat/cubits/single_contact_messages_cubit.dart index 878858b..a3e7656 100644 --- a/lib/chat/cubits/single_contact_messages_cubit.dart +++ b/lib/chat/cubits/single_contact_messages_cubit.dart @@ -107,6 +107,7 @@ class SingleContactMessagesCubit extends Cubit { table: 'SingleContactUnsentMessages', key: _remoteConversationRecordKey.toString(), fromBuffer: proto.Message.fromBuffer, + toBuffer: (x) => x.writeToBuffer(), closure: _processUnsentMessages, onError: (e, st) { log.error('Exception while processing unsent messages: $e\n$st\n'); diff --git a/packages/veilid_support/example/integration_test/app_test.dart b/packages/veilid_support/example/integration_test/app_test.dart index 5dc7acd..2d3d0e2 100644 --- a/packages/veilid_support/example/integration_test/app_test.dart +++ b/packages/veilid_support/example/integration_test/app_test.dart @@ -8,6 +8,7 @@ import 'fixtures/fixtures.dart'; import 'test_dht_log.dart'; import 'test_dht_record_pool.dart'; import 'test_dht_short_array.dart'; +import 'test_persistent_queue.dart'; import 'test_table_db_array.dart'; void main() { @@ -32,60 +33,70 @@ void main() { debugPrintSynchronously('Duration: ${endTime.difference(startTime)}'); }); - group('Attached Tests', () { + group('attached', () { setUpAll(veilidFixture.attach); tearDownAll(veilidFixture.detach); - group('DHT Support Tests', () { + group('persistent_queue', () { + test('persistent_queue:open_close', testPersistentQueueOpenClose); + test('persistent_queue:add', testPersistentQueueAdd); + test('persistent_queue:add_sync', testPersistentQueueAddSync); + test('persistent_queue:add_persist', testPersistentQueueAddPersist); + test('persistent_queue:add_sync_persist', + testPersistentQueueAddSyncPersist); + }); + + group('dht_support', () { setUpAll(updateProcessorFixture.setUp); setUpAll(tickerFixture.setUp); tearDownAll(tickerFixture.tearDown); tearDownAll(updateProcessorFixture.tearDown); - test('create pool', testDHTRecordPoolCreate); + test('create_pool', testDHTRecordPoolCreate); - group('DHTRecordPool Tests', () { + group('dht_record_pool', () { setUpAll(dhtRecordPoolFixture.setUp); tearDownAll(dhtRecordPoolFixture.tearDown); - test('create/delete record', testDHTRecordCreateDelete); - test('record scopes', testDHTRecordScopes); - test('create/delete deep record', testDHTRecordDeepCreateDelete); + test('dht_record_pool:create_delete', testDHTRecordCreateDelete); + test('dht_record_pool:scopes', testDHTRecordScopes); + test('dht_record_pool:deep_create_delete', + testDHTRecordDeepCreateDelete); }); - group('DHTShortArray Tests', () { + group('dht_short_array', () { setUpAll(dhtRecordPoolFixture.setUp); tearDownAll(dhtRecordPoolFixture.tearDown); for (final stride in [256, 16 /*64, 32, 16, 8, 4, 2, 1 */]) { - test('create shortarray stride=$stride', + test('dht_short_array:create_stride_$stride', makeTestDHTShortArrayCreateDelete(stride: stride)); - test('add shortarray stride=$stride', + test('dht_short_array:add_stride_$stride', makeTestDHTShortArrayAdd(stride: stride)); } }); - group('DHTLog Tests', () { + group('dht_log', () { setUpAll(dhtRecordPoolFixture.setUp); tearDownAll(dhtRecordPoolFixture.tearDown); for (final stride in [256, 16 /*64, 32, 16, 8, 4, 2, 1 */]) { - test('create log stride=$stride', + test('dht_log:create_stride_$stride', makeTestDHTLogCreateDelete(stride: stride)); test( timeout: const Timeout(Duration(seconds: 480)), - 'add/truncate log stride=$stride', + 'dht_log:add_truncate_stride_$stride', makeTestDHTLogAddTruncate(stride: stride), ); } }); }); - group('TableDB Tests', () { - group('TableDBArray Tests', () { - // test('create/delete TableDBArray', testTableDBArrayCreateDelete); + group('table_db', () { + group('table_db_array', () { + test('table_db_array:create_delete', testTableDBArrayCreateDelete); - group('TableDBArray Add/Get Tests', () { + group('table_db_array:add_get', () { for (final params in [ // (99, 3, 15), @@ -110,7 +121,7 @@ void main() { test( timeout: const Timeout(Duration(seconds: 480)), - 'add/remove TableDBArray count = $count batchSize=$batchSize', + 'table_db_array:add_remove_count=${count}_batchSize=$batchSize', makeTestTableDBArrayAddGetClear( count: count, singles: singles, @@ -120,7 +131,7 @@ void main() { } }); - group('TableDBArray Insert Tests', () { + group('table_db_array:insert', () { for (final params in [ // (99, 3, 15), @@ -145,7 +156,8 @@ void main() { test( timeout: const Timeout(Duration(seconds: 480)), - 'insert TableDBArray count=$count singles=$singles batchSize=$batchSize', + 'table_db_array:insert_count=${count}_' + 'singles=${singles}_batchSize=$batchSize', makeTestTableDBArrayInsert( count: count, singles: singles, @@ -155,7 +167,7 @@ void main() { } }); - group('TableDBArray Remove Tests', () { + group('table_db_array:remove', () { for (final params in [ // (99, 3, 15), @@ -180,7 +192,8 @@ void main() { test( timeout: const Timeout(Duration(seconds: 480)), - 'remove TableDBArray count=$count singles=$singles batchSize=$batchSize', + 'table_db_array:remove_count=${count}_' + 'singles=${singles}_batchSize=$batchSize', makeTestTableDBArrayRemove( count: count, singles: singles, diff --git a/packages/veilid_support/example/pubspec.lock b/packages/veilid_support/example/pubspec.lock index 9af9773..bc4ab6f 100644 --- a/packages/veilid_support/example/pubspec.lock +++ b/packages/veilid_support/example/pubspec.lock @@ -106,7 +106,7 @@ packages: source: hosted version: "1.1.2" collection: - dependency: transitive + dependency: "direct main" description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" diff --git a/packages/veilid_support/example/pubspec.yaml b/packages/veilid_support/example/pubspec.yaml index b8333e6..42f9885 100644 --- a/packages/veilid_support/example/pubspec.yaml +++ b/packages/veilid_support/example/pubspec.yaml @@ -7,6 +7,7 @@ environment: sdk: '>=3.3.4 <4.0.0' dependencies: + collection: ^1.19.1 cupertino_icons: ^1.0.8 flutter: sdk: flutter diff --git a/packages/veilid_support/lib/src/persistent_queue.dart b/packages/veilid_support/lib/src/persistent_queue.dart index 939a5b3..4e5e541 100644 --- a/packages/veilid_support/lib/src/persistent_queue.dart +++ b/packages/veilid_support/lib/src/persistent_queue.dart @@ -9,19 +9,20 @@ import 'config.dart'; import 'table_db.dart'; import 'veilid_log.dart'; -class PersistentQueue - with TableDBBackedFromBuffer> { +class PersistentQueue with TableDBBackedFromBuffer> { // PersistentQueue( {required String table, required String key, required T Function(Uint8List) fromBuffer, + required Uint8List Function(T) toBuffer, required Future Function(IList) closure, bool deleteOnClose = true, void Function(Object, StackTrace)? onError}) : _table = table, _key = key, _fromBuffer = fromBuffer, + _toBuffer = toBuffer, _closure = closure, _deleteOnClose = deleteOnClose, _onError = onError { @@ -34,11 +35,13 @@ class PersistentQueue // Close the sync add stream await _syncAddController.close(); + await _syncAddTask; // Stop the processing trigger await _queueReady.close(); + await _processorTask; - // Wait for any setStates to finish + // No more queue actions await _queueMutex.acquire(); // Clean up table if desired @@ -47,27 +50,40 @@ class PersistentQueue } } + Future get wait async { + // Ensure the init finished + await _initWait(); + + if (_queue.isEmpty) { + return; + } + final completer = Completer(); + _queueDoneCompleter = completer; + await completer.future; + } + Future _init(Completer _) async { // Start the processor - unawaited(Future.delayed(Duration.zero, () async { + _processorTask = Future.delayed(Duration.zero, () async { await _initWait(); await for (final _ in _queueReady.stream) { await _process(); } - })); + }); // Start the sync add controller - unawaited(Future.delayed(Duration.zero, () async { + _syncAddTask = Future.delayed(Duration.zero, () async { await _initWait(); await for (final elem in _syncAddController.stream) { await addAll(elem); } - })); + }); // Load the queue if we have one try { await _queueMutex.protect(() async { _queue = await load() ?? await store(IList.empty()); + _sendUpdateEventsInner(); }); } on Exception catch (e, st) { if (_onError != null) { @@ -78,11 +94,20 @@ class PersistentQueue } } + void _sendUpdateEventsInner() { + assert(_queueMutex.isLocked, 'must be locked'); + if (_queue.isNotEmpty) { + if (!_queueReady.isClosed) { + _queueReady.sink.add(null); + } + } else { + _queueDoneCompleter?.complete(); + } + } + Future _updateQueueInner(IList newQueue) async { _queue = await store(newQueue); - if (_queue.isNotEmpty) { - _queueReady.sink.add(null); - } + _sendUpdateEventsInner(); } Future add(T item) async { @@ -213,7 +238,7 @@ class PersistentQueue Uint8List valueToBuffer(IList val) { final writer = CodedBufferWriter(); for (final elem in val) { - writer.writeRawBytes(elem.writeToBuffer()); + writer.writeRawBytes(_toBuffer(elem)); } return writer.toBuffer(); } @@ -221,12 +246,16 @@ class PersistentQueue final String _table; final String _key; final T Function(Uint8List) _fromBuffer; + final Uint8List Function(T) _toBuffer; final bool _deleteOnClose; final WaitSet _initWait = WaitSet(); - final Mutex _queueMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); - IList _queue = IList.empty(); + final _queueMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); + var _queue = IList.empty(); final StreamController> _syncAddController = StreamController(); final StreamController _queueReady = StreamController(); final Future Function(IList) _closure; final void Function(Object, StackTrace)? _onError; + late Future _processorTask; + late Future _syncAddTask; + Completer? _queueDoneCompleter; } From fa72782f3996e430313d7f5de735d8a5b0370a1e Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 1 Jun 2025 15:09:22 -0400 Subject: [PATCH 80/93] persistent queue fixes --- packages/veilid_support/example/pubspec.lock | 16 ++- packages/veilid_support/example/pubspec.yaml | 10 +- .../lib/src/persistent_queue.dart | 107 +++++++----------- .../lib/src/table_db_array.dart | 77 ++++++------- packages/veilid_support/pubspec.lock | 16 ++- packages/veilid_support/pubspec.yaml | 13 ++- pubspec.lock | 14 +-- pubspec.yaml | 12 +- 8 files changed, 131 insertions(+), 134 deletions(-) diff --git a/packages/veilid_support/example/pubspec.lock b/packages/veilid_support/example/pubspec.lock index bc4ab6f..5c4355b 100644 --- a/packages/veilid_support/example/pubspec.lock +++ b/packages/veilid_support/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: "direct dev" description: name: async_tools - sha256: afd5426e76631172f8ce6a6359b264b092fa9d2a52cd2528100115be9525e067 + sha256: "9611c1efeae7e6d342721d0c2caf2e4783d91fba6a9637d7badfa2dccf8de2a2" url: "https://pub.dev" source: hosted - version: "0.1.9" + version: "0.1.10" bloc: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: bloc_advanced_tools - sha256: dfb142569814952af8d93e7fe045972d847e29382471687db59913e253202f6e + sha256: "63e57000df7259e3007dbfbbfd7dae3e0eca60eb2ac93cbe0c5a3de0e77c9972" url: "https://pub.dev" source: hosted - version: "0.1.12" + version: "0.1.13" boolean_selector: dependency: transitive description: @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + buffer: + dependency: transitive + description: + name: buffer + sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" + url: "https://pub.dev" + source: hosted + version: "1.2.3" change_case: dependency: transitive description: diff --git a/packages/veilid_support/example/pubspec.yaml b/packages/veilid_support/example/pubspec.yaml index 42f9885..86c8e7e 100644 --- a/packages/veilid_support/example/pubspec.yaml +++ b/packages/veilid_support/example/pubspec.yaml @@ -1,10 +1,10 @@ name: example description: "Veilid Support Example" -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: '>=3.3.4 <4.0.0' + sdk: ">=3.3.4 <4.0.0" dependencies: collection: ^1.19.1 @@ -15,7 +15,7 @@ dependencies: path: ../ dev_dependencies: - async_tools: ^0.1.9 + async_tools: ^0.1.10 integration_test: sdk: flutter lint_hard: ^6.0.0 @@ -23,5 +23,9 @@ dev_dependencies: veilid_test: path: ../../../../veilid/veilid-flutter/packages/veilid_test +# dependency_overrides: +# async_tools: +# path: ../../../../dart_async_tools + flutter: uses-material-design: true diff --git a/packages/veilid_support/lib/src/persistent_queue.dart b/packages/veilid_support/lib/src/persistent_queue.dart index 4e5e541..efb4c86 100644 --- a/packages/veilid_support/lib/src/persistent_queue.dart +++ b/packages/veilid_support/lib/src/persistent_queue.dart @@ -2,13 +2,15 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:async_tools/async_tools.dart'; +import 'package:buffer/buffer.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -import 'package:protobuf/protobuf.dart'; import 'config.dart'; import 'table_db.dart'; import 'veilid_log.dart'; +const _ksfSyncAdd = 'ksfSyncAdd'; + class PersistentQueue with TableDBBackedFromBuffer> { // PersistentQueue( @@ -17,7 +19,7 @@ class PersistentQueue with TableDBBackedFromBuffer> { required T Function(Uint8List) fromBuffer, required Uint8List Function(T) toBuffer, required Future Function(IList) closure, - bool deleteOnClose = true, + bool deleteOnClose = false, void Function(Object, StackTrace)? onError}) : _table = table, _key = key, @@ -33,13 +35,12 @@ class PersistentQueue with TableDBBackedFromBuffer> { // Ensure the init finished await _initWait(); - // Close the sync add stream - await _syncAddController.close(); - await _syncAddTask; + // Finish all sync adds + await serialFutureClose((this, _ksfSyncAdd)); // Stop the processing trigger + await _sspQueueReady.close(); await _queueReady.close(); - await _processorTask; // No more queue actions await _queueMutex.acquire(); @@ -50,7 +51,13 @@ class PersistentQueue with TableDBBackedFromBuffer> { } } - Future get wait async { + set deleteOnClose(bool d) { + _deleteOnClose = d; + } + + bool get deleteOnClose => _deleteOnClose; + + Future get waitEmpty async { // Ensure the init finished await _initWait(); @@ -64,21 +71,13 @@ class PersistentQueue with TableDBBackedFromBuffer> { Future _init(Completer _) async { // Start the processor - _processorTask = Future.delayed(Duration.zero, () async { + _sspQueueReady.follow(_queueReady.stream, true, (more) async { await _initWait(); - await for (final _ in _queueReady.stream) { + if (more) { await _process(); } }); - // Start the sync add controller - _syncAddTask = Future.delayed(Duration.zero, () async { - await _initWait(); - await for (final elem in _syncAddController.stream) { - await addAll(elem); - } - }); - // Load the queue if we have one try { await _queueMutex.protect(() async { @@ -98,7 +97,7 @@ class PersistentQueue with TableDBBackedFromBuffer> { assert(_queueMutex.isLocked, 'must be locked'); if (_queue.isNotEmpty) { if (!_queueReady.isClosed) { - _queueReady.sink.add(null); + _queueReady.sink.add(true); } } else { _queueDoneCompleter?.complete(); @@ -127,46 +126,24 @@ class PersistentQueue with TableDBBackedFromBuffer> { } void addSync(T item) { - _syncAddController.sink.add([item]); + serialFuture((this, _ksfSyncAdd), () async { + await add(item); + }); } void addAllSync(Iterable items) { - _syncAddController.sink.add(items); + serialFuture((this, _ksfSyncAdd), () async { + await addAll(items); + }); } - // Future get isEmpty async { - // await _initWait(); - // return state.asData!.value.isEmpty; - // } + Future pause() async { + await _sspQueueReady.pause(); + } - // Future get isNotEmpty async { - // await _initWait(); - // return state.asData!.value.isNotEmpty; - // } - - // Future get length async { - // await _initWait(); - // return state.asData!.value.length; - // } - - // Future pop() async { - // await _initWait(); - // return _processingMutex.protect(() async => _stateMutex.protect(() async { - // final removedItem = Output(); - // final queue = state.asData!.value.removeAt(0, removedItem); - // await _setStateInner(queue); - // return removedItem.value; - // })); - // } - - // Future> popAll() async { - // await _initWait(); - // return _processingMutex.protect(() async => _stateMutex.protect(() async { - // final queue = state.asData!.value; - // await _setStateInner(IList.empty); - // return queue; - // })); - // } + Future resume() async { + await _sspQueueReady.resume(); + } Future _process() async { try { @@ -210,9 +187,10 @@ class PersistentQueue with TableDBBackedFromBuffer> { IList valueFromBuffer(Uint8List bytes) { var out = IList(); try { - final reader = CodedBufferReader(bytes); - while (!reader.isAtEnd()) { - final bytes = reader.readBytesAsView(); + final reader = ByteDataReader()..add(bytes); + while (reader.remainingLength != 0) { + final count = reader.readUint32(); + final bytes = reader.read(count); try { final item = _fromBuffer(bytes); out = out.add(item); @@ -236,26 +214,29 @@ class PersistentQueue with TableDBBackedFromBuffer> { @override Uint8List valueToBuffer(IList val) { - final writer = CodedBufferWriter(); + final writer = ByteDataWriter(); for (final elem in val) { - writer.writeRawBytes(_toBuffer(elem)); + final bytes = _toBuffer(elem); + final count = bytes.lengthInBytes; + writer + ..writeUint32(count) + ..write(bytes); } - return writer.toBuffer(); + return writer.toBytes(); } final String _table; final String _key; final T Function(Uint8List) _fromBuffer; final Uint8List Function(T) _toBuffer; - final bool _deleteOnClose; + bool _deleteOnClose; final WaitSet _initWait = WaitSet(); final _queueMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); var _queue = IList.empty(); - final StreamController> _syncAddController = StreamController(); - final StreamController _queueReady = StreamController(); final Future Function(IList) _closure; final void Function(Object, StackTrace)? _onError; - late Future _processorTask; - late Future _syncAddTask; Completer? _queueDoneCompleter; + + final StreamController _queueReady = StreamController(); + final _sspQueueReady = SingleStateProcessor(); } diff --git a/packages/veilid_support/lib/src/table_db_array.dart b/packages/veilid_support/lib/src/table_db_array.dart index 8b59336..53adeb0 100644 --- a/packages/veilid_support/lib/src/table_db_array.dart +++ b/packages/veilid_support/lib/src/table_db_array.dart @@ -46,7 +46,7 @@ class _TableDBArrayBase { await _initWait(); } - Future _init(_) async { + Future _init(Completer _) async { // Load the array details await _mutex.protect(() async { _tableDB = await Veilid.instance.openTableDB(_table, 1); @@ -102,27 +102,27 @@ class _TableDBArrayBase { Future _add(Uint8List value) async { await _initWait(); - return _writeTransaction((t) async => _addInner(t, value)); + return _writeTransaction((t) => _addInner(t, value)); } Future _addAll(List values) async { await _initWait(); - return _writeTransaction((t) async => _addAllInner(t, values)); + return _writeTransaction((t) => _addAllInner(t, values)); } Future _insert(int pos, Uint8List value) async { await _initWait(); - return _writeTransaction((t) async => _insertInner(t, pos, value)); + return _writeTransaction((t) => _insertInner(t, pos, value)); } Future _insertAll(int pos, List values) async { await _initWait(); - return _writeTransaction((t) async => _insertAllInner(t, pos, values)); + return _writeTransaction((t) => _insertAllInner(t, pos, values)); } Future _get(int pos) async { await _initWait(); - return _mutex.protect(() async { + return _mutex.protect(() { if (!_open) { throw StateError('not open'); } @@ -132,7 +132,7 @@ class _TableDBArrayBase { Future> _getRange(int start, [int? end]) async { await _initWait(); - return _mutex.protect(() async { + return _mutex.protect(() { if (!_open) { throw StateError('not open'); } @@ -142,14 +142,13 @@ class _TableDBArrayBase { Future _remove(int pos, {Output? out}) async { await _initWait(); - return _writeTransaction((t) async => _removeInner(t, pos, out: out)); + return _writeTransaction((t) => _removeInner(t, pos, out: out)); } Future _removeRange(int start, int end, {Output>? out}) async { await _initWait(); - return _writeTransaction( - (t) async => _removeRangeInner(t, start, end, out: out)); + return _writeTransaction((t) => _removeRangeInner(t, start, end, out: out)); } Future clear() async { @@ -331,24 +330,24 @@ class _TableDBArrayBase { //////////////////////////////////////////////////////////// // Private implementation - static final Uint8List _headKey = Uint8List.fromList([$_, $H, $E, $A, $D]); + static final _headKey = Uint8List.fromList([$_, $H, $E, $A, $D]); static Uint8List _entryKey(int k) => (ByteData(4)..setUint32(0, k)).buffer.asUint8List(); static Uint8List _chunkKey(int n) => (ByteData(2)..setUint16(0, n)).buffer.asUint8List(); Future _writeTransaction( - Future Function(VeilidTableDBTransaction) closure) async => + Future Function(VeilidTableDBTransaction) closure) => _mutex.protect(() async { if (!_open) { throw StateError('not open'); } - final _oldLength = _length; - final _oldNextFree = _nextFree; - final _oldMaxEntry = _maxEntry; - final _oldHeadDelta = _headDelta; - final _oldTailDelta = _tailDelta; + final oldLength = _length; + final oldNextFree = _nextFree; + final oldMaxEntry = _maxEntry; + final oldHeadDelta = _headDelta; + final oldTailDelta = _tailDelta; try { final out = await transactionScope(_tableDB, (t) async { final out = await closure(t); @@ -365,11 +364,11 @@ class _TableDBArrayBase { return out; } on Exception { // restore head - _length = _oldLength; - _nextFree = _oldNextFree; - _maxEntry = _oldMaxEntry; - _headDelta = _oldHeadDelta; - _tailDelta = _oldTailDelta; + _length = oldLength; + _nextFree = oldNextFree; + _maxEntry = oldMaxEntry; + _headDelta = oldHeadDelta; + _tailDelta = oldTailDelta; // invalidate caches because they could have been written to _chunkCache.clear(); _dirtyChunks.clear(); @@ -415,7 +414,7 @@ class _TableDBArrayBase { _dirtyChunks[chunkNumber] = chunk; } - Future _insertIndexEntry(int pos) async => _insertIndexEntries(pos, 1); + Future _insertIndexEntry(int pos) => _insertIndexEntries(pos, 1); Future _insertIndexEntries(int start, int length) async { if (length == 0) { @@ -474,7 +473,7 @@ class _TableDBArrayBase { _tailDelta += length; } - Future _removeIndexEntry(int pos) async => _removeIndexEntries(pos, 1); + Future _removeIndexEntry(int pos) => _removeIndexEntries(pos, 1); Future _removeIndexEntries(int start, int length) async { if (length == 0) { @@ -624,20 +623,20 @@ class _TableDBArrayBase { var _initDone = false; final VeilidCrypto _crypto; final WaitSet _initWait = WaitSet(); - final Mutex _mutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); + final _mutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); // Change tracking - int _headDelta = 0; - int _tailDelta = 0; + var _headDelta = 0; + var _tailDelta = 0; // Head state - int _length = 0; - int _nextFree = 0; - int _maxEntry = 0; - static const int _indexStride = 16384; + var _length = 0; + var _nextFree = 0; + var _maxEntry = 0; + static const _indexStride = 16384; final List<(int, Uint8List)> _chunkCache = []; final Map _dirtyChunks = {}; - static const int _chunkCacheLength = 3; + static const _chunkCacheLength = 3; final StreamController _changeStream = StreamController.broadcast(); @@ -711,13 +710,12 @@ class TableDBArrayJson extends _TableDBArrayBase { Future add(T value) => _add(jsonEncodeBytes(value)); - Future addAll(List values) async => + Future addAll(List values) => _addAll(values.map(jsonEncodeBytes).toList()); - Future insert(int pos, T value) async => - _insert(pos, jsonEncodeBytes(value)); + Future insert(int pos, T value) => _insert(pos, jsonEncodeBytes(value)); - Future insertAll(int pos, List values) async => + Future insertAll(int pos, List values) => _insertAll(pos, values.map(jsonEncodeBytes).toList()); Future get( @@ -774,13 +772,12 @@ class TableDBArrayProtobuf Future add(T value) => _add(value.writeToBuffer()); - Future addAll(List values) async => + Future addAll(List values) => _addAll(values.map((x) => x.writeToBuffer()).toList()); - Future insert(int pos, T value) async => - _insert(pos, value.writeToBuffer()); + Future insert(int pos, T value) => _insert(pos, value.writeToBuffer()); - Future insertAll(int pos, List values) async => + Future insertAll(int pos, List values) => _insertAll(pos, values.map((x) => x.writeToBuffer()).toList()); Future get( diff --git a/packages/veilid_support/pubspec.lock b/packages/veilid_support/pubspec.lock index b4c7eef..0d2320e 100644 --- a/packages/veilid_support/pubspec.lock +++ b/packages/veilid_support/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: "direct main" description: name: async_tools - sha256: afd5426e76631172f8ce6a6359b264b092fa9d2a52cd2528100115be9525e067 + sha256: "9611c1efeae7e6d342721d0c2caf2e4783d91fba6a9637d7badfa2dccf8de2a2" url: "https://pub.dev" source: hosted - version: "0.1.9" + version: "0.1.10" bloc: dependency: "direct main" description: @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: bloc_advanced_tools - sha256: dfb142569814952af8d93e7fe045972d847e29382471687db59913e253202f6e + sha256: "63e57000df7259e3007dbfbbfd7dae3e0eca60eb2ac93cbe0c5a3de0e77c9972" url: "https://pub.dev" source: hosted - version: "0.1.12" + version: "0.1.13" boolean_selector: dependency: transitive description: @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + buffer: + dependency: "direct main" + description: + name: buffer + sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" + url: "https://pub.dev" + source: hosted + version: "1.2.3" build: dependency: transitive description: diff --git a/packages/veilid_support/pubspec.yaml b/packages/veilid_support/pubspec.yaml index 8864fa6..5fdb74b 100644 --- a/packages/veilid_support/pubspec.yaml +++ b/packages/veilid_support/pubspec.yaml @@ -7,9 +7,10 @@ environment: sdk: ">=3.2.0 <4.0.0" dependencies: - async_tools: ^0.1.9 + async_tools: ^0.1.10 bloc: ^9.0.0 - bloc_advanced_tools: ^0.1.12 + bloc_advanced_tools: ^0.1.13 + buffer: ^1.2.3 charcode: ^1.4.0 collection: ^1.19.1 convert: ^3.1.2 @@ -29,10 +30,10 @@ dependencies: path: ../../../veilid/veilid-flutter # dependency_overrides: -# async_tools: -# path: ../../../dart_async_tools -# bloc_advanced_tools: -# path: ../../../bloc_advanced_tools +# async_tools: +# path: ../../../dart_async_tools +# bloc_advanced_tools: +# path: ../../../bloc_advanced_tools dev_dependencies: build_runner: ^2.4.15 diff --git a/pubspec.lock b/pubspec.lock index 3b7f823..ae51a3d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -92,10 +92,9 @@ packages: async_tools: dependency: "direct main" description: - name: async_tools - sha256: afd5426e76631172f8ce6a6359b264b092fa9d2a52cd2528100115be9525e067 - url: "https://pub.dev" - source: hosted + path: "../dart_async_tools" + relative: true + source: path version: "0.1.9" auto_size_text: dependency: "direct main" @@ -156,10 +155,9 @@ packages: bloc_advanced_tools: dependency: "direct main" description: - name: bloc_advanced_tools - sha256: dfb142569814952af8d93e7fe045972d847e29382471687db59913e253202f6e - url: "https://pub.dev" - source: hosted + path: "../bloc_advanced_tools" + relative: true + source: path version: "0.1.12" blurry_modal_progress_hud: dependency: "direct main" diff --git a/pubspec.yaml b/pubspec.yaml index fccff99..e605c0e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,13 +15,13 @@ dependencies: animated_theme_switcher: ^2.0.10 ansicolor: ^2.0.3 archive: ^4.0.4 - async_tools: ^0.1.9 + async_tools: ^0.1.10 auto_size_text: ^3.0.0 awesome_extensions: ^2.0.21 badges: ^3.1.2 basic_utils: ^5.8.2 bloc: ^9.0.0 - bloc_advanced_tools: ^0.1.12 + bloc_advanced_tools: ^0.1.13 blurry_modal_progress_hud: ^1.1.1 change_case: ^2.2.0 charcode: ^1.4.0 @@ -108,10 +108,10 @@ dependencies: dependency_overrides: intl: ^0.20.2 # Until flutter_translate updates intl -# async_tools: -# path: ../dart_async_tools -# bloc_advanced_tools: -# path: ../bloc_advanced_tools +# async_tools: +# path: ../dart_async_tools +# bloc_advanced_tools: +# path: ../bloc_advanced_tools # searchable_listview: # path: ../Searchable-Listview # flutter_chat_core: From 2caaf35d52b0dc66873abc0af85ea9026442373e Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 1 Jun 2025 18:26:12 -0400 Subject: [PATCH 81/93] update deps, fix context --- .../views/invitation_dialog.dart | 6 +- lib/layout/home/home_screen.dart | 90 ++++++++++--------- pubspec.lock | 26 ++++-- 3 files changed, 68 insertions(+), 54 deletions(-) diff --git a/lib/contact_invitation/views/invitation_dialog.dart b/lib/contact_invitation/views/invitation_dialog.dart index 3cd0bfb..a5a7fad 100644 --- a/lib/contact_invitation/views/invitation_dialog.dart +++ b/lib/contact_invitation/views/invitation_dialog.dart @@ -291,10 +291,12 @@ class InvitationDialogState extends State { const Icon(Icons.error).paddingAll(16) ]).toCenter(), if (_validInvitation != null && !_isValidating) - Column(children: [ + Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ProfileWidget( profile: _validInvitation!.remoteProfile, - byline: _validInvitation!.remoteProfile.pronouns, + byline: _validInvitation!.remoteProfile.pronouns.isEmpty + ? null + : _validInvitation!.remoteProfile.pronouns, ).paddingLTRB(0, 0, 0, 16), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index c9f1ecb..2aefcd3 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -67,53 +67,55 @@ class HomeScreenState extends State final scaleConfig = theme.extension()!; await showAlertWidgetModal( - context: context, - title: translate('splash.beta_title'), - child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.warning, size: 64.scaled(context)), - RichText( - textScaler: MediaQuery.of(context).textScaler, - textAlign: TextAlign.center, - text: TextSpan( - children: [ - TextSpan( - text: translate('splash.beta_text'), + context: context, + title: translate('splash.beta_title'), + child: Builder( + builder: (context) => + Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Icon(Icons.warning, size: 64.scaled(context)), + RichText( + textScaler: MediaQuery.of(context).textScaler, + textAlign: TextAlign.center, + text: TextSpan( + children: [ + TextSpan( + text: translate('splash.beta_text'), + style: theme.textTheme.bodyMedium! + .copyWith(color: scale.primaryScale.appText), + ), + TextSpan( + text: 'https://veilid.com/chat/knownissues', + style: theme.textTheme.bodyMedium!.copyWith( + color: scaleConfig.useVisualIndicators + ? scale.secondaryScale.primaryText + : scale.secondaryScale.primary, + decoration: TextDecoration.underline, + ), + recognizer: TapGestureRecognizer() + ..onTap = () => launchUrlString( + 'https://veilid.com/chat/knownissues'), + ), + ], + ), + ), + Row(mainAxisSize: MainAxisSize.min, children: [ + StatefulBuilder( + builder: (context, setState) => Checkbox( + value: displayBetaWarning, + onChanged: (value) { + setState(() { + displayBetaWarning = value ?? true; + }); + }, + )), + Text( + translate('settings_page.display_beta_warning'), style: theme.textTheme.bodyMedium! .copyWith(color: scale.primaryScale.appText), ), - TextSpan( - text: 'https://veilid.com/chat/knownissues', - style: theme.textTheme.bodyMedium!.copyWith( - color: scaleConfig.useVisualIndicators - ? scale.secondaryScale.primaryText - : scale.secondaryScale.primary, - decoration: TextDecoration.underline, - ), - recognizer: TapGestureRecognizer() - ..onTap = () => - launchUrlString('https://veilid.com/chat/knownissues'), - ), - ], - ), - ), - Row(mainAxisSize: MainAxisSize.min, children: [ - StatefulBuilder( - builder: (context, setState) => Checkbox( - value: displayBetaWarning, - onChanged: (value) { - setState(() { - displayBetaWarning = value ?? true; - }); - }, - )), - Text( - translate('settings_page.display_beta_warning'), - style: theme.textTheme.bodyMedium! - .copyWith(color: scale.primaryScale.appText), - ), - ]), - ]), - ); + ]), + ]), + )); final preferencesInstance = PreferencesRepository.instance; await preferencesInstance.set(preferencesInstance.value.copyWith( diff --git a/pubspec.lock b/pubspec.lock index ae51a3d..779806e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -92,10 +92,11 @@ packages: async_tools: dependency: "direct main" description: - path: "../dart_async_tools" - relative: true - source: path - version: "0.1.9" + name: async_tools + sha256: "9611c1efeae7e6d342721d0c2caf2e4783d91fba6a9637d7badfa2dccf8de2a2" + url: "https://pub.dev" + source: hosted + version: "0.1.10" auto_size_text: dependency: "direct main" description: @@ -155,10 +156,11 @@ packages: bloc_advanced_tools: dependency: "direct main" description: - path: "../bloc_advanced_tools" - relative: true - source: path - version: "0.1.12" + name: bloc_advanced_tools + sha256: "63e57000df7259e3007dbfbbfd7dae3e0eca60eb2ac93cbe0c5a3de0e77c9972" + url: "https://pub.dev" + source: hosted + version: "0.1.13" blurry_modal_progress_hud: dependency: "direct main" description: @@ -175,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + buffer: + dependency: transitive + description: + name: buffer + sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" + url: "https://pub.dev" + source: hosted + version: "1.2.3" build: dependency: transitive description: From 8fe09555d13933bcf9cdfbf72a29246194c13dbd Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 1 Jun 2025 20:57:02 -0400 Subject: [PATCH 82/93] ensure reconciliation happens when remote conversation is added --- lib/chat/cubits/single_contact_messages_cubit.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/chat/cubits/single_contact_messages_cubit.dart b/lib/chat/cubits/single_contact_messages_cubit.dart index a3e7656..0ec1037 100644 --- a/lib/chat/cubits/single_contact_messages_cubit.dart +++ b/lib/chat/cubits/single_contact_messages_cubit.dart @@ -198,6 +198,9 @@ class SingleContactMessagesCubit extends Cubit { // Init the new DHTLog if we should _remoteMessagesRecordKey = remoteMessagesRecordKey; await _initRcvdMessagesDHTLog(); + + // Run reconciliation once for all input queues + _reconciliation.reconcileMessages(null); }); } From 3c276d42d9654118ecce6a8e58a6c052a3f5d934 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 1 Jun 2025 21:04:18 -0400 Subject: [PATCH 83/93] changelog --- CHANGELOG.md | 1 + .../test_persistent_queue.dart | 196 ++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 packages/veilid_support/example/integration_test/test_persistent_queue.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f7eefa..a5dc471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Fixed issue with Android 'back' button exiting the app (#331) - Deprecated accounts no longer crash application at startup - Simplify SingleContactMessagesCubit and MessageReconciliation +- Ensure first messages get received when opening a new chat - Update flutter_chat_ui to 2.0.0 - Accessibility improvements - Text scaling diff --git a/packages/veilid_support/example/integration_test/test_persistent_queue.dart b/packages/veilid_support/example/integration_test/test_persistent_queue.dart new file mode 100644 index 0000000..51f8004 --- /dev/null +++ b/packages/veilid_support/example/integration_test/test_persistent_queue.dart @@ -0,0 +1,196 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:async_tools/async_tools.dart'; +import 'package:collection/collection.dart'; +import 'package:test/test.dart'; +import 'package:veilid_support/veilid_support.dart'; + +Future testPersistentQueueOpenClose() async { + final pq = PersistentQueue( + table: 'persistent_queue_integration_test', + key: 'open_close', + fromBuffer: (buf) => utf8.decode(buf), + toBuffer: (s) => utf8.encode(s), + closure: (elems) async { + // + }); + + await pq.close(); +} + +Future testPersistentQueueAdd() async { + final added = {}; + for (var n = 0; n < 100; n++) { + final elem = 'FOOBAR #$n'; + added.add(elem); + } + + final done = {}; + final pq = PersistentQueue( + table: 'persistent_queue_integration_test', + key: 'add', + fromBuffer: (buf) => utf8.decode(buf), + toBuffer: (s) => utf8.encode(s), + closure: (elems) async { + done.addAll(elems); + }); + + var oddeven = false; + for (final chunk in added.slices(10)) { + if (!oddeven) { + await chunk.map(pq.add).wait; + } else { + await pq.addAll(chunk); + } + oddeven = !oddeven; + } + + await pq.close(); + + expect(done, equals(added)); +} + +Future testPersistentQueueAddSync() async { + final added = {}; + for (var n = 0; n < 100; n++) { + final elem = 'FOOBAR #$n'; + added.add(elem); + } + + final done = {}; + final pq = PersistentQueue( + table: 'persistent_queue_integration_test', + key: 'add_sync', + fromBuffer: (buf) => utf8.decode(buf), + toBuffer: (s) => utf8.encode(s), + closure: (elems) async { + done.addAll(elems); + }); + + var oddeven = false; + for (final chunk in added.slices(10)) { + if (!oddeven) { + await chunk.map((x) async { + await asyncSleep(Duration.zero); + pq.addSync(x); + }).wait; + } else { + pq.addAllSync(chunk); + } + oddeven = !oddeven; + } + + await pq.close(); + + expect(done, equals(added)); +} + +Future testPersistentQueueAddPersist() async { + final added = {}; + for (var n = 0; n < 100; n++) { + final elem = 'FOOBAR #$n'; + added.add(elem); + } + + final done = {}; + + late final PersistentQueue pq; + + pq = PersistentQueue( + table: 'persistent_queue_integration_test', + key: 'add_persist', + fromBuffer: (buf) => utf8.decode(buf), + toBuffer: (s) => utf8.encode(s), + closure: (elems) async { + done.addAll(elems); + }); + + // Start it paused + await pq.pause(); + + // Add all elements + var oddeven = false; + + for (final chunk in added.slices(10)) { + if (!oddeven) { + await chunk.map(pq.add).wait; + } else { + await pq.addAll(chunk); + } + oddeven = !oddeven; + } + + // Close the persistent queue + await pq.close(); + + // Create a new persistent queue that processes the items + final pq2 = PersistentQueue( + table: 'persistent_queue_integration_test', + key: 'add_persist', + fromBuffer: (buf) => utf8.decode(buf), + toBuffer: (s) => utf8.encode(s), + closure: (elems) async { + done.addAll(elems); + }); + await pq2.waitEmpty; + await pq2.close(); + + expect(done, equals(added)); +} + +Future testPersistentQueueAddSyncPersist() async { + final added = {}; + for (var n = 0; n < 100; n++) { + final elem = 'FOOBAR #$n'; + added.add(elem); + } + + final done = {}; + + late final PersistentQueue pq; + + pq = PersistentQueue( + table: 'persistent_queue_integration_test', + key: 'add_persist', + fromBuffer: (buf) => utf8.decode(buf), + toBuffer: (s) => utf8.encode(s), + closure: (elems) async { + done.addAll(elems); + }); + + // Start it paused + await pq.pause(); + + // Add all elements + var oddeven = false; + + for (final chunk in added.slices(10)) { + if (!oddeven) { + await chunk.map((x) async { + await asyncSleep(Duration.zero); + pq.addSync(x); + }).wait; + } else { + pq.addAllSync(chunk); + } + oddeven = !oddeven; + } + + // Close the persistent queue + await pq.close(); + + // Create a new persistent queue that processes the items + final pq2 = PersistentQueue( + table: 'persistent_queue_integration_test', + key: 'add_persist', + fromBuffer: (buf) => utf8.decode(buf), + toBuffer: (s) => utf8.encode(s), + closure: (elems) async { + done.addAll(elems); + }); + await pq2.waitEmpty; + await pq2.close(); + + expect(done, equals(added)); +} From 04092fed74e89657d18a1f0e4a7159972d56f016 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 1 Jun 2025 21:05:09 -0400 Subject: [PATCH 84/93] oops --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5dc471..2e11c9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - Accessibility improvements - Text scaling - Keyboard shortcuts Ctrl + / Ctrl - to change font size + - Keyboard shortcut Ctrl+Alt+C - to change color scheme + - Keyboard shortcut Ctrl+Alt+B - to change theme brightness ## v0.4.7 ## - *Community Contributions* From ee37d0dbba1b486ab5974f4d333ad6348795894c Mon Sep 17 00:00:00 2001 From: iKranium-Labs Date: Tue, 3 Jun 2025 17:47:09 +0000 Subject: [PATCH 85/93] refactor README.md for clarity and structure; enhance setup instructions and... --- README.md | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 160 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index dc02697..5e303cd 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,175 @@ # VeilidChat -VeilidChat is a chat application written for the Veilid (https://www.veilid.com) distributed application platform. It has a familiar and simple interface and is designed for private, and secure person-to-person communications. +## Overview -For more information about VeilidChat: https://veilid.com/chat/ +VeilidChat is a decentralized, secure, and private chat application built upon the [Veilid](https://www.veilid.com) distributed application platform. It offers a familiar messaging interface while leveraging Veilid's underlying end-to-end encrypted and peer-to-peer communication capabilities to ensure privacy and security for person-to-person communications without relying on centralized servers. -For more information about the Veilid network protocol and app development platform: https://veilid.com +For more information about VeilidChat: + +For more information about the Veilid network protocol and app development platform: ## Setup -While this is still in development, you must have a clone of the Veilid source checked out at `../veilid` relative to the working directory of this repository. +### Prerequisites + +VeilidChat is a Flutter application that interacts with the core Veilid library via Foreign Function Interface (FFI). While this is still in development, you **must** have a clone of the Veilid source checked out at `../veilid` relative to the working directory of this repository for the setup scripts to work. This is because the veilid-core source and build setup (including FFI components), which Veilidchat relies on, reside there. -### For Linux Systems: ``` -./dev-setup/setup_linux.sh +your_workspace/ +├── veilid/ <-- Veilid core repository +└── veilidchat/ <-- This repository ``` -### For Mac Systems: -``` -./dev-setup/setup_macos.sh +Refer to the main [Veilid repository](https://gitlab.com/veilid/veilid) for instructions. + +
+ +### Flutter Installation + +VeilidChat requires the Flutter SDK to build and run. Ensure Flutter is installed and available in your system's PATH. Choose one of the methods below: + + +**Option 1: Standard Installation (Recommended)** + +Follow the official Flutter documentation for your operating system to install the SDK directly. + +* **Official Flutter Install Guides:** + * [Windows](https://docs.flutter.dev/get-started/install/windows) + * [macOS](https://docs.flutter.dev/get-started/install/macos) + * [Linux](https://docs.flutter.dev/get-started/install/linux) + * [ChromeOS](https://docs.flutter.dev/get-started/install/chromeos) + + +**Option 2: Installation via IDE Extension (Beginner-friendly for VS Code)** + +Varioius IDEs may offer Flutter extensions that can assist with setup. For VS Code, the official Flutter extension can assist you through the SDK installation process. + +1. Open VS Code. +2. Go to the Extensions view (`Ctrl+Shift+X` or `Cmd+Shift+X`). +3. Search for "Flutter" and install the official extension published by Dart Code. +4. Follow the prompts from the extension to install the Flutter SDK. + +
+ +**Running Veilid Core Setup Scripts:** + +In order to run the VeilidChat application, you will need the [Veilid repository](https://gitlab.com/veilid/veilid) repository set up correctly as mentioned in [Prerequisites](#prerequisites) above. The veilidchat setup scripts in [`./dev-setup`](./dev-setup) handle building the necessary Veilid components and checking for or installing other required tools like `protoc`. + +**Note:** These scripts require Flutter to be **already installed and accessible in your system's PATH** before they are run. + +To run these setup scripts (from the [`veilidchat`](veilidchat) directory): + +* For Linux Systems: Run [./dev-setup/setup_linux.sh](./dev-setup/setup_linux.sh) (Installs protoc and protoc-gen-dart) +* For Mac Systems: Run [./dev-setup/setup_macos.sh](./dev-setup/setup_macos.sh) (Installs protoc and protoc-gen-dart) +* For Windows Systems: Run [./dev-setup/setup_windows.bat](./dev-setup/setup_windows.bat) (**Requires manual installation of protoc beforehand**) + (check [`./dev-setup`](./dev-setup) for other platforms) + +These scripts will check for required dependencies and set up the environment. For Windows users, please ensure you have manually downloaded and installed the protoc compiler (version 25.3 or higher is recommended) and added its directory to your system's PATH *before* running the setup script. The Windows script will verify its presence. + + +**Note on Python Environments:** The dev-setup scripts in the main Veilid repository may utilize Python virtual environments (`venv`) for managing dependencies needed for build processes (like FFI generation scripts). If your system uses a system-managed Python environment or you encounter permission issues, ensure you follow any instructions provided by the setup scripts regarding environment activation or configuration. + + +### Verifying Installation + +After installing Flutter and running the ./dev-setup scripts, verify that everything is set up correctly. Open a terminal and run the following command from anywhere accessible by your PATH: + +`$ flutter doctor` + +This command checks your environment and displays a report of the status of your Flutter installation and connected devices. It will list any missing dependencies or configuration issues for common development platforms (Android, iOS, Web, Desktop). + + +**Example Output (Partial):** ``` -## Updating Code +$ flutter doctor +Doctor summary (to see all details, run flutter doctor -v): +[√] Flutter (Channel stable, 3.x.x, on macOS 13.x.x 22Gxxx darwin-x64, locale en-US) +[√] Android toolchain - develop for Android devices (Android SDK version 3x.x.x) +[!] Xcode - develop for iOS and macOS + ✗ Xcode installation is incomplete. + Install Xcode from the App Store. +[√] Chrome - develop for the web +[√] Linux toolchain - develop for Linux desktop +[√] VS Code (version 1.xx.x) +[√] Connected device (1 available) -### To update the WASM binary from `veilid-wasm`: -* Debug WASM: run `./dev-setup/wasm_update.sh` -* Release WASM: run `./dev-setup/wasm_update.sh release` +! Doctor found issues in 1 category. +Address any issues reported by `flutter doctor` before proceeding. +``` +
+ +## Building and Launching + +VeilidChat is a Flutter application and can be built and launched on various platforms supported by Flutter, provided you have the necessary SDKs and devices/emulators configured as verified by `flutter doctor`. + +1. **Ensure Flutter dependencies are installed**: + From the `veilidchat` directory, run: + ```bash + flutter pub get + ``` + +2. **List available devices**: + To see which devices (simulators, emulators, connected physical devices, desktop targets, web browsers) are available to run the application on, use the command: + ```bash + flutter devices + ``` + +3. **Run on a specific device**: + Use the `flutter run` command followed by the `-d` flag and the device ID from the `flutter devices` list. + + * **Example (Android emulator/device):** + Assuming an Android device ID like `emulator-5554`: + ```bash + flutter run -d emulator-5554 + ``` + If only one device is connected, you can often omit the `-d` flag. + + * **Example (iOS simulator/device):** + Assuming an iOS simulator ID like `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`: + ```bash + flutter run -d xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` (replace with actual ID) + ``` + Or, to target the default iOS simulator: + ```bash + flutter run -d simulator + ``` + + * **Example (Linux Desktop):** + ```bash + flutter run -d linux + ``` + + * **Example (macOS Desktop):** + ```bash + flutter run -d macos + ``` + + * **Example (Windows Desktop):** + ```bash + flutter run -d windows + ``` + + * **Example (Web):** + ```bash + flutter run -d web # Or a specific browser like 'chrome' or 'firefox' if listed by `flutter devices` + ``` + This will typically launch the application in your selected web browser. + +
+ +## Updating the WASM Binary + +### To update the WASM binary +[from the `veilid-wasm` package located in [`../veilid/veilid-wasm`](../veilid/veilid-wasm)] + + +From the VeilidChat repository working directory ([`./veilidchat`](./veilidchat)), run the appropriate script: + +* Debug WASM: [`./dev-setup/wasm_update.sh`](./dev-setup/wasm_update.sh) +* Release WASM: [`./dev-setup/wasm_update.sh release`](./dev-setup/wasm_update.sh) + +
+ +Refer to the official [Flutter documentation](https://docs.flutter.dev/) for more detailed information on building and deployment with Flutter. \ No newline at end of file From 0ad7a1c0c1323187cd238886a3b7684e98e3ee0a Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Tue, 3 Jun 2025 13:00:38 -0500 Subject: [PATCH 86/93] Updated CHANGELOG for v0.4.8 release Also corrected some typos and linting in the new README.md --- CHANGELOG.md | 9 ++++-- README.md | 83 ++++++++++++++++++++++++++-------------------------- 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e11c9c..21576d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## UNRELEASED ## +## v0.4.8 ## - Fix reconciliation `advance()` - Add `pool stats` command @@ -13,8 +13,11 @@ - Keyboard shortcut Ctrl+Alt+C - to change color scheme - Keyboard shortcut Ctrl+Alt+B - to change theme brightness +- _Community Contributions_ + - Refactor README.md for clarity and structure; enhance setup instructions and Flutter installation guidance @iKranium-Labs // @iKranium + ## v0.4.7 ## -- *Community Contributions* +- _Community Contributions_ - Fix getting stuck on splash screen when veilid is already started @bmv437 / @bgrift - Fix routing to home after initial account creation @bmv437 / @bgrift - edit_account_form visual improvements @bmv437 / @bgrift @@ -79,7 +82,7 @@ - Support away/busy/free state - Support away/busy/free messages - Start of UI for auto-away feature (incomplete) -- *Community Contributions Shoutouts* +- _Community Contributions Shoutouts_ - @ethnh - @sajattack - @jasikpark diff --git a/README.md b/README.md index 5e303cd..c20c08e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ For more information about the Veilid network protocol and app development platf VeilidChat is a Flutter application that interacts with the core Veilid library via Foreign Function Interface (FFI). While this is still in development, you **must** have a clone of the Veilid source checked out at `../veilid` relative to the working directory of this repository for the setup scripts to work. This is because the veilid-core source and build setup (including FFI components), which Veilidchat relies on, reside there. -``` +```shell your_workspace/ ├── veilid/ <-- Veilid core repository └── veilidchat/ <-- This repository @@ -22,34 +22,28 @@ your_workspace/ Refer to the main [Veilid repository](https://gitlab.com/veilid/veilid) for instructions. -
- ### Flutter Installation VeilidChat requires the Flutter SDK to build and run. Ensure Flutter is installed and available in your system's PATH. Choose one of the methods below: - **Option 1: Standard Installation (Recommended)** Follow the official Flutter documentation for your operating system to install the SDK directly. -* **Official Flutter Install Guides:** - * [Windows](https://docs.flutter.dev/get-started/install/windows) - * [macOS](https://docs.flutter.dev/get-started/install/macos) - * [Linux](https://docs.flutter.dev/get-started/install/linux) - * [ChromeOS](https://docs.flutter.dev/get-started/install/chromeos) - +* **Official Flutter Install Guides:** + * [Windows](https://docs.flutter.dev/get-started/install/windows) + * [macOS](https://docs.flutter.dev/get-started/install/macos) + * [Linux](https://docs.flutter.dev/get-started/install/linux) + * [ChromeOS](https://docs.flutter.dev/get-started/install/chromeos) **Option 2: Installation via IDE Extension (Beginner-friendly for VS Code)** -Varioius IDEs may offer Flutter extensions that can assist with setup. For VS Code, the official Flutter extension can assist you through the SDK installation process. +Various IDEs may offer Flutter extensions that can assist with setup. For VS Code, the official Flutter extension can assist you through the SDK installation process. -1. Open VS Code. -2. Go to the Extensions view (`Ctrl+Shift+X` or `Cmd+Shift+X`). -3. Search for "Flutter" and install the official extension published by Dart Code. -4. Follow the prompts from the extension to install the Flutter SDK. - -
+1. Open VS Code. +2. Go to the Extensions view (`Ctrl+Shift+X` or `Cmd+Shift+X`). +3. Search for "Flutter" and install the official extension published by Dart Code. +4. Follow the prompts from the extension to install the Flutter SDK. **Running Veilid Core Setup Scripts:** @@ -59,18 +53,16 @@ In order to run the VeilidChat application, you will need the [Veilid repository To run these setup scripts (from the [`veilidchat`](veilidchat) directory): -* For Linux Systems: Run [./dev-setup/setup_linux.sh](./dev-setup/setup_linux.sh) (Installs protoc and protoc-gen-dart) -* For Mac Systems: Run [./dev-setup/setup_macos.sh](./dev-setup/setup_macos.sh) (Installs protoc and protoc-gen-dart) -* For Windows Systems: Run [./dev-setup/setup_windows.bat](./dev-setup/setup_windows.bat) (**Requires manual installation of protoc beforehand**) +* For Linux Systems: Run [./dev-setup/setup_linux.sh](./dev-setup/setup_linux.sh) (Installs protoc and protoc-gen-dart) +* For Mac Systems: Run [./dev-setup/setup_macos.sh](./dev-setup/setup_macos.sh) (Installs protoc and protoc-gen-dart) +* For Windows Systems: Run [./dev-setup/setup_windows.bat](./dev-setup/setup_windows.bat) (**Requires manual installation of protoc beforehand**) (check [`./dev-setup`](./dev-setup) for other platforms) These scripts will check for required dependencies and set up the environment. For Windows users, please ensure you have manually downloaded and installed the protoc compiler (version 25.3 or higher is recommended) and added its directory to your system's PATH *before* running the setup script. The Windows script will verify its presence. - **Note on Python Environments:** The dev-setup scripts in the main Veilid repository may utilize Python virtual environments (`venv`) for managing dependencies needed for build processes (like FFI generation scripts). If your system uses a system-managed Python environment or you encounter permission issues, ensure you follow any instructions provided by the setup scripts regarding environment activation or configuration. - -### Verifying Installation +## Verifying Installation After installing Flutter and running the ./dev-setup scripts, verify that everything is set up correctly. Open a terminal and run the following command from anywhere accessible by your PATH: @@ -78,9 +70,9 @@ After installing Flutter and running the ./dev-setup scripts, verify that everyt This command checks your environment and displays a report of the status of your Flutter installation and connected devices. It will list any missing dependencies or configuration issues for common development platforms (Android, iOS, Web, Desktop). - **Example Output (Partial):** -``` + +```shell $ flutter doctor Doctor summary (to see all details, run flutter doctor -v): @@ -98,78 +90,85 @@ Doctor summary (to see all details, run flutter doctor -v): Address any issues reported by `flutter doctor` before proceeding. ``` -
## Building and Launching VeilidChat is a Flutter application and can be built and launched on various platforms supported by Flutter, provided you have the necessary SDKs and devices/emulators configured as verified by `flutter doctor`. -1. **Ensure Flutter dependencies are installed**: +1. **Ensure Flutter dependencies are installed**: From the `veilidchat` directory, run: + ```bash flutter pub get ``` -2. **List available devices**: +2. **List available devices**: To see which devices (simulators, emulators, connected physical devices, desktop targets, web browsers) are available to run the application on, use the command: + ```bash flutter devices ``` -3. **Run on a specific device**: +3. **Run on a specific device**: Use the `flutter run` command followed by the `-d` flag and the device ID from the `flutter devices` list. - * **Example (Android emulator/device):** + * **Example (Android emulator/device):** Assuming an Android device ID like `emulator-5554`: + ```bash flutter run -d emulator-5554 ``` + If only one device is connected, you can often omit the `-d` flag. - * **Example (iOS simulator/device):** + * **Example (iOS simulator/device):** Assuming an iOS simulator ID like `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`: + ```bash flutter run -d xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` (replace with actual ID) ``` + Or, to target the default iOS simulator: + ```bash flutter run -d simulator ``` - * **Example (Linux Desktop):** + * **Example (Linux Desktop):** + ```bash flutter run -d linux ``` - * **Example (macOS Desktop):** + * **Example (macOS Desktop):** + ```bash flutter run -d macos ``` - * **Example (Windows Desktop):** + * **Example (Windows Desktop):** + ```bash flutter run -d windows ``` - * **Example (Web):** + * **Example (Web):** + ```bash flutter run -d web # Or a specific browser like 'chrome' or 'firefox' if listed by `flutter devices` ``` - This will typically launch the application in your selected web browser. -
+ This will typically launch the application in your selected web browser. ## Updating the WASM Binary ### To update the WASM binary -[from the `veilid-wasm` package located in [`../veilid/veilid-wasm`](../veilid/veilid-wasm)] +[from the `veilid-wasm` package located in [`../veilid/veilid-wasm`](../veilid/veilid-wasm)] From the VeilidChat repository working directory ([`./veilidchat`](./veilidchat)), run the appropriate script: -* Debug WASM: [`./dev-setup/wasm_update.sh`](./dev-setup/wasm_update.sh) -* Release WASM: [`./dev-setup/wasm_update.sh release`](./dev-setup/wasm_update.sh) - -
+* Debug WASM: [`./dev-setup/wasm_update.sh`](./dev-setup/wasm_update.sh) +* Release WASM: [`./dev-setup/wasm_update.sh release`](./dev-setup/wasm_update.sh) Refer to the official [Flutter documentation](https://docs.flutter.dev/) for more detailed information on building and deployment with Flutter. \ No newline at end of file From 2f597ef1a20f2b6437763972a68554bda18f9916 Mon Sep 17 00:00:00 2001 From: TC Johnson Date: Tue, 3 Jun 2025 13:02:10 -0500 Subject: [PATCH 87/93] =?UTF-8?q?Version=20update:=20v0.4.7=20=E2=86=92=20?= =?UTF-8?q?v0.4.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index a5b4502..84b9f17 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.4.7+0 +current_version = 0.4.8+0 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)\+(?P\d+) diff --git a/pubspec.yaml b/pubspec.yaml index e605c0e..cee2ec7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: veilidchat description: VeilidChat publish_to: "none" -version: 0.4.7+20 +version: 0.4.8+21 environment: sdk: ">=3.2.0 <4.0.0" From aeaf34e55ddea9d1af63369c422cced394996374 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 2 Jun 2025 15:47:37 -0400 Subject: [PATCH 88/93] fix lints --- lib/contacts/views/contacts_browser.dart | 8 +++--- .../lib/dht_support/src/dht_log/dht_log.dart | 14 +++++----- .../src/dht_log/dht_log_cubit.dart | 8 +++--- .../src/dht_log/dht_log_spine.dart | 27 ++++++++++--------- .../src/dht_log/dht_log_write.dart | 6 ++--- .../src/dht_record/dht_record.dart | 14 +++++----- .../src/dht_record/dht_record_pool.dart | 26 +++++++++--------- .../dht_record/dht_record_pool_private.dart | 4 ++- .../lib/dht_support/src/dht_record/stats.dart | 5 ++++ .../src/dht_short_array/dht_short_array.dart | 16 +++++------ .../src/interfaces/dht_closeable.dart | 2 +- .../src/interfaces/refreshable_cubit.dart | 2 +- 12 files changed, 70 insertions(+), 62 deletions(-) diff --git a/lib/contacts/views/contacts_browser.dart b/lib/contacts/views/contacts_browser.dart index 22c64c7..c04a4e4 100644 --- a/lib/contacts/views/contacts_browser.dart +++ b/lib/contacts/views/contacts_browser.dart @@ -83,11 +83,11 @@ class _ContactsBrowserState extends State final menuIconColor = scaleConfig.preferBorders ? scaleScheme.primaryScale.hoverBorder : scaleScheme.primaryScale.hoverBorder; - final menuBackgroundColor = scaleConfig.preferBorders - ? scaleScheme.primaryScale.activeElementBackground - : scaleScheme.primaryScale.activeElementBackground; + // final menuBackgroundColor = scaleConfig.preferBorders + // ? scaleScheme.primaryScale.activeElementBackground + // : scaleScheme.primaryScale.activeElementBackground; - final menuBorderColor = scaleScheme.primaryScale.hoverBorder; + // final menuBorderColor = scaleScheme.primaryScale.hoverBorder; PopupMenuEntry makeMenuButton( {required IconData iconData, diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart index 2687bdc..20471da 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart @@ -211,12 +211,12 @@ class DHTLog implements DHTDeleteable { OwnedDHTRecordPointer get recordPointer => _spine.recordPointer; /// Runs a closure allowing read-only access to the log - Future operate(Future Function(DHTLogReadOperations) closure) async { + Future operate(Future Function(DHTLogReadOperations) closure) { if (!isOpen) { throw StateError('log is not open'); } - return _spine.operate((spine) async { + return _spine.operate((spine) { final reader = _DHTLogRead._(spine); return closure(reader); }); @@ -228,12 +228,12 @@ class DHTLog implements DHTDeleteable { /// Throws DHTOperateException if the write could not be performed /// at this time Future operateAppend( - Future Function(DHTLogWriteOperations) closure) async { + Future Function(DHTLogWriteOperations) closure) { if (!isOpen) { throw StateError('log is not open'); } - return _spine.operateAppend((spine) async { + return _spine.operateAppend((spine) { final writer = _DHTLogWrite._(spine); return closure(writer); }); @@ -247,12 +247,12 @@ class DHTLog implements DHTDeleteable { /// eventual consistency pass. Future operateAppendEventual( Future Function(DHTLogWriteOperations) closure, - {Duration? timeout}) async { + {Duration? timeout}) { if (!isOpen) { throw StateError('log is not open'); } - return _spine.operateAppendEventual((spine) async { + return _spine.operateAppendEventual((spine) { final writer = _DHTLogWrite._(spine); return closure(writer); }, timeout: timeout); @@ -308,7 +308,7 @@ class DHTLog implements DHTDeleteable { int _openCount; // Watch mutex to ensure we keep the representation valid - final Mutex _listenMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); + final _listenMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); // Stream of external changes StreamController? _watchController; } diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_cubit.dart index a7884f9..3a618dc 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_cubit.dart @@ -106,13 +106,13 @@ class DHTLogCubit extends Cubit> await _refreshNoWait(forceRefresh: forceRefresh); } - Future _refreshNoWait({bool forceRefresh = false}) async => - busy((emit) async => _refreshInner(emit, forceRefresh: forceRefresh)); + Future _refreshNoWait({bool forceRefresh = false}) => + busy((emit) => _refreshInner(emit, forceRefresh: forceRefresh)); Future _refreshInner(void Function(AsyncValue>) emit, {bool forceRefresh = false}) async { late final int length; - final windowElements = await _log.operate((reader) async { + final windowElements = await _log.operate((reader) { length = reader.length; return _loadElementsFromReader(reader, _windowTail, _windowSize); }); @@ -237,7 +237,7 @@ class DHTLogCubit extends Cubit> late final DHTLog _log; final T Function(List data) _decodeElement; StreamSubscription? _subscription; - bool _wantsCloseRecord = false; + var _wantsCloseRecord = false; final _sspUpdate = SingleStatelessProcessor(); // Accumulated deltas since last update diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart index 6323692..0aebd6c 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart @@ -44,6 +44,8 @@ class _SubkeyData { _SubkeyData({required this.subkey, required this.data}); int subkey; Uint8List data; + // lint conflict + // ignore: omit_obvious_property_types bool changed = false; } @@ -123,12 +125,12 @@ class _DHTLogSpine { } // Will deep delete all segment records as they are children - Future delete() async => _spineMutex.protect(_spineRecord.delete); + Future delete() => _spineMutex.protect(_spineRecord.delete); - Future operate(Future Function(_DHTLogSpine) closure) async => - _spineMutex.protect(() async => closure(this)); + Future operate(Future Function(_DHTLogSpine) closure) => + _spineMutex.protect(() => closure(this)); - Future operateAppend(Future Function(_DHTLogSpine) closure) async => + Future operateAppend(Future Function(_DHTLogSpine) closure) => _spineMutex.protect(() async { final oldHead = _head; final oldTail = _tail; @@ -150,7 +152,7 @@ class _DHTLogSpine { }); Future operateAppendEventual(Future Function(_DHTLogSpine) closure, - {Duration? timeout}) async { + {Duration? timeout}) { final timeoutTs = timeout == null ? null : Veilid.instance.now().offset(TimestampDuration.fromDuration(timeout)); @@ -264,7 +266,7 @@ class _DHTLogSpine { ///////////////////////////////////////////////////////////////////////////// // Spine element management - static final Uint8List _emptySegmentKey = + static final _emptySegmentKey = Uint8List.fromList(List.filled(TypedKey.decodedLength(), 0)); static Uint8List _makeEmptySubkey() => Uint8List.fromList(List.filled( DHTLog.segmentsPerSubkey * TypedKey.decodedLength(), 0)); @@ -420,7 +422,7 @@ class _DHTLogSpine { Future<_DHTLogPosition?> lookupPositionBySegmentNumber( int segmentNumber, int segmentPos, - {bool onlyOpened = false}) async => + {bool onlyOpened = false}) => _spineCacheMutex.protect(() async { // See if we have this segment opened already final openedSegment = _openedSegments[segmentNumber]; @@ -468,7 +470,7 @@ class _DHTLogSpine { segmentNumber: segmentNumber); }); - Future<_DHTLogPosition?> lookupPosition(int pos) async { + Future<_DHTLogPosition?> lookupPosition(int pos) { assert(_spineMutex.isLocked, 'should be locked'); // Check if our position is in bounds @@ -487,7 +489,7 @@ class _DHTLogSpine { return lookupPositionBySegmentNumber(segmentNumber, segmentPos); } - Future _segmentClosed(int segmentNumber) async { + Future _segmentClosed(int segmentNumber) { assert(_spineMutex.isLocked, 'should be locked'); return _spineCacheMutex.protect(() async { final sa = _openedSegments[segmentNumber]!; @@ -709,7 +711,7 @@ class _DHTLogSpine { DHTShortArray.maxElements; // Spine head mutex to ensure we keep the representation valid - final Mutex _spineMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); + final _spineMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); // Subscription to head record internal changes StreamSubscription? _subscription; // Notify closure for external spine head changes @@ -729,9 +731,8 @@ class _DHTLogSpine { // LRU cache of DHT spine elements accessed recently // Pair of position and associated shortarray segment - final Mutex _spineCacheMutex = - Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); + final _spineCacheMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); final List _openCache; final Map _openedSegments; - static const int _openCacheSize = 3; + static const _openCacheSize = 3; } diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart index 397a1dc..00eaa94 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart @@ -60,7 +60,7 @@ class _DHTLogWrite extends _DHTLogRead implements DHTLogWriteOperations { if (aLookup.shortArray == bLookup.shortArray) { await bLookup.close(); return aLookup.scope((sa) => sa.operateWriteEventual( - (aWrite) async => aWrite.swap(aLookup.pos, bLookup.pos))); + (aWrite) => aWrite.swap(aLookup.pos, bLookup.pos))); } else { final bItem = Output(); return aLookup.scope( @@ -101,7 +101,7 @@ class _DHTLogWrite extends _DHTLogRead implements DHTLogWriteOperations { } // Write item to the segment - return lookup.scope((sa) async => sa.operateWrite((write) async { + return lookup.scope((sa) => sa.operateWrite((write) async { // If this a new segment, then clear it in case we have wrapped around if (lookup.pos == 0) { await write.clear(); @@ -140,7 +140,7 @@ class _DHTLogWrite extends _DHTLogRead implements DHTLogWriteOperations { dws.add((_) async { try { - await lookup.scope((sa) async => sa.operateWrite((write) async { + await lookup.scope((sa) => sa.operateWrite((write) async { // If this a new segment, then clear it in // case we have wrapped around if (lookup.pos == 0) { diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart index b9218e4..893cb80 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart @@ -92,7 +92,7 @@ class DHTRecord implements DHTDeleteable { /// Returns true if the deletion was processed immediately /// Returns false if the deletion was marked for later @override - Future delete() async => DHTRecordPool.instance.deleteRecord(key); + Future delete() => DHTRecordPool.instance.deleteRecord(key); //////////////////////////////////////////////////////////////////////////// // Public API @@ -122,7 +122,7 @@ class DHTRecord implements DHTDeleteable { {int subkey = -1, VeilidCrypto? crypto, DHTRecordRefreshMode refreshMode = DHTRecordRefreshMode.cached, - Output? outSeqNum}) async => + Output? outSeqNum}) => _wrapStats('get', () async { subkey = subkeyOrDefault(subkey); @@ -227,7 +227,7 @@ class DHTRecord implements DHTDeleteable { {int subkey = -1, VeilidCrypto? crypto, KeyPair? writer, - Output? outSeqNum}) async => + Output? outSeqNum}) => _wrapStats('tryWriteBytes', () async { subkey = subkeyOrDefault(subkey); final lastSeq = await _localSubkeySeq(subkey); @@ -238,8 +238,8 @@ class DHTRecord implements DHTDeleteable { key, subkey, encryptedNewValue, writer: writer ?? _writer); if (newValueData == null) { - // A newer value wasn't found on the set, but - // we may get a newer value when getting the value for the sequence number + // A newer value wasn't found on the set, but we may get a newer value + // when getting the value for the sequence number newValueData = await _routingContext.getDHTValue(key, subkey); if (newValueData == null) { assert(newValueData != null, "can't get value that was just set"); @@ -280,7 +280,7 @@ class DHTRecord implements DHTDeleteable { {int subkey = -1, VeilidCrypto? crypto, KeyPair? writer, - Output? outSeqNum}) async => + Output? outSeqNum}) => _wrapStats('eventualWriteBytes', () async { subkey = subkeyOrDefault(subkey); final lastSeq = await _localSubkeySeq(subkey); @@ -331,7 +331,7 @@ class DHTRecord implements DHTDeleteable { {int subkey = -1, VeilidCrypto? crypto, KeyPair? writer, - Output? outSeqNum}) async => + Output? outSeqNum}) => _wrapStats('eventualUpdateBytes', () async { subkey = subkeyOrDefault(subkey); diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart index 6dc0634..cfa123d 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool.dart @@ -20,13 +20,13 @@ part 'dht_record.dart'; part 'dht_record_pool_private.dart'; /// Maximum number of concurrent DHT operations to perform on the network -const int kMaxDHTConcurrency = 8; +const kMaxDHTConcurrency = 8; /// Total number of times to try in a 'VeilidAPIExceptionKeyNotFound' loop -const int kDHTKeyNotFoundTries = 3; +const kDHTKeyNotFoundTries = 3; /// Total number of times to try in a 'VeilidAPIExceptionTryAgain' loop -const int kDHTTryAgainTries = 3; +const kDHTTryAgainTries = 3; typedef DHTRecordPoolLogger = void Function(String message); @@ -105,7 +105,7 @@ class DHTRecordPool with TableDBBackedJson { int defaultSubkey = 0, VeilidCrypto? crypto, KeyPair? writer, - }) async => + }) => _mutex.protect(() async { final dhtctx = routingContext ?? _routingContext; @@ -139,7 +139,7 @@ class DHTRecordPool with TableDBBackedJson { VeilidRoutingContext? routingContext, TypedKey? parent, int defaultSubkey = 0, - VeilidCrypto? crypto}) async => + VeilidCrypto? crypto}) => _recordTagLock.protect(recordKey, closure: () async { final dhtctx = routingContext ?? _routingContext; @@ -164,7 +164,7 @@ class DHTRecordPool with TableDBBackedJson { TypedKey? parent, int defaultSubkey = 0, VeilidCrypto? crypto, - }) async => + }) => _recordTagLock.protect(recordKey, closure: () async { final dhtctx = routingContext ?? _routingContext; @@ -223,8 +223,8 @@ class DHTRecordPool with TableDBBackedJson { /// otherwise mark that record for deletion eventually /// Returns true if the deletion was processed immediately /// Returns false if the deletion was marked for later - Future deleteRecord(TypedKey recordKey) async => - _mutex.protect(() async => _deleteRecordInner(recordKey)); + Future deleteRecord(TypedKey recordKey) => + _mutex.protect(() => _deleteRecordInner(recordKey)); // If everything underneath is closed including itself, return the // list of children (and itself) to finally actually delete @@ -314,7 +314,7 @@ class DHTRecordPool with TableDBBackedJson { /// Generate default VeilidCrypto for a writer static Future privateCryptoFromTypedSecret( - TypedKey typedSecret) async => + TypedKey typedSecret) => VeilidCryptoPrivate.fromTypedKey(typedSecret, _cryptoDomainDHT); //////////////////////////////////////////////////////////////////////////// @@ -362,7 +362,7 @@ class DHTRecordPool with TableDBBackedJson { required VeilidCrypto crypto, required KeyPair? writer, required TypedKey? parent, - required int defaultSubkey}) async => + required int defaultSubkey}) => _stats.measure(recordKey, debugName, '_recordOpenCommon', () async { log('openDHTRecord: debugName=$debugName key=$recordKey'); @@ -428,8 +428,8 @@ class DHTRecordPool with TableDBBackedJson { // Already opened - // See if we need to reopen the record with a default writer and possibly - // a different routing context + // See if we need to reopen the record with a default writer and + // possibly a different routing context if (writer != null && openedRecordInfo.shared.defaultWriter == null) { await dhtctx.openDHTRecord(recordKey, writer: writer); // New writer if we didn't specify one before @@ -889,7 +889,7 @@ class DHTRecordPool with TableDBBackedJson { } /// Ticker to check watch state change requests - Future tick() async => _mutex.protect(() async { + Future tick() => _mutex.protect(() async { // See if any opened records need watch state changes for (final kv in _opened.entries) { final openedRecordKey = kv.key; diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool_private.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool_private.dart index 05b93b0..2474c87 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool_private.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_pool_private.dart @@ -1,7 +1,7 @@ part of 'dht_record_pool.dart'; // DHT crypto domain -const String _cryptoDomainDHT = 'dht'; +const _cryptoDomainDHT = 'dht'; // Singlefuture keys const _sfPollWatch = '_pollWatch'; @@ -32,6 +32,8 @@ class _SharedDHTRecordData { DHTRecordDescriptor recordDescriptor; KeyPair? defaultWriter; VeilidRoutingContext defaultRoutingContext; + // lint conflict + // ignore: omit_obvious_property_types bool needsWatchStateUpdate = false; _WatchState? unionWatchState; } diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/stats.dart b/packages/veilid_support/lib/dht_support/src/dht_record/stats.dart index 6388f5c..22b517b 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/stats.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/stats.dart @@ -80,7 +80,12 @@ class DHTCallStats { ' all latency: ${latency?.debugString()}\n'; ///////////////////////////// + + // lint conflict + // ignore: omit_obvious_property_types int calls = 0; + // lint conflict + // ignore: omit_obvious_property_types int timeouts = 0; LatencyStats? latency; LatencyStats? successLatency; diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart index ccf7d18..4a03cd9 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array.dart @@ -174,7 +174,7 @@ class DHTShortArray implements DHTDeleteable { /// Returns true if the deletion was processed immediately /// Returns false if the deletion was marked for later @override - Future delete() async => _head.delete(); + Future delete() => _head.delete(); //////////////////////////////////////////////////////////////////////////// // Public API @@ -201,12 +201,12 @@ class DHTShortArray implements DHTDeleteable { /// Runs a closure allowing read-only access to the shortarray Future operate( - Future Function(DHTShortArrayReadOperations) closure) async { + Future Function(DHTShortArrayReadOperations) closure) { if (!isOpen) { throw StateError('short array is not open"'); } - return _head.operate((head) async { + return _head.operate((head) { final reader = _DHTShortArrayRead._(head); return closure(reader); }); @@ -218,12 +218,12 @@ class DHTShortArray implements DHTDeleteable { /// Throws DHTOperateException if the write could not be performed /// at this time Future operateWrite( - Future Function(DHTShortArrayWriteOperations) closure) async { + Future Function(DHTShortArrayWriteOperations) closure) { if (!isOpen) { throw StateError('short array is not open"'); } - return _head.operateWrite((head) async { + return _head.operateWrite((head) { final writer = _DHTShortArrayWrite._(head); return closure(writer); }); @@ -237,12 +237,12 @@ class DHTShortArray implements DHTDeleteable { /// eventual consistency pass. Future operateWriteEventual( Future Function(DHTShortArrayWriteOperations) closure, - {Duration? timeout}) async { + {Duration? timeout}) { if (!isOpen) { throw StateError('short array is not open"'); } - return _head.operateWriteEventual((head) async { + return _head.operateWriteEventual((head) { final writer = _DHTShortArrayWrite._(head); return closure(writer); }, timeout: timeout); @@ -291,7 +291,7 @@ class DHTShortArray implements DHTDeleteable { int _openCount; // Watch mutex to ensure we keep the representation valid - final Mutex _listenMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); + final _listenMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); // Stream of external changes StreamController? _watchController; } diff --git a/packages/veilid_support/lib/dht_support/src/interfaces/dht_closeable.dart b/packages/veilid_support/lib/dht_support/src/interfaces/dht_closeable.dart index 0fb10ab..bda4afb 100644 --- a/packages/veilid_support/lib/dht_support/src/interfaces/dht_closeable.dart +++ b/packages/veilid_support/lib/dht_support/src/interfaces/dht_closeable.dart @@ -56,7 +56,7 @@ extension DHTDeletableExt on DHTDeleteable { /// Scopes a closure that conditionally deletes the DHTCloseable on exit Future maybeDeleteScope( - bool delete, Future Function(D) scopeFunction) async { + bool delete, Future Function(D) scopeFunction) { if (delete) { return deleteScope(scopeFunction); } diff --git a/packages/veilid_support/lib/dht_support/src/interfaces/refreshable_cubit.dart b/packages/veilid_support/lib/dht_support/src/interfaces/refreshable_cubit.dart index a04e1bb..d987b38 100644 --- a/packages/veilid_support/lib/dht_support/src/interfaces/refreshable_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/interfaces/refreshable_cubit.dart @@ -12,5 +12,5 @@ abstract mixin class RefreshableCubit { bool get wantsRefresh => _wantsRefresh; //////////////////////////////////////////////////////////////////////////// - bool _wantsRefresh = false; + var _wantsRefresh = false; } From d1559c949d9849191569ce8a3abb68bdf2013348 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 2 Jun 2025 15:56:00 -0400 Subject: [PATCH 89/93] fix lints --- lib/chat/cubits/chat_component_cubit.dart | 2 +- lib/chat/cubits/reconciliation/author_input_queue.dart | 2 +- lib/chat/cubits/reconciliation/author_input_source.dart | 4 ++-- lib/chat/cubits/reconciliation/message_integrity.dart | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/chat/cubits/chat_component_cubit.dart b/lib/chat/cubits/chat_component_cubit.dart index 737b1a4..c0473be 100644 --- a/lib/chat/cubits/chat_component_cubit.dart +++ b/lib/chat/cubits/chat_component_cubit.dart @@ -85,7 +85,7 @@ class ChatComponentCubit extends Cubit { _onChangedContacts(_contactListCubit.state); } - Future _initAsync(Completer _cancel) async { + Future _initAsync(Completer cancel) async { // Subscribe to remote user info await _updateConversationSubscriptions(); } diff --git a/lib/chat/cubits/reconciliation/author_input_queue.dart b/lib/chat/cubits/reconciliation/author_input_queue.dart index 9a65e82..f750e77 100644 --- a/lib/chat/cubits/reconciliation/author_input_queue.dart +++ b/lib/chat/cubits/reconciliation/author_input_queue.dart @@ -294,5 +294,5 @@ class AuthorInputQueue { InputWindow? _currentWindow; /// Desired maximum window length - static const int _maxWindowLength = 256; + static const _maxWindowLength = 256; } diff --git a/lib/chat/cubits/reconciliation/author_input_source.dart b/lib/chat/cubits/reconciliation/author_input_source.dart index f974dae..e7ba765 100644 --- a/lib/chat/cubits/reconciliation/author_input_source.dart +++ b/lib/chat/cubits/reconciliation/author_input_source.dart @@ -26,11 +26,11 @@ class AuthorInputSource { //////////////////////////////////////////////////////////////////////////// - Future getTailPosition() async => + Future getTailPosition() => _dhtLog.operate((reader) async => reader.length); Future> getWindow( - int startPosition, int windowLength) async => + int startPosition, int windowLength) => _dhtLog.operate((reader) async { // Don't allow negative length if (windowLength <= 0) { diff --git a/lib/chat/cubits/reconciliation/message_integrity.dart b/lib/chat/cubits/reconciliation/message_integrity.dart index 2fd1956..2c4bb85 100644 --- a/lib/chat/cubits/reconciliation/message_integrity.dart +++ b/lib/chat/cubits/reconciliation/message_integrity.dart @@ -19,7 +19,7 @@ class MessageIntegrity { //////////////////////////////////////////////////////////////////////////// // Public interface - Future generateMessageId(proto.Message? previous) async { + Future generateMessageId(proto.Message? previous) { if (previous == null) { // If there's no last sent message, // we start at a hash of the identity public key @@ -47,7 +47,7 @@ class MessageIntegrity { message.signature = signature.toProto(); } - Future verifyMessage(proto.Message message) async { + Future verifyMessage(proto.Message message) { // Ensure the message is signed assert(message.hasSignature(), 'should not verify unsigned message'); final signature = message.signature.toVeilid(); From b192c44d5c6f83a1970ab7d79d59d1a5d5459d5f Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 2 Jun 2025 16:10:19 -0400 Subject: [PATCH 90/93] remove swap from dhtlog --- .../reconciliation/message_integrity.dart | 5 ++ .../src/dht_log/dht_log_write.dart | 54 ------------------- .../dht_short_array_write.dart | 1 + .../src/interfaces/dht_random_write.dart | 5 -- .../src/interfaces/interfaces.dart | 1 + 5 files changed, 7 insertions(+), 59 deletions(-) diff --git a/lib/chat/cubits/reconciliation/message_integrity.dart b/lib/chat/cubits/reconciliation/message_integrity.dart index 2c4bb85..40b3b18 100644 --- a/lib/chat/cubits/reconciliation/message_integrity.dart +++ b/lib/chat/cubits/reconciliation/message_integrity.dart @@ -47,6 +47,11 @@ class MessageIntegrity { message.signature = signature.toProto(); } + // XXX: change bool to an enum to allow for reconciling deleted + // XXX: messages. if a message is deleted it will not verify, but its + // XXX: signature will still be in place for the message chain. + // XXX: it should be added to a list to check for a ControlDelete that + // XXX: appears later in the message log. Future verifyMessage(proto.Message message) { // Ensure the message is signed assert(message.hasSignature(), 'should not verify unsigned message'); diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart index 00eaa94..e3697c8 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart @@ -36,60 +36,6 @@ class _DHTLogWrite extends _DHTLogRead implements DHTLogWriteOperations { return true; } - @override - Future swap(int aPos, int bPos) async { - if (aPos < 0 || aPos >= _spine.length) { - throw IndexError.withLength(aPos, _spine.length); - } - if (bPos < 0 || bPos >= _spine.length) { - throw IndexError.withLength(bPos, _spine.length); - } - final aLookup = await _spine.lookupPosition(aPos); - if (aLookup == null) { - throw DHTExceptionInvalidData('_DHTLogWrite::swap aPos=$aPos bPos=$bPos ' - '_spine.length=${_spine.length}'); - } - final bLookup = await _spine.lookupPosition(bPos); - if (bLookup == null) { - await aLookup.close(); - throw DHTExceptionInvalidData('_DHTLogWrite::swap aPos=$aPos bPos=$bPos ' - '_spine.length=${_spine.length}'); - } - - // Swap items in the segments - if (aLookup.shortArray == bLookup.shortArray) { - await bLookup.close(); - return aLookup.scope((sa) => sa.operateWriteEventual( - (aWrite) => aWrite.swap(aLookup.pos, bLookup.pos))); - } else { - final bItem = Output(); - return aLookup.scope( - (sa) => bLookup.scope((sb) => sa.operateWriteEventual((aWrite) async { - if (bItem.value == null) { - final aItem = await aWrite.get(aLookup.pos); - if (aItem == null) { - throw DHTExceptionInvalidData( - '_DHTLogWrite::swap aPos=$aPos bPos=$bPos ' - 'aLookup.pos=${aLookup.pos} bLookup.pos=${bLookup.pos} ' - '_spine.length=${_spine.length}'); - } - await sb.operateWriteEventual((bWrite) async { - final success = await bWrite - .tryWriteItem(bLookup.pos, aItem, output: bItem); - if (!success) { - throw const DHTExceptionOutdated(); - } - }); - } - final success = - await aWrite.tryWriteItem(aLookup.pos, bItem.value!); - if (!success) { - throw const DHTExceptionOutdated(); - } - }))); - } - } - @override Future add(Uint8List value) async { // Allocate empty index at the end of the list diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart index fa3b1c6..bd3431d 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_write.dart @@ -6,6 +6,7 @@ part of 'dht_short_array.dart'; abstract class DHTShortArrayWriteOperations implements DHTRandomRead, + DHTRandomSwap, DHTRandomWrite, DHTInsertRemove, DHTAdd, diff --git a/packages/veilid_support/lib/dht_support/src/interfaces/dht_random_write.dart b/packages/veilid_support/lib/dht_support/src/interfaces/dht_random_write.dart index 5b3f032..0d8f3ac 100644 --- a/packages/veilid_support/lib/dht_support/src/interfaces/dht_random_write.dart +++ b/packages/veilid_support/lib/dht_support/src/interfaces/dht_random_write.dart @@ -23,11 +23,6 @@ abstract class DHTRandomWrite { /// of the container. Future tryWriteItem(int pos, Uint8List newValue, {Output? output}); - - /// Swap items at position 'aPos' and 'bPos' in the DHTArray. - /// Throws an IndexError if either of the positions swapped exceeds the length - /// of the container - Future swap(int aPos, int bPos); } extension DHTRandomWriteExt on DHTRandomWrite { diff --git a/packages/veilid_support/lib/dht_support/src/interfaces/interfaces.dart b/packages/veilid_support/lib/dht_support/src/interfaces/interfaces.dart index a162dc8..8a019dd 100644 --- a/packages/veilid_support/lib/dht_support/src/interfaces/interfaces.dart +++ b/packages/veilid_support/lib/dht_support/src/interfaces/interfaces.dart @@ -3,6 +3,7 @@ export 'dht_clear.dart'; export 'dht_closeable.dart'; export 'dht_insert_remove.dart'; export 'dht_random_read.dart'; +export 'dht_random_swap.dart'; export 'dht_random_write.dart'; export 'dht_truncate.dart'; export 'exceptions.dart'; From 28580bad887be76487b5dc9fd8c481c7bd485769 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 5 Jun 2025 23:43:13 +0200 Subject: [PATCH 91/93] new qr code scanner --- assets/i18n/en.json | 7 +- ios/Podfile.lock | 7 - .../cubits/contact_invitation_list_cubit.dart | 10 + .../views/camera_qr_scanner.dart | 473 ++++++++++++++++++ .../views/scan_invitation_dialog.dart | 351 ++++--------- lib/contact_invitation/views/views.dart | 1 + lib/theme/views/scanner_error_widget.dart | 54 -- lib/theme/views/views.dart | 1 - macos/Flutter/GeneratedPluginRegistrant.swift | 2 - macos/Podfile.lock | 7 - .../lib/dht_support/src/dht_log/dht_log.dart | 2 +- .../src/dht_log/dht_log_spine.dart | 13 +- .../src/dht_log/dht_log_write.dart | 12 +- .../dht_short_array/dht_short_array_head.dart | 2 +- .../dht_support/src/interfaces/dht_add.dart | 22 +- .../src/interfaces/dht_random_swap.dart | 9 + .../src/interfaces/exceptions.dart | 19 +- pubspec.lock | 14 +- pubspec.yaml | 3 +- 19 files changed, 636 insertions(+), 373 deletions(-) create mode 100644 lib/contact_invitation/views/camera_qr_scanner.dart delete mode 100644 lib/theme/views/scanner_error_widget.dart create mode 100644 packages/veilid_support/lib/dht_support/src/interfaces/dht_random_swap.dart diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 50b0904..206c0b0 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -225,15 +225,16 @@ "scan_invitation_dialog": { "title": "Scan Contact Invite", "instructions": "Position the contact invite QR code in the frame", - "scan_qr_here": "Click here to scan a contact invite QR code:", - "paste_qr_here": "Camera scanning is only available on mobile devices. You can copy a QR code image and paste it here:", + "scan_qr_here": "Click here to scan a contact invite QR code with your device's camera:", + "paste_qr_here": "You can copy a QR code image and paste it by clicking here:", "scan": "Scan", "paste": "Paste", "not_an_image": "Pasted data is not an image", "could_not_decode_image": "Could not decode pasted image", "not_a_valid_qr_code": "Not a valid QR code", "error": "Failed to capture QR code", - "permission_error": "Capturing QR codes requires camera permisions. Allow camera permissions for VeilidChat in your settings." + "permission_error": "Capturing QR codes requires camera permisions. Allow camera permissions for VeilidChat in your settings.", + "camera_error": "Camera error" }, "enter_pin_dialog": { "enter_pin": "Enter PIN", diff --git a/ios/Podfile.lock b/ios/Podfile.lock index add7488..0f5fb0f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -6,9 +6,6 @@ PODS: - Flutter (1.0.0) - flutter_native_splash (2.4.3): - Flutter - - mobile_scanner (7.0.0): - - Flutter - - FlutterMacOS - package_info_plus (0.4.5): - Flutter - pasteboard (0.0.1): @@ -38,7 +35,6 @@ DEPENDENCIES: - file_saver (from `.symlinks/plugins/file_saver/ios`) - Flutter (from `Flutter`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - pasteboard (from `.symlinks/plugins/pasteboard/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) @@ -59,8 +55,6 @@ EXTERNAL SOURCES: :path: Flutter flutter_native_splash: :path: ".symlinks/plugins/flutter_native_splash/ios" - mobile_scanner: - :path: ".symlinks/plugins/mobile_scanner/darwin" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" pasteboard: @@ -87,7 +81,6 @@ SPEC CHECKSUMS: file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf - mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 diff --git a/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart b/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart index 332341d..b31c453 100644 --- a/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart +++ b/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:async_tools/async_tools.dart'; import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; +import 'package:convert/convert.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:fixnum/fixnum.dart'; import 'package:flutter/foundation.dart'; @@ -165,6 +166,11 @@ class ContactInvitationListCubit }); }); + log.debug('createInvitation:\n' + 'contactRequestInboxKey=$contactRequestInboxKey\n' + 'bytes=${signedContactInvitationBytes.lengthInBytes}\n' + '${hex.encode(signedContactInvitationBytes)}'); + return (signedContactInvitationBytes, contactRequestInboxKey); } @@ -222,6 +228,10 @@ class ContactInvitationListCubit required GetEncryptionKeyCallback getEncryptionKeyCallback, required CancelRequest cancelRequest, }) async { + log.debug('validateInvitation:\n' + 'bytes=${inviteData.lengthInBytes}\n' + '${hex.encode(inviteData)}'); + final pool = DHTRecordPool.instance; // Get contact request inbox from invitation diff --git a/lib/contact_invitation/views/camera_qr_scanner.dart b/lib/contact_invitation/views/camera_qr_scanner.dart new file mode 100644 index 0000000..11d8f77 --- /dev/null +++ b/lib/contact_invitation/views/camera_qr_scanner.dart @@ -0,0 +1,473 @@ +import 'dart:async'; + +import 'package:async_tools/async_tools.dart'; +import 'package:awesome_extensions/awesome_extensions.dart'; +import 'package:camera/camera.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:zxing2/qrcode.dart'; + +import '../../theme/theme.dart'; + +enum _FrameState { + notFound, + formatError, + checksumError, +} + +class _ScannerOverlay extends CustomPainter { + _ScannerOverlay(this.scanWindow, this.frameColor); + + final Rect scanWindow; + final Color? frameColor; + + @override + void paint(Canvas canvas, Size size) { + final backgroundPath = Path()..addRect(Rect.largest); + final cutoutPath = Path()..addRect(scanWindow); + + final backgroundPaint = Paint() + ..color = (frameColor ?? Colors.black).withAlpha(127) + ..style = PaintingStyle.fill + ..blendMode = BlendMode.dstOut; + + final backgroundWithCutout = Path.combine( + PathOperation.difference, + backgroundPath, + cutoutPath, + ); + canvas.drawPath(backgroundWithCutout, backgroundPaint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +/// Camera QR scanner +class CameraQRScanner extends StatefulWidget { + const CameraQRScanner( + {required Widget Function(BuildContext) loadingBuilder, + required Widget Function( + BuildContext, Object error, StackTrace? stackTrace) + errorBuilder, + required Widget Function(BuildContext) bottomRowBuilder, + required void Function(String) showNotification, + required void Function(String, Object? error, StackTrace? stackTrace) + logError, + required void Function(T) onDone, + T? Function(Result)? onDetect, + T? Function(CameraImage)? onImageAvailable, + Size? scanSize, + Color? formatErrorFrameColor, + Color? checksumErrorFrameColor, + String? cameraErrorMessage, + String? deniedErrorMessage, + String? deniedWithoutPromptErrorMessage, + String? restrictedErrorMessage, + super.key}) + : _loadingBuilder = loadingBuilder, + _errorBuilder = errorBuilder, + _bottomRowBuilder = bottomRowBuilder, + _showNotification = showNotification, + _logError = logError, + _scanSize = scanSize, + _onDetect = onDetect, + _onDone = onDone, + _onImageAvailable = onImageAvailable, + _formatErrorFrameColor = formatErrorFrameColor, + _checksumErrorFrameColor = checksumErrorFrameColor, + _cameraErrorMessage = cameraErrorMessage, + _deniedErrorMessage = deniedErrorMessage, + _deniedWithoutPromptErrorMessage = deniedWithoutPromptErrorMessage, + _restrictedErrorMessage = restrictedErrorMessage; + @override + State> createState() => _CameraQRScannerState(); + + //////////////////////////////////////////////////////////////////////////// + + final Widget Function(BuildContext) _loadingBuilder; + final Widget Function(BuildContext, Object error, StackTrace? stackTrace) + _errorBuilder; + final Widget Function(BuildContext) _bottomRowBuilder; + final void Function(String) _showNotification; + final void Function(String, Object? error, StackTrace? stackTrace) _logError; + final T? Function(Result)? _onDetect; + final void Function(T) _onDone; + final T? Function(CameraImage)? _onImageAvailable; + + final Size? _scanSize; + final Color? _formatErrorFrameColor; + final Color? _checksumErrorFrameColor; + final String? _cameraErrorMessage; + final String? _deniedErrorMessage; + final String? _deniedWithoutPromptErrorMessage; + final String? _restrictedErrorMessage; +} + +class _CameraQRScannerState extends State> + with WidgetsBindingObserver, TickerProviderStateMixin { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + + // Async Init + _initWait.add(_init); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + unawaited(_controller?.dispose()); + super.dispose(); + } + + // #docregion AppLifecycle + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + final cameraController = _controller; + + // App state changed before we got the chance to initialize. + if (cameraController == null || !cameraController.value.isInitialized) { + return; + } + + if (state == AppLifecycleState.inactive) { + unawaited(cameraController.dispose()); + } else if (state == AppLifecycleState.resumed) { + unawaited(_initializeCameraController(cameraController.description)); + } + } + // #enddocregion AppLifecycle + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final activeColor = theme.colorScheme.primary; + final inactiveColor = theme.colorScheme.onPrimary; + + final scanSize = widget._scanSize; + final scanWindow = scanSize == null + ? null + : Rect.fromCenter( + center: Offset.zero, + width: scanSize.width, + height: scanSize.height, + ); + + return Scaffold( + body: FutureBuilder( + future: _initWait(), + builder: (context, av) => av.when( + error: (e, st) => widget._errorBuilder(context, e, st), + loading: () => widget._loadingBuilder(context), + data: (data, isComplete) => Column( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(1), + child: Center( + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + _cameraPreviewWidget(context), + if (scanWindow != null) + IgnorePointer( + child: CustomPaint( + foregroundPainter: _ScannerOverlay( + scanWindow, + switch (_frameState) { + _FrameState.notFound => null, + _FrameState.formatError => + widget._formatErrorFrameColor, + _FrameState.checksumError => + widget._checksumErrorFrameColor + }), + )), + ]), + ), + ), + ), + widget._bottomRowBuilder(context), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _cameraToggleWidget(), + _torchToggleWidget(activeColor, inactiveColor) + ], + ), + ], + ), + ))); + } + + /// Display the preview from the camera + /// (or a message if the preview is not available). + Widget _cameraPreviewWidget(BuildContext context) { + final cameraController = _controller; + + if (cameraController == null || !cameraController.value.isInitialized) { + return widget._loadingBuilder(context); + } else { + return Listener( + onPointerDown: (_) => _pointers++, + onPointerUp: (_) => _pointers--, + child: CameraPreview( + cameraController, + child: LayoutBuilder( + builder: (context, constraints) => GestureDetector( + behavior: HitTestBehavior.opaque, + onScaleStart: _handleScaleStart, + onScaleUpdate: _handleScaleUpdate, + onTapDown: (details) => + _onViewFinderTap(details, constraints), + )), + ), + ); + } + } + + void _handleScaleStart(ScaleStartDetails details) { + _baseScale = _currentScale; + } + + Future _handleScaleUpdate(ScaleUpdateDetails details) async { + // When there are not exactly two fingers on screen don't scale + if (_controller == null || _pointers != 2) { + return; + } + + _currentScale = (_baseScale * details.scale) + .clamp(_minAvailableZoom, _maxAvailableZoom); + + await _controller!.setZoomLevel(_currentScale); + } + + Widget _torchToggleWidget(Color activeColor, Color inactiveColor) => + IconButton( + icon: const Icon(Icons.highlight), + color: _controller?.value.flashMode == FlashMode.torch + ? activeColor + : inactiveColor, + onPressed: _controller != null + ? () => _onSetFlashModeButtonPressed( + _controller?.value.flashMode == FlashMode.torch + ? FlashMode.off + : FlashMode.torch) + : null, + ); + + Widget _cameraToggleWidget() { + final currentCameraDescription = _controller?.description; + return IconButton( + icon: + Icon(isAndroid ? Icons.flip_camera_android : Icons.flip_camera_ios), + onPressed: (currentCameraDescription == null || _cameras.isEmpty) + ? null + : () { + final nextCameraIndex = + (_cameras.indexOf(currentCameraDescription) + 1) % + _cameras.length; + unawaited(_onNewCameraSelected(_cameras[nextCameraIndex])); + }); + } + + void _onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { + final cameraController = _controller; + if (cameraController == null) { + return; + } + + final offset = Offset( + details.localPosition.dx / constraints.maxWidth, + details.localPosition.dy / constraints.maxHeight, + ); + unawaited(cameraController.setExposurePoint(offset)); + unawaited(cameraController.setFocusPoint(offset)); + } + + Future _onNewCameraSelected(CameraDescription cameraDescription) { + if (_controller != null) { + return _controller!.setDescription(cameraDescription); + } else { + return _initializeCameraController(cameraDescription); + } + } + + Future _initializeCameraController( + CameraDescription cameraDescription) async { + final cameraController = CameraController( + cameraDescription, + kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, + enableAudio: false, + imageFormatGroup: ImageFormatGroup.jpeg, + ); + + _controller = cameraController; + + // If the controller is updated then update the UI. + cameraController.addListener(() { + if (mounted) { + setState(() {}); + } + if (cameraController.value.hasError && + (cameraController.value.errorDescription?.isNotEmpty ?? false)) { + widget._showNotification( + '${widget._cameraErrorMessage ?? 'Camera error'}: ' + '${cameraController.value.errorDescription!}'); + } + }); + + try { + await cameraController.initialize(); + + try { + _maxAvailableZoom = await cameraController.getMaxZoomLevel(); + _minAvailableZoom = await cameraController.getMinZoomLevel(); + } on PlatformException { + _maxAvailableZoom = 1; + _minAvailableZoom = 1; + } + + await cameraController.startImageStream((cameraImage) { + final out = + (widget._onImageAvailable ?? _onImageAvailable)(cameraImage); + if (out != null) { + _controller = null; + unawaited(cameraController.dispose()); + widget._onDone(out); + } + }); + } on CameraException catch (e, st) { + switch (e.code) { + case 'CameraAccessDenied': + widget._showNotification( + widget._deniedErrorMessage ?? 'You have denied camera access.'); + case 'CameraAccessDeniedWithoutPrompt': + // iOS only + widget._showNotification(widget._deniedWithoutPromptErrorMessage ?? + 'Please go to Settings app to enable camera access.'); + case 'CameraAccessRestricted': + // iOS only + widget._showNotification( + widget._restrictedErrorMessage ?? 'Camera access is restricted.'); + default: + _showCameraException(e, st); + } + } + + if (mounted) { + setState(() {}); + } + } + + T? _onImageAvailable(CameraImage cameraImage) { + try { + final plane = cameraImage.planes.firstOrNull; + if (plane == null) { + return null; + } + + // final image = JpegDecoder().decode(plane.bytes); + // if (image == null) { + // return; + // } + + // final abgrImage = image + // .convert(numChannels: 4) + // .getBytes(order: ChannelOrder.abgr) + // .buffer + // .asInt32List(); + + final abgrImage = plane.bytes.buffer.asInt32List(); + + final source = + RGBLuminanceSource(cameraImage.width, cameraImage.height, abgrImage); + + final bitmap = BinaryBitmap(HybridBinarizer(source)); + + final reader = QRCodeReader(); + try { + final result = reader.decode(bitmap); + return widget._onDetect?.call(result); + } on NotFoundException { + _setFrameState(_FrameState.notFound); + } on FormatReaderException { + _setFrameState(_FrameState.formatError); + } on ChecksumException { + _setFrameState(_FrameState.checksumError); + } + + // Should also catch errors from QRCodeReader + // ignore: avoid_catches_without_on_clauses + } catch (e, st) { + widget._logError('Unexpected error: $e\n$st', e, st); + } + return null; + } + + void _setFrameState(_FrameState frameState) { + if (mounted) { + if (_frameState != frameState) { + setState(() { + _frameState = frameState; + }); + } + } + } + + void _onSetFlashModeButtonPressed(FlashMode mode) { + unawaited(_setFlashMode(mode).then((_) { + if (mounted) { + setState(() {}); + } + })); + } + + Future _setFlashMode(FlashMode mode) async { + if (_controller == null) { + return; + } + + try { + await _controller!.setFlashMode(mode); + } on CameraException catch (e, st) { + _showCameraException(e, st); + rethrow; + } + } + + void _showCameraException(CameraException e, StackTrace st) { + _logCameraException(e, st); + widget._showNotification('Error: ${e.code}\n${e.description}'); + } + + void _logCameraException(CameraException e, StackTrace st) { + final code = e.code; + final message = e.description; + widget._logError( + 'CameraException: $code${message == null ? '' : '\nMessage: $message'}', + e, + st); + } + + Future _init(Completer cancel) async { + _cameras = await availableCameras(); + if (_cameras.isNotEmpty) { + await _onNewCameraSelected(_cameras.first); + } + } + + //////////////////////////////////////////////////////////////////////////// + + CameraController? _controller; + final _initWait = WaitSet(); + late final List _cameras; + var _minAvailableZoom = 1.0; + var _maxAvailableZoom = 1.0; + var _currentScale = 1.0; + var _baseScale = 1.0; + var _pointers = 0; + _FrameState _frameState = _FrameState.notFound; +} diff --git a/lib/contact_invitation/views/scan_invitation_dialog.dart b/lib/contact_invitation/views/scan_invitation_dialog.dart index 8fbdf5c..058383d 100644 --- a/lib/contact_invitation/views/scan_invitation_dialog.dart +++ b/lib/contact_invitation/views/scan_invitation_dialog.dart @@ -2,106 +2,19 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:awesome_extensions/awesome_extensions.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:image/image.dart' as img; -import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:pasteboard/pasteboard.dart'; import 'package:provider/provider.dart'; import 'package:zxing2/qrcode.dart'; import '../../notifications/notifications.dart'; import '../../theme/theme.dart'; +import '../../tools/tools.dart'; +import 'camera_qr_scanner.dart'; import 'invitation_dialog.dart'; -// class BarcodeOverlay extends CustomPainter { -// BarcodeOverlay({ -// required this.barcode, -// required this.boxFit, -// required this.capture, -// required this.size, -// }); - -// final BarcodeCapture capture; -// final Barcode barcode; -// final BoxFit boxFit; -// final Size size; - -// @override -// void paint(Canvas canvas, Size size) { -// final adjustedSize = applyBoxFit(boxFit, size, size); - -// var verticalPadding = size.height - adjustedSize.destination.height; -// var horizontalPadding = size.width - adjustedSize.destination.width; -// if (verticalPadding > 0) { -// verticalPadding = verticalPadding / 2; -// } else { -// verticalPadding = 0; -// } - -// if (horizontalPadding > 0) { -// horizontalPadding = horizontalPadding / 2; -// } else { -// horizontalPadding = 0; -// } - -// final ratioWidth = (Platform.isIOS ? capture.size.width : size.width) / -// adjustedSize.destination.width; -// final ratioHeight = (Platform.isIOS ? capture.size.height : size.height) / -// adjustedSize.destination.height; - -// final adjustedOffset = []; -// for (final offset in barcode.corners) { -// adjustedOffset.add( -// Offset( -// offset.dx / ratioWidth + horizontalPadding, -// offset.dy / ratioHeight + verticalPadding, -// ), -// ); -// } -// final cutoutPath = Path()..addPolygon(adjustedOffset, true); - -// final backgroundPaint = Paint() -// ..color = Colors.red.withOpacity(0.3) -// ..style = PaintingStyle.fill -// ..blendMode = BlendMode.dstOut; - -// canvas.drawPath(cutoutPath, backgroundPaint); -// } - -// @override -// bool shouldRepaint(covariant CustomPainter oldDelegate) => false; -// } - -class ScannerOverlay extends CustomPainter { - ScannerOverlay(this.scanWindow); - - final Rect scanWindow; - - @override - void paint(Canvas canvas, Size size) { - final backgroundPath = Path()..addRect(Rect.largest); - final cutoutPath = Path()..addRect(scanWindow); - - final backgroundPaint = Paint() - ..color = Colors.black.withAlpha(127) - ..style = PaintingStyle.fill - ..blendMode = BlendMode.dstOut; - - final backgroundWithCutout = Path.combine( - PathOperation.difference, - backgroundPath, - cutoutPath, - ); - canvas.drawPath(backgroundWithCutout, backgroundPaint); - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => false; -} - class ScanInvitationDialog extends StatefulWidget { const ScanInvitationDialog({required Locator locator, super.key}) : _locator = locator; @@ -122,7 +35,7 @@ class ScanInvitationDialog extends StatefulWidget { } class ScanInvitationDialogState extends State { - bool scanned = false; + var _scanned = false; @override void initState() { @@ -131,14 +44,14 @@ class ScanInvitationDialogState extends State { void onValidationCancelled() { setState(() { - scanned = false; + _scanned = false; }); } void onValidationSuccess() {} void onValidationFailed() { setState(() { - scanned = false; + _scanned = false; }); } @@ -146,142 +59,62 @@ class ScanInvitationDialogState extends State { Future scanQRImage(BuildContext context) async { final theme = Theme.of(context); - //final textTheme = theme.textTheme; final scale = theme.extension()!; - final windowSize = MediaQuery.of(context).size; - //final maxDialogWidth = min(windowSize.width - 64.0, 800.0 - 64.0); - //final maxDialogHeight = windowSize.height - 64.0; - final scanWindow = Rect.fromCenter( - center: MediaQuery.of(context).size.center(Offset.zero), - width: 200, - height: 200, - ); - - final cameraController = MobileScannerController(); try { return showDialog( context: context, builder: (context) => Stack( fit: StackFit.expand, children: [ - MobileScanner( - fit: BoxFit.contain, - scanWindow: scanWindow, - controller: cameraController, - errorBuilder: (context, error) => - ScannerErrorWidget(error: error), - onDetect: (c) { - final barcode = c.barcodes.firstOrNull; - - final barcodeBytes = barcode?.rawBytes; - if (barcodeBytes != null) { - cameraController.dispose(); - Navigator.pop(context, barcodeBytes); - } - }), - CustomPaint( - painter: ScannerOverlay(scanWindow), - ), - Align( - alignment: Alignment.bottomCenter, - child: Container( - alignment: Alignment.bottomCenter, - height: 100, - color: Colors.black.withAlpha(127), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - IconButton( - color: Colors.white, - icon: ValueListenableBuilder( - valueListenable: cameraController, - builder: (context, state, child) { - switch (state.torchState) { - case TorchState.off: - return Icon(Icons.flash_off, - color: - scale.grayScale.subtleBackground); - case TorchState.on: - return Icon(Icons.flash_on, - color: scale.primaryScale.primary); - case TorchState.auto: - return Icon(Icons.flash_auto, - color: scale.primaryScale.primary); - case TorchState.unavailable: - return Icon(Icons.no_flash, - color: scale.primaryScale.primary); - } - }, - ), - iconSize: 32, - onPressed: cameraController.toggleTorch, - ), - SizedBox( - width: windowSize.width - 120, - height: 50, - child: FittedBox( - child: Text( + CameraQRScanner( + scanSize: const Size(200, 200), + loadingBuilder: (context) => waitingPage(), + errorBuilder: (coRntext, e, st) => errorPage(e, st), + bottomRowBuilder: (context) => FittedBox( + fit: BoxFit.scaleDown, + child: Text( translate( 'scan_invitation_dialog.instructions'), - overflow: TextOverflow.fade, - style: Theme.of(context) - .textTheme - .labelLarge! - .copyWith(color: Colors.white), - ), - ), + overflow: TextOverflow.ellipsis, + style: theme.textTheme.labelLarge), ), - IconButton( - color: Colors.white, - icon: ValueListenableBuilder( - valueListenable: cameraController, - builder: (context, state, child) { - switch (state.cameraDirection) { - case CameraFacing.front: - return const Icon(Icons.camera_front); - case CameraFacing.back: - return const Icon(Icons.camera_rear); - case CameraFacing.external: - return const Icon(Icons.camera_alt); - case CameraFacing.unknown: - return const Icon(Icons.question_mark); - } - }, - ), - iconSize: 32, - onPressed: cameraController.switchCamera, - ), - ], - ), - ), - ), + showNotification: (s) {}, + logError: log.error, + cameraErrorMessage: + translate('scan_invitation_dialog.camera_error'), + deniedErrorMessage: + translate('scan_invitation_dialog.permission_error'), + deniedWithoutPromptErrorMessage: + translate('scan_invitation_dialog.permission_error'), + restrictedErrorMessage: + translate('scan_invitation_dialog.permission_error'), + onDetect: (result) { + final byteSegments = result + .resultMetadata[ResultMetadataType.byteSegments]; + if (byteSegments != null) { + final segs = byteSegments as List; + + final byteData = Uint8List.fromList(segs[0].toList()); + return byteData; + } + return null; + }, + onDone: (result) { + Navigator.of(context).pop(result); + }), Align( alignment: Alignment.topRight, child: IconButton( color: Colors.white, - icon: - Icon(Icons.close, color: scale.grayScale.primary), - iconSize: 32, - onPressed: () => { - SchedulerBinding.instance - .addPostFrameCallback((_) { - cameraController.dispose(); - Navigator.pop(context); - }) - })), + icon: Icon(Icons.close, + color: scale.primaryScale.appText), + iconSize: 32.scaled(context), + onPressed: () { + Navigator.of(context).pop(); + })), ], )); - } on MobileScannerException catch (e) { - if (e.errorCode == MobileScannerErrorCode.permissionDenied) { - context - .read() - .error(text: translate('scan_invitation_dialog.permission_error')); - } else { - context - .read() - .error(text: translate('scan_invitation_dialog.error')); - } } on Exception catch (_) { context .read() @@ -342,76 +175,66 @@ class ScanInvitationDialogState extends State { InvitationDialogState dialogState, Future Function({required Uint8List inviteData}) validateInviteData) { - //final theme = Theme.of(context); - //final scale = theme.extension()!; - //final textTheme = theme.textTheme; - //final height = MediaQuery.of(context).size.height; - - if (isiOS || isAndroid) { - return Column(mainAxisSize: MainAxisSize.min, children: [ - if (!scanned) - Text( - translate('scan_invitation_dialog.scan_qr_here'), - ).paddingLTRB(0, 0, 0, 8), - if (!scanned) - Container( - constraints: const BoxConstraints(maxHeight: 200), - child: ElevatedButton( - onPressed: dialogState.isValidating - ? null - : () async { - final inviteData = await scanQRImage(context); - if (inviteData != null) { - setState(() { - scanned = true; - }); - await validateInviteData(inviteData: inviteData); - } - }, - child: Text(translate('scan_invitation_dialog.scan'))), - ).paddingLTRB(0, 0, 0, 8) - ]); + if (_scanned) { + return const SizedBox.shrink(); } - return Column(mainAxisSize: MainAxisSize.min, children: [ - if (!scanned) + + final children = []; + if (isiOS || isAndroid) { + children.addAll([ Text( - translate('scan_invitation_dialog.paste_qr_here'), + translate('scan_invitation_dialog.scan_qr_here'), ).paddingLTRB(0, 0, 0, 8), - if (!scanned) Container( constraints: const BoxConstraints(maxHeight: 200), child: ElevatedButton( onPressed: dialogState.isValidating ? null : () async { - final inviteData = await pasteQRImage(context); + final inviteData = await scanQRImage(context); if (inviteData != null) { - await validateInviteData(inviteData: inviteData); setState(() { - scanned = true; + _scanned = true; }); + await validateInviteData(inviteData: inviteData); } }, - child: Text(translate('scan_invitation_dialog.paste'))), + child: Text(translate('scan_invitation_dialog.scan'))), ).paddingLTRB(0, 0, 0, 8) + ]); + } + + children.addAll([ + Text( + translate('scan_invitation_dialog.paste_qr_here'), + ).paddingLTRB(0, 0, 0, 8), + Container( + constraints: const BoxConstraints(maxHeight: 200), + child: ElevatedButton( + onPressed: dialogState.isValidating + ? null + : () async { + final inviteData = await pasteQRImage(context); + if (inviteData != null) { + await validateInviteData(inviteData: inviteData); + setState(() { + _scanned = true; + }); + } + }, + child: Text(translate('scan_invitation_dialog.paste'))), + ).paddingLTRB(0, 0, 0, 8) ]); + + return Column(mainAxisSize: MainAxisSize.min, children: children); } @override - // ignore: prefer_expression_function_bodies - Widget build(BuildContext context) { - return InvitationDialog( - locator: widget._locator, - onValidationCancelled: onValidationCancelled, - onValidationSuccess: onValidationSuccess, - onValidationFailed: onValidationFailed, - inviteControlIsValid: inviteControlIsValid, - buildInviteControl: buildInviteControl); - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties.add(DiagnosticsProperty('scanned', scanned)); - } + Widget build(BuildContext context) => InvitationDialog( + locator: widget._locator, + onValidationCancelled: onValidationCancelled, + onValidationSuccess: onValidationSuccess, + onValidationFailed: onValidationFailed, + inviteControlIsValid: inviteControlIsValid, + buildInviteControl: buildInviteControl); } diff --git a/lib/contact_invitation/views/views.dart b/lib/contact_invitation/views/views.dart index 241513d..319296b 100644 --- a/lib/contact_invitation/views/views.dart +++ b/lib/contact_invitation/views/views.dart @@ -1,3 +1,4 @@ +export 'camera_qr_scanner.dart'; export 'contact_invitation_display.dart'; export 'contact_invitation_item_widget.dart'; export 'contact_invitation_list_widget.dart'; diff --git a/lib/theme/views/scanner_error_widget.dart b/lib/theme/views/scanner_error_widget.dart deleted file mode 100644 index d5463f4..0000000 --- a/lib/theme/views/scanner_error_widget.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:mobile_scanner/mobile_scanner.dart'; - -class ScannerErrorWidget extends StatelessWidget { - const ScannerErrorWidget({required this.error, super.key}); - - final MobileScannerException error; - - @override - Widget build(BuildContext context) { - String errorMessage; - - switch (error.errorCode) { - case MobileScannerErrorCode.controllerUninitialized: - errorMessage = 'Controller not ready.'; - case MobileScannerErrorCode.permissionDenied: - errorMessage = 'Permission denied'; - case MobileScannerErrorCode.unsupported: - errorMessage = 'Scanning is unsupported on this device'; - default: - errorMessage = 'Generic Error'; - } - - return ColoredBox( - color: Colors.black, - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Padding( - padding: EdgeInsets.only(bottom: 16), - child: Icon(Icons.error, color: Colors.white), - ), - Text( - errorMessage, - style: const TextStyle(color: Colors.white), - ), - Text( - error.errorDetails?.message ?? '', - style: const TextStyle(color: Colors.white), - ), - ], - ), - ), - ); - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties.add(DiagnosticsProperty('error', error)); - } -} diff --git a/lib/theme/views/views.dart b/lib/theme/views/views.dart index 1144440..b62c4bc 100644 --- a/lib/theme/views/views.dart +++ b/lib/theme/views/views.dart @@ -4,7 +4,6 @@ export 'pop_control.dart'; export 'preferences/preferences.dart'; export 'recovery_key_widget.dart'; export 'responsive.dart'; -export 'scanner_error_widget.dart'; export 'styled_widgets/styled_button_box.dart'; export 'styled_widgets/styled_widgets.dart'; export 'widget_helpers.dart'; diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index a599497..7e4a42f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,7 +6,6 @@ import FlutterMacOS import Foundation import file_saver -import mobile_scanner import package_info_plus import pasteboard import path_provider_foundation @@ -21,7 +20,6 @@ import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) - MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index beb7d0e..bd9fccf 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -2,9 +2,6 @@ PODS: - file_saver (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) - - mobile_scanner (7.0.0): - - Flutter - - FlutterMacOS - package_info_plus (0.0.1): - FlutterMacOS - pasteboard (0.0.1): @@ -34,7 +31,6 @@ PODS: DEPENDENCIES: - file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - - mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) @@ -52,8 +48,6 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/file_saver/macos FlutterMacOS: :path: Flutter/ephemeral - mobile_scanner: - :path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin package_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos pasteboard: @@ -80,7 +74,6 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 package_info_plus: f0052d280d17aa382b932f399edf32507174e870 pasteboard: 278d8100149f940fb795d6b3a74f0720c890ecb7 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart index 20471da..da74df1 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log.dart @@ -243,7 +243,7 @@ class DHTLog implements DHTDeleteable { /// Will execute the closure multiple times if a consistent write to the DHT /// is not achieved. Timeout if specified will be thrown as a /// TimeoutException. The closure should return a value if its changes also - /// succeeded, and throw DHTExceptionTryAgain to trigger another + /// succeeded, and throw DHTExceptionOutdated to trigger another /// eventual consistency pass. Future operateAppendEventual( Future Function(DHTLogWriteOperations) closure, diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart index 0aebd6c..ce231e2 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart @@ -250,12 +250,13 @@ class _DHTLogSpine { final headDelta = _ringDistance(newHead, oldHead); final tailDelta = _ringDistance(newTail, oldTail); if (headDelta > _positionLimit ~/ 2 || tailDelta > _positionLimit ~/ 2) { - throw DHTExceptionInvalidData('_DHTLogSpine::_updateHead ' - '_head=$_head _tail=$_tail ' - 'oldHead=$oldHead oldTail=$oldTail ' - 'newHead=$newHead newTail=$newTail ' - 'headDelta=$headDelta tailDelta=$tailDelta ' - '_positionLimit=$_positionLimit'); + throw DHTExceptionInvalidData( + cause: '_DHTLogSpine::_updateHead ' + '_head=$_head _tail=$_tail ' + 'oldHead=$oldHead oldTail=$oldTail ' + 'newHead=$newHead newTail=$newTail ' + 'headDelta=$headDelta tailDelta=$tailDelta ' + '_positionLimit=$_positionLimit'); } } diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart index e3697c8..590fbb2 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_write.dart @@ -18,7 +18,8 @@ class _DHTLogWrite extends _DHTLogRead implements DHTLogWriteOperations { final lookup = await _spine.lookupPosition(pos); if (lookup == null) { throw DHTExceptionInvalidData( - '_DHTLogRead::tryWriteItem pos=$pos _spine.length=${_spine.length}'); + cause: '_DHTLogRead::tryWriteItem pos=$pos ' + '_spine.length=${_spine.length}'); } // Write item to the segment @@ -75,10 +76,11 @@ class _DHTLogWrite extends _DHTLogRead implements DHTLogWriteOperations { final lookup = await _spine.lookupPosition(insertPos + valueIdx); if (lookup == null) { - throw DHTExceptionInvalidData('_DHTLogWrite::addAll ' - '_spine.length=${_spine.length}' - 'insertPos=$insertPos valueIdx=$valueIdx ' - 'values.length=${values.length} '); + throw DHTExceptionInvalidData( + cause: '_DHTLogWrite::addAll ' + '_spine.length=${_spine.length}' + 'insertPos=$insertPos valueIdx=$valueIdx ' + 'values.length=${values.length} '); } final sacount = min(remaining, DHTShortArray.maxElements - lookup.pos); diff --git a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart index 1785b28..5b224cb 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_short_array/dht_short_array_head.dart @@ -531,7 +531,7 @@ class _DHTShortArrayHead { //////////////////////////////////////////////////////////////////////////// // Head/element mutex to ensure we keep the representation valid - final Mutex _headMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); + final _headMutex = Mutex(debugLockTimeout: kIsDebugMode ? 60 : null); // Subscription to head record internal changes StreamSubscription? _subscription; // Notify closure for external head changes diff --git a/packages/veilid_support/lib/dht_support/src/interfaces/dht_add.dart b/packages/veilid_support/lib/dht_support/src/interfaces/dht_add.dart index dc79350..28d2fbb 100644 --- a/packages/veilid_support/lib/dht_support/src/interfaces/dht_add.dart +++ b/packages/veilid_support/lib/dht_support/src/interfaces/dht_add.dart @@ -15,27 +15,37 @@ abstract class DHTAdd { Future add(Uint8List value); /// Try to add a list of items to the DHT container. - /// Return if the elements were successfully added. - /// Throws DHTExceptionTryAgain if the state changed before the elements could + /// Return the number of elements successfully added. + /// Throws DHTExceptionTryAgain if the state changed before any elements could /// be added or a newer value was found on the network. + /// Throws DHTConcurrencyLimit if the number values in the list was too large + /// at this time /// Throws a StateError if the container exceeds its maximum size. Future addAll(List values); } extension DHTAddExt on DHTAdd { /// Convenience function: - /// Like tryAddItem but also encodes the input value as JSON and parses the - /// returned element as JSON + /// Like add but also encodes the input value as JSON Future addJson( T newValue, ) => add(jsonEncodeBytes(newValue)); /// Convenience function: - /// Like tryAddItem but also encodes the input value as a protobuf object - /// and parses the returned element as a protobuf object + /// Like add but also encodes the input value as a protobuf object Future addProtobuf( T newValue, ) => add(newValue.writeToBuffer()); + + /// Convenience function: + /// Like addAll but also encodes the input values as JSON + Future addAllJson(List values) => + addAll(values.map(jsonEncodeBytes).toList()); + + /// Convenience function: + /// Like addAll but also encodes the input values as protobuf objects + Future addAllProtobuf(List values) => + addAll(values.map((x) => x.writeToBuffer()).toList()); } diff --git a/packages/veilid_support/lib/dht_support/src/interfaces/dht_random_swap.dart b/packages/veilid_support/lib/dht_support/src/interfaces/dht_random_swap.dart new file mode 100644 index 0000000..8aa4dc1 --- /dev/null +++ b/packages/veilid_support/lib/dht_support/src/interfaces/dht_random_swap.dart @@ -0,0 +1,9 @@ +//////////////////////////////////////////////////////////////////////////// +// Writer interface +// ignore: one_member_abstracts +abstract class DHTRandomSwap { + /// Swap items at position 'aPos' and 'bPos' in the DHTArray. + /// Throws an IndexError if either of the positions swapped exceeds the length + /// of the container + Future swap(int aPos, int bPos); +} diff --git a/packages/veilid_support/lib/dht_support/src/interfaces/exceptions.dart b/packages/veilid_support/lib/dht_support/src/interfaces/exceptions.dart index 134f5fa..01354f0 100644 --- a/packages/veilid_support/lib/dht_support/src/interfaces/exceptions.dart +++ b/packages/veilid_support/lib/dht_support/src/interfaces/exceptions.dart @@ -1,14 +1,25 @@ class DHTExceptionOutdated implements Exception { const DHTExceptionOutdated( - [this.cause = 'operation failed due to newer dht value']); + {this.cause = 'operation failed due to newer dht value'}); final String cause; @override String toString() => 'DHTExceptionOutdated: $cause'; } +class DHTConcurrencyLimit implements Exception { + const DHTConcurrencyLimit( + {required this.limit, + this.cause = 'failed due to maximum parallel operation limit'}); + final String cause; + final int limit; + + @override + String toString() => 'DHTConcurrencyLimit: $cause (limit=$limit)'; +} + class DHTExceptionInvalidData implements Exception { - const DHTExceptionInvalidData(this.cause); + const DHTExceptionInvalidData({this.cause = 'data was invalid'}); final String cause; @override @@ -16,7 +27,7 @@ class DHTExceptionInvalidData implements Exception { } class DHTExceptionCancelled implements Exception { - const DHTExceptionCancelled([this.cause = 'operation was cancelled']); + const DHTExceptionCancelled({this.cause = 'operation was cancelled'}); final String cause; @override @@ -25,7 +36,7 @@ class DHTExceptionCancelled implements Exception { class DHTExceptionNotAvailable implements Exception { const DHTExceptionNotAvailable( - [this.cause = 'request could not be completed at this time']); + {this.cause = 'request could not be completed at this time'}); final String cause; @override diff --git a/pubspec.lock b/pubspec.lock index 779806e..95c1262 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -274,7 +274,7 @@ packages: source: hosted version: "1.3.1" camera: - dependency: transitive + dependency: "direct main" description: name: camera sha256: "413d2b34fe28496c35c69ede5b232fb9dd5ca2c3a4cb606b14efc1c7546cc8cb" @@ -394,7 +394,7 @@ packages: source: hosted version: "1.19.1" convert: - dependency: transitive + dependency: "direct main" description: name: convert sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 @@ -912,14 +912,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" - mobile_scanner: - dependency: "direct main" - description: - name: mobile_scanner - sha256: "72f06a071aa8b14acea3ab43ea7949eefe4a2469731ae210e006ba330a033a8c" - url: "https://pub.dev" - source: hosted - version: "7.0.0" nested: dependency: transitive description: @@ -1765,7 +1757,7 @@ packages: path: "../veilid/veilid-flutter" relative: true source: path - version: "0.4.6" + version: "0.4.7" veilid_support: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index cee2ec7..5cdbcb0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,10 +23,12 @@ dependencies: bloc: ^9.0.0 bloc_advanced_tools: ^0.1.13 blurry_modal_progress_hud: ^1.1.1 + camera: ^0.11.1 change_case: ^2.2.0 charcode: ^1.4.0 circular_profile_avatar: ^2.0.5 circular_reveal_animation: ^2.0.1 + convert: ^3.1.2 cupertino_icons: ^1.0.8 equatable: ^2.0.7 expansion_tile_group: ^2.2.0 @@ -58,7 +60,6 @@ dependencies: json_annotation: ^4.9.0 loggy: ^2.0.3 meta: ^1.16.0 - mobile_scanner: ^7.0.0 package_info_plus: ^8.3.0 pasteboard: ^0.4.0 path: ^1.9.1 From a1a6872e3ff534221c32dad97fddb7e9af9807d2 Mon Sep 17 00:00:00 2001 From: "Ethan Hindmarsh #YOLO #Ally.lol" Date: Fri, 6 Jun 2025 21:58:13 -0500 Subject: [PATCH 92/93] update version bump logic --- version_bump.sh | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/version_bump.sh b/version_bump.sh index cd17b37..9733313 100755 --- a/version_bump.sh +++ b/version_bump.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Fail out if any step has an error set -e @@ -12,8 +12,11 @@ elif [ "$1" == "minor" ]; then elif [ "$1" == "major" ]; then echo Bumping major version PART=major +elif [ "$1" == "build" ]; then + echo Bumping build code + PART=build else - echo Unsupported part! Specify 'patch', 'minor', or 'major' + echo Unsupported part! Specify 'build', 'patch', 'minor', or 'major' exit 1 fi @@ -25,7 +28,6 @@ increment_buildcode() { local new_buildcode=$((buildcode + 1)) echo "${major_minor_patch}+${new_buildcode}" } - # Function to get the current version from pubspec.yaml get_current_version() { awk '/^version: / { print $2 }' pubspec.yaml @@ -43,25 +45,23 @@ update_version() { eval "$SED_CMD 's/version: .*/version: ${new_version}/' pubspec.yaml" } -# I pray none of this errors! - I think it should popup an error should that happen.. - current_version=$(get_current_version) echo "Current Version: $current_version" -# Bump the major, minor, or patch version using bump2version -bump2version --current-version $current_version $PART +if [ "$PART" == "build" ]; then + final_version=$(increment_buildcode $current_version) +else + # Bump the major, minor, or patch version using bump2version + bump2version --current-version $current_version $PART -# Get the new version after bump2version -new_version=$(get_current_version) + new_version_base=$(get_current_version) -# Preserve the current build code -buildcode=${current_version#*+} -new_version="${new_version%+*}+${buildcode}" - -# Increment the build code -final_version=$(increment_buildcode $new_version) + buildcode=${current_version#*+} + intermediate_version="${new_version_base%+*}+${buildcode}" + final_version=$(increment_buildcode $intermediate_version) +fi # Update pubspec.yaml with the final version update_version $final_version @@ -71,4 +71,3 @@ echo "New Version: $final_version" #git add pubspec.yaml #git commit -m "Bump version to $final_version" #git tag "v$final_version" - From 7a1fa6dfb8c865acfe3ed68a45ad8a0c58abdcc9 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sat, 14 Jun 2025 15:31:15 -0400 Subject: [PATCH 93/93] update for api fixes --- assets/i18n/en.json | 3 ++- .../cubits/contact_invitation_list_cubit.dart | 11 ++++++++++ .../views/invitation_dialog.dart | 11 ++++++++++ .../src/dht_record/dht_record.dart | 20 +++++++++++-------- .../lib/identity_support/super_identity.dart | 10 ++++++---- 5 files changed, 42 insertions(+), 13 deletions(-) diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 206c0b0..f3348b5 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -209,7 +209,8 @@ "protected_with_pin": "Contact invitation is protected with a PIN", "protected_with_password": "Contact invitation is protected with a password", "invalid_pin": "Invalid PIN", - "invalid_password": "Invalid password" + "invalid_password": "Invalid password", + "invalid_identity": "Contact invitation is for an missing or unavailable identity" }, "waiting_invitation": { "accepted": "Contact invitation accepted from {name}", diff --git a/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart b/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart index b31c453..4875263 100644 --- a/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart +++ b/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart @@ -20,6 +20,13 @@ class ContactInviteInvalidKeyException implements Exception { final EncryptionKeyType type; } +class ContactInviteInvalidIdentityException implements Exception { + const ContactInviteInvalidIdentityException( + this.contactSuperIdentityRecordKey) + : super(); + final TypedKey contactSuperIdentityRecordKey; +} + typedef GetEncryptionKeyCallback = Future Function( VeilidCryptoSystem cs, EncryptionKeyType encryptionKeyType, @@ -301,6 +308,10 @@ class ContactInvitationListCubit final contactSuperIdentity = await SuperIdentity.open( superRecordKey: contactSuperIdentityRecordKey) .withCancel(cancelRequest); + if (contactSuperIdentity == null) { + throw ContactInviteInvalidIdentityException( + contactSuperIdentityRecordKey); + } // Verify final idcs = await contactSuperIdentity.currentInstance.cryptoSystem; diff --git a/lib/contact_invitation/views/invitation_dialog.dart b/lib/contact_invitation/views/invitation_dialog.dart index a5a7fad..f4c7fcc 100644 --- a/lib/contact_invitation/views/invitation_dialog.dart +++ b/lib/contact_invitation/views/invitation_dialog.dart @@ -216,6 +216,17 @@ class InvitationDialogState extends State { _isValidating = false; _validInvitation = validatedContactInvitation; }); + } on ContactInviteInvalidIdentityException catch (_) { + if (mounted) { + context + .read() + .error(text: translate('invitation_dialog.invalid_identity')); + } + setState(() { + _isValidating = false; + _validInvitation = null; + widget.onValidationFailed(); + }); } on ContactInviteInvalidKeyException catch (e) { String errorText; switch (e.type) { diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart index 893cb80..6fb9d9e 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record.dart @@ -226,7 +226,7 @@ class DHTRecord implements DHTDeleteable { Future tryWriteBytes(Uint8List newValue, {int subkey = -1, VeilidCrypto? crypto, - KeyPair? writer, + SetDHTValueOptions? options, Output? outSeqNum}) => _wrapStats('tryWriteBytes', () async { subkey = subkeyOrDefault(subkey); @@ -236,7 +236,9 @@ class DHTRecord implements DHTDeleteable { // Set the new data if possible var newValueData = await _routingContext.setDHTValue( key, subkey, encryptedNewValue, - writer: writer ?? _writer); + options: SetDHTValueOptions( + writer: options?.writer ?? _writer, + allowOffline: options?.allowOffline)); if (newValueData == null) { // A newer value wasn't found on the set, but we may get a newer value // when getting the value for the sequence number @@ -292,7 +294,8 @@ class DHTRecord implements DHTDeleteable { // Set the new data newValueData = await _routingContext.setDHTValue( key, subkey, encryptedNewValue, - writer: writer ?? _writer); + options: SetDHTValueOptions( + writer: writer ?? _writer, allowOffline: false)); // Repeat if newer data on the network was found } while (newValueData != null); @@ -351,7 +354,8 @@ class DHTRecord implements DHTDeleteable { oldValue = await tryWriteBytes(updatedValue, subkey: subkey, crypto: crypto, - writer: writer, + options: SetDHTValueOptions( + writer: writer ?? _writer, allowOffline: false), outSeqNum: outSeqNum); // Repeat update if newer data on the network was found @@ -362,12 +366,12 @@ class DHTRecord implements DHTDeleteable { Future tryWriteJson(T Function(dynamic) fromJson, T newValue, {int subkey = -1, VeilidCrypto? crypto, - KeyPair? writer, + SetDHTValueOptions? options, Output? outSeqNum}) => tryWriteBytes(jsonEncodeBytes(newValue), subkey: subkey, crypto: crypto, - writer: writer, + options: options, outSeqNum: outSeqNum) .then((out) { if (out == null) { @@ -381,12 +385,12 @@ class DHTRecord implements DHTDeleteable { T Function(List) fromBuffer, T newValue, {int subkey = -1, VeilidCrypto? crypto, - KeyPair? writer, + SetDHTValueOptions? options, Output? outSeqNum}) => tryWriteBytes(newValue.writeToBuffer(), subkey: subkey, crypto: crypto, - writer: writer, + options: options, outSeqNum: outSeqNum) .then((out) { if (out == null) { diff --git a/packages/veilid_support/lib/identity_support/super_identity.dart b/packages/veilid_support/lib/identity_support/super_identity.dart index c8fd59d..008967d 100644 --- a/packages/veilid_support/lib/identity_support/super_identity.dart +++ b/packages/veilid_support/lib/identity_support/super_identity.dart @@ -98,18 +98,20 @@ sealed class SuperIdentity with _$SuperIdentity { } /// Opens an existing super identity, validates it, and returns it - static Future open({required TypedKey superRecordKey}) async { + static Future open({required TypedKey superRecordKey}) async { final pool = DHTRecordPool.instance; // SuperIdentity DHT record is public/unencrypted return (await pool.openRecordRead(superRecordKey, debugName: 'SuperIdentity::openSuperIdentity::SuperIdentityRecord')) .deleteScope((superRec) async { - final superIdentity = (await superRec.getJson(SuperIdentity.fromJson, - refreshMode: DHTRecordRefreshMode.network))!; + final superIdentity = await superRec.getJson(SuperIdentity.fromJson, + refreshMode: DHTRecordRefreshMode.network); + if (superIdentity == null) { + return null; + } await superIdentity.validate(superRecordKey: superRecordKey); - return superIdentity; }); }