diff --git a/assets/translations/en.json b/assets/translations/en.json index d5adf64d..15727826 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -333,7 +333,20 @@ "create_master_account": "Create master account", "enter_username_and_password": "Enter username and strong password", "finish": "Everything is initialized", - "checks": "Checks have been completed \n{} out of {}" + "checks": "Checks have been completed \n{} out of {}", + "steps": { + "hosting": "Hosting", + "server_type": "Server type", + "dns_provider": "DNS provider", + "backups_provider": "Backups", + "domain": "Domain", + "master_account": "Master account", + "server": "Server", + "dns_setup": "DNS setup", + "nixos_installation": "NixOS installation", + "server_reboot": "Server reboot", + "final_checks": "Final checks" + } }, "recovering": { "generic_error": "Operation failed, please try again.", diff --git a/lib/main.dart b/lib/main.dart index cde6d45c..702d8280 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,9 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/theming/factory/app_theme_factory.dart'; -import 'package:selfprivacy/ui/pages/setup/initializing/initializing.dart'; -import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart'; -import 'package:selfprivacy/ui/pages/root_route.dart'; +import 'package:selfprivacy/ui/router/router.dart'; import 'package:wakelock/wakelock.dart'; import 'package:timezone/data/latest.dart' as tz; @@ -20,7 +18,7 @@ import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await HiveConfig.init(); - await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + // await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); try { /// Wakelock support for Linux @@ -43,21 +41,20 @@ void main() async { fallbackColor: BrandColors.primary, ); - BlocOverrides.runZoned( - () => runApp( - Localization( - child: MyApp( - lightThemeData: lightThemeData, - darkThemeData: darkThemeData, - ), + Bloc.observer = SimpleBlocObserver(); + + runApp( + Localization( + child: SelfprivacyApp( + lightThemeData: lightThemeData, + darkThemeData: darkThemeData, ), ), - blocObserver: SimpleBlocObserver(), ); } -class MyApp extends StatelessWidget { - const MyApp({ +class SelfprivacyApp extends StatelessWidget { + SelfprivacyApp({ required this.lightThemeData, required this.darkThemeData, super.key, @@ -66,6 +63,8 @@ class MyApp extends StatelessWidget { final ThemeData lightThemeData; final ThemeData darkThemeData; + final _appRouter = RootRouter(); + @override Widget build(final BuildContext context) => Localization( child: AnnotatedRegion( @@ -76,10 +75,11 @@ class MyApp extends StatelessWidget { final BuildContext context, final AppSettingsState appSettings, ) => - MaterialApp( + MaterialApp.router( + routeInformationParser: _appRouter.defaultRouteParser(), + routerDelegate: _appRouter.delegate(), scaffoldMessengerKey: getIt.get().scaffoldMessengerKey, - navigatorKey: getIt.get().navigatorKey, localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, diff --git a/lib/ui/components/not_ready_card/not_ready_card.dart b/lib/ui/components/not_ready_card/not_ready_card.dart index 379abf27..5e62e093 100644 --- a/lib/ui/components/not_ready_card/not_ready_card.dart +++ b/lib/ui/components/not_ready_card/not_ready_card.dart @@ -1,7 +1,7 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart'; -import 'package:selfprivacy/ui/pages/setup/initializing/initializing.dart'; -import 'package:selfprivacy/utils/route_transitions/basic.dart'; +import 'package:selfprivacy/ui/router/router.dart'; import 'package:easy_localization/easy_localization.dart'; class NotReadyCard extends StatelessWidget { @@ -13,11 +13,7 @@ class NotReadyCard extends StatelessWidget { child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - onTap: () => Navigator.of(context).push( - materialRoute( - const InitializingPage(), - ), - ), + onTap: () => context.pushRoute(const InitializingRoute()), title: Text( 'not_ready_card.in_menu'.tr(), style: Theme.of(context).textTheme.titleSmall?.copyWith( diff --git a/lib/ui/components/pre_styled_buttons/flash_fab.dart b/lib/ui/components/pre_styled_buttons/flash_fab.dart index f0087b9f..e069bcec 100644 --- a/lib/ui/components/pre_styled_buttons/flash_fab.dart +++ b/lib/ui/components/pre_styled_buttons/flash_fab.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:ionicons/ionicons.dart'; @@ -7,7 +8,12 @@ import 'package:selfprivacy/ui/components/jobs_content/jobs_content.dart'; import 'package:selfprivacy/ui/helpers/modals.dart'; class BrandFab extends StatefulWidget { - const BrandFab({super.key}); + const BrandFab({ + this.extended = false, + super.key, + }); + + final bool extended; @override State createState() => _BrandFabState(); @@ -64,20 +70,35 @@ class _BrandFabState extends State ), ); }, - child: AnimatedBuilder( - animation: _colorTween, - builder: (final BuildContext context, final Widget? child) { - final double v = _animationController.value; - final IconData icon = - v > 0.5 ? Ionicons.flash : Ionicons.flash_outline; - return Transform.scale( - scale: 1 + (v < 0.5 ? v : 1 - v) * 2, - child: Icon( - icon, - color: _colorTween.value, + isExtended: widget.extended, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AnimatedBuilder( + animation: _colorTween, + builder: (final BuildContext context, final Widget? child) { + final double v = _animationController.value; + final IconData icon = + v > 0.5 ? Ionicons.flash : Ionicons.flash_outline; + return Transform.scale( + scale: 1 + (v < 0.5 ? v : 1 - v) * 2, + child: Icon( + icon, + color: _colorTween.value, + ), + ); + }, + ), + if (widget.extended) + const SizedBox( + width: 8, ), - ); - }, + if (widget.extended) + Text( + 'jobs.title'.tr(), + ), + ], ), ), ); diff --git a/lib/ui/components/brand_hero_screen/brand_hero_screen.dart b/lib/ui/layouts/brand_hero_screen.dart similarity index 62% rename from lib/ui/components/brand_hero_screen/brand_hero_screen.dart rename to lib/ui/layouts/brand_hero_screen.dart index 68f5d772..d51bca4f 100644 --- a/lib/ui/components/brand_hero_screen/brand_hero_screen.dart +++ b/lib/ui/layouts/brand_hero_screen.dart @@ -1,6 +1,8 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart'; import 'package:selfprivacy/ui/helpers/widget_size.dart'; +import 'package:selfprivacy/utils/breakpoints.dart'; class BrandHeroScreen extends StatelessWidget { const BrandHeroScreen({ @@ -13,6 +15,7 @@ class BrandHeroScreen extends StatelessWidget { this.heroTitle = '', this.heroSubtitle, this.onBackButtonPressed, + this.bodyPadding = const EdgeInsets.all(16.0), }); final List children; @@ -23,6 +26,7 @@ class BrandHeroScreen extends StatelessWidget { final String heroTitle; final String? heroSubtitle; final VoidCallback? onBackButtonPressed; + final EdgeInsetsGeometry bodyPadding; @override Widget build(final BuildContext context) { @@ -64,7 +68,7 @@ class BrandHeroScreen extends StatelessWidget { ), ), SliverPadding( - padding: const EdgeInsets.all(16.0), + padding: bodyPadding, sliver: SliverList( delegate: SliverChildListDelegate(children), ), @@ -98,50 +102,50 @@ class HeroSliverAppBar extends StatefulWidget { class _HeroSliverAppBarState extends State { Size _size = Size.zero; @override - Widget build(final BuildContext context) => SliverAppBar( - expandedHeight: - widget.hasHeroIcon ? 148.0 + _size.height : 72.0 + _size.height, - primary: true, - pinned: true, - stretch: true, - leading: widget.hasBackButton - ? IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: widget.onBackButtonPressed ?? - () => Navigator.of(context).pop(), - ) - : null, - flexibleSpace: FlexibleSpaceBar( - title: LayoutBuilder( - builder: (final context, final constraints) => SizedBox( - width: constraints.maxWidth - 72.0, - child: WidgetSize( - onChange: (final Size size) => setState(() => _size = size), - child: Text( - widget.heroTitle, - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: Theme.of(context).colorScheme.onSurface, - ), - overflow: TextOverflow.fade, - textAlign: TextAlign.center, - ), + Widget build(final BuildContext context) { + final isMobile = Breakpoints.small.isActive(context); + return SliverAppBar( + expandedHeight: + widget.hasHeroIcon ? 148.0 + _size.height : 72.0 + _size.height, + primary: true, + pinned: isMobile, + stretch: true, + surfaceTintColor: isMobile ? null : Colors.transparent, + leading: (widget.hasBackButton && isMobile) + ? const AutoLeadingButton() + : const SizedBox.shrink(), + flexibleSpace: FlexibleSpaceBar( + title: LayoutBuilder( + builder: (final context, final constraints) => SizedBox( + width: constraints.maxWidth - 72.0, + child: WidgetSize( + onChange: (final Size size) => setState(() => _size = size), + child: Text( + widget.heroTitle, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Theme.of(context).colorScheme.onSurface, + ), + overflow: TextOverflow.fade, + textAlign: TextAlign.center, ), ), ), - expandedTitleScale: 1.2, - centerTitle: true, - collapseMode: CollapseMode.pin, - titlePadding: const EdgeInsets.only( - bottom: 12.0, - top: 16.0, - ), - background: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SizedBox(height: 72.0), - if (widget.hasHeroIcon) widget.heroIconWidget, - ], - ), ), - ); + expandedTitleScale: 1.2, + centerTitle: true, + collapseMode: CollapseMode.pin, + titlePadding: const EdgeInsets.only( + bottom: 12.0, + top: 16.0, + ), + background: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox(height: 72.0), + if (widget.hasHeroIcon) widget.heroIconWidget, + ], + ), + ), + ); + } } diff --git a/lib/ui/layouts/responsive_layout_with_infobox.dart b/lib/ui/layouts/responsive_layout_with_infobox.dart new file mode 100644 index 00000000..4ef467ab --- /dev/null +++ b/lib/ui/layouts/responsive_layout_with_infobox.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/utils/breakpoints.dart'; + +class ResponsiveLayoutWithInfobox extends StatelessWidget { + const ResponsiveLayoutWithInfobox({ + required this.primaryColumn, + this.topChild, + this.secondaryColumn, + super.key, + }); + + final Widget? topChild; + final Widget primaryColumn; + final Widget? secondaryColumn; + + @override + Widget build(final BuildContext context) { + final hasSecondaryColumn = secondaryColumn != null; + final hasTopChild = topChild != null; + + if (Breakpoints.large.isActive(context)) { + return LayoutBuilder( + builder: (final context, final constraints) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (hasTopChild) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: constraints.maxWidth * 0.9, + child: topChild, + ), + ], + ), + if (hasTopChild) const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: hasSecondaryColumn + ? constraints.maxWidth * 0.7 + : constraints.maxWidth * 0.9, + child: primaryColumn, + ), + if (hasSecondaryColumn) const SizedBox(width: 16), + if (hasSecondaryColumn) + SizedBox( + width: constraints.maxWidth * 0.2, + child: secondaryColumn, + ), + ], + ), + ], + ), + ); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (hasTopChild) topChild!, + const SizedBox(height: 16), + primaryColumn, + const SizedBox(height: 32), + if (hasSecondaryColumn) secondaryColumn!, + ], + ); + } +} diff --git a/lib/ui/layouts/root_scaffold_with_navigation.dart b/lib/ui/layouts/root_scaffold_with_navigation.dart new file mode 100644 index 00000000..f8e1048b --- /dev/null +++ b/lib/ui/layouts/root_scaffold_with_navigation.dart @@ -0,0 +1,276 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart'; +import 'package:selfprivacy/ui/components/support_drawer/support_drawer.dart'; +import 'package:selfprivacy/ui/router/root_destinations.dart'; +import 'package:selfprivacy/utils/breakpoints.dart'; + +class RootScaffoldWithNavigation extends StatelessWidget { + const RootScaffoldWithNavigation({ + required this.child, + required this.title, + required this.destinations, + this.showBottomBar = true, + this.showFab = true, + super.key, + }); + + final Widget child; + final String title; + final bool showBottomBar; + final List destinations; + final bool showFab; + + @override + // ignore: prefer_expression_function_bodies + Widget build(final BuildContext context) { + return Scaffold( + appBar: Breakpoints.mediumAndUp.isActive(context) + ? PreferredSize( + preferredSize: const Size.fromHeight(52), + child: _RootAppBar(title: title), + ) + : null, + endDrawer: const SupportDrawer(), + endDrawerEnableOpenDragGesture: false, + body: Row( + children: [ + if (Breakpoints.medium.isActive(context)) + MainScreenNavigationRail( + destinations: destinations, + showFab: showFab, + ), + if (Breakpoints.large.isActive(context)) + MainScreenNavigationDrawer( + destinations: destinations, + ), + Expanded(child: child), + ], + ), + bottomNavigationBar: BottomBar( + destinations: destinations, + hidden: !(Breakpoints.small.isActive(context) && showBottomBar), + key: const Key('bottomBar'), + ), + ); + } +} + +class _RootAppBar extends StatelessWidget { + const _RootAppBar({ + required this.title, + }); + + final String title; + + @override + Widget build(final BuildContext context) => AppBar( + title: AnimatedSwitcher( + duration: const Duration(milliseconds: 200), + transitionBuilder: + (final Widget child, final Animation animation) => + SlideTransition( + position: animation.drive( + Tween( + begin: const Offset(0.0, 0.2), + end: Offset.zero, + ), + ), + child: FadeTransition( + opacity: animation, + child: child, + ), + ), + child: SizedBox( + key: ValueKey(title), + width: double.infinity, + child: Text( + title, + ), + ), + ), + leading: context.router.pageCount > 1 + ? IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => context.router.pop(), + ) + : null, + actions: const [ + SizedBox.shrink(), + ], + ); +} + +class MainScreenNavigationRail extends StatelessWidget { + const MainScreenNavigationRail({ + required this.destinations, + this.showFab = true, + super.key, + }); + + final List destinations; + final bool showFab; + + @override + Widget build(final BuildContext context) { + int? activeIndex = destinations.indexWhere( + (final destination) => + context.router.isRouteActive(destination.route.routeName), + ); + + final prevActiveIndex = destinations.indexWhere( + (final destination) => context.router.stack + .any((final route) => route.name == destination.route.routeName), + ); + + if (activeIndex == -1) { + if (prevActiveIndex != -1) { + activeIndex = prevActiveIndex; + } else { + activeIndex = 0; + } + } + + final isExtended = Breakpoints.large.isActive(context); + + return LayoutBuilder( + builder: (final context, final constraints) => SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight), + child: IntrinsicHeight( + child: NavigationRail( + backgroundColor: Colors.transparent, + labelType: isExtended + ? NavigationRailLabelType.none + : NavigationRailLabelType.all, + extended: isExtended, + leading: showFab + ? const BrandFab( + extended: false, + ) + : null, + groupAlignment: 0.0, + destinations: destinations + .map( + (final destination) => NavigationRailDestination( + icon: Icon(destination.icon), + label: Text(destination.label), + ), + ) + .toList(), + selectedIndex: activeIndex, + onDestinationSelected: (final index) { + context.router.replaceAll([destinations[index].route]); + }, + ), + ), + ), + ), + ); + } +} + +class BottomBar extends StatelessWidget { + const BottomBar({ + required this.destinations, + required this.hidden, + super.key, + }); + + final List destinations; + final bool hidden; + + @override + Widget build(final BuildContext context) { + final prevActiveIndex = destinations.indexWhere( + (final destination) => context.router.stack + .any((final route) => route.name == destination.route.routeName), + ); + + print(prevActiveIndex); + + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + height: hidden ? 0 : 80, + curve: Curves.easeInOut, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + ), + child: NavigationBar( + selectedIndex: prevActiveIndex == -1 ? 0 : prevActiveIndex, + labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected, + onDestinationSelected: (final index) { + context.router.replaceAll([destinations[index].route]); + }, + destinations: destinations + .map( + (final destination) => NavigationDestination( + icon: Icon(destination.icon), + label: destination.label, + ), + ) + .toList(), + ), + ); + } +} + +class MainScreenNavigationDrawer extends StatelessWidget { + const MainScreenNavigationDrawer({ + required this.destinations, + this.showFab = true, + super.key, + }); + + final List destinations; + final bool showFab; + + @override + Widget build(final BuildContext context) { + int? activeIndex = destinations.indexWhere( + (final destination) => + context.router.isRouteActive(destination.route.routeName), + ); + + final prevActiveIndex = destinations.indexWhere( + (final destination) => context.router.stack + .any((final route) => route.name == destination.route.routeName), + ); + + if (activeIndex == -1) { + if (prevActiveIndex != -1) { + activeIndex = prevActiveIndex; + } else { + activeIndex = 0; + } + } + + return SizedBox( + height: MediaQuery.of(context).size.height, + width: 296, + child: NavigationDrawer( + key: const Key('PrimaryNavigationDrawer'), + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + surfaceTintColor: Colors.transparent, + selectedIndex: activeIndex, + onDestinationSelected: (final index) { + context.router.replaceAll([destinations[index].route]); + }, + children: [ + const Padding( + padding: EdgeInsets.all(16.0), + child: BrandFab(extended: true), + ), + const SizedBox(height: 16), + ...destinations.map( + (final destination) => NavigationDrawerDestination( + icon: Icon(destination.icon), + label: Text(destination.label), + ), + ), + ], + ), + ); + } +} diff --git a/lib/ui/pages/backup_details/backup_details.dart b/lib/ui/pages/backup_details/backup_details.dart index 93cb0139..fd2757f8 100644 --- a/lib/ui/pages/backup_details/backup_details.dart +++ b/lib/ui/pages/backup_details/backup_details.dart @@ -6,7 +6,7 @@ import 'package:selfprivacy/logic/models/json/backup.dart'; import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_cards/outlined_card.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/helpers/modals.dart'; diff --git a/lib/ui/pages/devices/devices.dart b/lib/ui/pages/devices/devices.dart index 31010fd4..fe37418c 100644 --- a/lib/ui/pages/devices/devices.dart +++ b/lib/ui/pages/devices/devices.dart @@ -5,7 +5,7 @@ import 'package:selfprivacy/logic/common_enum/common_enum.dart'; import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/json/api_token.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart'; import 'package:selfprivacy/ui/pages/devices/new_device.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; diff --git a/lib/ui/pages/devices/new_device.dart b/lib/ui/pages/devices/new_device.dart index 8310b127..3d16101f 100644 --- a/lib/ui/pages/devices/new_device.dart +++ b/lib/ui/pages/devices/new_device.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; class NewDeviceScreen extends StatelessWidget { const NewDeviceScreen({super.key}); diff --git a/lib/ui/pages/dns_details/dns_details.dart b/lib/ui/pages/dns_details/dns_details.dart index 692921eb..be11f4ef 100644 --- a/lib/ui/pages/dns_details/dns_details.dart +++ b/lib/ui/pages/dns_details/dns_details.dart @@ -4,7 +4,7 @@ import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/utils/network_utils.dart'; diff --git a/lib/ui/pages/more/about_application.dart b/lib/ui/pages/more/about_application.dart index 7a9dc11e..110a944c 100644 --- a/lib/ui/pages/more/about_application.dart +++ b/lib/ui/pages/more/about_application.dart @@ -1,67 +1,70 @@ import 'package:flutter/material.dart'; -import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; +import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:package_info/package_info.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:url_launcher/url_launcher.dart'; class AboutApplicationPage extends StatelessWidget { const AboutApplicationPage({super.key}); @override - Widget build(final BuildContext context) => SafeArea( - child: Scaffold( - appBar: PreferredSize( - preferredSize: const Size.fromHeight(52), - child: BrandHeader( - title: 'about_application_page.title'.tr(), - hasBackButton: true, + Widget build(final BuildContext context) { + final bool isReady = context.watch().state + is ServerInstallationFinished; + + return BrandHeroScreen( + hasBackButton: true, + hasFlashButton: false, + heroTitle: 'about_application_page.title'.tr(), + children: [ + FutureBuilder( + future: _packageVersion(), + builder: (final context, final snapshot) => BrandText.body1( + 'about_application_page.application_version_text' + .tr(args: [snapshot.data.toString()]), + ), + ), + if (isReady) + FutureBuilder( + future: _apiVersion(), + builder: (final context, final snapshot) => BrandText.body1( + 'about_application_page.api_version_text' + .tr(args: [snapshot.data.toString()]), ), ), - body: ListView( - padding: paddingH15V0, + const SizedBox(height: 10), + // Button to call showAboutDialog + TextButton( + onPressed: () => showAboutDialog( + context: context, + applicationName: 'SelfPrivacy', + applicationLegalese: '© 2022 SelfPrivacy', + // Link to privacy policy children: [ - const SizedBox(height: 10), - FutureBuilder( - future: _packageVersion(), - builder: (final context, final snapshot) => BrandText.body1( - 'about_application_page.application_version_text' - .tr(args: [snapshot.data.toString()]), - ), - ), - FutureBuilder( - future: _apiVersion(), - builder: (final context, final snapshot) => BrandText.body1( - 'about_application_page.api_version_text' - .tr(args: [snapshot.data.toString()]), - ), - ), - const SizedBox(height: 10), - // Button to call showAboutDialog TextButton( - onPressed: () => showAboutDialog( - context: context, - applicationName: 'SelfPrivacy', - applicationLegalese: '© 2022 SelfPrivacy', - // Link to privacy policy - children: [ - TextButton( - onPressed: () => launchUrl( - Uri.parse('https://selfprivacy.ru/privacy-policy'), - mode: LaunchMode.externalApplication, - ), - child: Text('about_application_page.privacy_policy'.tr()), - ), - ], + onPressed: () => launchUrl( + Uri.parse('https://selfprivacy.ru/privacy-policy'), + mode: LaunchMode.externalApplication, ), - child: const Text('Show about dialog'), + child: Text('about_application_page.privacy_policy'.tr()), ), ], ), + child: const Text('Show about dialog'), ), - ); + const SizedBox(height: 8), + const Divider(height: 0), + const SizedBox(height: 8), + const BrandMarkdown( + fileName: 'about', + ), + ], + ); + } Future _packageVersion() async { String packageVersion = 'unknown'; diff --git a/lib/ui/pages/more/app_settings/developer_settings.dart b/lib/ui/pages/more/app_settings/developer_settings.dart new file mode 100644 index 00000000..09ab8840 --- /dev/null +++ b/lib/ui/pages/more/app_settings/developer_settings.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/api_maps/staging_options.dart'; +import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; +import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; +import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; +import 'package:easy_localization/easy_localization.dart'; + +class DeveloperSettingsPage extends StatefulWidget { + const DeveloperSettingsPage({super.key}); + + @override + State createState() => _DeveloperSettingsPageState(); +} + +class _DeveloperSettingsPageState extends State { + @override + Widget build(final BuildContext context) => BrandHeroScreen( + hasBackButton: true, + hasFlashButton: false, + bodyPadding: const EdgeInsets.symmetric(vertical: 16), + heroTitle: 'developer_settings.title'.tr(), + heroSubtitle: 'developer_settings.subtitle'.tr(), + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Text( + 'developer_settings.server_setup'.tr(), + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ), + SwitchListTile( + title: Text('developer_settings.use_staging_acme'.tr()), + subtitle: + Text('developer_settings.use_staging_acme_description'.tr()), + value: StagingOptions.stagingAcme, + onChanged: null, + ), + Padding( + padding: const EdgeInsets.all(16), + child: Text( + 'developer_settings.routing'.tr(), + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ), + ListTile( + title: Text('developer_settings.reset_onboarding'.tr()), + subtitle: + Text('developer_settings.reset_onboarding_description'.tr()), + enabled: + !context.watch().state.isOnboardingShowing, + onTap: () => context + .read() + .turnOffOnboarding(isOnboardingShowing: true), + ), + Padding( + padding: const EdgeInsets.all(16), + child: Text( + 'developer_settings.cubit_statuses'.tr(), + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ), + ListTile( + title: const Text('ApiDevicesCubit'), + subtitle: Text( + context.watch().state.status.toString(), + ), + ), + ListTile( + title: const Text('RecoveryKeyCubit'), + subtitle: Text( + context.watch().state.loadingStatus.toString(), + ), + ), + ], + ); +} diff --git a/lib/ui/pages/more/console.dart b/lib/ui/pages/more/console.dart index 6ad31ea5..6811e39a 100644 --- a/lib/ui/pages/more/console.dart +++ b/lib/ui/pages/more/console.dart @@ -7,14 +7,14 @@ import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/models/message.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; -class Console extends StatefulWidget { - const Console({super.key}); +class ConsolePage extends StatefulWidget { + const ConsolePage({super.key}); @override - State createState() => _ConsoleState(); + State createState() => _ConsolePageState(); } -class _ConsoleState extends State { +class _ConsolePageState extends State { @override void initState() { getIt.get().addListener(update); diff --git a/lib/ui/pages/more/more.dart b/lib/ui/pages/more/more.dart index 77b1d34a..9ea2c49b 100644 --- a/lib/ui/pages/more/more.dart +++ b/lib/ui/pages/more/more.dart @@ -1,3 +1,4 @@ +import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:ionicons/ionicons.dart'; @@ -8,19 +9,8 @@ import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; -import 'package:selfprivacy/ui/pages/devices/devices.dart'; -import 'package:selfprivacy/ui/pages/recovery_key/recovery_key.dart'; -import 'package:selfprivacy/ui/pages/server_storage/binds_migration/services_migration.dart'; -import 'package:selfprivacy/ui/pages/setup/initializing/initializing.dart'; -import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart'; -import 'package:selfprivacy/ui/pages/root_route.dart'; -import 'package:selfprivacy/ui/pages/users/users.dart'; -import 'package:selfprivacy/utils/route_transitions/basic.dart'; - -import 'package:selfprivacy/ui/pages/more/about_us.dart'; -import 'package:selfprivacy/ui/pages/more/app_settings/app_setting.dart'; -import 'package:selfprivacy/ui/pages/more/console.dart'; -import 'package:selfprivacy/ui/pages/more/about_application.dart'; +import 'package:selfprivacy/utils/breakpoints.dart'; +import 'package:selfprivacy/ui/router/router.dart'; class MorePage extends StatelessWidget { const MorePage({super.key}); @@ -34,12 +24,14 @@ class MorePage extends StatelessWidget { context.watch().state.usesBinds; return Scaffold( - appBar: PreferredSize( - preferredSize: const Size.fromHeight(52), - child: BrandHeader( - title: 'basis.more'.tr(), - ), - ), + appBar: Breakpoints.small.isActive(context) + ? PreferredSize( + preferredSize: const Size.fromHeight(52), + child: BrandHeader( + title: 'basis.more'.tr(), + ), + ) + : null, body: ListView( children: [ Padding( @@ -50,7 +42,7 @@ class MorePage extends StatelessWidget { _MoreMenuItem( title: 'storage.start_migration_button'.tr(), iconData: Icons.drive_file_move_outline, - goTo: ServicesMigrationPage( + goTo: () => ServicesMigrationRoute( diskStatus: context .watch() .state @@ -77,7 +69,7 @@ class MorePage extends StatelessWidget { _MoreMenuItem( title: 'more_page.configuration_wizard'.tr(), iconData: Icons.change_history_outlined, - goTo: const InitializingPage(), + goTo: () => const InitializingRoute(), subtitle: 'not_ready_card.in_menu'.tr(), accent: true, ), @@ -85,47 +77,43 @@ class MorePage extends StatelessWidget { _MoreMenuItem( title: 'more_page.create_ssh_key'.tr(), iconData: Ionicons.key_outline, - goTo: const UserDetails( + goTo: () => UserDetailsRoute( login: 'root', ), ), if (isReady) _MoreMenuItem( iconData: Icons.password_outlined, - goTo: const RecoveryKey(), + goTo: () => const RecoveryKeyRoute(), title: 'recovery_key.key_main_header'.tr(), ), if (isReady) _MoreMenuItem( iconData: Icons.devices_outlined, - goTo: const DevicesScreen(), + goTo: () => const DevicesRoute(), title: 'devices.main_screen.header'.tr(), ), _MoreMenuItem( title: 'more_page.application_settings'.tr(), iconData: Icons.settings_outlined, - goTo: const AppSettingsPage(), - ), - _MoreMenuItem( - title: 'more_page.about_project'.tr(), - iconData: BrandIcons.engineer, - goTo: const AboutUsPage(), + goTo: () => const AppSettingsRoute(), ), _MoreMenuItem( title: 'more_page.about_application'.tr(), iconData: BrandIcons.fire, - goTo: const AboutApplicationPage(), + goTo: () => const AboutApplicationRoute(), + longGoTo: const DeveloperSettingsRoute(), ), if (!isReady) _MoreMenuItem( title: 'more_page.onboarding'.tr(), iconData: BrandIcons.start, - goTo: const OnboardingPage(nextPage: RootPage()), + goTo: () => const OnboardingRoute(), ), _MoreMenuItem( title: 'more_page.console'.tr(), iconData: BrandIcons.terminal, - goTo: const Console(), + goTo: () => const ConsoleRoute(), ), ], ), @@ -140,14 +128,16 @@ class _MoreMenuItem extends StatelessWidget { const _MoreMenuItem({ required this.iconData, required this.title, + required this.goTo, this.subtitle, - this.goTo, + this.longGoTo, this.accent = false, }); final IconData iconData; final String title; - final Widget? goTo; + final PageRouteInfo Function() goTo; + final PageRouteInfo? longGoTo; final String? subtitle; final bool accent; @@ -160,9 +150,9 @@ class _MoreMenuItem extends StatelessWidget { tertiary: accent, child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - onTap: goTo != null - ? () => Navigator.of(context).push(materialRoute(goTo!)) - : null, + onTap: () => context.pushRoute(goTo()), + onLongPress: + longGoTo != null ? () => context.pushRoute(longGoTo!) : null, leading: Icon( iconData, size: 24, diff --git a/lib/ui/pages/onboarding/onboarding.dart b/lib/ui/pages/onboarding/onboarding.dart index ac865b11..34c49b93 100644 --- a/lib/ui/pages/onboarding/onboarding.dart +++ b/lib/ui/pages/onboarding/onboarding.dart @@ -1,13 +1,13 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/utils/route_transitions/basic.dart'; +import 'package:selfprivacy/ui/router/router.dart'; import 'package:easy_localization/easy_localization.dart'; class OnboardingPage extends StatefulWidget { - const OnboardingPage({required this.nextPage, super.key}); + const OnboardingPage({super.key}); - final Widget nextPage; @override State createState() => _OnboardingPageState(); } @@ -22,14 +22,14 @@ class _OnboardingPageState extends State { @override Widget build(final BuildContext context) => Scaffold( - body: PageView( - controller: pageController, - children: [ - _withPadding(firstPage()), - _withPadding(secondPage()), - ], - ), - ); + body: PageView( + controller: pageController, + children: [ + _withPadding(firstPage()), + _withPadding(secondPage()), + ], + ), + ); Widget _withPadding(final Widget child) => Padding( padding: const EdgeInsets.symmetric( @@ -142,10 +142,10 @@ class _OnboardingPageState extends State { BrandButton.rised( onPressed: () { context.read().turnOffOnboarding(); - Navigator.of(context).pushAndRemoveUntil( - materialRoute(widget.nextPage), - (final route) => false, - ); + context.router.replaceAll([ + const RootRoute(), + const InitializingRoute(), + ]); }, text: 'basis.got_it'.tr(), ), diff --git a/lib/ui/pages/providers/providers.dart b/lib/ui/pages/providers/providers.dart index d234c984..1b9ffb47 100644 --- a/lib/ui/pages/providers/providers.dart +++ b/lib/ui/pages/providers/providers.dart @@ -13,6 +13,7 @@ import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart'; import 'package:selfprivacy/ui/pages/backup_details/backup_details.dart'; import 'package:selfprivacy/ui/pages/dns_details/dns_details.dart'; import 'package:selfprivacy/ui/pages/server_details/server_details_screen.dart'; +import 'package:selfprivacy/utils/breakpoints.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; GlobalKey navigatorKey = GlobalKey(); @@ -61,12 +62,14 @@ class _ProvidersPageState extends State { } return Scaffold( - appBar: PreferredSize( - preferredSize: const Size.fromHeight(52), - child: BrandHeader( - title: 'basis.providers_title'.tr(), - ), - ), + appBar: Breakpoints.small.isActive(context) + ? PreferredSize( + preferredSize: const Size.fromHeight(52), + child: BrandHeader( + title: 'basis.providers_title'.tr(), + ), + ) + : null, body: ListView( padding: paddingH15V0, children: [ diff --git a/lib/ui/pages/recovery_key/recovery_key.dart b/lib/ui/pages/recovery_key/recovery_key.dart index 02a7ed9a..006f72d0 100644 --- a/lib/ui/pages/recovery_key/recovery_key.dart +++ b/lib/ui/pages/recovery_key/recovery_key.dart @@ -1,4 +1,3 @@ -import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -8,18 +7,18 @@ import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/pages/recovery_key/recovery_key_receiving.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; -class RecoveryKey extends StatefulWidget { - const RecoveryKey({super.key}); +class RecoveryKeyPage extends StatefulWidget { + const RecoveryKeyPage({super.key}); @override - State createState() => _RecoveryKeyState(); + State createState() => _RecoveryKeyPageState(); } -class _RecoveryKeyState extends State { +class _RecoveryKeyPageState extends State { @override void initState() { super.initState(); @@ -29,7 +28,7 @@ class _RecoveryKeyState extends State { @override Widget build(final BuildContext context) { final RecoveryKeyState keyStatus = context.watch().state; - + final List widgets; String? subtitle = keyStatus.exists ? null : 'recovery_key.key_main_description'.tr(); diff --git a/lib/ui/pages/recovery_key/recovery_key_receiving.dart b/lib/ui/pages/recovery_key/recovery_key_receiving.dart index afca6d20..85c4939f 100644 --- a/lib/ui/pages/recovery_key/recovery_key_receiving.dart +++ b/lib/ui/pages/recovery_key/recovery_key_receiving.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart'; class RecoveryKeyReceiving extends StatelessWidget { diff --git a/lib/ui/pages/root_route.dart b/lib/ui/pages/root_route.dart index ce1f344c..5fd2645f 100644 --- a/lib/ui/pages/root_route.dart +++ b/lib/ui/pages/root_route.dart @@ -1,89 +1,149 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/ui/components/brand_tab_bar/brand_tab_bar.dart'; -import 'package:selfprivacy/ui/pages/more/more.dart'; -import 'package:selfprivacy/ui/pages/providers/providers.dart'; -import 'package:selfprivacy/ui/pages/services/services.dart'; -import 'package:selfprivacy/ui/pages/users/users.dart'; +import 'package:selfprivacy/ui/layouts/root_scaffold_with_navigation.dart'; +import 'package:selfprivacy/ui/router/root_destinations.dart'; -import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart'; +import 'package:selfprivacy/ui/router/router.dart'; -class RootPage extends StatefulWidget { +class RootPage extends StatefulWidget implements AutoRouteWrapper { const RootPage({super.key}); @override State createState() => _RootPageState(); + + @override + Widget wrappedRoute(final BuildContext context) => this; } class _RootPageState extends State with TickerProviderStateMixin { - late TabController tabController; + bool shouldUseSplitView() => false; - late final AnimationController _controller = AnimationController( - duration: const Duration(milliseconds: 400), - vsync: this, - ); - late final Animation _animation = CurvedAnimation( - parent: _controller, - curve: Curves.fastOutSlowIn, - ); - - @override - void initState() { - tabController = TabController(length: 4, vsync: this); - tabController.addListener(() { - setState(() { - tabController.index == 2 - ? _controller.forward() - : _controller.reverse(); - }); - }); - super.initState(); - } - - @override - void dispose() { - tabController.dispose(); - _controller.dispose(); - super.dispose(); - } + final destinations = rootDestinations; @override Widget build(final BuildContext context) { final bool isReady = context.watch().state is ServerInstallationFinished; - return Provider( - create: (final _) => ChangeTab(tabController.animateTo), - child: Scaffold( - body: TabBarView( - controller: tabController, - children: const [ - ProvidersPage(), - ServicesPage(), - UsersPage(), - MorePage(), + if (context.read().state.isOnboardingShowing) { + context.router.replace(const OnboardingRoute()); + } + + return AutoRouter( + builder: (final context, final child) { + final currentDestinationIndex = destinations.indexWhere( + (final destination) => + context.router.isRouteActive(destination.route.routeName), + ); + final routeName = getRouteTitle(context.router.current.name).tr(); + return RootScaffoldWithNavigation( + title: routeName, + destinations: destinations, + showBottomBar: !(currentDestinationIndex == -1), + showFab: isReady, + child: child, + ); + }, + ); + } +} + +class MainScreenNavigationRail extends StatelessWidget { + const MainScreenNavigationRail({ + required this.destinations, + super.key, + }); + + final List destinations; + + @override + Widget build(final BuildContext context) { + int? activeIndex = destinations.indexWhere( + (final destination) => + context.router.isRouteActive(destination.route.routeName), + ); + if (activeIndex == -1) { + activeIndex = null; + } + + return Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + height: MediaQuery.of(context).size.height, + width: 72, + child: LayoutBuilder( + builder: (final context, final constraints) => SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight), + child: IntrinsicHeight( + child: NavigationRail( + backgroundColor: Colors.transparent, + labelType: NavigationRailLabelType.all, + destinations: destinations + .map( + (final destination) => NavigationRailDestination( + icon: Icon(destination.icon), + label: Text(destination.label), + ), + ) + .toList(), + selectedIndex: activeIndex, + onDestinationSelected: (final index) { + context.router.replaceAll([destinations[index].route]); + }, + ), + ), + ), + ), + ), + ), + ); + } +} + +class MainScreenNavigationDrawer extends StatelessWidget { + const MainScreenNavigationDrawer({ + required this.destinations, + super.key, + }); + + final List destinations; + + @override + Widget build(final BuildContext context) { + int? activeIndex = destinations.indexWhere( + (final destination) => + context.router.isRouteActive(destination.route.routeName), + ); + if (activeIndex == -1) { + activeIndex = null; + } + + return SizedBox( + height: MediaQuery.of(context).size.height, + width: 296, + child: LayoutBuilder( + builder: (final context, final constraints) => NavigationDrawer( + // backgroundColor: Theme.of(context).colorScheme.surfaceVariant, + // surfaceTintColor: Colors.transparent, + key: const Key('PrimaryNavigationDrawer'), + selectedIndex: activeIndex, + onDestinationSelected: (final index) { + context.router.replaceAll([destinations[index].route]); + }, + children: [ + const SizedBox(height: 18), + ...destinations.map( + (final destination) => NavigationDrawerDestination( + icon: Icon(destination.icon), + label: Text(destination.label), + ), + ), ], ), - bottomNavigationBar: BrandTabBar( - controller: tabController, - ), - floatingActionButton: isReady - ? SizedBox( - height: 104 + 16, - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - ScaleTransition( - scale: _animation, - child: const AddUserFab(), - ), - const SizedBox(height: 16), - const BrandFab(), - ], - ), - ) - : null, ), ); } diff --git a/lib/ui/pages/server_details/server_details_screen.dart b/lib/ui/pages/server_details/server_details_screen.dart index 487e1a25..a0891630 100644 --- a/lib/ui/pages/server_details/server_details_screen.dart +++ b/lib/ui/pages/server_details/server_details_screen.dart @@ -12,7 +12,7 @@ import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/ui/components/brand_button/segmented_buttons.dart'; import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; diff --git a/lib/ui/pages/server_storage/binds_migration/migration_process_page.dart b/lib/ui/pages/server_storage/binds_migration/migration_process_page.dart index 2f896673..227fb501 100644 --- a/lib/ui/pages/server_storage/binds_migration/migration_process_page.dart +++ b/lib/ui/pages/server_storage/binds_migration/migration_process_page.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_linear_indicator/brand_linear_indicator.dart'; import 'package:selfprivacy/ui/pages/root_route.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; diff --git a/lib/ui/pages/server_storage/extending_volume.dart b/lib/ui/pages/server_storage/extending_volume.dart index d40c628c..808cd6d5 100644 --- a/lib/ui/pages/server_storage/extending_volume.dart +++ b/lib/ui/pages/server_storage/extending_volume.dart @@ -6,7 +6,7 @@ import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart' import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/price.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/ui/pages/root_route.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; diff --git a/lib/ui/pages/server_storage/server_storage.dart b/lib/ui/pages/server_storage/server_storage.dart index 2a5206e2..313fb5de 100644 --- a/lib/ui/pages/server_storage/server_storage.dart +++ b/lib/ui/pages/server_storage/server_storage.dart @@ -5,7 +5,7 @@ import 'package:selfprivacy/logic/cubit/server_installation/server_installation_ import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/ui/components/brand_button/outlined_button.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/ui/pages/server_storage/extending_volume.dart'; import 'package:selfprivacy/ui/components/storage_list_items/server_storage_list_item.dart'; diff --git a/lib/ui/pages/services/service_page.dart b/lib/ui/pages/services/service_page.dart index 22db2bf6..8f344a85 100644 --- a/lib/ui/pages/services/service_page.dart +++ b/lib/ui/pages/services/service_page.dart @@ -7,7 +7,7 @@ import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/pages/server_storage/binds_migration/services_migration.dart'; import 'package:selfprivacy/utils/launch_url.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; @@ -50,7 +50,11 @@ class _ServicePageState extends State { service.svgIcon, width: 48.0, height: 48.0, - color: Theme.of(context).colorScheme.onBackground, + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.onBackground, + BlendMode.srcIn, + ) + // color: Theme.of(context).colorScheme.onBackground, ), heroTitle: service.displayName, children: [ diff --git a/lib/ui/pages/services/services.dart b/lib/ui/pages/services/services.dart index 54192367..3e919158 100644 --- a/lib/ui/pages/services/services.dart +++ b/lib/ui/pages/services/services.dart @@ -12,6 +12,7 @@ import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/ui/pages/services/service_page.dart'; +import 'package:selfprivacy/utils/breakpoints.dart'; import 'package:selfprivacy/utils/launch_url.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/ui_helpers.dart'; @@ -34,12 +35,14 @@ class _ServicesPageState extends State { .sort((final a, final b) => a.status.index.compareTo(b.status.index)); return Scaffold( - appBar: PreferredSize( - preferredSize: const Size.fromHeight(52), - child: BrandHeader( - title: 'basis.services'.tr(), - ), - ), + appBar: Breakpoints.small.isActive(context) + ? PreferredSize( + preferredSize: const Size.fromHeight(52), + child: BrandHeader( + title: 'basis.services'.tr(), + ), + ) + : null, body: RefreshIndicator( onRefresh: () async { context.read().reload(); diff --git a/lib/ui/pages/setup/initializing/initializing.dart b/lib/ui/pages/setup/initializing/initializing.dart index ca502b65..fe512ee2 100644 --- a/lib/ui/pages/setup/initializing/initializing.dart +++ b/lib/ui/pages/setup/initializing/initializing.dart @@ -1,7 +1,7 @@ +import 'package:auto_route/auto_route.dart'; import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/provider_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; @@ -9,17 +9,19 @@ import 'package:selfprivacy/logic/cubit/forms/setup/initializing/backblaze_form_ import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/domain_setup_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/root_user_form_cubit.dart'; -import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; +import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; +import 'package:selfprivacy/ui/components/brand_button/outlined_button.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart'; import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart'; -import 'package:selfprivacy/ui/pages/root_route.dart'; +import 'package:selfprivacy/ui/components/support_drawer/support_drawer.dart'; +import 'package:selfprivacy/ui/layouts/responsive_layout_with_infobox.dart'; import 'package:selfprivacy/ui/pages/setup/initializing/server_provider_picker.dart'; import 'package:selfprivacy/ui/pages/setup/initializing/server_type_picker.dart'; import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart'; -import 'package:selfprivacy/utils/route_transitions/basic.dart'; +import 'package:selfprivacy/ui/router/router.dart'; +import 'package:selfprivacy/utils/breakpoints.dart'; class InitializingPage extends StatelessWidget { const InitializingPage({super.key}); @@ -48,99 +50,123 @@ class InitializingPage extends StatelessWidget { ][cubit.state.progress.index](); } + const steps = [ + 'initializing.steps.hosting', + 'initializing.steps.server_type', + 'initializing.steps.dns_provider', + 'initializing.steps.backups_provider', + 'initializing.steps.domain', + 'initializing.steps.master_account', + 'initializing.steps.server', + 'initializing.steps.dns_setup', + 'initializing.steps.nixos_installation', + 'initializing.steps.server_reboot', + 'initializing.steps.final_checks', + ]; + return BlocListener( listener: (final context, final state) { if (cubit.state is ServerInstallationFinished) { - Navigator.of(context) - .pushReplacement(materialRoute(const RootPage())); + context.router.popUntilRoot(); } }, child: Scaffold( - appBar: AppBar( - actions: [ - if (cubit.state is ServerInstallationFinished) - IconButton( - icon: const Icon(Icons.check), - onPressed: () { - Navigator.of(context) - .pushReplacement(materialRoute(const RootPage())); - }, - ) - ], - title: Text( - 'more_page.configuration_wizard'.tr(), - ), - bottom: PreferredSize( - preferredSize: const Size.fromHeight(28), - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), - child: ProgressBar( - steps: const [ - 'Hosting', - 'Server Type', - 'CloudFlare', - 'Backblaze', - 'Domain', - 'User', - 'Server', - 'Installation', - ], - activeIndex: cubit.state.porgressBar, - ), - ), - ), - ), - body: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(16.0, 0, 16.0, 0.0), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: actualInitializingPage, - ), - ), - ConstrainedBox( - constraints: BoxConstraints( - minHeight: MediaQuery.of(context).size.height - - MediaQuery.of(context).padding.top - - MediaQuery.of(context).padding.bottom - - 566, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - alignment: Alignment.center, - child: BrandButton.text( - title: cubit.state is ServerInstallationFinished - ? 'basis.close'.tr() - : 'basis.later'.tr(), - onPressed: () { - Navigator.of(context).pushAndRemoveUntil( - materialRoute(const RootPage()), - (final predicate) => false, - ); - }, - ), + endDrawer: const SupportDrawer(), + endDrawerEnableOpenDragGesture: false, + appBar: Breakpoints.large.isActive(context) + ? null + : AppBar( + actions: [ + if (cubit.state is ServerInstallationFinished) + IconButton( + icon: const Icon(Icons.check), + onPressed: () { + context.router.popUntilRoot(); + }, ), - if (cubit.state is ServerInstallationEmpty || - cubit.state is ServerInstallationNotFinished) - Container( - alignment: Alignment.center, - child: BrandButton.text( - title: 'basis.connect_to_existing'.tr(), - onPressed: () { - Navigator.of(context).push( - materialRoute( - const RecoveryRouting(), - ), - ); - }, + const SizedBox.shrink(), + ], + title: Text( + 'more_page.configuration_wizard'.tr(), + ), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(28), + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + child: ProgressBar( + steps: const [ + 'Hosting', + 'Server Type', + 'CloudFlare', + 'Backblaze', + 'Domain', + 'User', + 'Server', + 'Installation', + ], + activeIndex: cubit.state.porgressBar, + ), + ), + ), + ), + body: LayoutBuilder( + builder: (final context, final constraints) => Row( + children: [ + if (Breakpoints.large.isActive(context)) + _ProgressDrawer( + steps: steps, + cubit: cubit, + constraints: constraints, + ), + SizedBox( + width: constraints.maxWidth - + (Breakpoints.large.isActive(context) ? 300 : 0), + height: constraints.maxHeight, + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: Breakpoints.large.isActive(context) + ? const EdgeInsets.all(16.0) + : const EdgeInsets.fromLTRB(16.0, 0, 16.0, 0.0), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: actualInitializingPage, ), - ) - ], + ), + if (!Breakpoints.large.isActive(context)) + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + alignment: Alignment.center, + child: BrandButton.text( + title: + cubit.state is ServerInstallationFinished + ? 'basis.close'.tr() + : 'basis.later'.tr(), + onPressed: () { + context.router.popUntilRoot(); + }, + ), + ), + if (cubit.state is ServerInstallationEmpty || + cubit.state is ServerInstallationNotFinished) + Container( + alignment: Alignment.center, + child: BrandButton.text( + title: 'basis.connect_to_existing'.tr(), + onPressed: () { + context.router + .replace(const RecoveryRoute()); + }, + ), + ) + ], + ), + ], + ), ), ), ], @@ -179,57 +205,55 @@ class InitializingPage extends StatelessWidget { ), ); - void _showModal(final BuildContext context, final Widget widget) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (final BuildContext context) => widget, - ); - } - Widget _stepCloudflare(final ServerInstallationCubit initializingCubit) => BlocProvider( create: (final context) => DnsProviderFormCubit(initializingCubit), child: Builder( - builder: (final context) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${'initializing.connect_to_server_provider'.tr()}Cloudflare', - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 16), - Text( - 'initializing.manage_domain_dns'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 32), - CubitFormTextField( - formFieldCubit: context.read().apiKey, - textAlign: TextAlign.center, - scrollPadding: const EdgeInsets.only(bottom: 70), - decoration: InputDecoration( - hintText: 'initializing.cloudflare_api_token'.tr(), + builder: (final context) => ResponsiveLayoutWithInfobox( + topChild: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${'initializing.connect_to_server_provider'.tr()}Cloudflare', + style: Theme.of(context).textTheme.headlineSmall, ), - ), - const SizedBox(height: 32), - BrandButton.rised( - onPressed: () => - context.read().trySubmit(), - text: 'basis.connect'.tr(), - ), - const SizedBox(height: 10), - BrandButton.text( - onPressed: () => _showModal( - context, - const _HowTo( - fileName: 'how_cloudflare', + const SizedBox(height: 16), + Text( + 'initializing.manage_domain_dns'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + primaryColumn: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CubitFormTextField( + formFieldCubit: context.read().apiKey, + textAlign: TextAlign.center, + scrollPadding: const EdgeInsets.only(bottom: 70), + decoration: InputDecoration( + hintText: 'initializing.cloudflare_api_token'.tr(), ), ), - title: 'initializing.how'.tr(), - ), - ], + const SizedBox(height: 32), + BrandButton.filled( + onPressed: () => + context.read().trySubmit(), + text: 'basis.connect'.tr(), + ), + const SizedBox(height: 10), + BrandOutlinedButton( + onPressed: () { + context.read().showArticle( + article: 'how_cloudflare', + context: context, + ); + Scaffold.of(context).openEndDrawer(); + }, + title: 'initializing.how'.tr(), + ), + ], + ), ), ), ); @@ -240,50 +264,57 @@ class InitializingPage extends StatelessWidget { child: Builder( builder: (final context) { final formCubitState = context.watch().state; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${'initializing.connect_to_server_provider'.tr()}Backblaze', - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 32), - CubitFormTextField( - formFieldCubit: context.read().keyId, - textAlign: TextAlign.center, - scrollPadding: const EdgeInsets.only(bottom: 70), - decoration: const InputDecoration( - hintText: 'KeyID', + return ResponsiveLayoutWithInfobox( + topChild: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${'initializing.connect_to_server_provider'.tr()}Backblaze', + style: Theme.of(context).textTheme.headlineSmall, ), - ), - const SizedBox(height: 16), - CubitFormTextField( - formFieldCubit: - context.read().applicationKey, - textAlign: TextAlign.center, - scrollPadding: const EdgeInsets.only(bottom: 70), - decoration: const InputDecoration( - hintText: 'Master Application Key', - ), - ), - const SizedBox(height: 32), - BrandButton.rised( - onPressed: formCubitState.isSubmitting - ? null - : () => context.read().trySubmit(), - text: 'basis.connect'.tr(), - ), - const SizedBox(height: 10), - BrandButton.text( - onPressed: () => _showModal( - context, - const _HowTo( - fileName: 'how_backblaze', + ], + ), + primaryColumn: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CubitFormTextField( + formFieldCubit: context.read().keyId, + textAlign: TextAlign.center, + scrollPadding: const EdgeInsets.only(bottom: 70), + decoration: const InputDecoration( + hintText: 'KeyID', ), ), - title: 'initializing.how'.tr(), - ), - ], + const SizedBox(height: 16), + CubitFormTextField( + formFieldCubit: + context.read().applicationKey, + textAlign: TextAlign.center, + scrollPadding: const EdgeInsets.only(bottom: 70), + decoration: const InputDecoration( + hintText: 'Master Application Key', + ), + ), + const SizedBox(height: 32), + BrandButton.rised( + onPressed: formCubitState.isSubmitting + ? null + : () => context.read().trySubmit(), + text: 'basis.connect'.tr(), + ), + const SizedBox(height: 10), + BrandButton.text( + onPressed: () { + context.read().showArticle( + article: 'how_backblaze', + context: context, + ); + Scaffold.of(context).openEndDrawer(); + }, + title: 'initializing.how'.tr(), + ), + ], + ), ); }, ), @@ -296,9 +327,8 @@ class InitializingPage extends StatelessWidget { builder: (final context) { final DomainSetupState state = context.watch().state; - return SizedBox( - width: double.infinity, - child: Column( + return ResponsiveLayoutWithInfobox( + topChild: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( @@ -310,7 +340,11 @@ class InitializingPage extends StatelessWidget { 'initializing.use_this_domain_text'.tr(), style: Theme.of(context).textTheme.bodyMedium, ), - const SizedBox(height: 32), + ], + ), + primaryColumn: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ if (state is Empty) Text( 'initializing.no_connected_domains'.tr(), @@ -350,7 +384,7 @@ class InitializingPage extends StatelessWidget { ], if (state is Empty) ...[ const SizedBox(height: 30), - BrandButton.rised( + BrandButton.filled( onPressed: () => context.read().load(), child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -367,7 +401,7 @@ class InitializingPage extends StatelessWidget { ], if (state is Loaded) ...[ const SizedBox(height: 32), - BrandButton.rised( + BrandButton.filled( onPressed: () => context.read().saveDomain(), text: 'initializing.save_domain'.tr(), @@ -388,74 +422,83 @@ class InitializingPage extends StatelessWidget { builder: (final context) { final formCubitState = context.watch().state; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'initializing.create_master_account'.tr(), - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 16), - Text( - 'initializing.enter_username_and_password'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - if (formCubitState.isErrorShown) const SizedBox(height: 16), - if (formCubitState.isErrorShown) + return ResponsiveLayoutWithInfobox( + topChild: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Text( - 'users.username_rule'.tr(), - style: TextStyle( - color: Theme.of(context).colorScheme.error, + 'initializing.create_master_account'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 16), + Text( + 'initializing.enter_username_and_password'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + primaryColumn: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (formCubitState.isErrorShown) const SizedBox(height: 16), + if (formCubitState.isErrorShown) + Text( + 'users.username_rule'.tr(), + style: TextStyle( + color: Theme.of(context).colorScheme.error, + ), + ), + const SizedBox(height: 32), + CubitFormTextField( + formFieldCubit: context.read().userName, + textAlign: TextAlign.center, + scrollPadding: const EdgeInsets.only(bottom: 70), + decoration: InputDecoration( + hintText: 'basis.username'.tr(), ), ), - const SizedBox(height: 32), - CubitFormTextField( - formFieldCubit: context.read().userName, - textAlign: TextAlign.center, - scrollPadding: const EdgeInsets.only(bottom: 70), - decoration: InputDecoration( - hintText: 'basis.username'.tr(), - ), - ), - const SizedBox(height: 16), - BlocBuilder, FieldCubitState>( - bloc: context.read().isVisible, - builder: (final context, final state) { - final bool isVisible = state.value; - return CubitFormTextField( - obscureText: !isVisible, - formFieldCubit: - context.read().password, - textAlign: TextAlign.center, - scrollPadding: const EdgeInsets.only(bottom: 70), - decoration: InputDecoration( - hintText: 'basis.password'.tr(), - suffixIcon: IconButton( - icon: Icon( - isVisible ? Icons.visibility : Icons.visibility_off, + const SizedBox(height: 16), + BlocBuilder, FieldCubitState>( + bloc: context.read().isVisible, + builder: (final context, final state) { + final bool isVisible = state.value; + return CubitFormTextField( + obscureText: !isVisible, + formFieldCubit: + context.read().password, + textAlign: TextAlign.center, + scrollPadding: const EdgeInsets.only(bottom: 70), + decoration: InputDecoration( + hintText: 'basis.password'.tr(), + suffixIcon: IconButton( + icon: Icon( + isVisible + ? Icons.visibility + : Icons.visibility_off, + ), + onPressed: () => context + .read() + .isVisible + .setValue(!isVisible), ), - onPressed: () => context - .read() - .isVisible - .setValue(!isVisible), + suffixIconConstraints: + const BoxConstraints(minWidth: 60), + prefixIconConstraints: + const BoxConstraints(maxWidth: 60), + prefixIcon: Container(), ), - suffixIconConstraints: - const BoxConstraints(minWidth: 60), - prefixIconConstraints: - const BoxConstraints(maxWidth: 60), - prefixIcon: Container(), - ), - ); - }, - ), - const SizedBox(height: 32), - BrandButton.rised( - onPressed: formCubitState.isSubmitting - ? null - : () => context.read().trySubmit(), - text: 'basis.connect'.tr(), - ), - ], + ); + }, + ), + const SizedBox(height: 32), + BrandButton.filled( + onPressed: formCubitState.isSubmitting + ? null + : () => context.read().trySubmit(), + text: 'basis.connect'.tr(), + ), + ], + ), ); }, ), @@ -465,27 +508,28 @@ class InitializingPage extends StatelessWidget { final bool isLoading = (appConfigCubit.state as ServerInstallationNotFinished).isLoading; return Builder( - builder: (final context) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'initializing.final'.tr(), - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 16), - Text( - 'initializing.create_server'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 128), - BrandButton.rised( - onPressed: - isLoading ? null : appConfigCubit.createServerAndSetDnsRecords, - text: isLoading - ? 'basis.loading'.tr() - : 'initializing.create_server'.tr(), - ), - ], + builder: (final context) => ResponsiveLayoutWithInfobox( + topChild: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'initializing.final'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 16), + Text( + 'initializing.create_server'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + primaryColumn: BrandButton.filled( + onPressed: + isLoading ? null : appConfigCubit.createServerAndSetDnsRecords, + text: isLoading + ? 'basis.loading'.tr() + : 'initializing.create_server'.tr(), + ), ), ); } @@ -514,84 +558,200 @@ class InitializingPage extends StatelessWidget { return Builder( builder: (final context) => SizedBox( width: double.infinity, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'initializing.checks'.tr(args: [doneCount.toString(), '4']), - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 16), - if (text != null) + child: ResponsiveLayoutWithInfobox( + topChild: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Text( - text, - style: Theme.of(context).textTheme.bodyMedium, + 'initializing.checks'.tr(args: [doneCount.toString(), '4']), + style: Theme.of(context).textTheme.headlineSmall, ), - const SizedBox(height: 128), - const SizedBox(height: 10), - if (doneCount == 0 && state.dnsMatches != null) - Column( - children: state.dnsMatches!.entries.map((final entry) { - final String domain = entry.key; - final bool isCorrect = entry.value; - return Row( - children: [ - if (isCorrect) - const Icon(Icons.check, color: Colors.green), - if (!isCorrect) - const Icon(Icons.schedule, color: Colors.amber), - const SizedBox(width: 10), - Text(domain), - ], - ); - }).toList(), - ), - const SizedBox(height: 10), - if (!state.isLoading) - Row( - children: [ - Text( - 'initializing.until_the_next_check'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - BrandTimer( - startDateTime: state.timerStart!, - duration: state.duration!, - ) - ], - ), - if (state.isLoading) - Text( - 'initializing.check'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - ], + const SizedBox(height: 16), + if (text != null) + Text( + text, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + primaryColumn: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 128), + const SizedBox(height: 10), + if (doneCount == 0 && state.dnsMatches != null) + Column( + children: state.dnsMatches!.entries.map((final entry) { + final String domain = entry.key; + final bool isCorrect = entry.value; + return Row( + children: [ + if (isCorrect) + const Icon(Icons.check, color: Colors.green), + if (!isCorrect) + const Icon(Icons.schedule, color: Colors.amber), + const SizedBox(width: 10), + Text(domain), + ], + ); + }).toList(), + ), + const SizedBox(height: 10), + if (!state.isLoading) + Row( + children: [ + Text( + 'initializing.until_the_next_check'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + BrandTimer( + startDateTime: state.timerStart!, + duration: state.duration!, + ) + ], + ), + if (state.isLoading) + Text( + 'initializing.check'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), ), ), ); } } -class _HowTo extends StatelessWidget { - const _HowTo({ - required this.fileName, +class _ProgressDrawer extends StatelessWidget { + const _ProgressDrawer({ + required this.steps, + required this.cubit, + required this.constraints, }); - final String fileName; + final List steps; + final ServerInstallationCubit cubit; + final BoxConstraints constraints; @override - Widget build(final BuildContext context) => BrandBottomSheet( - isExpended: true, - child: Padding( - padding: paddingH15V0, - child: ListView( - padding: const EdgeInsets.symmetric(vertical: 16), + Widget build(final BuildContext context) => SizedBox( + width: 300, + height: constraints.maxHeight, + child: Drawer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - BrandMarkdown( - fileName: fileName, + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'more_page.configuration_wizard'.tr(), + style: Theme.of(context).textTheme.titleLarge, + ), + ), + Flexible( + fit: FlexFit.tight, + child: SingleChildScrollView( + child: Column( + children: [ + ...steps.map((final step) { + final index = steps.indexOf(step); + return _StepIndicator( + title: step.tr(), + isCurrent: index == cubit.state.progress.index, + isCompleted: index < cubit.state.progress.index, + ); + }).toList(), + ], + ), + ), + ), + // const Spacer(), + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (cubit.state is ServerInstallationEmpty || + cubit.state is ServerInstallationNotFinished) + Container( + alignment: Alignment.center, + child: BrandButton.filled( + text: 'basis.connect_to_existing'.tr(), + onPressed: () { + context.router.replace(const RecoveryRoute()); + }, + ), + ), + ConstrainedBox( + constraints: const BoxConstraints( + minWidth: double.infinity, + ), + child: OutlinedButton( + child: Text( + cubit.state is ServerInstallationFinished + ? 'basis.close'.tr() + : 'basis.later'.tr(), + ), + onPressed: () { + context.router.popUntilRoot(); + }, + ), + ), + ], + ), ), ], ), ), ); } + +class _StepIndicator extends StatelessWidget { + const _StepIndicator({ + required this.title, + required this.isCompleted, + required this.isCurrent, + }); + + final String title; + final bool isCompleted; + final bool isCurrent; + + @override + Widget build(final BuildContext context) => ListTile( + selected: isCurrent, + leading: isCurrent + ? const _StepCurrentIcon() + : isCompleted + ? const _StepCompletedIcon() + : const _StepPendingIcon(), + title: Text( + title, + ), + textColor: Theme.of(context).colorScheme.onSurfaceVariant, + iconColor: Theme.of(context).colorScheme.onSurfaceVariant, + ); +} + +class _StepCompletedIcon extends StatelessWidget { + const _StepCompletedIcon(); + + @override + Widget build(final BuildContext context) => const Icon(Icons.check_circle); +} + +class _StepPendingIcon extends StatelessWidget { + const _StepPendingIcon(); + + @override + Widget build(final BuildContext context) => const Icon(Icons.circle_outlined); +} + +class _StepCurrentIcon extends StatelessWidget { + const _StepCurrentIcon(); + + @override + Widget build(final BuildContext context) => + const Icon(Icons.build_circle_outlined); +} diff --git a/lib/ui/pages/setup/initializing/server_provider_picker.dart b/lib/ui/pages/setup/initializing/server_provider_picker.dart index dfec01b9..6813d9cd 100644 --- a/lib/ui/pages/setup/initializing/server_provider_picker.dart +++ b/lib/ui/pages/setup/initializing/server_provider_picker.dart @@ -2,16 +2,15 @@ import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/provider_form_cubit.dart'; +import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; -import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/outlined_button.dart'; import 'package:selfprivacy/ui/components/brand_cards/outlined_card.dart'; -import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart'; +import 'package:selfprivacy/ui/layouts/responsive_layout_with_infobox.dart'; import 'package:selfprivacy/utils/launch_url.dart'; class ServerProviderPicker extends StatefulWidget { @@ -98,56 +97,49 @@ class ProviderInputDataPage extends StatelessWidget { final ProviderFormCubit providerCubit; @override - Widget build(final BuildContext context) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${'initializing.connect_to_server_provider'.tr()}${providerInfo.providerType.displayName}", - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 16), - Text( - 'initializing.connect_to_server_provider_text'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 32), - CubitFormTextField( - formFieldCubit: providerCubit.apiKey, - textAlign: TextAlign.center, - scrollPadding: const EdgeInsets.only(bottom: 70), - decoration: const InputDecoration( - hintText: 'Provider API Token', + Widget build(final BuildContext context) => ResponsiveLayoutWithInfobox( + topChild: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${'initializing.connect_to_server_provider'.tr()}${providerInfo.providerType.displayName}", + style: Theme.of(context).textTheme.headlineSmall, ), - ), - const SizedBox(height: 32), - BrandButton.filled( - child: Text('basis.connect'.tr()), - onPressed: () => providerCubit.trySubmit(), - ), - const SizedBox(height: 10), - BrandOutlinedButton( - child: Text('initializing.how'.tr()), - onPressed: () => showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (final BuildContext context) => BrandBottomSheet( - isExpended: true, - child: Padding( - padding: paddingH15V0, - child: ListView( - padding: const EdgeInsets.symmetric(vertical: 16), - children: [ - BrandMarkdown( - fileName: providerInfo.pathToHow, - ), - ], - ), - ), + const SizedBox(height: 16), + Text( + 'initializing.connect_to_server_provider_text'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + primaryColumn: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CubitFormTextField( + formFieldCubit: providerCubit.apiKey, + textAlign: TextAlign.center, + scrollPadding: const EdgeInsets.only(bottom: 70), + decoration: const InputDecoration( + hintText: 'Provider API Token', ), ), - ), - ], + const SizedBox(height: 32), + BrandButton.filled( + child: Text('basis.connect'.tr()), + onPressed: () => providerCubit.trySubmit(), + ), + const SizedBox(height: 10), + BrandOutlinedButton( + child: Text('initializing.how'.tr()), + onPressed: () { + context.read().showArticle( + article: providerInfo.pathToHow, + context: context, + ); + }, + ), + ], + ), ); } @@ -164,175 +156,182 @@ class ProviderSelectionPage extends StatelessWidget { @override Widget build(final BuildContext context) => SizedBox( width: double.infinity, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'initializing.connect_to_server'.tr(), - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 10), - Text( - 'initializing.select_provider'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 10), - OutlinedCard( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - width: 40, - height: 40, - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(40), - color: const Color(0xFFD50C2D), + child: ResponsiveLayoutWithInfobox( + topChild: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'initializing.connect_to_server'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 10), + Text( + 'initializing.select_provider'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + primaryColumn: Column( + children: [ + OutlinedCard( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 40, + height: 40, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(40), + color: const Color(0xFFD50C2D), + ), + child: SvgPicture.asset( + 'assets/images/logos/hetzner.svg', + ), ), - child: SvgPicture.asset( - 'assets/images/logos/hetzner.svg', + const SizedBox(width: 16), + Text( + 'Hetzner Cloud', + style: Theme.of(context).textTheme.titleMedium, ), - ), - const SizedBox(width: 16), - Text( - 'Hetzner Cloud', - style: Theme.of(context).textTheme.titleMedium, - ), - ], - ), - const SizedBox(height: 16), - Text( - 'initializing.select_provider_countries_title'.tr(), - style: Theme.of(context).textTheme.bodyLarge, - ), - Text( - 'initializing.select_provider_countries_text_hetzner' - .tr(), - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox(height: 16), - Text( - 'initializing.select_provider_price_title'.tr(), - style: Theme.of(context).textTheme.bodyLarge, - ), - Text( - 'initializing.select_provider_price_text_hetzner'.tr(), - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox(height: 16), - Text( - 'initializing.select_provider_payment_title'.tr(), - style: Theme.of(context).textTheme.bodyLarge, - ), - Text( - 'initializing.select_provider_payment_text_hetzner'.tr(), - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox(height: 16), - Text( - 'initializing.select_provider_email_notice'.tr(), - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox(height: 16), - BrandButton.filled( - child: Text('basis.select'.tr()), - onPressed: () { - serverInstallationCubit - .setServerProviderType(ServerProvider.hetzner); - callback(ServerProvider.hetzner); - }, - ), - // Outlined button that will open website - BrandOutlinedButton( - onPressed: () => - launchURL('https://www.hetzner.com/cloud'), - title: 'initializing.select_provider_site_button'.tr(), - ), - ], + ], + ), + const SizedBox(height: 16), + Text( + 'initializing.select_provider_countries_title'.tr(), + style: Theme.of(context).textTheme.bodyLarge, + ), + Text( + 'initializing.select_provider_countries_text_hetzner' + .tr(), + style: Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(height: 16), + Text( + 'initializing.select_provider_price_title'.tr(), + style: Theme.of(context).textTheme.bodyLarge, + ), + Text( + 'initializing.select_provider_price_text_hetzner'.tr(), + style: Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(height: 16), + Text( + 'initializing.select_provider_payment_title'.tr(), + style: Theme.of(context).textTheme.bodyLarge, + ), + Text( + 'initializing.select_provider_payment_text_hetzner' + .tr(), + style: Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(height: 16), + Text( + 'initializing.select_provider_email_notice'.tr(), + style: Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(height: 16), + BrandButton.filled( + child: Text('basis.select'.tr()), + onPressed: () { + serverInstallationCubit + .setServerProviderType(ServerProvider.hetzner); + callback(ServerProvider.hetzner); + }, + ), + // Outlined button that will open website + BrandOutlinedButton( + onPressed: () => + launchURL('https://www.hetzner.com/cloud'), + title: 'initializing.select_provider_site_button'.tr(), + ), + ], + ), ), ), - ), - const SizedBox(height: 16), - OutlinedCard( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - width: 40, - height: 40, - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(40), - color: const Color(0xFF0080FF), + const SizedBox(height: 16), + OutlinedCard( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 40, + height: 40, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(40), + color: const Color(0xFF0080FF), + ), + child: SvgPicture.asset( + 'assets/images/logos/digital_ocean.svg', + ), ), - child: SvgPicture.asset( - 'assets/images/logos/digital_ocean.svg', + const SizedBox(width: 16), + Text( + 'Digital Ocean', + style: Theme.of(context).textTheme.titleMedium, ), - ), - const SizedBox(width: 16), - Text( - 'Digital Ocean', - style: Theme.of(context).textTheme.titleMedium, - ), - ], - ), - const SizedBox(height: 16), - Text( - 'initializing.select_provider_countries_title'.tr(), - style: Theme.of(context).textTheme.bodyLarge, - ), - Text( - 'initializing.select_provider_countries_text_do'.tr(), - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox(height: 16), - Text( - 'initializing.select_provider_price_title'.tr(), - style: Theme.of(context).textTheme.bodyLarge, - ), - Text( - 'initializing.select_provider_price_text_do'.tr(), - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox(height: 16), - Text( - 'initializing.select_provider_payment_title'.tr(), - style: Theme.of(context).textTheme.bodyLarge, - ), - Text( - 'initializing.select_provider_payment_text_do'.tr(), - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox(height: 16), - BrandButton.filled( - child: Text('basis.select'.tr()), - onPressed: () { - serverInstallationCubit - .setServerProviderType(ServerProvider.digitalOcean); - callback(ServerProvider.digitalOcean); - }, - ), - // Outlined button that will open website - BrandOutlinedButton( - onPressed: () => - launchURL('https://www.digitalocean.com'), - title: 'initializing.select_provider_site_button'.tr(), - ), - ], + ], + ), + const SizedBox(height: 16), + Text( + 'initializing.select_provider_countries_title'.tr(), + style: Theme.of(context).textTheme.bodyLarge, + ), + Text( + 'initializing.select_provider_countries_text_do'.tr(), + style: Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(height: 16), + Text( + 'initializing.select_provider_price_title'.tr(), + style: Theme.of(context).textTheme.bodyLarge, + ), + Text( + 'initializing.select_provider_price_text_do'.tr(), + style: Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(height: 16), + Text( + 'initializing.select_provider_payment_title'.tr(), + style: Theme.of(context).textTheme.bodyLarge, + ), + Text( + 'initializing.select_provider_payment_text_do'.tr(), + style: Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(height: 16), + BrandButton.filled( + child: Text('basis.select'.tr()), + onPressed: () { + serverInstallationCubit.setServerProviderType( + ServerProvider.digitalOcean, + ); + callback(ServerProvider.digitalOcean); + }, + ), + // Outlined button that will open website + BrandOutlinedButton( + onPressed: () => + launchURL('https://www.digitalocean.com'), + title: 'initializing.select_provider_site_button'.tr(), + ), + ], + ), ), ), - ), - const SizedBox(height: 16), - InfoBox(text: 'initializing.select_provider_notice'.tr()), - ], + ], + ), + secondaryColumn: + InfoBox(text: 'initializing.select_provider_notice'.tr()), ), ); } diff --git a/lib/ui/pages/setup/initializing/server_type_picker.dart b/lib/ui/pages/setup/initializing/server_type_picker.dart index 5a6632c8..2b9f1748 100644 --- a/lib/ui/pages/setup/initializing/server_type_picker.dart +++ b/lib/ui/pages/setup/initializing/server_type_picker.dart @@ -7,6 +7,7 @@ import 'package:selfprivacy/logic/models/server_provider_location.dart'; import 'package:selfprivacy/logic/models/server_type.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart'; +import 'package:selfprivacy/ui/layouts/responsive_layout_with_infobox.dart'; class ServerTypePicker extends StatefulWidget { const ServerTypePicker({ @@ -70,50 +71,67 @@ class SelectLocationPage extends StatelessWidget { if ((snapshot.data as List).isEmpty) { return Text('initializing.no_locations_found'.tr()); } - return Column( - children: [ - Text( - 'initializing.choose_location_type'.tr(), - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 16), - Text( - 'initializing.choose_location_type_text'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 16), - ...(snapshot.data! as List).map( - (final location) => SizedBox( - width: double.infinity, - child: InkWell( - onTap: () { - callback(location); - }, - child: Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${location.flag ?? ''} ${location.title}', - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 8), - if (location.description != null) - Text( - location.description!, - style: Theme.of(context).textTheme.bodyMedium, + return ResponsiveLayoutWithInfobox( + topChild: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'initializing.choose_location_type'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 16), + Text( + 'initializing.choose_location_type_text'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + primaryColumn: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...(snapshot.data! as List).map( + (final location) => Column( + children: [ + SizedBox( + width: double.infinity, + child: Card( + clipBehavior: Clip.antiAlias, + child: InkResponse( + highlightShape: BoxShape.rectangle, + onTap: () { + callback(location); + }, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${location.flag ?? ''} ${location.title}', + style: Theme.of(context) + .textTheme + .titleMedium, + ), + const SizedBox(height: 8), + if (location.description != null) + Text( + location.description!, + style: Theme.of(context) + .textTheme + .bodyMedium, + ), + ], ), - ], + ), + ), ), ), - ), + const SizedBox(height: 8), + ], ), ), - ), - const SizedBox(height: 24), - ], + ], + ), ); } else { return const Center(child: CircularProgressIndicator()); @@ -180,121 +198,145 @@ class SelectTypePage extends StatelessWidget { ], ); } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'initializing.choose_server_type'.tr(), - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 16), - Text( - 'initializing.choose_server_type_text'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 16), - ...(snapshot.data! as List).map( - (final type) => SizedBox( - width: double.infinity, - child: InkWell( - onTap: () { - serverInstallationCubit.setServerType(type); - }, - child: Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - type.title, - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 8), - Row( - children: [ - Icon( - Icons.memory_outlined, - color: - Theme.of(context).colorScheme.onSurface, - ), - const SizedBox(width: 8), - Text( - 'server.core_count'.plural(type.cores), - style: - Theme.of(context).textTheme.bodyMedium, - ), - ], - ), - const SizedBox(height: 8), - Row( - children: [ - Icon( - Icons.memory_outlined, - color: - Theme.of(context).colorScheme.onSurface, - ), - const SizedBox(width: 8), - Text( - 'initializing.choose_server_type_ram' - .tr(args: [type.ram.toString()]), - style: - Theme.of(context).textTheme.bodyMedium, - ), - ], - ), - const SizedBox(height: 8), - Row( - children: [ - Icon( - Icons.sd_card_outlined, - color: - Theme.of(context).colorScheme.onSurface, - ), - const SizedBox(width: 8), - Text( - 'initializing.choose_server_type_storage' - .tr( - args: [type.disk.gibibyte.toString()], + return ResponsiveLayoutWithInfobox( + topChild: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'initializing.choose_server_type'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 16), + Text( + 'initializing.choose_server_type_text'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + primaryColumn: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...(snapshot.data! as List).map( + (final type) => Column( + children: [ + SizedBox( + width: double.infinity, + child: InkWell( + onTap: () { + serverInstallationCubit.setServerType(type); + }, + child: Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + type.title, + style: Theme.of(context) + .textTheme + .titleMedium, ), - style: - Theme.of(context).textTheme.bodyMedium, - ), - ], - ), - const SizedBox(height: 8), - const Divider(height: 8), - const SizedBox(height: 8), - Row( - children: [ - Icon( - Icons.payments_outlined, - color: - Theme.of(context).colorScheme.onSurface, - ), - const SizedBox(width: 8), - Text( - 'initializing.choose_server_type_payment_per_month' - .tr( - args: [ - '${type.price.value.toString()} ${type.price.currency}' + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.memory_outlined, + color: Theme.of(context) + .colorScheme + .onSurface, + ), + const SizedBox(width: 8), + Text( + 'server.core_count' + .plural(type.cores), + style: Theme.of(context) + .textTheme + .bodyMedium, + ), ], ), - style: - Theme.of(context).textTheme.bodyLarge, - ), - ], + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.memory_outlined, + color: Theme.of(context) + .colorScheme + .onSurface, + ), + const SizedBox(width: 8), + Text( + 'initializing.choose_server_type_ram' + .tr(args: [type.ram.toString()]), + style: Theme.of(context) + .textTheme + .bodyMedium, + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.sd_card_outlined, + color: Theme.of(context) + .colorScheme + .onSurface, + ), + const SizedBox(width: 8), + Text( + 'initializing.choose_server_type_storage' + .tr( + args: [ + type.disk.gibibyte.toString() + ], + ), + style: Theme.of(context) + .textTheme + .bodyMedium, + ), + ], + ), + const SizedBox(height: 8), + const Divider(height: 8), + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.payments_outlined, + color: Theme.of(context) + .colorScheme + .onSurface, + ), + const SizedBox(width: 8), + Text( + 'initializing.choose_server_type_payment_per_month' + .tr( + args: [ + '${type.price.value.toString()} ${type.price.currency}' + ], + ), + style: Theme.of(context) + .textTheme + .bodyLarge, + ), + ], + ), + ], + ), ), - ], + ), ), ), - ), + const SizedBox(height: 8), + ], ), ), - ), - const SizedBox(height: 16), - InfoBox(text: 'initializing.choose_server_type_notice'.tr()), - ], + ], + ), + secondaryColumn: + InfoBox(text: 'initializing.choose_server_type_notice'.tr()), ); } else { return const Center(child: CircularProgressIndicator()); diff --git a/lib/ui/pages/setup/recovering/recover_by_new_device_key.dart b/lib/ui/pages/setup/recovering/recover_by_new_device_key.dart index bb2ebbf0..7eeb2812 100644 --- a/lib/ui/pages/setup/recovering/recover_by_new_device_key.dart +++ b/lib/ui/pages/setup/recovering/recover_by_new_device_key.dart @@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_device_form_cubit.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:cubit_form/cubit_form.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; diff --git a/lib/ui/pages/setup/recovering/recover_by_old_token.dart b/lib/ui/pages/setup/recovering/recover_by_old_token.dart index 2b65ba8b..d9dbba3a 100644 --- a/lib/ui/pages/setup/recovering/recover_by_old_token.dart +++ b/lib/ui/pages/setup/recovering/recover_by_old_token.dart @@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_device_form_cubit.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; import 'package:cubit_form/cubit_form.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; diff --git a/lib/ui/pages/setup/recovering/recover_by_recovery_key.dart b/lib/ui/pages/setup/recovering/recover_by_recovery_key.dart index c47b924f..bd308f31 100644 --- a/lib/ui/pages/setup/recovering/recover_by_recovery_key.dart +++ b/lib/ui/pages/setup/recovering/recover_by_recovery_key.dart @@ -5,7 +5,7 @@ import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_device_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; class RecoverByRecoveryKey extends StatelessWidget { const RecoverByRecoveryKey({super.key}); diff --git a/lib/ui/pages/setup/recovering/recovery_confirm_backblaze.dart b/lib/ui/pages/setup/recovering/recovery_confirm_backblaze.dart index 0b7e7a9e..12c23d54 100644 --- a/lib/ui/pages/setup/recovering/recovery_confirm_backblaze.dart +++ b/lib/ui/pages/setup/recovering/recovery_confirm_backblaze.dart @@ -6,7 +6,7 @@ import 'package:selfprivacy/logic/cubit/forms/setup/initializing/backblaze_form_ import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; class RecoveryConfirmBackblaze extends StatelessWidget { diff --git a/lib/ui/pages/setup/recovering/recovery_confirm_cloudflare.dart b/lib/ui/pages/setup/recovering/recovery_confirm_cloudflare.dart index 4b766e56..4bf670a3 100644 --- a/lib/ui/pages/setup/recovering/recovery_confirm_cloudflare.dart +++ b/lib/ui/pages/setup/recovering/recovery_confirm_cloudflare.dart @@ -6,7 +6,7 @@ import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_fo import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; class RecoveryConfirmCloudflare extends StatelessWidget { diff --git a/lib/ui/pages/setup/recovering/recovery_confirm_server.dart b/lib/ui/pages/setup/recovering/recovery_confirm_server.dart index 3b47cc76..948e2288 100644 --- a/lib/ui/pages/setup/recovering/recovery_confirm_server.dart +++ b/lib/ui/pages/setup/recovering/recovery_confirm_server.dart @@ -4,7 +4,7 @@ import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_depe import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; class RecoveryConfirmServer extends StatefulWidget { const RecoveryConfirmServer({super.key}); diff --git a/lib/ui/pages/setup/recovering/recovery_method_select.dart b/lib/ui/pages/setup/recovering/recovery_method_select.dart index 68129d57..8aaefe35 100644 --- a/lib/ui/pages/setup/recovering/recovery_method_select.dart +++ b/lib/ui/pages/setup/recovering/recovery_method_select.dart @@ -4,7 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_cards/outlined_card.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_old_token.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; diff --git a/lib/ui/pages/setup/recovering/recovery_routing.dart b/lib/ui/pages/setup/recovering/recovery_routing.dart index 68108214..23f72f8f 100644 --- a/lib/ui/pages/setup/recovering/recovery_routing.dart +++ b/lib/ui/pages/setup/recovering/recovery_routing.dart @@ -5,7 +5,7 @@ import 'package:selfprivacy/logic/cubit/server_installation/server_installation_ import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/recovering/recovery_domain_form_cubit.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/pages/root_route.dart'; import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_old_token.dart'; import 'package:selfprivacy/ui/pages/setup/recovering/recover_by_recovery_key.dart'; diff --git a/lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart b/lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart index f850cde7..50c99ca7 100644 --- a/lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart +++ b/lib/ui/pages/setup/recovering/recovery_server_provider_connected.dart @@ -4,7 +4,7 @@ import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/provider_form_cubit.dart'; import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:cubit_form/cubit_form.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; diff --git a/lib/ui/pages/users/user.dart b/lib/ui/pages/users/user.dart index 1d781811..2658d3cc 100644 --- a/lib/ui/pages/users/user.dart +++ b/lib/ui/pages/users/user.dart @@ -12,7 +12,7 @@ class _User extends StatelessWidget { Widget build(final BuildContext context) => InkWell( onTap: () { Navigator.of(context).push( - materialRoute(UserDetails(login: user.login)), + materialRoute(UserDetailsPage(login: user.login)), ); }, child: Container( diff --git a/lib/ui/pages/users/user_details.dart b/lib/ui/pages/users/user_details.dart index 3ae5b86d..0fc0447c 100644 --- a/lib/ui/pages/users/user_details.dart +++ b/lib/ui/pages/users/user_details.dart @@ -1,7 +1,7 @@ part of 'users.dart'; -class UserDetails extends StatelessWidget { - const UserDetails({ +class UserDetailsPage extends StatelessWidget { + const UserDetailsPage({ required this.login, super.key, }); diff --git a/lib/ui/pages/users/users.dart b/lib/ui/pages/users/users.dart index caf6d441..51b29828 100644 --- a/lib/ui/pages/users/users.dart +++ b/lib/ui/pages/users/users.dart @@ -16,12 +16,13 @@ import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/outlined_button.dart'; import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; -import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart'; import 'package:selfprivacy/ui/components/list_tiles/list_tile_on_surface_variant.dart'; import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart'; +import 'package:selfprivacy/utils/breakpoints.dart'; import 'package:selfprivacy/utils/ui_helpers.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; @@ -106,12 +107,14 @@ class UsersPage extends StatelessWidget { } return Scaffold( - appBar: PreferredSize( - preferredSize: const Size.fromHeight(52), - child: BrandHeader( - title: 'basis.users'.tr(), - ), - ), + appBar: Breakpoints.small.isActive(context) + ? PreferredSize( + preferredSize: const Size.fromHeight(52), + child: BrandHeader( + title: 'basis.users'.tr(), + ), + ) + : null, body: child, ); } diff --git a/lib/ui/router/root_destinations.dart b/lib/ui/router/root_destinations.dart new file mode 100644 index 00000000..0d981894 --- /dev/null +++ b/lib/ui/router/root_destinations.dart @@ -0,0 +1,46 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; +import 'package:selfprivacy/ui/router/router.dart'; + +class RouteDestination { + const RouteDestination({ + required this.route, + required this.icon, + required this.label, + required this.title, + }); + + final PageRouteInfo route; + final IconData icon; + final String label; + final String title; +} + +final rootDestinations = [ + RouteDestination( + route: const ProvidersRoute(), + icon: BrandIcons.server, + label: 'basis.providers'.tr(), + title: 'basis.providers_title'.tr(), + ), + RouteDestination( + route: const ServicesRoute(), + icon: BrandIcons.box, + label: 'basis.services'.tr(), + title: 'basis.services'.tr(), + ), + RouteDestination( + route: const UsersRoute(), + icon: BrandIcons.users, + label: 'basis.users'.tr(), + title: 'basis.users'.tr(), + ), + RouteDestination( + route: const MoreRoute(), + icon: Icons.menu_rounded, + label: 'basis.more'.tr(), + title: 'basis.more'.tr(), + ), +]; diff --git a/lib/ui/router/router.dart b/lib/ui/router/router.dart new file mode 100644 index 00000000..92c588e0 --- /dev/null +++ b/lib/ui/router/router.dart @@ -0,0 +1,122 @@ +import 'package:animations/animations.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/models/disk_status.dart'; +import 'package:selfprivacy/logic/models/service.dart'; +import 'package:selfprivacy/ui/pages/devices/devices.dart'; +import 'package:selfprivacy/ui/pages/more/about_application.dart'; +import 'package:selfprivacy/ui/pages/more/app_settings/app_settings.dart'; +import 'package:selfprivacy/ui/pages/more/app_settings/developer_settings.dart'; +import 'package:selfprivacy/ui/pages/more/console.dart'; +import 'package:selfprivacy/ui/pages/more/more.dart'; +import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart'; +import 'package:selfprivacy/ui/pages/providers/providers.dart'; +import 'package:selfprivacy/ui/pages/recovery_key/recovery_key.dart'; +import 'package:selfprivacy/ui/pages/root_route.dart'; +import 'package:selfprivacy/ui/pages/server_storage/binds_migration/services_migration.dart'; +import 'package:selfprivacy/ui/pages/services/services.dart'; +import 'package:selfprivacy/ui/pages/setup/initializing/initializing.dart'; +import 'package:selfprivacy/ui/pages/setup/recovering/recovery_routing.dart'; +import 'package:selfprivacy/ui/pages/users/users.dart'; + +part 'router.gr.dart'; + +Widget fadeThroughTransition( + final BuildContext context, + final Animation animation, + final Animation secondaryAnimation, + final Widget child, +) => + SharedAxisTransition( + key: UniqueKey(), + animation: animation, + secondaryAnimation: secondaryAnimation, + transitionType: SharedAxisTransitionType.vertical, + child: child, + ); + +@MaterialAutoRouter( + // transitionsBuilder: fadeThroughTransition, + replaceInRouteName: 'Page|Screen|Routing,Route', + routes: [ + AutoRoute( + page: OnboardingPage, + ), + AutoRoute(page: InitializingPage), + AutoRoute(page: RecoveryRouting), + AutoRoute( + page: RootPage, + initial: true, + children: [ + CustomRoute( + page: ProvidersPage, + usesPathAsKey: true, + initial: true, + transitionsBuilder: fadeThroughTransition, + durationInMilliseconds: 200, + ), + CustomRoute( + page: ServicesPage, + usesPathAsKey: true, + transitionsBuilder: fadeThroughTransition, + durationInMilliseconds: 200, + ), + CustomRoute( + page: UsersPage, + usesPathAsKey: true, + transitionsBuilder: fadeThroughTransition, + durationInMilliseconds: 200, + ), + CustomRoute( + page: MorePage, + usesPathAsKey: true, + transitionsBuilder: fadeThroughTransition, + durationInMilliseconds: 200, + ), + AutoRoute(page: AppSettingsPage), + AutoRoute(page: UserDetailsPage), + AutoRoute(page: RecoveryKeyPage), + AutoRoute(page: DevicesScreen), + AutoRoute(page: AboutApplicationPage), + AutoRoute(page: DeveloperSettingsPage), + ], + ), + AutoRoute(page: ServicesMigrationPage), + AutoRoute(page: ConsolePage), + ], +) +class RootRouter extends _$RootRouter { + RootRouter(); +} + +// Function to map route names to route titles +String getRouteTitle(final String routeName) { + switch (routeName) { + case 'RootRoute': + return 'basis.app_name'; + case 'ProvidersRoute': + return 'basis.providers_title'; + case 'ServicesRoute': + return 'basis.services'; + case 'UsersRoute': + return 'basis.users'; + case 'MoreRoute': + return 'basis.more'; + case 'AppSettingsRoute': + return 'application_settings.title'; + case 'UserDetailsRoute': + return '[User Details]'; + case 'RecoveryKeyRoute': + return 'recovery_key.key_main_header'; + case 'DevicesRoute': + return 'devices.main_screen.header'; + case 'AboutApplicationRoute': + return 'about_us_page.title'; + case 'ConsoleRoute': + return '[Console]'; + case 'DeveloperSettingsRoute': + return 'developer_settings.title'; + default: + return routeName; + } +} diff --git a/lib/ui/router/router.gr.dart b/lib/ui/router/router.gr.dart new file mode 100644 index 00000000..eae72e33 --- /dev/null +++ b/lib/ui/router/router.gr.dart @@ -0,0 +1,474 @@ +// ************************************************************************** +// AutoRouteGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// AutoRouteGenerator +// ************************************************************************** +// +// ignore_for_file: type=lint + +part of 'router.dart'; + +class _$RootRouter extends RootStackRouter { + _$RootRouter([GlobalKey? navigatorKey]) : super(navigatorKey); + + @override + final Map pagesMap = { + OnboardingRoute.name: (routeData) { + return MaterialPageX( + routeData: routeData, + child: const OnboardingPage(), + ); + }, + InitializingRoute.name: (routeData) { + return MaterialPageX( + routeData: routeData, + child: const InitializingPage(), + ); + }, + RecoveryRoute.name: (routeData) { + return MaterialPageX( + routeData: routeData, + child: const RecoveryRouting(), + ); + }, + RootRoute.name: (routeData) { + return MaterialPageX( + routeData: routeData, + child: WrappedRoute(child: const RootPage()), + ); + }, + ServicesMigrationRoute.name: (routeData) { + final args = routeData.argsAs(); + return MaterialPageX( + routeData: routeData, + child: ServicesMigrationPage( + services: args.services, + diskStatus: args.diskStatus, + isMigration: args.isMigration, + key: args.key, + ), + ); + }, + ConsoleRoute.name: (routeData) { + return MaterialPageX( + routeData: routeData, + child: const ConsolePage(), + ); + }, + ProvidersRoute.name: (routeData) { + return CustomPage( + routeData: routeData, + child: const ProvidersPage(), + transitionsBuilder: fadeThroughTransition, + durationInMilliseconds: 200, + opaque: true, + barrierDismissible: false, + ); + }, + ServicesRoute.name: (routeData) { + return CustomPage( + routeData: routeData, + child: const ServicesPage(), + transitionsBuilder: fadeThroughTransition, + durationInMilliseconds: 200, + opaque: true, + barrierDismissible: false, + ); + }, + UsersRoute.name: (routeData) { + return CustomPage( + routeData: routeData, + child: const UsersPage(), + transitionsBuilder: fadeThroughTransition, + durationInMilliseconds: 200, + opaque: true, + barrierDismissible: false, + ); + }, + MoreRoute.name: (routeData) { + return CustomPage( + routeData: routeData, + child: const MorePage(), + transitionsBuilder: fadeThroughTransition, + durationInMilliseconds: 200, + opaque: true, + barrierDismissible: false, + ); + }, + AppSettingsRoute.name: (routeData) { + return MaterialPageX( + routeData: routeData, + child: const AppSettingsPage(), + ); + }, + UserDetailsRoute.name: (routeData) { + final args = routeData.argsAs(); + return MaterialPageX( + routeData: routeData, + child: UserDetailsPage( + login: args.login, + key: args.key, + ), + ); + }, + RecoveryKeyRoute.name: (routeData) { + return MaterialPageX( + routeData: routeData, + child: const RecoveryKeyPage(), + ); + }, + DevicesRoute.name: (routeData) { + return MaterialPageX( + routeData: routeData, + child: const DevicesScreen(), + ); + }, + AboutApplicationRoute.name: (routeData) { + return MaterialPageX( + routeData: routeData, + child: const AboutApplicationPage(), + ); + }, + DeveloperSettingsRoute.name: (routeData) { + return MaterialPageX( + routeData: routeData, + child: const DeveloperSettingsPage(), + ); + }, + }; + + @override + List get routes => [ + RouteConfig( + OnboardingRoute.name, + path: '/onboarding-page', + ), + RouteConfig( + InitializingRoute.name, + path: '/initializing-page', + ), + RouteConfig( + RecoveryRoute.name, + path: '/recovery-routing', + ), + RouteConfig( + RootRoute.name, + path: '/', + children: [ + RouteConfig( + ProvidersRoute.name, + path: '', + parent: RootRoute.name, + usesPathAsKey: true, + ), + RouteConfig( + ServicesRoute.name, + path: 'services-page', + parent: RootRoute.name, + usesPathAsKey: true, + ), + RouteConfig( + UsersRoute.name, + path: 'users-page', + parent: RootRoute.name, + usesPathAsKey: true, + ), + RouteConfig( + MoreRoute.name, + path: 'more-page', + parent: RootRoute.name, + usesPathAsKey: true, + ), + RouteConfig( + AppSettingsRoute.name, + path: 'app-settings-page', + parent: RootRoute.name, + ), + RouteConfig( + UserDetailsRoute.name, + path: 'user-details-page', + parent: RootRoute.name, + ), + RouteConfig( + RecoveryKeyRoute.name, + path: 'recovery-key-page', + parent: RootRoute.name, + ), + RouteConfig( + DevicesRoute.name, + path: 'devices-screen', + parent: RootRoute.name, + ), + RouteConfig( + AboutApplicationRoute.name, + path: 'about-application-page', + parent: RootRoute.name, + ), + RouteConfig( + DeveloperSettingsRoute.name, + path: 'developer-settings-page', + parent: RootRoute.name, + ), + ], + ), + RouteConfig( + ServicesMigrationRoute.name, + path: '/services-migration-page', + ), + RouteConfig( + ConsoleRoute.name, + path: '/console-page', + ), + ]; +} + +/// generated route for +/// [OnboardingPage] +class OnboardingRoute extends PageRouteInfo { + const OnboardingRoute() + : super( + OnboardingRoute.name, + path: '/onboarding-page', + ); + + static const String name = 'OnboardingRoute'; +} + +/// generated route for +/// [InitializingPage] +class InitializingRoute extends PageRouteInfo { + const InitializingRoute() + : super( + InitializingRoute.name, + path: '/initializing-page', + ); + + static const String name = 'InitializingRoute'; +} + +/// generated route for +/// [RecoveryRouting] +class RecoveryRoute extends PageRouteInfo { + const RecoveryRoute() + : super( + RecoveryRoute.name, + path: '/recovery-routing', + ); + + static const String name = 'RecoveryRoute'; +} + +/// generated route for +/// [RootPage] +class RootRoute extends PageRouteInfo { + const RootRoute({List? children}) + : super( + RootRoute.name, + path: '/', + initialChildren: children, + ); + + static const String name = 'RootRoute'; +} + +/// generated route for +/// [ServicesMigrationPage] +class ServicesMigrationRoute extends PageRouteInfo { + ServicesMigrationRoute({ + required List services, + required DiskStatus diskStatus, + required bool isMigration, + Key? key, + }) : super( + ServicesMigrationRoute.name, + path: '/services-migration-page', + args: ServicesMigrationRouteArgs( + services: services, + diskStatus: diskStatus, + isMigration: isMigration, + key: key, + ), + ); + + static const String name = 'ServicesMigrationRoute'; +} + +class ServicesMigrationRouteArgs { + const ServicesMigrationRouteArgs({ + required this.services, + required this.diskStatus, + required this.isMigration, + this.key, + }); + + final List services; + + final DiskStatus diskStatus; + + final bool isMigration; + + final Key? key; + + @override + String toString() { + return 'ServicesMigrationRouteArgs{services: $services, diskStatus: $diskStatus, isMigration: $isMigration, key: $key}'; + } +} + +/// generated route for +/// [ConsolePage] +class ConsoleRoute extends PageRouteInfo { + const ConsoleRoute() + : super( + ConsoleRoute.name, + path: '/console-page', + ); + + static const String name = 'ConsoleRoute'; +} + +/// generated route for +/// [ProvidersPage] +class ProvidersRoute extends PageRouteInfo { + const ProvidersRoute() + : super( + ProvidersRoute.name, + path: '', + ); + + static const String name = 'ProvidersRoute'; +} + +/// generated route for +/// [ServicesPage] +class ServicesRoute extends PageRouteInfo { + const ServicesRoute() + : super( + ServicesRoute.name, + path: 'services-page', + ); + + static const String name = 'ServicesRoute'; +} + +/// generated route for +/// [UsersPage] +class UsersRoute extends PageRouteInfo { + const UsersRoute() + : super( + UsersRoute.name, + path: 'users-page', + ); + + static const String name = 'UsersRoute'; +} + +/// generated route for +/// [MorePage] +class MoreRoute extends PageRouteInfo { + const MoreRoute() + : super( + MoreRoute.name, + path: 'more-page', + ); + + static const String name = 'MoreRoute'; +} + +/// generated route for +/// [AppSettingsPage] +class AppSettingsRoute extends PageRouteInfo { + const AppSettingsRoute() + : super( + AppSettingsRoute.name, + path: 'app-settings-page', + ); + + static const String name = 'AppSettingsRoute'; +} + +/// generated route for +/// [UserDetailsPage] +class UserDetailsRoute extends PageRouteInfo { + UserDetailsRoute({ + required String login, + Key? key, + }) : super( + UserDetailsRoute.name, + path: 'user-details-page', + args: UserDetailsRouteArgs( + login: login, + key: key, + ), + ); + + static const String name = 'UserDetailsRoute'; +} + +class UserDetailsRouteArgs { + const UserDetailsRouteArgs({ + required this.login, + this.key, + }); + + final String login; + + final Key? key; + + @override + String toString() { + return 'UserDetailsRouteArgs{login: $login, key: $key}'; + } +} + +/// generated route for +/// [RecoveryKeyPage] +class RecoveryKeyRoute extends PageRouteInfo { + const RecoveryKeyRoute() + : super( + RecoveryKeyRoute.name, + path: 'recovery-key-page', + ); + + static const String name = 'RecoveryKeyRoute'; +} + +/// generated route for +/// [DevicesScreen] +class DevicesRoute extends PageRouteInfo { + const DevicesRoute() + : super( + DevicesRoute.name, + path: 'devices-screen', + ); + + static const String name = 'DevicesRoute'; +} + +/// generated route for +/// [AboutApplicationPage] +class AboutApplicationRoute extends PageRouteInfo { + const AboutApplicationRoute() + : super( + AboutApplicationRoute.name, + path: 'about-application-page', + ); + + static const String name = 'AboutApplicationRoute'; +} + +/// generated route for +/// [DeveloperSettingsPage] +class DeveloperSettingsRoute extends PageRouteInfo { + const DeveloperSettingsRoute() + : super( + DeveloperSettingsRoute.name, + path: 'developer-settings-page', + ); + + static const String name = 'DeveloperSettingsRoute'; +}