mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-09 09:31:13 +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,
|
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;
|
||||||
|
|
|
@ -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(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue