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,
useMaterial3: true,
scaffoldBackgroundColor: colorScheme.background,
appBarTheme: AppBarTheme(
elevation: 0,
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
),
);
return materialThemeData;

View File

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

View File

@ -1,74 +1,102 @@
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';
class BrandHeroScreen extends StatelessWidget {
const BrandHeroScreen({
required this.children,
final super.key,
this.headerTitle = '',
this.hasBackButton = true,
this.hasFlashButton = true,
this.heroIcon,
this.heroTitle,
this.heroIconWidget,
this.heroTitle = '',
this.heroSubtitle,
this.onBackButtonPressed,
});
final List<Widget> children;
final String headerTitle;
final bool hasBackButton;
final bool hasFlashButton;
final IconData? heroIcon;
final String? heroTitle;
final Widget? heroIconWidget;
final String heroTitle;
final String? heroSubtitle;
final VoidCallback? onBackButtonPressed;
@override
Widget build(final BuildContext context) => SafeArea(
child: Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(52.0),
child: BrandHeader(
title: headerTitle,
hasBackButton: hasBackButton,
onBackButtonPressed: onBackButtonPressed,
Widget build(final BuildContext context) {
final Widget heroIconWidget = this.heroIconWidget ??
Icon(
heroIcon ?? Icons.help,
size: 48.0,
color: Theme.of(context).colorScheme.onBackground,
);
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,
body: ListView(
padding: const EdgeInsets.all(16.0),
children: <Widget>[
if (heroIcon != null)
Container(
alignment: Alignment.bottomLeft,
child: Icon(
heroIcon,
size: 48.0,
if (heroSubtitle != null)
SliverPadding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 4.0,
),
sliver: SliverList(
delegate: SliverChildListDelegate([
Text(
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(
heroTitle!,
style: Theme.of(context).textTheme.headlineMedium!.copyWith(
color: Theme.of(context).colorScheme.onBackground,
),
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,
],
]),
),
),
SliverPadding(
padding: const EdgeInsets.all(16.0),
sliver: SliverList(
delegate: SliverChildListDelegate(children),
),
),
),
);
],
),
);
}
}

View File

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

View File

@ -15,8 +15,9 @@ class AboutApplicationPage extends StatelessWidget {
appBar: PreferredSize(
preferredSize: const Size.fromHeight(52),
child: BrandHeader(
title: 'about_application_page.title'.tr(),
hasBackButton: true),
title: 'about_application_page.title'.tr(),
hasBackButton: true,
),
),
body: ListView(
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/ui/components/brand_button/segmented_buttons.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_icons/brand_icons.dart';
import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart';

View File

@ -47,82 +47,87 @@ class _SelectTimezoneState extends State<SelectTimezone> {
@override
Widget build(final BuildContext context) => Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(52),
child: BrandHeader(
title: 'server.select_timezone'.tr(),
hasBackButton: true,
appBar: AppBar(
title: Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Text('server.select_timezone'.tr()),
),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),
),
body: ListView(
controller: controller,
children: locations
.asMap()
.map((final key, final value) {
final duration =
Duration(milliseconds: value.currentTimeZone.offset);
final area = value.currentTimeZone.abbreviation
.replaceAll(RegExp(r'[\d+()-]'), '');
body: SafeArea(
child: ListView(
controller: controller,
children: locations
.asMap()
.map((final key, final value) {
final duration =
Duration(milliseconds: value.currentTimeZone.offset);
final area = value.currentTimeZone.abbreviation
.replaceAll(RegExp(r'[\d+()-]'), '');
String timezoneName = value.name;
if (context.locale.toString() == 'ru') {
timezoneName = russian[value.name] ??
() {
final arr = value.name.split('/')..removeAt(0);
return arr.join('/');
}();
}
String timezoneName = value.name;
if (context.locale.toString() == 'ru') {
timezoneName = russian[value.name] ??
() {
final arr = value.name.split('/')..removeAt(0);
return arr.join('/');
}();
}
return MapEntry(
key,
Container(
height: 75,
padding: const EdgeInsets.symmetric(horizontal: 20),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: BrandColors.dividerColor,
return MapEntry(
key,
Container(
height: 75,
padding: const EdgeInsets.symmetric(horizontal: 20),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
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
.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,
),
),
],
),
),
),
),
);
})
.values
.toList(),
);
})
.values
.toList(),
),
),
);
}

View File

@ -47,25 +47,14 @@ class _ServicePageState extends State<ServicePage> {
return BrandHeroScreen(
hasBackButton: true,
heroIconWidget: SvgPicture.string(
service.svgIcon,
width: 48.0,
height: 48.0,
color: Theme.of(context).colorScheme.onBackground,
),
heroTitle: service.displayName,
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),
const SizedBox(height: 16),
if (service.url != null)

View File

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