diff --git a/lib/layout/home/active_account_page_controller_wrapper.dart b/lib/layout/home/active_account_page_controller_wrapper.dart deleted file mode 100644 index 79314a1..0000000 --- a/lib/layout/home/active_account_page_controller_wrapper.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'dart:async'; - -import 'package:async_tools/async_tools.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:veilid_support/veilid_support.dart'; - -import '../../account_manager/account_manager.dart'; - -class ActiveAccountPageControllerWrapper { - ActiveAccountPageControllerWrapper(Locator locator, int initialPage) { - pageController = PageController(initialPage: initialPage, keepPage: false); - - final activeLocalAccountCubit = locator(); - _subscription = - activeLocalAccountCubit.stream.listen((activeLocalAccountRecordKey) { - singleFuture(this, () async { - final localAccounts = locator().state; - final activeIndex = localAccounts.indexWhere( - (x) => x.superIdentity.recordKey == activeLocalAccountRecordKey); - if (pageController.page == activeIndex) { - return; - } - await pageController.animateToPage(activeIndex, - duration: const Duration(milliseconds: 250), - curve: Curves.fastOutSlowIn); - }); - }); - } - - void dispose() { - unawaited(_subscription.cancel()); - } - - late PageController pageController; - late StreamSubscription _subscription; -} diff --git a/lib/layout/home/home.dart b/lib/layout/home/home.dart index cb0cef7..74990ef 100644 --- a/lib/layout/home/home.dart +++ b/lib/layout/home/home.dart @@ -1,4 +1,3 @@ -export 'active_account_page_controller_wrapper.dart'; export 'drawer_menu/drawer_menu.dart'; export 'home_account_invalid.dart'; export 'home_account_locked.dart'; diff --git a/lib/layout/home/home_account_ready/home_account_ready_main.dart b/lib/layout/home/home_account_ready/home_account_ready_main.dart index aa9b1ba..e28904e 100644 --- a/lib/layout/home/home_account_ready/home_account_ready_main.dart +++ b/lib/layout/home/home_account_ready/home_account_ready_main.dart @@ -1,4 +1,5 @@ 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_translate/flutter_translate.dart'; @@ -35,27 +36,32 @@ class _HomeAccountReadyMainState extends State { final theme = Theme.of(context); final scale = theme.extension()!; - return Column(children: [ - Row(children: [ - IconButton( - icon: const Icon(Icons.menu), - color: scale.secondaryScale.borderText, - constraints: const BoxConstraints.expand(height: 64, width: 64), - style: ButtonStyle( - backgroundColor: - WidgetStateProperty.all(scale.primaryScale.hoverBorder), - shape: WidgetStateProperty.all(const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(16))))), - tooltip: translate('menu.settings_tooltip'), - onPressed: () async { - final ctrl = context.read(); - await ctrl.toggle?.call(); - //await GoRouterHelper(context).push('/settings'); - }).paddingLTRB(0, 0, 8, 0), - ProfileWidget(profile: profile).expanded(), - ]).paddingAll(8), - const MainPager().expanded() - ]); + return ColoredBox( + color: scale.primaryScale.subtleBorder, + child: Column(children: [ + Row(children: [ + IconButton( + icon: const Icon(Icons.menu), + color: scale.secondaryScale.borderText, + constraints: + const BoxConstraints.expand(height: 64, width: 64), + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + scale.primaryScale.hoverBorder), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder( + borderRadius: + BorderRadius.all(Radius.circular(16))))), + tooltip: translate('menu.settings_tooltip'), + onPressed: () async { + final ctrl = context.read(); + await ctrl.toggle?.call(); + //await GoRouterHelper(context).push('/settings'); + }).paddingLTRB(0, 0, 8, 0), + ProfileWidget(profile: profile).expanded(), + ]).paddingAll(8), + MainPager(key: _mainPagerKey).expanded() + ])); }); Widget buildPhone(BuildContext context) => @@ -107,4 +113,7 @@ class _HomeAccountReadyMainState extends State { ) ? buildTablet(context) : buildPhone(context); + + //////////////////////////////////////////////////////////////////////////// + final _mainPagerKey = GlobalKey(debugLabel: '_mainPagerKey'); } 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 b51d72f..1e79344 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 @@ -28,25 +28,6 @@ class MainPager extends StatefulWidget { class MainPagerState extends State with TickerProviderStateMixin { ////////////////////////////////////////////////////////////////// - var _currentPage = 0; - final pageController = PreloadPageController(); - - final _selectedIconList = [Icons.person, Icons.chat]; - // final _unselectedIconList = [ - // Icons.chat_outlined, - // Icons.person_outlined - // ]; - final _fabIconList = [ - Icons.person_add_sharp, - Icons.add_comment_sharp, - ]; - final _bottomLabelList = [ - translate('pager.contacts'), - translate('pager.chats'), - ]; - - ////////////////////////////////////////////////////////////////// - @override void initState() { super.initState(); @@ -124,10 +105,10 @@ class MainPagerState extends State with TickerProviderStateMixin { } Widget _bottomSheetBuilder(BuildContext sheetContext, BuildContext context) { - if (_currentPage == 0) { + if (currentPage == 0) { // New contact invitation return newContactBottomSheetBuilder(sheetContext, context); - } else if (_currentPage == 1) { + } else if (currentPage == 1) { // New chat return newChatBottomSheetBuilder(sheetContext, context); } else { @@ -148,11 +129,12 @@ class MainPagerState extends State with TickerProviderStateMixin { body: NotificationListener( onNotification: onScrollNotification, child: PreloadPageView( + key: _pageViewKey, controller: pageController, preloadPagesCount: 2, onPageChanged: (index) { setState(() { - _currentPage = index; + currentPage = index; }); }, children: const [ @@ -176,7 +158,7 @@ class MainPagerState extends State with TickerProviderStateMixin { items: _buildBottomBarItems(), hasNotch: true, fabLocation: StylishBarFabLocation.end, - currentIndex: _currentPage, + currentIndex: currentPage, onTap: (index) async { await pageController.animateToPage(index, duration: 250.ms, curve: Curves.easeInOut); @@ -189,7 +171,7 @@ class MainPagerState extends State with TickerProviderStateMixin { foregroundColor: scale.secondaryScale.borderText, backgroundColor: scale.secondaryScale.hoverBorder, builder: (context) => Icon( - _fabIconList[_currentPage], + _fabIconList[currentPage], color: scale.secondaryScale.borderText, ), bottomSheetBuilder: (sheetContext) => @@ -198,10 +180,33 @@ class MainPagerState extends State with TickerProviderStateMixin { ); } + ////////////////////////////////////////////////////////////////// + + final _selectedIconList = [Icons.person, Icons.chat]; + // final _unselectedIconList = [ + // Icons.chat_outlined, + // Icons.person_outlined + // ]; + final _fabIconList = [ + Icons.person_add_sharp, + Icons.add_comment_sharp, + ]; + final _bottomLabelList = [ + translate('pager.contacts'), + translate('pager.chats'), + ]; + final _pageViewKey = GlobalKey(debugLabel: '_pageViewKey'); + + // key-accessible controller + int currentPage = 0; + final pageController = PreloadPageController(); + @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(DiagnosticsProperty( - 'pageController', pageController)); + properties + ..add(IntProperty('currentPage', currentPage)) + ..add(DiagnosticsProperty( + 'pageController', pageController)); } } diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index 8ade6f5..939c1dc 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -1,16 +1,16 @@ import 'dart:math'; -import 'package:async_tools/async_tools.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart'; import 'package:provider/provider.dart'; +import 'package:transitioned_indexed_stack/transitioned_indexed_stack.dart'; import 'package:veilid_support/veilid_support.dart'; import '../../account_manager/account_manager.dart'; import '../../chat/chat.dart'; import '../../theme/theme.dart'; import '../../tools/tools.dart'; -import 'active_account_page_controller_wrapper.dart'; import 'drawer_menu/drawer_menu.dart'; import 'home_account_invalid.dart'; import 'home_account_locked.dart'; @@ -23,34 +23,71 @@ class HomeScreen extends StatefulWidget { @override HomeScreenState createState() => HomeScreenState(); + + static HomeScreenState? of(BuildContext context) => + context.findAncestorStateOfType(); } -class HomeScreenState extends State { +class HomeScreenState extends State + with SingleTickerProviderStateMixin { @override void initState() { + // Chat animation setup (open in phone mode) + _chatAnimationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 250), + ); + _chatAnimation = Tween( + begin: const Offset(1, 0), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _chatAnimationController, + curve: Curves.easeInOut, + )); + + // Account animation setup + super.initState(); } @override void dispose() { + _chatAnimationController.dispose(); super.dispose(); } Widget _buildAccountReadyDeviceSpecific(BuildContext context) { - final hasActiveChat = context.watch().state != null; if (responsiveVisibility( context: context, tablet: false, tabletLandscape: false, desktop: false)) { - if (hasActiveChat) { - return const HomeAccountReadyChat(); - } + return BlocConsumer( + listener: (context, activeChat) { + final hasActiveChat = activeChat != null; + if (hasActiveChat) { + _chatAnimationController.forward(); + } else { + _chatAnimationController.reset(); + } + }, + builder: (context, activeChat) => Stack( + children: [ + const HomeAccountReadyMain(), + Offstage( + offstage: activeChat == null, + child: SlideTransition( + position: _chatAnimation, + child: const HomeAccountReadyChat())), + ], + )); } return const HomeAccountReadyMain(); } - Widget _buildAccount(BuildContext context, TypedKey superIdentityRecordKey, + Widget _buildAccountPage( + BuildContext context, + TypedKey superIdentityRecordKey, PerAccountCollectionState perAccountCollectionState) { switch (perAccountCollectionState.accountInfo.status) { case AccountInfoStatus.accountInvalid: @@ -81,38 +118,25 @@ class HomeScreenState extends State { return const HomeNoActive(); } - return Provider( - lazy: false, - create: (context) => - ActiveAccountPageControllerWrapper(context.read, activeIndex), - dispose: (context, value) { - value.dispose(); - }, - child: Builder( - builder: (context) => PageView.builder( - onPageChanged: (idx) { - singleFuture(this, () async { - await AccountRepository.instance.switchToAccount( - localAccounts[idx].superIdentity.recordKey); - }); - }, - controller: context - .read() - .pageController, - itemCount: localAccounts.length, - itemBuilder: (context, index) { - final superIdentityRecordKey = - localAccounts[index].superIdentity.recordKey; - final perAccountCollectionState = - perAccountCollectionBlocMapState - .get(superIdentityRecordKey); - if (perAccountCollectionState == null) { - return HomeAccountMissing( - key: ValueKey(superIdentityRecordKey)); - } - return _buildAccount(context, superIdentityRecordKey, - perAccountCollectionState); - }))); + final accountPages = []; + + for (var i = 0; i < localAccounts.length; i++) { + final superIdentityRecordKey = localAccounts[i].superIdentity.recordKey; + final perAccountCollectionState = + perAccountCollectionBlocMapState.get(superIdentityRecordKey); + if (perAccountCollectionState == null) { + return HomeAccountMissing(key: ValueKey(superIdentityRecordKey)); + } + final accountPage = _buildAccountPage( + context, superIdentityRecordKey, perAccountCollectionState); + accountPages.add(KeyedSubtree.wrap(accountPage, i)); + } + + return SlideIndexedStack( + index: activeIndex, + beginSlideOffset: const Offset(1, 0), + children: accountPages, + ); } @override @@ -134,15 +158,20 @@ class HomeScreenState extends State { child: ZoomDrawer( controller: _zoomDrawerController, //menuBackgroundColor: Colors.transparent, - menuScreen: const DrawerMenu(), - mainScreen: DecoratedBox( - decoration: BoxDecoration( - color: scale.primaryScale.activeElementBackground), - child: Provider.value( - value: _zoomDrawerController, - child: Builder(builder: _buildAccountPageView))), + 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: 24, - showShadow: true, + //showShadow: false, angle: 0, drawerShadowsBackgroundColor: theme.shadowColor, mainScreenOverlayColor: theme.shadowColor.withAlpha(0x3F), @@ -151,10 +180,15 @@ class HomeScreenState extends State { // reverseDuration: const Duration(milliseconds: 250), menuScreenTapClose: true, mainScreenTapClose: true, + //disableDragGesture: false, mainScreenScale: .25, slideWidth: min(360, MediaQuery.of(context).size.width * 0.9), ))); } + //////////////////////////////////////////////////////////////////////////// + final _zoomDrawerController = ZoomDrawerController(); + late final Animation _chatAnimation; + late final AnimationController _chatAnimationController; } diff --git a/packages/veilid_support/lib/src/config.dart b/packages/veilid_support/lib/src/config.dart index 889a8fd..430717e 100644 --- a/packages/veilid_support/lib/src/config.dart +++ b/packages/veilid_support/lib/src/config.dart @@ -2,6 +2,12 @@ import 'dart:io' show Platform; import 'package:veilid/veilid.dart'; +// ignore: do_not_use_environment +const bool _kReleaseMode = bool.fromEnvironment('dart.vm.product'); +// ignore: do_not_use_environment +const bool _kProfileMode = bool.fromEnvironment('dart.vm.profile'); +const bool _kDebugMode = !_kReleaseMode && !_kProfileMode; + Map getDefaultVeilidPlatformConfig( bool isWeb, String appName) { final ignoreLogTargetsStr = @@ -16,7 +22,9 @@ Map getDefaultVeilidPlatformConfig( logging: VeilidWASMConfigLogging( performance: VeilidWASMConfigLoggingPerformance( enabled: true, - level: VeilidConfigLogLevel.debug, + level: _kDebugMode + ? VeilidConfigLogLevel.debug + : VeilidConfigLogLevel.info, logsInTimings: true, logsInConsole: false, ignoreLogTargets: ignoreLogTargets), @@ -29,8 +37,11 @@ Map getDefaultVeilidPlatformConfig( return VeilidFFIConfig( logging: VeilidFFIConfigLogging( terminal: VeilidFFIConfigLoggingTerminal( - enabled: false, - level: VeilidConfigLogLevel.debug, + enabled: + _kDebugMode && (Platform.isIOS || Platform.isAndroid), + level: _kDebugMode + ? VeilidConfigLogLevel.debug + : VeilidConfigLogLevel.info, ignoreLogTargets: ignoreLogTargets), otlp: VeilidFFIConfigLoggingOtlp( enabled: false, diff --git a/pubspec.lock b/pubspec.lock index 16edf0a..5bdc0dc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.4.1" + animated_switcher_transitions: + dependency: "direct main" + description: + name: animated_switcher_transitions + sha256: "0f3ef1b46ab3f0b5efe784dcff55bbeabdc75a3b9bcbefbf2315468c9cec87c3" + url: "https://pub.dev" + source: hosted + version: "1.0.0" animated_theme_switcher: dependency: "direct main" description: @@ -1399,6 +1407,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + transitioned_indexed_stack: + dependency: "direct main" + description: + name: transitioned_indexed_stack + sha256: "8023abb5efe72e6d40cc3775fb03d7504c32ac918ec2ce7f9ba6804753820259" + url: "https://pub.dev" + source: hosted + version: "1.0.2" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a81d632..a1f097b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,6 +8,7 @@ environment: flutter: '>=3.22.1' dependencies: + animated_switcher_transitions: ^1.0.0 animated_theme_switcher: ^2.0.10 ansicolor: ^2.0.2 archive: ^3.6.1 @@ -83,6 +84,7 @@ dependencies: stack_trace: ^1.11.1 stream_transform: ^2.1.0 stylish_bottom_bar: ^1.1.0 + transitioned_indexed_stack: ^1.0.2 uuid: ^4.4.0 veilid: # veilid: ^0.0.1