From 2fc20f43c3402b5f40d6cb65e87809a0fb8c74df Mon Sep 17 00:00:00 2001 From: inexcode Date: Thu, 6 Oct 2022 10:38:29 +0300 Subject: [PATCH] fix(ui): New app bar now properly supports long titles --- .../brand_hero_screen/brand_hero_screen.dart | 111 ++++++++++++------ lib/ui/helpers/widget_size.dart | 45 +++++++ 2 files changed, 123 insertions(+), 33 deletions(-) create mode 100644 lib/ui/helpers/widget_size.dart diff --git a/lib/ui/components/brand_hero_screen/brand_hero_screen.dart b/lib/ui/components/brand_hero_screen/brand_hero_screen.dart index b36cb2d9..5eb07858 100644 --- a/lib/ui/components/brand_hero_screen/brand_hero_screen.dart +++ b/lib/ui/components/brand_hero_screen/brand_hero_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart'; +import 'package:selfprivacy/ui/helpers/widget_size.dart'; class BrandHeroScreen extends StatelessWidget { const BrandHeroScreen({ @@ -32,44 +33,17 @@ class BrandHeroScreen extends StatelessWidget { 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, - ], - ), - ), + HeroSliverAppBar( + heroTitle: heroTitle, + hasHeroIcon: hasHeroIcon, + hasBackButton: hasBackButton, + onBackButtonPressed: onBackButtonPressed, + heroIconWidget: heroIconWidget, ), if (heroSubtitle != null) SliverPadding( @@ -100,3 +74,74 @@ class BrandHeroScreen extends StatelessWidget { ); } } + +class HeroSliverAppBar extends StatefulWidget { + const HeroSliverAppBar({ + required this.heroTitle, + required this.hasHeroIcon, + required this.hasBackButton, + required this.onBackButtonPressed, + required this.heroIconWidget, + final super.key, + }); + + final String heroTitle; + final bool hasHeroIcon; + final bool hasBackButton; + final VoidCallback? onBackButtonPressed; + final Widget heroIconWidget; + + @override + State createState() => _HeroSliverAppBarState(); +} + +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, + ), + ), + ), + ), + 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/helpers/widget_size.dart b/lib/ui/helpers/widget_size.dart new file mode 100644 index 00000000..11fa7b4f --- /dev/null +++ b/lib/ui/helpers/widget_size.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; + +class WidgetSize extends StatefulWidget { + const WidgetSize({ + required this.onChange, + required this.child, + final super.key, + }); + final Widget child; + final Function onChange; + + @override + State createState() => _WidgetSizeState(); +} + +class _WidgetSizeState extends State { + @override + Widget build(final BuildContext context) { + SchedulerBinding.instance.addPostFrameCallback(postFrameCallback); + return Container( + key: widgetKey, + child: widget.child, + ); + } + + var widgetKey = GlobalKey(); + Size? oldSize; + + void postFrameCallback(_) { + final context = widgetKey.currentContext; + if (context == null) { + return; + } + ; + + final newSize = context.size; + if (oldSize == newSize) { + return; + } + + oldSize = newSize; + widget.onChange(newSize); + } +}