diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 2551515..ed96192 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -58,12 +58,15 @@ "account_page": { "contact_invitations": "Contact Invitations" }, - "accounts_menu": { - "invite_contact": "Invite Contact", + "add_contact_sheet": { + "new_contact": "New Contact", "create_invite": "Create Invitation", "scan_invite": "Scan Invitation", "paste_invite": "Paste Invitation" }, + "add_chat_sheet": { + "new_chat": "New Chat" + }, "create_invitation_dialog": { "title": "Create Contact Invitation", "connect_with_me": "Connect with me on VeilidChat!", diff --git a/lib/account_manager/views/new_account_page/new_account_page.dart b/lib/account_manager/views/new_account_page/new_account_page.dart index f81f30d..53ba671 100644 --- a/lib/account_manager/views/new_account_page/new_account_page.dart +++ b/lib/account_manager/views/new_account_page/new_account_page.dart @@ -113,7 +113,9 @@ class NewAccountPageState extends State { body: _newAccountForm( context, onSubmit: (formKey) async { + // dismiss the keyboard by unfocusing the textfield FocusScope.of(context).unfocus(); + try { final name = _formKey.currentState!.fields[formFieldName]!.value as String; diff --git a/lib/app.dart b/lib/app.dart index 9dcab2f..e0c08c5 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,6 +1,7 @@ import 'package:animated_theme_switcher/animated_theme_switcher.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'; @@ -12,9 +13,15 @@ import 'init.dart'; import 'layout/splash.dart'; import 'router/router.dart'; import 'settings/settings.dart'; +import 'theme/models/theme_preference.dart'; import 'tick.dart'; +import 'tools/loggy.dart'; import 'veilid_processor/veilid_processor.dart'; +class ReloadThemeIntent extends Intent { + const ReloadThemeIntent(); +} + class VeilidChatApp extends StatelessWidget { const VeilidChatApp({ required this.initialThemeData, @@ -25,6 +32,28 @@ class VeilidChatApp extends StatelessWidget { final ThemeData initialThemeData; + void _reloadTheme(BuildContext context) { + log.info('Reloading theme'); + final theme = + PreferencesRepository.instance.value.themePreferences.themeData(); + ThemeSwitcher.of(context).changeTheme(theme: theme); + } + + Widget _buildShortcuts( + {required BuildContext context, + required Widget Function(BuildContext) builder}) => + ThemeSwitcher( + builder: (context) => Shortcuts( + shortcuts: { + LogicalKeySet( + LogicalKeyboardKey.alt, LogicalKeyboardKey.keyR): + const ReloadThemeIntent(), + }, + child: Actions(actions: >{ + ReloadThemeIntent: CallbackAction( + onInvoke: (intent) => _reloadTheme(context)), + }, child: Focus(autofocus: true, child: builder(context))))); + @override Widget build(BuildContext context) => FutureProvider( initialData: null, @@ -68,21 +97,24 @@ class VeilidChatApp extends StatelessWidget { ) ], child: BackgroundTicker( - builder: (context) => MaterialApp.router( - debugShowCheckedModeBanner: false, - routerConfig: context.watch().router(), - title: translate('app.title'), - theme: theme, - localizationsDelegates: [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - FormBuilderLocalizations.delegate, - localizationDelegate - ], - supportedLocales: - localizationDelegate.supportedLocales, - locale: localizationDelegate.currentLocale, - )), + child: _buildShortcuts( + context: context, + builder: (context) => MaterialApp.router( + debugShowCheckedModeBanner: false, + routerConfig: + context.watch().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/new_chat_bottom_sheet.dart b/lib/chat/views/new_chat_bottom_sheet.dart new file mode 100644 index 0000000..e900b6c --- /dev/null +++ b/lib/chat/views/new_chat_bottom_sheet.dart @@ -0,0 +1,71 @@ +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 '../../tools/tools.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: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + 'Group and custom chat functionality is not available yet') + // Column(children: [ + // IconButton( + // onPressed: () async { + // Navigator.pop(sheetContext); + // await CreateInvitationDialog.show(context); + // }, + // iconSize: 64, + // icon: const Icon(Icons.contact_page), + // color: scale.primaryScale.background), + // Text( + // translate('accounts_menu.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.background), + // Text( + // translate('accounts_menu.scan_invite'), + // ) + // ]), + // Column(children: [ + // IconButton( + // onPressed: () async { + // Navigator.pop(sheetContext); + // await PasteInvitationDialog.show(context); + // }, + // iconSize: 64, + // icon: const Icon(Icons.paste), + // color: scale.primaryScale.background), + // Text( + // translate('accounts_menu.paste_invite'), + // ) + // ]) + ]).paddingAll(16)))); +} diff --git a/lib/chat/views/views.dart b/lib/chat/views/views.dart index 6230e65..1999862 100644 --- a/lib/chat/views/views.dart +++ b/lib/chat/views/views.dart @@ -1,3 +1,4 @@ export 'chat_component.dart'; export 'empty_chat_widget.dart'; +export 'new_chat_bottom_sheet.dart'; export 'no_conversation_widget.dart'; diff --git a/lib/chat_list/views/chat_single_contact_list_widget.dart b/lib/chat_list/views/chat_single_contact_list_widget.dart index c689e22..0e212dd 100644 --- a/lib/chat_list/views/chat_single_contact_list_widget.dart +++ b/lib/chat_list/views/chat_single_contact_list_widget.dart @@ -7,7 +7,6 @@ import 'package:searchable_listview/searchable_listview.dart'; import '../../contacts/contacts.dart'; import '../../proto/proto.dart' as proto; -import '../../theme/theme.dart'; import '../../tools/tools.dart'; import '../chat_list.dart'; @@ -17,10 +16,6 @@ class ChatSingleContactListWidget extends StatelessWidget { @override // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { - final theme = Theme.of(context); - //final textTheme = theme.textTheme; - final scale = theme.extension()!; - final contactListV = context.watch().state; return contactListV.builder((context, contactList) { @@ -29,55 +24,49 @@ class ChatSingleContactListWidget extends StatelessWidget { valueMapper: (c) => c); final chatListV = context.watch().state; - return chatListV.builder((context, chatList) => SizedBox.expand( + return chatListV + .builder((context, chatList) => SizedBox.expand( child: styledTitleContainer( context: context, title: translate('chat_list.chats'), child: SizedBox.expand( - child: (chatList.isEmpty) - ? const EmptyChatListWidget() - : SearchableList( - initialList: chatList.toList(), - builder: (l, i, c) { + child: (chatList.isEmpty) + ? const EmptyChatListWidget() + : SearchableList( + initialList: chatList.toList(), + builder: (l, i, c) { + final contact = + contactMap[c.remoteConversationRecordKey]; + if (contact == null) { + return const Text('...'); + } + return ChatSingleContactItemWidget( + contact: contact, + disabled: contactListV.busy); + }, + filter: (value) { + final lowerValue = value.toLowerCase(); + return chatList.where((c) { final contact = contactMap[c.remoteConversationRecordKey]; if (contact == null) { - return const Text('...'); + return false; } - return ChatSingleContactItemWidget( - contact: contact, - disabled: contactListV.busy); - }, - filter: (value) { - final lowerValue = value.toLowerCase(); - return chatList.where((c) { - final contact = - contactMap[c.remoteConversationRecordKey]; - if (contact == null) { - return false; - } - return contact.editedProfile.name - .toLowerCase() - .contains(lowerValue) || - contact.editedProfile.pronouns - .toLowerCase() - .contains(lowerValue); - }).toList(); - }, - spaceBetweenSearchAndList: 4, - inputDecoration: InputDecoration( - labelText: translate('chat_list.search'), - contentPadding: const EdgeInsets.all(2), - fillColor: scale.primaryScale.elementBackground, - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: scale.primaryScale.hoverBorder, - ), - borderRadius: BorderRadius.circular(8), - ), - ), - ).paddingAll(8)))) - .paddingLTRB(8, 0, 8, 8)); + return contact.editedProfile.name + .toLowerCase() + .contains(lowerValue) || + contact.editedProfile.pronouns + .toLowerCase() + .contains(lowerValue); + }).toList(); + }, + spaceBetweenSearchAndList: 4, + inputDecoration: InputDecoration( + labelText: translate('chat_list.search'), + ), + ), + ).paddingAll(8)))) + .paddingLTRB(8, 0, 8, 8); }); } } diff --git a/lib/contact_invitation/views/create_invitation_dialog.dart b/lib/contact_invitation/views/create_invitation_dialog.dart index be814a0..8365744 100644 --- a/lib/contact_invitation/views/create_invitation_dialog.dart +++ b/lib/contact_invitation/views/create_invitation_dialog.dart @@ -182,7 +182,7 @@ class CreateInvitationDialogState extends State { LengthLimitingTextInputFormatter(128), ], decoration: InputDecoration( - border: const OutlineInputBorder(), + //border: const OutlineInputBorder(), hintText: translate('create_invitation_dialog.enter_message_hint'), labelText: translate('create_invitation_dialog.message')), diff --git a/lib/contact_invitation/views/new_contact_bottom_sheet.dart b/lib/contact_invitation/views/new_contact_bottom_sheet.dart new file mode 100644 index 0000000..32bd725 --- /dev/null +++ b/lib/contact_invitation/views/new_contact_bottom_sheet.dart @@ -0,0 +1,72 @@ +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 '../../tools/tools.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.background), + 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.background), + 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.background), + Text( + translate('add_contact_sheet.paste_invite'), + ) + ]) + ]).paddingAll(16)))); +} diff --git a/lib/contact_invitation/views/new_contact_invitation_bottom_sheet.dart b/lib/contact_invitation/views/new_contact_invitation_bottom_sheet.dart deleted file mode 100644 index 8228245..0000000 --- a/lib/contact_invitation/views/new_contact_invitation_bottom_sheet.dart +++ /dev/null @@ -1,66 +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 'paste_invitation_dialog.dart'; -import 'scan_invitation_dialog.dart'; -import 'create_invitation_dialog.dart'; - -Widget newContactInvitationBottomSheetBuilder( - BuildContext sheetContext, BuildContext context) { - final theme = Theme.of(sheetContext); - final textTheme = theme.textTheme; - final scale = theme.extension()!; - - return KeyboardListener( - focusNode: FocusNode(), - onKeyEvent: (ke) { - if (ke.logicalKey == LogicalKeyboardKey.escape) { - Navigator.pop(sheetContext); - } - }, - child: SizedBox( - height: 200, - child: Column(children: [ - Text(translate('accounts_menu.invite_contact'), - style: textTheme.titleMedium) - .paddingAll(8), - 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.background), - Text(translate('accounts_menu.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.background), - Text(translate('accounts_menu.scan_invite')) - ]), - Column(children: [ - IconButton( - onPressed: () async { - Navigator.pop(sheetContext); - await PasteInvitationDialog.show(context); - }, - iconSize: 64, - icon: const Icon(Icons.paste), - color: scale.primaryScale.background), - Text(translate('accounts_menu.paste_invite')) - ]) - ]).expanded() - ]))); -} diff --git a/lib/contact_invitation/views/paste_invitation_dialog.dart b/lib/contact_invitation/views/paste_invitation_dialog.dart index 3e19c1c..ead492b 100644 --- a/lib/contact_invitation/views/paste_invitation_dialog.dart +++ b/lib/contact_invitation/views/paste_invitation_dialog.dart @@ -125,7 +125,6 @@ class PasteInvitationDialogState extends State { maxLines: null, controller: _pasteTextController, decoration: const InputDecoration( - border: OutlineInputBorder(), hintText: '--- BEGIN VEILIDCHAT CONTACT INVITE ----\n' 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n' '---- END VEILIDCHAT CONTACT INVITE -----\n', diff --git a/lib/contact_invitation/views/views.dart b/lib/contact_invitation/views/views.dart index 3a7b8ec..726f0b9 100644 --- a/lib/contact_invitation/views/views.dart +++ b/lib/contact_invitation/views/views.dart @@ -3,6 +3,6 @@ export 'contact_invitation_item_widget.dart'; export 'contact_invitation_list_widget.dart'; export 'create_invitation_dialog.dart'; export 'invitation_dialog.dart'; -export 'new_contact_invitation_bottom_sheet.dart'; +export 'new_contact_bottom_sheet.dart'; export 'paste_invitation_dialog.dart'; export 'scan_invitation_dialog.dart'; diff --git a/lib/contacts/views/contact_item_widget.dart b/lib/contacts/views/contact_item_widget.dart index 1306ef3..9016212 100644 --- a/lib/contacts/views/contact_item_widget.dart +++ b/lib/contacts/views/contact_item_widget.dart @@ -29,14 +29,15 @@ class ContactItemWidget extends StatelessWidget { final remoteConversationKey = contact.remoteConversationRecordKey.toVeilid(); - const selected = - false; // xxx: eventually when we have selectable contacts: activeContactCubit.state == remoteConversationRecordKey; + const selected = false; // xxx: eventually when we have selectable contacts: + // activeContactCubit.state == remoteConversationRecordKey; return Container( margin: const EdgeInsets.fromLTRB(0, 4, 0, 0), clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( color: selected + // ignore: dead_code ? scale.primaryScale.activeElementBackground : scale.primaryScale.hoverElementBackground, shape: RoundedRectangleBorder( @@ -102,9 +103,11 @@ class ContactItemWidget extends StatelessWidget { ? Text(contact.editedProfile.pronouns) : null, iconColor: selected + // ignore: dead_code ? scale.primaryScale.appText : scale.primaryScale.subtleText, textColor: selected + // ignore: dead_code ? scale.primaryScale.appText : scale.primaryScale.subtleText, selectedColor: scale.primaryScale.appText, diff --git a/lib/contacts/views/contact_list_widget.dart b/lib/contacts/views/contact_list_widget.dart index f5b775b..7fc0a08 100644 --- a/lib/contacts/views/contact_list_widget.dart +++ b/lib/contacts/views/contact_list_widget.dart @@ -6,7 +6,6 @@ import 'package:flutter_translate/flutter_translate.dart'; import 'package:searchable_listview/searchable_listview.dart'; import '../../proto/proto.dart' as proto; -import '../../theme/theme.dart'; import '../../tools/tools.dart'; import 'contact_item_widget.dart'; import 'empty_contact_list_widget.dart'; @@ -26,47 +25,33 @@ class ContactListWidget extends StatelessWidget { } @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - //final textTheme = theme.textTheme; - final scale = theme.extension()!; - - return SizedBox.expand( - child: styledTitleContainer( - context: context, - title: translate('contact_list.title'), - child: SizedBox.expand( - child: (contactList.isEmpty) - ? const EmptyContactListWidget() - : SearchableList( - initialList: contactList.toList(), - builder: (l, i, c) => - ContactItemWidget(contact: c, disabled: disabled), - filter: (value) { - final lowerValue = value.toLowerCase(); - return contactList - .where((element) => - element.editedProfile.name - .toLowerCase() - .contains(lowerValue) || - element.editedProfile.pronouns - .toLowerCase() - .contains(lowerValue)) - .toList(); - }, - spaceBetweenSearchAndList: 4, - inputDecoration: InputDecoration( - labelText: translate('contact_list.search'), - contentPadding: const EdgeInsets.all(2), - fillColor: scale.primaryScale.appText, - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: scale.primaryScale.hoverBorder, - ), - borderRadius: BorderRadius.circular(8), - ), - ), - ).paddingAll(8), - ))).paddingLTRB(8, 0, 8, 8); - } + Widget build(BuildContext context) => SizedBox.expand( + child: styledTitleContainer( + context: context, + title: translate('contact_list.title'), + child: SizedBox.expand( + child: (contactList.isEmpty) + ? const EmptyContactListWidget() + : SearchableList( + initialList: contactList.toList(), + builder: (l, i, c) => + ContactItemWidget(contact: c, disabled: disabled), + filter: (value) { + final lowerValue = value.toLowerCase(); + return contactList + .where((element) => + element.editedProfile.name + .toLowerCase() + .contains(lowerValue) || + element.editedProfile.pronouns + .toLowerCase() + .contains(lowerValue)) + .toList(); + }, + spaceBetweenSearchAndList: 4, + inputDecoration: InputDecoration( + labelText: translate('contact_list.search'), + ), + ).paddingAll(8), + ))).paddingLTRB(8, 0, 8, 8); } diff --git a/lib/layout/home/home_account_ready/home_account_ready_chat.dart b/lib/layout/home/home_account_ready/home_account_ready_chat.dart index ffeaa05..621f9e8 100644 --- a/lib/layout/home/home_account_ready/home_account_ready_chat.dart +++ b/lib/layout/home/home_account_ready/home_account_ready_chat.dart @@ -12,8 +12,6 @@ class HomeAccountReadyChat extends StatefulWidget { } class HomeAccountReadyChatState extends State { - final _unfocusNode = FocusNode(); - @override void initState() { super.initState(); @@ -26,7 +24,6 @@ class HomeAccountReadyChatState extends State { @override void dispose() { - _unfocusNode.dispose(); super.dispose(); } @@ -42,8 +39,6 @@ class HomeAccountReadyChatState extends State { @override Widget build(BuildContext context) => SafeArea( - child: GestureDetector( - onTap: () => FocusScope.of(context).requestFocus(_unfocusNode), child: buildChatComponent(context), - )); + ); } diff --git a/lib/layout/home/home_account_ready/main_pager/account_page.dart b/lib/layout/home/home_account_ready/main_pager/account_page.dart index b97ebe1..3d0cfad 100644 --- a/lib/layout/home/home_account_ready/main_pager/account_page.dart +++ b/lib/layout/home/home_account_ready/main_pager/account_page.dart @@ -18,8 +18,6 @@ class AccountPage extends StatefulWidget { } class AccountPageState extends State { - final _unfocusNode = FocusNode(); - @override void initState() { super.initState(); @@ -27,7 +25,6 @@ class AccountPageState extends State { @override void dispose() { - _unfocusNode.dispose(); super.dispose(); } diff --git a/lib/layout/home/home_account_ready/main_pager/chats_page.dart b/lib/layout/home/home_account_ready/main_pager/chats_page.dart index 1c7e7fe..bdea8e3 100644 --- a/lib/layout/home/home_account_ready/main_pager/chats_page.dart +++ b/lib/layout/home/home_account_ready/main_pager/chats_page.dart @@ -11,8 +11,6 @@ class ChatsPage extends StatefulWidget { } class ChatsPageState extends State { - final _unfocusNode = FocusNode(); - @override void initState() { super.initState(); @@ -20,7 +18,6 @@ class ChatsPageState extends State { @override void dispose() { - _unfocusNode.dispose(); super.dispose(); } diff --git a/lib/layout/home/home_account_ready/main_pager/main_pager.dart b/lib/layout/home/home_account_ready/main_pager/main_pager.dart index 0e121a4..8c03eda 100644 --- a/lib/layout/home/home_account_ready/main_pager/main_pager.dart +++ b/lib/layout/home/home_account_ready/main_pager/main_pager.dart @@ -5,9 +5,9 @@ import 'package:flutter/rendering.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:preload_page_view/preload_page_view.dart'; -import 'package:stylish_bottom_bar/model/bar_items.dart'; import 'package:stylish_bottom_bar/stylish_bottom_bar.dart'; +import '../../../../chat/chat.dart'; import '../../../../contact_invitation/contact_invitation.dart'; import '../../../../theme/theme.dart'; import '../../../../tools/tools.dart'; @@ -28,8 +28,6 @@ class MainPager extends StatefulWidget { class MainPagerState extends State with TickerProviderStateMixin { ////////////////////////////////////////////////////////////////// - final _unfocusNode = FocusNode(); - var _currentPage = 0; final pageController = PreloadPageController(); @@ -56,7 +54,6 @@ class MainPagerState extends State with TickerProviderStateMixin { @override void dispose() { - _unfocusNode.dispose(); pageController.dispose(); super.dispose(); } @@ -127,21 +124,13 @@ class MainPagerState extends State with TickerProviderStateMixin { }); } - Widget _onNewChatBottomSheetBuilder( - BuildContext sheetContext, BuildContext context) => - const SizedBox( - height: 200, - child: Center( - child: Text( - 'Group and custom chat functionality is not available yet'))); - Widget _bottomSheetBuilder(BuildContext sheetContext, BuildContext context) { if (_currentPage == 0) { // New contact invitation - return newContactInvitationBottomSheetBuilder(sheetContext, context); + return newContactBottomSheetBuilder(sheetContext, context); } else if (_currentPage == 1) { // New chat - return _onNewChatBottomSheetBuilder(sheetContext, context); + return newChatBottomSheetBuilder(sheetContext, context); } else { // Unknown error return debugPage('unknown page'); diff --git a/lib/layout/home/home_shell.dart b/lib/layout/home/home_shell.dart index bd1949c..8851730 100644 --- a/lib/layout/home/home_shell.dart +++ b/lib/layout/home/home_shell.dart @@ -19,8 +19,6 @@ class HomeShell extends StatefulWidget { } class HomeShellState extends State { - final _unfocusNode = FocusNode(); - @override void initState() { super.initState(); @@ -28,7 +26,6 @@ class HomeShellState extends State { @override void dispose() { - _unfocusNode.dispose(); super.dispose(); } @@ -69,11 +66,9 @@ class HomeShellState extends State { // XXX: eventually write account switcher here return SafeArea( - child: GestureDetector( - onTap: () => FocusScope.of(context).requestFocus(_unfocusNode), - child: DecoratedBox( - decoration: BoxDecoration( - color: scale.primaryScale.activeElementBackground), - child: buildWithLogin(context)))); + child: DecoratedBox( + decoration: BoxDecoration( + color: scale.primaryScale.activeElementBackground), + child: buildWithLogin(context))); } } diff --git a/lib/settings/settings_page.dart b/lib/settings/settings_page.dart index c84e7d7..5eb89fb 100644 --- a/lib/settings/settings_page.dart +++ b/lib/settings/settings_page.dart @@ -53,11 +53,11 @@ class SettingsPageState extends State { child: ListView( children: [ buildSettingsPageColorPreferences( - onChanged: () => setState(() {})), + context: context, onChanged: () => setState(() {})), buildSettingsPageBrightnessPreferences( - onChanged: () => setState(() {})), - ], + context: context, onChanged: () => setState(() {})), + ].map((x) => x.paddingLTRB(0, 0, 0, 8)).toList(), ), - ).paddingSymmetric(horizontal: 24, vertical: 8), + ).paddingSymmetric(horizontal: 24, vertical: 16), ))); } diff --git a/lib/theme/models/radix_generator.dart b/lib/theme/models/radix_generator.dart index efc3bdb..415b628 100644 --- a/lib/theme/models/radix_generator.dart +++ b/lib/theme/models/radix_generator.dart @@ -1,7 +1,10 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_chat_ui/flutter_chat_ui.dart'; import 'package:radix_colors/radix_colors.dart'; +import '../../tools/tools.dart'; import 'scale_color.dart'; import 'scale_scheme.dart'; @@ -571,26 +574,27 @@ ColorScheme _scaleToColorScheme(Brightness brightness, ScaleScheme scale) => Colors.red, // scale.primaryScale.hoverElementBackground, onPrimaryContainer: Colors.green, //scale.primaryScale.subtleText, secondary: scale.secondaryScale.background, - onSecondary: scale.secondaryScale.appText, + onSecondary: scale.secondaryScale.foregroundText, secondaryContainer: scale.secondaryScale.hoverElementBackground, onSecondaryContainer: scale.secondaryScale.subtleText, tertiary: scale.tertiaryScale.background, - onTertiary: scale.tertiaryScale.appText, + onTertiary: scale.tertiaryScale.foregroundText, tertiaryContainer: scale.tertiaryScale.hoverElementBackground, onTertiaryContainer: scale.tertiaryScale.subtleText, error: scale.errorScale.background, - onError: scale.errorScale.appText, + onError: scale.errorScale.foregroundText, errorContainer: scale.errorScale.hoverElementBackground, onErrorContainer: scale.errorScale.subtleText, background: scale.grayScale.appBackground, // reviewed onBackground: scale.grayScale.appText, // reviewed - surface: scale.primaryScale.activeElementBackground, // reviewed - onSurface: scale.primaryScale.subtleText, // reviewed + surface: scale.primaryScale.background, // reviewed + onSurface: scale.primaryScale.foregroundText, // reviewed surfaceVariant: scale.primaryScale.elementBackground, - onSurfaceVariant: scale.primaryScale.subtleText, // ?? reviewed a little + onSurfaceVariant: + scale.primaryScale.foregroundText, // ?? reviewed a little outline: scale.primaryScale.border, outlineVariant: scale.primaryScale.subtleBorder, - shadow: RadixColors.dark.gray.step1, + shadow: const Color(0xFF000000), scrim: scale.primaryScale.background, inverseSurface: scale.primaryScale.subtleText, onInverseSurface: scale.primaryScale.subtleBackground, @@ -612,7 +616,7 @@ ChatTheme makeChatTheme(ScaleScheme scale, TextTheme textTheme) => contentPadding: const EdgeInsets.fromLTRB(8, 12, 8, 12), border: const OutlineInputBorder( borderSide: BorderSide.none, - borderRadius: BorderRadius.all(Radius.circular(16))), + borderRadius: BorderRadius.all(Radius.circular(8))), ), inputContainerDecoration: BoxDecoration(color: scale.primaryScale.border), @@ -641,10 +645,39 @@ ChatTheme makeChatTheme(ScaleScheme scale, TextTheme textTheme) => fontSize: 64, )); +TextTheme _makeTextTheme(Brightness brightness) { + late final TextTheme textTheme; + if (Platform.isIOS) { + textTheme = (brightness == Brightness.light) + ? Typography.blackCupertino + : Typography.whiteCupertino; + } else if (Platform.isMacOS) { + textTheme = (brightness == Brightness.light) + ? Typography.blackRedwoodCity + : Typography.whiteRedwoodCity; + } else if (Platform.isAndroid || Platform.isFuchsia) { + textTheme = (brightness == Brightness.light) + ? Typography.blackMountainView + : Typography.whiteMountainView; + } else if (Platform.isLinux) { + textTheme = (brightness == Brightness.light) + ? Typography.blackHelsinki + : Typography.whiteHelsinki; + } else if (Platform.isWindows) { + textTheme = (brightness == Brightness.light) + ? Typography.blackRedmond + : Typography.whiteRedmond; + } else { + log.warning('unknown platform'); + textTheme = (brightness == Brightness.light) + ? Typography.blackHelsinki + : Typography.whiteHelsinki; + } + return textTheme; +} + ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) { - final textTheme = (brightness == Brightness.light) - ? Typography.blackCupertino - : Typography.whiteCupertino; + final textTheme = _makeTextTheme(brightness); final radix = _radixScheme(brightness, themeColor); final scaleScheme = radix.toScale(); final colorScheme = _scaleToColorScheme(brightness, scaleScheme); @@ -655,8 +688,42 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) { bottomSheetTheme: themeData.bottomSheetTheme.copyWith( elevation: 0, modalElevation: 0, - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(16))), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16)))), + canvasColor: scaleScheme.primaryScale.subtleBackground, + chipTheme: themeData.chipTheme.copyWith( + backgroundColor: scaleScheme.primaryScale.elementBackground, + selectedColor: scaleScheme.primaryScale.activeElementBackground, + surfaceTintColor: scaleScheme.primaryScale.hoverElementBackground, + checkmarkColor: scaleScheme.primaryScale.background, + 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))), + ), + focusColor: scaleScheme.primaryScale.activeElementBackground, + hoverColor: scaleScheme.primaryScale.hoverElementBackground, + inputDecorationTheme: themeData.inputDecorationTheme.copyWith( + border: OutlineInputBorder( + borderSide: BorderSide(color: scaleScheme.primaryScale.border), + borderRadius: BorderRadius.circular(8)), + 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))), extensions: >[ scaleScheme, ]); diff --git a/lib/theme/views/brightness_preferences.dart b/lib/theme/views/brightness_preferences.dart index 0c7ab10..2f3f410 100644 --- a/lib/theme/views/brightness_preferences.dart +++ b/lib/theme/views/brightness_preferences.dart @@ -22,7 +22,7 @@ List> _getBrightnessDropdownItems() { } Widget buildSettingsPageBrightnessPreferences( - {required void Function() onChanged}) { + {required BuildContext context, required void Function() onChanged}) { final preferencesRepository = PreferencesRepository.instance; final themePreferences = preferencesRepository.value.themePreferences; return ThemeSwitcher.withTheme( diff --git a/lib/theme/views/color_preferences.dart b/lib/theme/views/color_preferences.dart index a364e00..4a5219d 100644 --- a/lib/theme/views/color_preferences.dart +++ b/lib/theme/views/color_preferences.dart @@ -30,7 +30,8 @@ List> _getThemeDropdownItems() { .toList(); } -Widget buildSettingsPageColorPreferences({required void Function() onChanged}) { +Widget buildSettingsPageColorPreferences( + {required BuildContext context, required void Function() onChanged}) { final preferencesRepository = PreferencesRepository.instance; final themePreferences = preferencesRepository.value.themePreferences; return ThemeSwitcher.withTheme( diff --git a/lib/tick.dart b/lib/tick.dart index 279de11..197a1d6 100644 --- a/lib/tick.dart +++ b/lib/tick.dart @@ -1,24 +1,17 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:veilid_support/veilid_support.dart'; import 'veilid_processor/veilid_processor.dart'; class BackgroundTicker extends StatefulWidget { - const BackgroundTicker({required this.builder, super.key}); + const BackgroundTicker({required this.child, super.key}); - final Widget Function(BuildContext) builder; + final Widget child; @override BackgroundTickerState createState() => BackgroundTickerState(); - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties.add(ObjectFlagProperty.has( - 'builder', builder)); - } } class BackgroundTickerState extends State { @@ -48,7 +41,7 @@ class BackgroundTickerState extends State { @override // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { - return widget.builder(context); + return widget.child; } Future _onTick() async { diff --git a/lib/tools/widget_helpers.dart b/lib/tools/widget_helpers.dart index 0e9928e..3d8be63 100644 --- a/lib/tools/widget_helpers.dart +++ b/lib/tools/widget_helpers.dart @@ -154,7 +154,7 @@ Widget styledTitleContainer({ title, style: textTheme.titleMedium! .copyWith(color: scale.primaryScale.subtleText), - ).paddingLTRB(8, 8, 8, 8), + ).paddingLTRB(8, 8, 8, 4), DecoratedBox( decoration: ShapeDecoration( color: @@ -168,6 +168,43 @@ Widget styledTitleContainer({ ])); } +Widget styledBottomSheet({ + required BuildContext context, + required String title, + required Widget child, + Color? borderColor, + Color? backgroundColor, +}) { + final theme = Theme.of(context); + final scale = theme.extension()!; + final textTheme = theme.textTheme; + + return DecoratedBox( + decoration: ShapeDecoration( + color: borderColor ?? scale.primaryScale.border, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16)))), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Text( + title, + style: textTheme.titleMedium! + .copyWith(color: scale.primaryScale.subtleText), + ).paddingLTRB(8, 8, 8, 4), + DecoratedBox( + decoration: ShapeDecoration( + color: + backgroundColor ?? scale.primaryScale.subtleBackground, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16)))), + child: child) + .paddingLTRB(4, 4, 4, 0) + ])); +} + bool get isPlatformDark => WidgetsBinding.instance.platformDispatcher.platformBrightness == Brightness.dark; diff --git a/lib/veilid_processor/views/developer.dart b/lib/veilid_processor/views/developer.dart index 978ab8f..4e70ff9 100644 --- a/lib/veilid_processor/views/developer.dart +++ b/lib/veilid_processor/views/developer.dart @@ -80,6 +80,18 @@ class _DeveloperPageState extends State { return; } + if (debugCommand.startsWith('change_log_ignore ')) { + final args = debugCommand.split(' '); + if (args.length < 3) { + _debugOut('Incorrect number of arguments'); + return; + } + final layer = args[1]; + final changes = args[2].split(','); + Veilid.instance.changeLogIgnore(layer, changes); + return; + } + if (debugCommand == 'ellet') { setState(() { _showEllet = !_showEllet; diff --git a/packages/veilid_support/lib/src/config.dart b/packages/veilid_support/lib/src/config.dart index c13a8b8..889a8fd 100644 --- a/packages/veilid_support/lib/src/config.dart +++ b/packages/veilid_support/lib/src/config.dart @@ -1,6 +1,7 @@ -import 'package:veilid/veilid.dart'; import 'dart:io' show Platform; +import 'package:veilid/veilid.dart'; + Map getDefaultVeilidPlatformConfig( bool isWeb, String appName) { final ignoreLogTargetsStr =