feat: MD3 app bars

Fixed #123 spent @2h
pull/126/head
Inex Code 2022-10-04 13:34:56 +03:00 committed by Gitea
parent 571e32ecff
commit 129eb76a04
9 changed files with 170 additions and 163 deletions

View File

@ -66,11 +66,6 @@ abstract class AppThemeFactory {
typography: appTypography, typography: appTypography,
useMaterial3: true, useMaterial3: true,
scaffoldBackgroundColor: colorScheme.background, scaffoldBackgroundColor: colorScheme.background,
appBarTheme: AppBarTheme(
elevation: 0,
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
),
); );
return materialThemeData; return materialThemeData;

View File

@ -1,6 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
class BrandHeader extends StatelessWidget { class BrandHeader extends StatelessWidget {
const BrandHeader({ const BrandHeader({
@ -15,25 +13,17 @@ class BrandHeader extends StatelessWidget {
final VoidCallback? onBackButtonPressed; final VoidCallback? onBackButtonPressed;
@override @override
Widget build(final BuildContext context) => Container( Widget build(final BuildContext context) => AppBar(
height: 52, title: Padding(
alignment: Alignment.centerLeft, padding: const EdgeInsets.only(top: 4.0),
padding: EdgeInsets.only( child: Text(title),
left: hasBackButton ? 1 : 15,
), ),
child: Row( leading: hasBackButton
children: [ ? IconButton(
if (hasBackButton) ...[ icon: const Icon(Icons.arrow_back),
IconButton(
icon: const Icon(BrandIcons.arrowLeft),
onPressed: onPressed:
onBackButtonPressed ?? () => Navigator.of(context).pop(), onBackButtonPressed ?? () => Navigator.of(context).pop(),
), )
const SizedBox(width: 10), : null,
],
BrandText.h4(title),
const Spacer(),
],
),
); );
} }

View File

@ -1,74 +1,102 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart'; import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart';
class BrandHeroScreen extends StatelessWidget { class BrandHeroScreen extends StatelessWidget {
const BrandHeroScreen({ const BrandHeroScreen({
required this.children, required this.children,
final super.key, final super.key,
this.headerTitle = '',
this.hasBackButton = true, this.hasBackButton = true,
this.hasFlashButton = true, this.hasFlashButton = true,
this.heroIcon, this.heroIcon,
this.heroTitle, this.heroIconWidget,
this.heroTitle = '',
this.heroSubtitle, this.heroSubtitle,
this.onBackButtonPressed, this.onBackButtonPressed,
}); });
final List<Widget> children; final List<Widget> children;
final String headerTitle;
final bool hasBackButton; final bool hasBackButton;
final bool hasFlashButton; final bool hasFlashButton;
final IconData? heroIcon; final IconData? heroIcon;
final String? heroTitle; final Widget? heroIconWidget;
final String heroTitle;
final String? heroSubtitle; final String? heroSubtitle;
final VoidCallback? onBackButtonPressed; final VoidCallback? onBackButtonPressed;
@override @override
Widget build(final BuildContext context) => SafeArea( Widget build(final BuildContext context) {
child: Scaffold( final Widget heroIconWidget = this.heroIconWidget ??
appBar: PreferredSize( Icon(
preferredSize: const Size.fromHeight(52.0), heroIcon ?? Icons.help,
child: BrandHeader( size: 48.0,
title: headerTitle, color: Theme.of(context).colorScheme.onBackground,
hasBackButton: hasBackButton, );
onBackButtonPressed: onBackButtonPressed, final bool hasHeroIcon = heroIcon != null || this.heroIconWidget != null;
const EdgeInsetsGeometry heroTitlePadding = EdgeInsets.only(
bottom: 12.0,
top: 16.0,
);
return Scaffold(
floatingActionButton: hasFlashButton ? const BrandFab() : null,
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: hasHeroIcon ? 160.0 : 96.0,
pinned: true,
stretch: true,
leading: hasBackButton
? IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: onBackButtonPressed ??
() => Navigator.of(context).pop(),
)
: null,
flexibleSpace: FlexibleSpaceBar(
title: Text(
heroTitle,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
),
expandedTitleScale: 1.2,
centerTitle: true,
collapseMode: CollapseMode.pin,
titlePadding: heroTitlePadding,
background: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (hasHeroIcon) heroIconWidget,
],
),
), ),
), ),
floatingActionButton: hasFlashButton ? const BrandFab() : null, if (heroSubtitle != null)
body: ListView( SliverPadding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.symmetric(
children: <Widget>[ horizontal: 16.0,
if (heroIcon != null) vertical: 4.0,
Container( ),
alignment: Alignment.bottomLeft, sliver: SliverList(
child: Icon( delegate: SliverChildListDelegate([
heroIcon, Text(
size: 48.0, heroSubtitle!,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onBackground,
),
textAlign: hasHeroIcon ? TextAlign.center : TextAlign.start,
), ),
), ]),
const SizedBox(height: 8.0), ),
if (heroTitle != null) ),
Text( SliverPadding(
heroTitle!, padding: const EdgeInsets.all(16.0),
style: Theme.of(context).textTheme.headlineMedium!.copyWith( sliver: SliverList(
color: Theme.of(context).colorScheme.onBackground, delegate: SliverChildListDelegate(children),
), ),
textAlign: TextAlign.start,
),
const SizedBox(height: 8.0),
if (heroSubtitle != null)
Text(
heroSubtitle!,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onBackground,
),
textAlign: TextAlign.start,
),
const SizedBox(height: 16.0),
...children,
],
), ),
), ],
); ),
);
}
} }

View File

