mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-09 01:21:14 +00:00
Merge pull request 'feat: MD3 app bars' (#126) from fix/better-app-bars into master
Reviewed-on: https://git.selfprivacy.org/kherel/selfprivacy.org.app/pulls/126
This commit is contained in:
commit
408b359a2a
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue