From 2fa3cbd21c865edf3517ae67d3cf572f78c0aa74 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Thu, 11 Jul 2024 23:04:08 -0400 Subject: [PATCH] sliver refactor --- assets/i18n/en.json | 8 +- ios/Podfile.lock | 6 + .../views/edit_account_page.dart | 28 +-- .../views/new_account_page.dart | 23 ++- lib/account_manager/views/profile_widget.dart | 2 +- .../views/show_recovery_key_page.dart | 18 +- lib/app.dart | 3 +- lib/chat_list/views/chat_list_widget.dart | 57 +++--- .../views/contact_invitation_list_widget.dart | 91 +++++----- lib/contacts/views/contact_list_widget.dart | 79 ++++----- lib/layout/home/drawer_menu/drawer_menu.dart | 28 +-- lib/layout/home/home_screen.dart | 5 - lib/layout/home/main_pager/account_page.dart | 86 --------- lib/layout/home/main_pager/chats_page.dart | 5 +- lib/layout/home/main_pager/contacts_page.dart | 59 +++++++ lib/layout/home/main_pager/main_pager.dart | 51 ++---- lib/layout/splash.dart | 15 +- lib/settings/settings_page.dart | 6 - lib/theme/views/styled_scaffold.dart | 6 +- lib/theme/views/widget_helpers.dart | 165 +++++++++++++++++- lib/tools/native_safe_area.dart | 111 ++++++++++++ lib/tools/window_control.dart | 53 +++++- lib/veilid_processor/views/developer.dart | 113 ++++++------ pubspec.lock | 72 ++++++++ pubspec.yaml | 7 + 25 files changed, 710 insertions(+), 387 deletions(-) delete mode 100644 lib/layout/home/main_pager/account_page.dart create mode 100644 lib/layout/home/main_pager/contacts_page.dart create mode 100644 lib/tools/native_safe_area.dart diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 577d6bb..e24d560 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -97,8 +97,9 @@ "invalid_account_title": "Invalid Account", "invalid_account_text": "Account is invalid, removing from list" }, - "account_page": { - "contact_invitations": "Contact Invitations" + "contacts_page": { + "contacts": "Contacts", + "invitations": "Invitations" }, "add_contact_sheet": { "new_contact": "New Contact", @@ -176,14 +177,13 @@ "password_does_not_match": "Password does not match" }, "contact_list": { - "title": "Contacts", "invite_people": "Invite people to VeilidChat", "search": "Search contacts", "invitation": "Invitation" }, "chat_list": { "search": "Search chats", - "start_a_conversation": "Start a conversation", + "start_a_conversation": "Start A Conversation", "chats": "Chats", "groups": "Groups" }, diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f9cadda..3a17844 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -58,6 +58,8 @@ PODS: - nanopb/encode (= 2.30910.0) - nanopb/decode (2.30910.0) - nanopb/encode (2.30910.0) + - native_device_orientation (0.0.1): + - Flutter - package_info_plus (0.4.5): - Flutter - pasteboard (0.0.1): @@ -91,6 +93,7 @@ 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`) @@ -129,6 +132,8 @@ 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: @@ -169,6 +174,7 @@ SPEC CHECKSUMS: MLKitVision: e858c5f125ecc288e4a31127928301eaba9ae0c1 mobile_scanner: 8564358885a9253c43f822435b70f9345c87224f nanopb: 438bc412db1928dac798aa6fd75726007be04262 + native_device_orientation: 348b10c346a60ebbc62fb235a4fdb5d1b61a8f55 package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c pasteboard: 982969ebaa7c78af3e6cc7761e8f5e77565d9ce0 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 diff --git a/lib/account_manager/views/edit_account_page.dart b/lib/account_manager/views/edit_account_page.dart index 47e2ab2..59adeaf 100644 --- a/lib/account_manager/views/edit_account_page.dart +++ b/lib/account_manager/views/edit_account_page.dart @@ -44,25 +44,11 @@ class EditAccountPage extends StatefulWidget { } } -class _EditAccountPageState extends State { - bool _isInAsyncCall = false; - - @override - void initState() { - super.initState(); - - WidgetsBinding.instance.addPostFrameCallback((_) async { - await changeWindowSetup( - TitleBarStyle.normal, OrientationCapability.portraitOnly); - }); - } - - @override - void dispose() { - unawaited( - changeWindowSetup(TitleBarStyle.normal, OrientationCapability.normal)); - super.dispose(); - } +class _EditAccountPageState extends WindowSetupState { + _EditAccountPageState() + : super( + titleBarStyle: TitleBarStyle.normal, + orientationCapability: OrientationCapability.portraitOnly); Widget _editAccountForm(BuildContext context, {required Future Function(GlobalKey) @@ -314,4 +300,8 @@ class _EditAccountPageState extends State { ]).paddingSymmetric(horizontal: 24, vertical: 8))) .withModalHUD(context, displayModalHUD); } + + //////////////////////////////////////////////////////////////////////////// + + bool _isInAsyncCall = false; } diff --git a/lib/account_manager/views/new_account_page.dart b/lib/account_manager/views/new_account_page.dart index cc2a333..943e79e 100644 --- a/lib/account_manager/views/new_account_page.dart +++ b/lib/account_manager/views/new_account_page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -20,18 +22,11 @@ class NewAccountPage extends StatefulWidget { State createState() => _NewAccountPageState(); } -class _NewAccountPageState extends State { - bool _isInAsyncCall = false; - - @override - void initState() { - super.initState(); - - WidgetsBinding.instance.addPostFrameCallback((_) async { - await changeWindowSetup( - TitleBarStyle.normal, OrientationCapability.portraitOnly); - }); - } +class _NewAccountPageState extends WindowSetupState { + _NewAccountPageState() + : super( + titleBarStyle: TitleBarStyle.normal, + orientationCapability: OrientationCapability.portraitOnly); Widget _newAccountForm(BuildContext context, {required Future Function(GlobalKey) onSubmit}) { @@ -120,4 +115,8 @@ class _NewAccountPageState extends State { )).paddingSymmetric(horizontal: 24, vertical: 8), ).withModalHUD(context, displayModalHUD); } + + //////////////////////////////////////////////////////////////////////////// + + bool _isInAsyncCall = false; } diff --git a/lib/account_manager/views/profile_widget.dart b/lib/account_manager/views/profile_widget.dart index 9d06648..672b13a 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(16 * scaleConfig.borderRadiusScale))), + Radius.circular(12 * scaleConfig.borderRadiusScale))), ), child: Row(children: [ const Spacer(), diff --git a/lib/account_manager/views/show_recovery_key_page.dart b/lib/account_manager/views/show_recovery_key_page.dart index 33de475..2649bcf 100644 --- a/lib/account_manager/views/show_recovery_key_page.dart +++ b/lib/account_manager/views/show_recovery_key_page.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; @@ -29,22 +30,17 @@ class ShowRecoveryKeyPage extends StatefulWidget { _name = name; @override - ShowRecoveryKeyPageState createState() => ShowRecoveryKeyPageState(); + State createState() => _ShowRecoveryKeyPageState(); final WritableSuperIdentity _writableSuperIdentity; final String _name; } -class ShowRecoveryKeyPageState extends State { - @override - void initState() { - super.initState(); - - WidgetsBinding.instance.addPostFrameCallback((_) async { - await changeWindowSetup( - TitleBarStyle.normal, OrientationCapability.portraitOnly); - }); - } +class _ShowRecoveryKeyPageState extends WindowSetupState { + _ShowRecoveryKeyPageState() + : super( + titleBarStyle: TitleBarStyle.normal, + orientationCapability: OrientationCapability.portraitOnly); Future _shareRecoveryKey( BuildContext context, Uint8List recoveryKey, String name) async { diff --git a/lib/app.dart b/lib/app.dart index 3480b18..ec5c9be 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -15,7 +15,6 @@ import 'init.dart'; import 'layout/splash.dart'; import 'router/router.dart'; import 'settings/settings.dart'; -import 'theme/models/theme_preference.dart'; import 'theme/theme.dart'; import 'tick.dart'; import 'tools/loggy.dart'; @@ -92,7 +91,7 @@ class VeilidChatApp extends StatelessWidget { Widget build(BuildContext context) => FutureProvider( initialData: null, create: (context) async => VeilidChatGlobalInit.initialize(), - builder: (context, child) { + builder: (context, __) { final globalInit = context.watch(); if (globalInit == null) { // Splash screen until we're done with init diff --git a/lib/chat_list/views/chat_list_widget.dart b/lib/chat_list/views/chat_list_widget.dart index e91cbba..67bcc5d 100644 --- a/lib/chat_list/views/chat_list_widget.dart +++ b/lib/chat_list/views/chat_list_widget.dart @@ -60,36 +60,33 @@ class ChatListWidget extends StatelessWidget { final chatListV = context.watch().state; 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.map((x) => x.value).toList(), - itemBuilder: (c) { - switch (c.whichKind()) { - case proto.Chat_Kind.direct: - return _itemBuilderDirect( - c.direct, - contactMap, - contactListV.busy || chatListV.busy); - 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), - spaceBetweenSearchAndList: 4, - inputDecoration: InputDecoration( - labelText: translate('chat_list.search'), - ), - ), - ).paddingAll(8)))) + child: styledTitleContainer( + context: context, + 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, + contactListV.busy || chatListV.busy); + 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), + spaceBetweenSearchAndList: 4, + inputDecoration: InputDecoration( + labelText: translate('chat_list.search'), + ), + ).paddingAll(8), + ))) .paddingLTRB(8, 0, 8, 8); }); } diff --git a/lib/contact_invitation/views/contact_invitation_list_widget.dart b/lib/contact_invitation/views/contact_invitation_list_widget.dart index ad72027..56a6b9a 100644 --- a/lib/contact_invitation/views/contact_invitation_list_widget.dart +++ b/lib/contact_invitation/views/contact_invitation_list_widget.dart @@ -2,6 +2,7 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; import '../../proto/proto.dart' as proto; import '../../theme/theme.dart'; @@ -31,58 +32,58 @@ class ContactInvitationListWidget extends StatefulWidget { } class ContactInvitationListWidgetState - extends State { - final ScrollController _scrollController = ScrollController(); + extends State + with SingleTickerProviderStateMixin { + late final _controller = AnimationController( + vsync: this, duration: const Duration(milliseconds: 250), value: 1); + late final _animation = + CurvedAnimation(parent: _controller, curve: Curves.easeInOut); + bool _expanded = true; @override // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { final theme = Theme.of(context); - //final textTheme = theme.textTheme; + // final textTheme = theme.textTheme; final scale = theme.extension()!; final scaleConfig = theme.extension()!; - return Container( - width: double.infinity, - margin: const EdgeInsets.fromLTRB(4, 0, 4, 4), - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16 * scaleConfig.borderRadiusScale), - )), - constraints: const BoxConstraints(maxHeight: 100), - child: Container( - width: double.infinity, - decoration: ShapeDecoration( - color: scale.primaryScale.subtleBackground, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16 * scaleConfig.borderRadiusScale), - )), - child: ListView.builder( - shrinkWrap: true, - controller: _scrollController, - itemCount: widget.contactInvitationRecordList.length, - itemBuilder: (context, index) { - if (index < 0 || - index >= widget.contactInvitationRecordList.length) { - return null; - } - return ContactInvitationItemWidget( - contactInvitationRecord: - widget.contactInvitationRecordList[index], - disabled: widget.disabled, - key: ObjectKey(widget.contactInvitationRecordList[index])) - .paddingLTRB(4, 2, 4, 2); - }, - findChildIndexCallback: (key) { - final index = widget.contactInvitationRecordList.indexOf( - (key as ObjectKey).value! as proto.ContactInvitationRecord); - if (index == -1) { - return null; - } - return index; - }, - ).paddingLTRB(4, 6, 4, 6)), - ); + return styledExpandingSliver( + context: context, + animation: _animation, + expanded: _expanded, + backgroundColor: scaleConfig.preferBorders + ? scale.primaryScale.subtleBackground + : scale.primaryScale.subtleBorder, + onTap: () { + setState(() { + _expanded = !_expanded; + }); + _controller.animateTo(_expanded ? 1 : 0); + }, + title: translate('contacts_page.invitations'), + sliver: SliverList.builder( + itemCount: widget.contactInvitationRecordList.length, + itemBuilder: (context, index) { + if (index < 0 || + index >= widget.contactInvitationRecordList.length) { + return null; + } + return ContactInvitationItemWidget( + contactInvitationRecord: + widget.contactInvitationRecordList[index], + disabled: widget.disabled, + key: ObjectKey(widget.contactInvitationRecordList[index])) + .paddingLTRB(4, 2, 4, 2); + }, + findChildIndexCallback: (key) { + final index = widget.contactInvitationRecordList.indexOf( + (key as ObjectKey).value! as proto.ContactInvitationRecord); + if (index == -1) { + return null; + } + return index; + }, + )); } } diff --git a/lib/contacts/views/contact_list_widget.dart b/lib/contacts/views/contact_list_widget.dart index eb9958d..f5d4e46 100644 --- a/lib/contacts/views/contact_list_widget.dart +++ b/lib/contacts/views/contact_list_widget.dart @@ -28,49 +28,50 @@ class ContactListWidget extends StatefulWidget { } } -class _ContactListWidgetState extends State { - @override - void initState() { - super.initState(); - } - - @override - void dispose() { - super.dispose(); - } - +class _ContactListWidgetState extends State + with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { final theme = Theme.of(context); + //final textTheme = theme.textTheme; final scale = theme.extension()!; + final scaleConfig = theme.extension()!; - return styledTitleContainer( - context: context, - title: translate('contact_list.title'), - child: SearchableList( - shrinkWrap: true, - initialList: widget.contactList.toList(), - itemBuilder: (c) => - ContactItemWidget(contact: c, disabled: widget.disabled) - .paddingLTRB(0, 4, 0, 0), - filter: (value) { - final lowerValue = value.toLowerCase(); - return widget.contactList - .where((element) => - element.nickname.toLowerCase().contains(lowerValue) || - element.profile.name.toLowerCase().contains(lowerValue) || - element.profile.pronouns.toLowerCase().contains(lowerValue)) - .toList(); - }, - searchFieldHeight: 40, - spaceBetweenSearchAndList: 4, - emptyWidget: const EmptyContactListWidget(), - defaultSuffixIconColor: scale.primaryScale.border, - closeKeyboardWhenScrolling: true, - inputDecoration: InputDecoration( - labelText: translate('contact_list.search'), - ), - ).paddingAll(8), - ).paddingLTRB(8, 0, 8, 8); + return SliverLayoutBuilder( + builder: (context, constraints) => styledHeaderSliver( + context: context, + backgroundColor: scaleConfig.preferBorders + ? scale.primaryScale.subtleBackground + : scale.primaryScale.subtleBorder, + title: translate('contacts_page.contacts'), + sliver: SliverFillRemaining( + child: SearchableList.sliver( + initialList: widget.contactList.toList(), + itemBuilder: (c) => + ContactItemWidget(contact: c, disabled: widget.disabled) + .paddingLTRB(0, 4, 0, 0), + filter: (value) { + final lowerValue = value.toLowerCase(); + return widget.contactList + .where((element) => + element.nickname.toLowerCase().contains(lowerValue) || + element.profile.name + .toLowerCase() + .contains(lowerValue) || + element.profile.pronouns + .toLowerCase() + .contains(lowerValue)) + .toList(); + }, + searchFieldHeight: 40, + spaceBetweenSearchAndList: 4, + emptyWidget: const EmptyContactListWidget(), + defaultSuffixIconColor: scale.primaryScale.border, + closeKeyboardWhenScrolling: true, + inputDecoration: InputDecoration( + labelText: translate('contact_list.search'), + ), + ), + ))); } } diff --git a/lib/layout/home/drawer_menu/drawer_menu.dart b/lib/layout/home/drawer_menu/drawer_menu.dart index 8f1ec07..6d7209c 100644 --- a/lib/layout/home/drawer_menu/drawer_menu.dart +++ b/lib/layout/home/drawer_menu/drawer_menu.dart @@ -143,7 +143,7 @@ class _DrawerMenuState extends State { (scaleConfig.preferBorders || scaleConfig.useVisualIndicators) ? activeBorder : null, - borderRadius: 16 * scaleConfig.borderRadiusScale, + borderRadius: 12 * scaleConfig.borderRadiusScale, callback: callback, footerButtonIcon: loggedIn ? Icons.edit_outlined : null, footerCallback: footerCallback, @@ -197,11 +197,11 @@ class _DrawerMenuState extends State { loading: () => _wrapInBox( child: buildProgressIndicator(), color: scaleScheme.grayScale.subtleBorder, - borderRadius: 16 * scaleConfig.borderRadiusScale), + borderRadius: 12 * scaleConfig.borderRadiusScale), error: (err, st) => _wrapInBox( child: errorPage(err, st), color: scaleScheme.errorScale.subtleBorder, - borderRadius: 16 * scaleConfig.borderRadiusScale), + borderRadius: 12 * scaleConfig.borderRadiusScale), ); loggedInAccounts.add(loggedInAccount.paddingLTRB(0, 0, 0, 8)); } else { @@ -254,7 +254,7 @@ class _DrawerMenuState extends State { return IconButton( icon: icon, color: border, - constraints: const BoxConstraints.expand(height: 64, width: 64), + constraints: const BoxConstraints.expand(height: 48, width: 48), style: ButtonStyle( backgroundColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.hovered)) { @@ -269,18 +269,18 @@ class _DrawerMenuState extends State { return RoundedRectangleBorder( side: BorderSide(color: hoverBorder, width: 2), borderRadius: BorderRadius.all( - Radius.circular(16 * scaleConfig.borderRadiusScale))); + Radius.circular(12 * scaleConfig.borderRadiusScale))); } if (states.contains(WidgetState.focused)) { return RoundedRectangleBorder( side: BorderSide(color: activeBorder, width: 2), borderRadius: BorderRadius.all( - Radius.circular(16 * scaleConfig.borderRadiusScale))); + Radius.circular(12 * scaleConfig.borderRadiusScale))); } return RoundedRectangleBorder( side: BorderSide(color: border, width: 2), borderRadius: BorderRadius.all( - Radius.circular(16 * scaleConfig.borderRadiusScale))); + Radius.circular(12 * scaleConfig.borderRadiusScale))); })), tooltip: tooltip, onPressed: onPressed); @@ -413,12 +413,18 @@ class _DrawerMenuState extends State { _getBottomButtons(), Row(children: [ Text('${translate('menu.version')} $packageInfoVersion', - style: theme.textTheme.labelMedium! - .copyWith(color: scale.tertiaryScale.hoverBorder)), + style: theme.textTheme.labelMedium!.copyWith( + color: scaleConfig.preferBorders + ? scale.tertiaryScale.hoverBorder + : scale.tertiaryScale.subtleBackground)), const Spacer(), SignalStrengthMeterWidget( - color: scale.tertiaryScale.hoverBorder, - inactiveColor: scale.tertiaryScale.border, + color: scaleConfig.preferBorders + ? scale.tertiaryScale.hoverBorder + : scale.tertiaryScale.subtleBackground, + inactiveColor: scaleConfig.preferBorders + ? scale.tertiaryScale.border + : scale.tertiaryScale.elementBackground, ), ]) ]).paddingAll(16), diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index c9fe1e5..f482757 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -8,7 +8,6 @@ import 'package:veilid_support/veilid_support.dart'; import '../../account_manager/account_manager.dart'; import '../../theme/theme.dart'; -import '../../tools/tools.dart'; import 'drawer_menu/drawer_menu.dart'; import 'home_account_invalid.dart'; import 'home_account_locked.dart'; @@ -37,9 +36,6 @@ class HomeScreenState extends State .indexWhere((x) => x.superIdentity.recordKey == activeLocalAccount); final canClose = activeIndex != -1; - await changeWindowSetup( - TitleBarStyle.normal, OrientationCapability.normal); - if (!canClose) { await _zoomDrawerController.open!(); } @@ -129,7 +125,6 @@ class HomeScreenState extends State final canClose = activeIndex != -1; return SafeArea( - bottom: false, child: DefaultTextStyle( style: theme.textTheme.bodySmall!, child: ZoomDrawer( diff --git a/lib/layout/home/main_pager/account_page.dart b/lib/layout/home/main_pager/account_page.dart deleted file mode 100644 index c9a30b1..0000000 --- a/lib/layout/home/main_pager/account_page.dart +++ /dev/null @@ -1,86 +0,0 @@ -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_translate/flutter_translate.dart'; - -import '../../../contact_invitation/contact_invitation.dart'; -import '../../../contacts/contacts.dart'; -import '../../../theme/theme.dart'; - -class AccountPage extends StatefulWidget { - const AccountPage({ - super.key, - }); - - @override - AccountPageState createState() => AccountPageState(); -} - -class AccountPageState extends State { - @override - void initState() { - super.initState(); - } - - @override - void dispose() { - super.dispose(); - } - - @override - // ignore: prefer_expression_function_bodies - Widget build(BuildContext context) { - final theme = Theme.of(context); - final textTheme = theme.textTheme; - 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() ?? - const IListConst([]); - - return SizedBox( - child: Column(children: [ - if (contactInvitationRecordList.isNotEmpty) - ExpansionTile( - tilePadding: const EdgeInsets.fromLTRB(8, 0, 8, 0), - backgroundColor: scale.primaryScale.border, - collapsedBackgroundColor: scale.primaryScale.border, - dense: true, - minTileHeight: 16, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16 * scaleConfig.borderRadiusScale), - ), - collapsedShape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(16 * scaleConfig.borderRadiusScale), - ), - title: Text( - translate('account_page.contact_invitations'), - textAlign: TextAlign.center, - style: textTheme.titleSmall! - .copyWith(color: scale.primaryScale.borderText), - ), - iconColor: scale.primaryScale.borderText, - collapsedIconColor: scale.primaryScale.borderText, - initiallyExpanded: true, - children: [ - ContactInvitationListWidget( - contactInvitationRecordList: contactInvitationRecordList, - disabled: cilBusy) - ], - ).paddingLTRB(8, 0, 8, 8), - ContactListWidget(contactList: contactList, disabled: ciBusy).expanded(), - ])); - } -} diff --git a/lib/layout/home/main_pager/chats_page.dart b/lib/layout/home/main_pager/chats_page.dart index 1765b62..2146b3d 100644 --- a/lib/layout/home/main_pager/chats_page.dart +++ b/lib/layout/home/main_pager/chats_page.dart @@ -1,4 +1,3 @@ -import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/material.dart'; import '../../../chat_list/chat_list.dart'; @@ -24,8 +23,6 @@ class ChatsPageState extends State { @override // ignore: prefer_expression_function_bodies Widget build(BuildContext context) { - return Column(children: [ - const ChatListWidget().expanded(), - ]); + return const ChatListWidget(); } } diff --git a/lib/layout/home/main_pager/contacts_page.dart b/lib/layout/home/main_pager/contacts_page.dart new file mode 100644 index 0000000..d858270 --- /dev/null +++ b/lib/layout/home/main_pager/contacts_page.dart @@ -0,0 +1,59 @@ +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 '../../../contact_invitation/contact_invitation.dart'; +import '../../../contacts/contacts.dart'; + +class ContactsPage extends StatefulWidget { + const ContactsPage({ + super.key, + }); + + @override + ContactsPageState createState() => ContactsPageState(); +} + +class ContactsPageState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + // ignore: prefer_expression_function_bodies + Widget build(BuildContext context) { + // final theme = Theme.of(context); + // final textTheme = theme.textTheme; + // 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() ?? + const IListConst([]); + + return CustomScrollView(slivers: [ + if (contactInvitationRecordList.isNotEmpty) + SliverPadding( + padding: const EdgeInsets.only(bottom: 8), + sliver: ContactInvitationListWidget( + contactInvitationRecordList: contactInvitationRecordList, + disabled: cilBusy)), + ContactListWidget(contactList: contactList, disabled: ciBusy), + ]).paddingLTRB(8, 0, 8, 8); + } +} diff --git a/lib/layout/home/main_pager/main_pager.dart b/lib/layout/home/main_pager/main_pager.dart index bfa476f..68ef39b 100644 --- a/lib/layout/home/main_pager/main_pager.dart +++ b/lib/layout/home/main_pager/main_pager.dart @@ -13,9 +13,9 @@ import 'package:provider/provider.dart'; import '../../../chat/chat.dart'; import '../../../contact_invitation/contact_invitation.dart'; import '../../../theme/theme.dart'; -import 'account_page.dart'; import 'bottom_sheet_action_button.dart'; import 'chats_page.dart'; +import 'contacts_page.dart'; class MainPager extends StatefulWidget { const MainPager({super.key}); @@ -41,25 +41,6 @@ class MainPagerState extends State with TickerProviderStateMixin { super.dispose(); } - bool _onScrollNotification(ScrollNotification notification) { - if (notification is UserScrollNotification && - notification.metrics.axis == Axis.vertical) { - switch (notification.direction) { - case ScrollDirection.forward: - // _hideBottomBarAnimationController.reverse(); - // _fabAnimationController.forward(from: 0); - break; - case ScrollDirection.reverse: - // _hideBottomBarAnimationController.forward(); - // _fabAnimationController.reverse(from: 1); - break; - case ScrollDirection.idle: - break; - } - } - return false; - } - Future scanContactInvitationDialog(BuildContext context) async { await showDialog( context: context, @@ -162,21 +143,19 @@ class MainPagerState extends State with TickerProviderStateMixin { return Scaffold( //extendBody: true, backgroundColor: Colors.transparent, - body: NotificationListener( - onNotification: _onScrollNotification, - child: PreloadPageView( - key: _pageViewKey, - controller: pageController, - preloadPagesCount: 2, - onPageChanged: (index) { - setState(() { - currentPage = index; - }); - }, - children: const [ - AccountPage(), - ChatsPage(), - ])), + body: PreloadPageView( + key: _pageViewKey, + controller: pageController, + preloadPagesCount: 2, + onPageChanged: (index) { + setState(() { + currentPage = index; + }); + }, + children: const [ + ContactsPage(), + ChatsPage(), + ]), // appBar: AppBar( // toolbarHeight: 24, // title: Text( @@ -240,7 +219,7 @@ class MainPagerState extends State with TickerProviderStateMixin { // ]; final _fabIconList = [ Icons.person_add_sharp, - Icons.add_comment_sharp, + Icons.chat, ]; final _bottomLabelList = [ translate('pager.contacts'), diff --git a/lib/layout/splash.dart b/lib/layout/splash.dart index 2113193..c3af797 100644 --- a/lib/layout/splash.dart +++ b/lib/layout/splash.dart @@ -11,16 +11,11 @@ class Splash extends StatefulWidget { State createState() => _SplashState(); } -class _SplashState extends State { - @override - void initState() { - super.initState(); - - WidgetsBinding.instance.addPostFrameCallback((_) async { - await changeWindowSetup( - TitleBarStyle.hidden, OrientationCapability.normal); - }); - } +class _SplashState extends WindowSetupState { + _SplashState() + : super( + titleBarStyle: TitleBarStyle.hidden, + orientationCapability: OrientationCapability.portraitOnly); @override Widget build(BuildContext context) => PopScope( diff --git a/lib/settings/settings_page.dart b/lib/settings/settings_page.dart index eefbbd2..5b92643 100644 --- a/lib/settings/settings_page.dart +++ b/lib/settings/settings_page.dart @@ -7,7 +7,6 @@ import 'package:go_router/go_router.dart'; import '../layout/default_app_bar.dart'; import '../theme/theme.dart'; -import '../tools/tools.dart'; import '../veilid_processor/veilid_processor.dart'; import 'settings.dart'; @@ -26,11 +25,6 @@ class SettingsPageState extends State { @override void initState() { super.initState(); - - WidgetsBinding.instance.addPostFrameCallback((_) async { - await changeWindowSetup( - TitleBarStyle.normal, OrientationCapability.normal); - }); } @override diff --git a/lib/theme/views/styled_scaffold.dart b/lib/theme/views/styled_scaffold.dart index 055684b..af9e4eb 100644 --- a/lib/theme/views/styled_scaffold.dart +++ b/lib/theme/views/styled_scaffold.dart @@ -12,7 +12,7 @@ class StyledScaffold extends StatelessWidget { final scale = theme.extension()!; final scaleConfig = theme.extension()!; - return isDesktop + final scaffold = isDesktop ? clipBorder( clipEnabled: true, borderEnabled: scaleConfig.useVisualIndicators, @@ -21,6 +21,10 @@ class StyledScaffold extends StatelessWidget { child: Scaffold(appBar: appBar, body: body, key: key)) .paddingAll(32) : Scaffold(appBar: appBar, body: body, key: key); + + return GestureDetector( + onTap: () => FocusManager.instance.primaryFocus?.unfocus(), + child: scaffold); } //////////////////////////////////////////////////////////////////////////// diff --git a/lib/theme/views/widget_helpers.dart b/lib/theme/views/widget_helpers.dart index 1bd71bc..6e485fc 100644 --- a/lib/theme/views/widget_helpers.dart +++ b/lib/theme/views/widget_helpers.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:async_tools/async_tools.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:bloc_advanced_tools/bloc_advanced_tools.dart'; @@ -5,8 +7,10 @@ import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart'; 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:motion_toast/motion_toast.dart'; import 'package:quickalert/quickalert.dart'; +import 'package:sliver_expandable/sliver_expandable.dart'; import '../theme.dart'; @@ -132,7 +136,7 @@ void showErrorToast(BuildContext context, String message) { contentPadding: const EdgeInsets.all(16), primaryColor: scale.errorScale.elementBackground, secondaryColor: scale.errorScale.calloutBackground, - borderRadius: 16 * scaleConfig.borderRadiusScale, + borderRadius: 12 * scaleConfig.borderRadiusScale, toastDuration: const Duration(seconds: 4), animationDuration: const Duration(milliseconds: 1000), displayBorder: scaleConfig.useVisualIndicators, @@ -152,7 +156,7 @@ void showInfoToast(BuildContext context, String message) { contentPadding: const EdgeInsets.all(16), primaryColor: scale.tertiaryScale.elementBackground, secondaryColor: scale.tertiaryScale.calloutBackground, - borderRadius: 16 * scaleConfig.borderRadiusScale, + borderRadius: 12 * scaleConfig.borderRadiusScale, toastDuration: const Duration(seconds: 2), animationDuration: const Duration(milliseconds: 500), displayBorder: scaleConfig.useVisualIndicators, @@ -160,6 +164,159 @@ void showInfoToast(BuildContext context, String message) { ).show(context); } +SliverAppBar styledSliverAppBar( + {required BuildContext context, required String title, Color? titleColor}) { + final theme = Theme.of(context); + final scale = theme.extension()!; + //final scaleConfig = theme.extension()!; + final textTheme = theme.textTheme; + + return SliverAppBar( + title: Text( + title, + style: textTheme.titleSmall! + .copyWith(color: titleColor ?? scale.primaryScale.borderText), + ), + pinned: true, + ); +} + +Widget styledHeaderSliver( + {required BuildContext context, + required String title, + required Widget sliver, + Color? borderColor, + Color? innerColor, + Color? titleColor, + Color? backgroundColor, + void Function()? onTap}) { + final theme = Theme.of(context); + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + final textTheme = theme.textTheme; + + return SliverStickyHeader( + header: ColoredBox( + color: backgroundColor ?? Colors.transparent, + child: DecoratedBox( + decoration: ShapeDecoration( + color: borderColor ?? scale.primaryScale.border, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: + Radius.circular(12 * scaleConfig.borderRadiusScale), + topRight: Radius.circular( + 12 * scaleConfig.borderRadiusScale)))), + child: ListTile( + onTap: onTap, + title: Text(title, + textAlign: TextAlign.center, + style: textTheme.titleSmall!.copyWith( + color: titleColor ?? scale.primaryScale.borderText)), + ), + )), + sliver: DecoratedSliver( + decoration: ShapeDecoration( + color: borderColor ?? scale.primaryScale.border, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + bottomLeft: + Radius.circular(8 * scaleConfig.borderRadiusScale), + bottomRight: + Radius.circular(8 * scaleConfig.borderRadiusScale)))), + sliver: SliverPadding( + padding: const EdgeInsets.all(4), + sliver: DecoratedSliver( + decoration: ShapeDecoration( + color: innerColor ?? scale.primaryScale.subtleBackground, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 8 * scaleConfig.borderRadiusScale))), + sliver: SliverPadding( + padding: const EdgeInsets.all(8), + sliver: sliver, + )))), + ); +} + +Widget styledExpandingSliver( + {required BuildContext context, + required String title, + required Widget sliver, + required bool expanded, + required Animation animation, + Color? borderColor, + Color? innerColor, + Color? titleColor, + Color? backgroundColor, + void Function()? onTap}) { + final theme = Theme.of(context); + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + final textTheme = theme.textTheme; + + return SliverStickyHeader( + header: ColoredBox( + color: backgroundColor ?? Colors.transparent, + child: DecoratedBox( + decoration: ShapeDecoration( + color: borderColor ?? scale.primaryScale.border, + shape: RoundedRectangleBorder( + borderRadius: expanded + ? BorderRadius.only( + topLeft: Radius.circular( + 12 * scaleConfig.borderRadiusScale), + topRight: Radius.circular( + 12 * scaleConfig.borderRadiusScale)) + : BorderRadius.circular( + 12 * scaleConfig.borderRadiusScale))), + child: ListTile( + onTap: onTap, + title: Text(title, + textAlign: TextAlign.center, + style: textTheme.titleSmall!.copyWith( + color: titleColor ?? scale.primaryScale.borderText)), + trailing: AnimatedBuilder( + animation: animation, + builder: (context, child) => Transform.rotate( + angle: (animation.value - 0.5) * pi, + child: child, + ), + child: Icon(Icons.chevron_left, + color: borderColor ?? scale.primaryScale.borderText), + ), + ), + )), + sliver: SliverExpandable( + sliver: DecoratedSliver( + decoration: ShapeDecoration( + color: borderColor ?? scale.primaryScale.border, + shape: RoundedRectangleBorder( + borderRadius: expanded + ? BorderRadius.only( + bottomLeft: Radius.circular( + 8 * scaleConfig.borderRadiusScale), + bottomRight: Radius.circular( + 8 * scaleConfig.borderRadiusScale)) + : BorderRadius.circular( + 8 * scaleConfig.borderRadiusScale))), + sliver: SliverPadding( + padding: const EdgeInsets.all(4), + sliver: DecoratedSliver( + decoration: ShapeDecoration( + color: + innerColor ?? scale.primaryScale.subtleBackground, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 8 * scaleConfig.borderRadiusScale))), + sliver: SliverPadding( + padding: const EdgeInsets.all(8), + sliver: sliver, + )))), + animation: animation, + )); +} + Widget styledTitleContainer({ required BuildContext context, required String title, @@ -178,7 +335,7 @@ Widget styledTitleContainer({ color: borderColor ?? scale.primaryScale.border, shape: RoundedRectangleBorder( borderRadius: - BorderRadius.circular(16 * scaleConfig.borderRadiusScale), + BorderRadius.circular(12 * scaleConfig.borderRadiusScale), )), child: Column(children: [ Text( @@ -192,7 +349,7 @@ Widget styledTitleContainer({ backgroundColor ?? scale.primaryScale.subtleBackground, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( - 16 * scaleConfig.borderRadiusScale), + 12 * scaleConfig.borderRadiusScale), )), child: child) .paddingAll(4) diff --git a/lib/tools/native_safe_area.dart b/lib/tools/native_safe_area.dart new file mode 100644 index 0000000..ed3746e --- /dev/null +++ b/lib/tools/native_safe_area.dart @@ -0,0 +1,111 @@ +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/tools/window_control.dart b/lib/tools/window_control.dart index 37816f3..2e9a21f 100644 --- a/lib/tools/window_control.dart +++ b/lib/tools/window_control.dart @@ -1,10 +1,13 @@ import 'dart:async'; +import 'package:async_tools/async_tools.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; 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; @@ -27,7 +30,7 @@ Future initializeWindowControl() async { skipTaskbar: false, ); await windowManager.waitUntilReadyToShow(windowOptions, () async { - await changeWindowSetup( + await _asyncChangeWindowSetup( TitleBarStyle.hidden, OrientationCapability.normal); await windowManager.show(); await windowManager.focus(); @@ -35,7 +38,9 @@ Future initializeWindowControl() async { } } -Future changeWindowSetup(TitleBarStyle titleBarStyle, +const kWindowSetup = '__windowSetup'; + +Future _asyncChangeWindowSetup(TitleBarStyle titleBarStyle, OrientationCapability orientationCapability) async { if (isDesktop) { await windowManager.setTitleBarStyle(titleBarStyle); @@ -59,3 +64,47 @@ Future changeWindowSetup(TitleBarStyle titleBarStyle, } } } + +void changeWindowSetup( + TitleBarStyle titleBarStyle, OrientationCapability orientationCapability) { + singleFuture( + kWindowSetup, + () async => + _asyncChangeWindowSetup(titleBarStyle, orientationCapability)); +} + +abstract class WindowSetupState extends State { + WindowSetupState( + {required this.titleBarStyle, required this.orientationCapability}); + + @override + void initState() { + changeWindowSetup(this.titleBarStyle, this.orientationCapability); + super.initState(); + } + + @override + void activate() { + changeWindowSetup(this.titleBarStyle, this.orientationCapability); + super.activate(); + } + + @override + void deactivate() { + changeWindowSetup(TitleBarStyle.normal, OrientationCapability.normal); + super.deactivate(); + } + + //////////////////////////////////////////////////////////////////////////// + final TitleBarStyle titleBarStyle; + final OrientationCapability orientationCapability; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(EnumProperty('titleBarStyle', titleBarStyle)) + ..add(EnumProperty( + 'orientationCapability', orientationCapability)); + } +} diff --git a/lib/veilid_processor/views/developer.dart b/lib/veilid_processor/views/developer.dart index ed495ae..bb0b256 100644 --- a/lib/veilid_processor/views/developer.dart +++ b/lib/veilid_processor/views/developer.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:ansicolor/ansicolor.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:cool_dropdown/cool_dropdown.dart'; @@ -45,11 +47,6 @@ class _DeveloperPageState extends State { void initState() { super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) async { - await changeWindowSetup( - TitleBarStyle.normal, OrientationCapability.normal); - }); - _terminalController.addListener(() { setState(() {}); }); @@ -273,61 +270,59 @@ class _DeveloperPageState extends State { body: GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: SafeArea( - bottom: false, child: Column(children: [ - Stack(alignment: AlignmentDirectional.center, children: [ - Image.asset('assets/images/ellet.png'), - TerminalView(globalDebugTerminal, - textStyle: kDefaultTerminalStyle, - controller: _terminalController, - keyboardType: TextInputType.none, - //autofocus: true, - backgroundOpacity: _showEllet ? 0.75 : 1.0, - onSecondaryTapDown: (details, offset) async { - await copySelection(context); - }) - ]).expanded(), - TextField( - controller: _debugCommandController, - onTapOutside: (event) { - FocusManager.instance.primaryFocus?.unfocus(); - }, - 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, - hintText: translate('developer.command'), - suffixIcon: IconButton( - icon: Icon(Icons.send, - color: _debugCommandController.text.isEmpty - ? scale.primaryScale.primary.withAlpha(0x3F) - : scale.primaryScale.primary), - onPressed: _debugCommandController.text.isEmpty - ? null - : () async { - final debugCommand = - _debugCommandController.text; - _debugCommandController.clear(); - await _sendDebugCommand(debugCommand); - }, - )), - onChanged: (_) { - setState(() => {}); - }, - onSubmitted: (debugCommand) async { - _debugCommandController.clear(); - await _sendDebugCommand(debugCommand); - }, - ).paddingAll(4) - ])))); + Stack(alignment: AlignmentDirectional.center, children: [ + Image.asset('assets/images/ellet.png'), + TerminalView(globalDebugTerminal, + textStyle: kDefaultTerminalStyle, + controller: _terminalController, + keyboardType: TextInputType.none, + //autofocus: true, + backgroundOpacity: _showEllet ? 0.75 : 1.0, + onSecondaryTapDown: (details, offset) async { + await copySelection(context); + }) + ]).expanded(), + TextField( + controller: _debugCommandController, + onTapOutside: (event) { + FocusManager.instance.primaryFocus?.unfocus(); + }, + 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, + hintText: translate('developer.command'), + suffixIcon: IconButton( + icon: Icon(Icons.send, + color: _debugCommandController.text.isEmpty + ? scale.primaryScale.primary.withAlpha(0x3F) + : scale.primaryScale.primary), + onPressed: _debugCommandController.text.isEmpty + ? null + : () async { + final debugCommand = _debugCommandController.text; + _debugCommandController.clear(); + await _sendDebugCommand(debugCommand); + }, + )), + onChanged: (_) { + setState(() => {}); + }, + onSubmitted: (debugCommand) async { + _debugCommandController.clear(); + await _sendDebugCommand(debugCommand); + }, + ).paddingAll(4) + ])))); } @override diff --git a/pubspec.lock b/pubspec.lock index 8ab1f26..6bde90f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "67.0.0" + accordion: + dependency: "direct main" + description: + name: accordion + sha256: "0eca3d1c619c6df63d6e384010fd2ef1164e7385d7102f88a6b56f658f160cd0" + url: "https://pub.dev" + source: hosted + version: "2.6.0" analyzer: dependency: transitive description: @@ -449,6 +457,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.5" + expansion_tile_group: + dependency: "direct main" + description: + name: expansion_tile_group + sha256: "6918433891481c7d98cbc604d7b4c93509986e8134d52940853301ad6fbff404" + url: "https://pub.dev" + source: hosted + version: "1.2.4" fast_immutable_collections: dependency: "direct main" description: @@ -620,6 +636,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.2.1" + flutter_sticky_header: + dependency: "direct main" + description: + name: flutter_sticky_header + sha256: "017f398fbb45a589e01491861ca20eb6570a763fd9f3888165a978e11248c709" + url: "https://pub.dev" + source: hosted + version: "0.6.5" flutter_svg: dependency: "direct main" description: @@ -681,6 +705,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + get: + dependency: transitive + description: + name: get + sha256: e4e7335ede17452b391ed3b2ede016545706c01a02292a6c97619705e7d2a85e + url: "https://pub.dev" + source: hosted + version: "4.6.6" glob: dependency: transitive description: @@ -897,6 +929,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.10.0" + 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: @@ -1326,6 +1366,30 @@ packages: description: flutter source: sdk version: "0.0.99" + sliver_expandable: + dependency: "direct main" + description: + name: sliver_expandable + sha256: ae20eb848bd0ba9dd704732ad654438ac5a5bea2b023fa3cf80a086166d96d97 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + sliver_fill_remaining_box_adapter: + dependency: "direct main" + description: + name: sliver_fill_remaining_box_adapter + sha256: "2a222c0f09eb07c37857ce2526c0fbf3b17b2bd1b1ff0e890085f2f7a9ba1927" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + sliver_tools: + dependency: "direct main" + description: + name: sliver_tools + sha256: eae28220badfb9d0559207badcbbc9ad5331aac829a88cb0964d330d2a4636a6 + url: "https://pub.dev" + source: hosted + version: "0.2.12" smart_auth: dependency: transitive description: @@ -1583,6 +1647,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.4.0" + value_layout_builder: + dependency: transitive + description: + name: value_layout_builder + sha256: "98202ec1807e94ac72725b7f0d15027afde513c55c69ff3f41bcfccb950831bc" + url: "https://pub.dev" + source: hosted + version: "0.3.1" vector_graphics: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 801d5ae..716c8b8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,6 +8,7 @@ environment: flutter: '>=3.22.1' dependencies: + accordion: ^2.6.0 animated_bottom_navigation_bar: ^1.3.3 animated_switcher_transitions: ^1.0.0 animated_theme_switcher: ^2.0.10 @@ -27,6 +28,7 @@ dependencies: cool_dropdown: ^2.1.0 cupertino_icons: ^1.0.8 equatable: ^2.0.5 + expansion_tile_group: ^1.2.4 fast_immutable_collections: ^10.2.4 file_saver: ^0.2.13 fixnum: ^1.1.0 @@ -46,6 +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_svg: ^2.0.10+1 flutter_translate: ^4.1.0 flutter_zoom_drawer: ^3.2.0 @@ -60,6 +63,7 @@ dependencies: meta: ^1.12.0 mobile_scanner: ^5.1.1 motion_toast: ^2.10.0 + native_device_orientation: ^2.0.3 package_info_plus: ^8.0.0 pasteboard: ^0.2.0 path: ^1.9.0 @@ -81,6 +85,9 @@ dependencies: share_plus: ^9.0.0 shared_preferences: ^2.2.3 signal_strength_indicator: ^0.4.1 + sliver_expandable: ^1.1.1 + sliver_fill_remaining_box_adapter: ^1.0.0 + sliver_tools: ^0.2.12 sorted_list: git: url: https://gitlab.com/veilid/dart-sorted-list-improved.git