@ -91,7 +91,6 @@ class _DnsDetailsPageState extends State<DnsDetailsPage> {
if (!isReady) { if (!isReady) {
return BrandHeroScreen( return BrandHeroScreen(
hasBackButton: true, hasBackButton: true,
headerTitle: '',
heroIcon: BrandIcons.globe, heroIcon: BrandIcons.globe,
heroTitle: 'domain.screen_title'.tr(), heroTitle: 'domain.screen_title'.tr(),
heroSubtitle: 'not_ready_card.in_menu'.tr(), heroSubtitle: 'not_ready_card.in_menu'.tr(),

View File

@ -15,8 +15,9 @@ class AboutApplicationPage extends StatelessWidget {
appBar: PreferredSize( appBar: PreferredSize(
preferredSize: const Size.fromHeight(52), preferredSize: const Size.fromHeight(52),
child: BrandHeader( child: BrandHeader(
title: 'about_application_page.title'.tr(), title: 'about_application_page.title'.tr(),
hasBackButton: true), hasBackButton: true,
),
), ),
body: ListView( body: ListView(
padding: paddingH15V0, padding: paddingH15V0,

View File

@ -12,7 +12,6 @@ import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart';
import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/logic/models/job.dart';
import 'package:selfprivacy/ui/components/brand_button/segmented_buttons.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_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/components/brand_hero_screen/brand_hero_screen.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.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_loader/brand_loader.dart';

View File

@ -47,82 +47,87 @@ class _SelectTimezoneState extends State<SelectTimezone> {
@override @override
Widget build(final BuildContext context) => Scaffold( Widget build(final BuildContext context) => Scaffold(
appBar: PreferredSize( appBar: AppBar(
preferredSize: const Size.fromHeight(52), title: Padding(
child: BrandHeader( padding: const EdgeInsets.only(top: 4.0),
title: 'server.select_timezone'.tr(), child: Text('server.select_timezone'.tr()),
hasBackButton: true, ),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
), ),
), ),
body: ListView( body: SafeArea(
controller: controller, child: ListView(
children: locations controller: controller,
.asMap() children: locations
.map((final key, final value) { .asMap()
final duration = .map((final key, final value) {
Duration(milliseconds: value.currentTimeZone.offset); final duration =
final area = value.currentTimeZone.abbreviation Duration(milliseconds: value.currentTimeZone.offset);
.replaceAll(RegExp(r'[\d+()-]'), ''); final area = value.currentTimeZone.abbreviation
.replaceAll(RegExp(r'[\d+()-]'), '');
String timezoneName = value.name; String timezoneName = value.name;
if (context.locale.toString() == 'ru') { if (context.locale.toString() == 'ru') {
timezoneName = russian[value.name] ?? timezoneName = russian[value.name] ??
() { () {
final arr = value.name.split('/')..removeAt(0); final arr = value.name.split('/')..removeAt(0);
return arr.join('/'); return arr.join('/');
}(); }();
} }
return MapEntry( return MapEntry(
key, key,
Container( Container(
height: 75, height: 75,
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
decoration: const BoxDecoration( decoration: const BoxDecoration(
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
color: BrandColors.dividerColor, color: BrandColors.dividerColor,
),
),
),
child: InkWell(
onTap: () {
context
.read<ServerDetailsCubit>()
.repository
.setTimezone(
timezoneName,
);
Navigator.of(context).pop();
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
BrandText.body1(
timezoneName,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
BrandText.small(
'GMT ${duration.toDayHourMinuteFormat()} ${area.isNotEmpty ? '($area)' : ''}',
style: const TextStyle(
fontSize: 13,
),
),
],
),
), ),
), ),
), ),
child: InkWell( );
onTap: () { })
context .values
.read<ServerDetailsCubit>() .toList(),
.repository ),
.setTimezone(
timezoneName,
);
Navigator.of(context).pop();
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
BrandText.body1(
timezoneName,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
BrandText.small(
'GMT ${duration.toDayHourMinuteFormat()} ${area.isNotEmpty ? '($area)' : ''}',
style: const TextStyle(
fontSize: 13,
),
),
],
),
),
),
),
);
})
.values
.toList(),
), ),
); );
} }

View File

@ -47,25 +47,14 @@ class _ServicePageState extends State<ServicePage> {
return BrandHeroScreen( return BrandHeroScreen(
hasBackButton: true, hasBackButton: true,
heroIconWidget: SvgPicture.string(
service.svgIcon,
width: 48.0,
height: 48.0,
color: Theme.of(context).colorScheme.onBackground,
),
heroTitle: service.displayName,
children: [ children: [
Container(
alignment: Alignment.center,
child: SvgPicture.string(
service.svgIcon,
width: 48.0,
height: 48.0,
color: Theme.of(context).colorScheme.onBackground,
),
),
const SizedBox(height: 16),
Text(
service.displayName,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium!.copyWith(
color: Theme.of(context).colorScheme.onBackground,
),
),
const SizedBox(height: 16),
ServiceStatusCard(status: service.status), ServiceStatusCard(status: service.status),
const SizedBox(height: 16), const SizedBox(height: 16),
if (service.url != null) if (service.url != null)

View File

@ -415,7 +415,8 @@ class InitializingPage extends StatelessWidget {
BrandText.h2('initializing.create_master_account'.tr()), BrandText.h2('initializing.create_master_account'.tr()),
const SizedBox(height: 10), const SizedBox(height: 10),
BrandText.body2( BrandText.body2(
'initializing.enter_nickname_and_password'.tr()), 'initializing.enter_nickname_and_password'.tr(),
),
const Spacer(), const Spacer(),
CubitFormTextField( CubitFormTextField(
formFieldCubit: context.read<RootUserFormCubit>().userName, formFieldCubit: context.read<RootUserFormCubit>().userName,