Polish home page and carousel view

Tweak implementation of AppBtn semantics
This commit is contained in:
Shawn 2022-09-19 14:21:30 -06:00
parent 44bd0d15fa
commit 662925cbfa
11 changed files with 149 additions and 143 deletions

View File

@ -50,6 +50,7 @@ class _AppPageIndicatorState extends State<AppPageIndicator> {
return Semantics( return Semantics(
container: true, container: true,
liveRegion: true, liveRegion: true,
readOnly: true,
label: StringUtils.supplant($strings.appPageSemanticSwipe, { label: StringUtils.supplant($strings.appPageSemanticSwipe, {
'{pageTitle}': widget.semanticPageTitle, '{pageTitle}': widget.semanticPageTitle,
'{count}': (value % (widget.count) + 1).toString(), '{count}': (value % (widget.count) + 1).toString(),

View File

@ -138,19 +138,14 @@ class AppBtn extends StatelessWidget {
// add press effect: // add press effect:
if (pressEffect) button = _ButtonPressEffect(button); if (pressEffect) button = _ButtonPressEffect(button);
// add semantics: // add semantics?
if (semanticLabel.isEmpty) { if (semanticLabel.isEmpty) return button;
button = ExcludeSemantics(child: button); return Semantics(
} else { label: semanticLabel,
button = Semantics( button: true,
label: semanticLabel, container: true,
button: true, child: ExcludeSemantics(child: button),
container: true, );
child: button,
);
}
return button;
} }
} }

View File

@ -28,11 +28,13 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
late PageController _controller; late PageController _controller;
double get _currentOffset { double get _currentOffset {
bool inited = _controller.hasClients && _controller.position.haveDimensions; bool hasOffset = _controller.hasClients && _controller.position.haveDimensions;
return inited ? _controller.page! : _controller.initialPage * 1.0; return hasOffset ? _controller.page! : _controller.initialPage * 1.0;
} }
int get _currentIndex => _currentOffset.round() % _artifacts.length; final _currentIndex = ValueNotifier(0);
HighlightData get _currentArtifact => _artifacts[_currentIndex.value];
@override @override
void initState() { void initState() {
@ -43,9 +45,11 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
// start at a high offset so we can scroll backwards: // start at a high offset so we can scroll backwards:
initialPage: _artifacts.length * 9999, initialPage: _artifacts.length * 9999,
viewportFraction: 0.5, viewportFraction: 0.5,
); )..addListener(_handleCarouselScroll);
} }
void _handleCarouselScroll() => _currentIndex.value = _currentOffset.round() % _artifacts.length;
@override @override
void dispose() { void dispose() {
_controller.dispose(); _controller.dispose();
@ -74,20 +78,24 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
color: $styles.colors.greyStrong, color: $styles.colors.greyStrong,
child: AnimatedBuilder(animation: _controller, builder: _buildScreen), child: AnimatedBuilder(
animation: _currentIndex,
builder: (context, __) => _buildScreen(context),
),
); );
} }
Widget _buildScreen(BuildContext context, _) { Widget _buildScreen(BuildContext context) {
final double w = context.widthPx; final double w = context.widthPx;
final double backdropWidth = w <= _maxElementWidth ? w : min(w * _partialElementWidth, _maxElementWidth); final double backdropWidth = w <= _maxElementWidth ? w : min(w * _partialElementWidth, _maxElementWidth);
final double backdropHeight = math.min(context.heightPx * 0.65, _maxElementHeight); final double backdropHeight = math.min(context.heightPx * 0.65, _maxElementHeight);
final bool small = backdropHeight / _maxElementHeight < 0.7; final bool small = backdropHeight / _maxElementHeight < 0.7;
final HighlightData artifact = _artifacts[_currentIndex];
return Stack( return Stack(
children: [ children: [
Positioned.fill(child: _BlurredImageBg(url: artifact.imageUrl)), Positioned.fill(
child: _BlurredImageBg(url: _currentArtifact.imageUrl),
),
Column( Column(
children: [ children: [
SimpleHeader($strings.artifactsTitleArtifacts, showBackBtn: false, isTransparent: true), SimpleHeader($strings.artifactsTitleArtifacts, showBackBtn: false, isTransparent: true),
@ -113,27 +121,24 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
child: PageView.builder( child: PageView.builder(
controller: _controller, controller: _controller,
clipBehavior: Clip.none, clipBehavior: Clip.none,
itemBuilder: (context, index) { itemBuilder: (context, index) => AnimatedBuilder(
bool isCurrentIndex = index % _artifacts.length == _currentIndex; animation: _controller,
return ExcludeSemantics( builder: (_, __) {
excluding: isCurrentIndex == false, bool isCurrentIndex = index % _artifacts.length == _currentIndex.value;
child: MergeSemantics( return ExcludeSemantics(
child: Semantics( excluding: !isCurrentIndex,
onIncrease: () => _handleArtifactTap(_currentIndex + 1), child: _CarouselItem(
onDecrease: () => _handleArtifactTap(_currentIndex - 1), index: index,
child: _CarouselItem( currentPage: _currentOffset,
index: index, artifact: _artifacts[index % _artifacts.length],
currentPage: _currentOffset, bottomPadding: backdropHeight,
artifact: _artifacts[index % _artifacts.length], maxWidth: backdropWidth,
bottomPadding: backdropHeight, maxHeight: backdropHeight,
maxWidth: backdropWidth, onPressed: () => _handleArtifactTap(index),
maxHeight: backdropHeight,
onPressed: () => _handleArtifactTap(index),
),
), ),
), );
); },
}, ),
), ),
), ),
), ),
@ -148,7 +153,7 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Gap($styles.insets.md), Gap($styles.insets.md),
_buildContent(context, artifact, backdropWidth, small), _buildContent(context, backdropWidth, small),
Gap(small ? $styles.insets.sm : $styles.insets.md), Gap(small ? $styles.insets.sm : $styles.insets.md),
AppBtn.from( AppBtn.from(
text: $strings.artifactsButtonBrowse, text: $strings.artifactsButtonBrowse,
@ -169,16 +174,20 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
); );
} }
Widget _buildContent(BuildContext context, HighlightData artifact, double width, bool small) { Widget _buildContent(BuildContext context, double width, bool small) {
return Container( return Semantics(
width: width, onIncrease: () => _handleArtifactTap(_currentOffset.round() + 1),
padding: EdgeInsets.symmetric(horizontal: $styles.insets.sm), onDecrease: () => _handleArtifactTap(_currentOffset.round() - 1),
child: Column( liveRegion: true,
mainAxisSize: MainAxisSize.min, child: Container(
mainAxisAlignment: MainAxisAlignment.center, width: width,
children: [ padding: EdgeInsets.symmetric(horizontal: $styles.insets.sm),
IgnorePointer( child: Column(
child: ExcludeSemantics( mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
IgnorePointer(
ignoringSemantics: false,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -187,7 +196,7 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
alignment: Alignment.center, alignment: Alignment.center,
child: StaticTextScale( child: StaticTextScale(
child: Text( child: Text(
artifact.title, _currentArtifact.title,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: $styles.text.h2.copyWith(color: $styles.colors.black, height: 1.2), style: $styles.text.h2.copyWith(color: $styles.colors.black, height: 1.2),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -197,21 +206,21 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
), ),
if (!small) Gap($styles.insets.xxs), if (!small) Gap($styles.insets.xxs),
Text( Text(
artifact.date.isEmpty ? '--' : artifact.date, _currentArtifact.date.isEmpty ? '--' : _currentArtifact.date,
style: $styles.text.body, style: $styles.text.body,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],
).animate(key: ValueKey(artifact.artifactId)).fadeIn(), ).animate(key: ValueKey(_currentArtifact.artifactId)).fadeIn(),
), ),
), Gap(small ? $styles.insets.xs : $styles.insets.sm),
Gap(small ? $styles.insets.xs : $styles.insets.sm), AppPageIndicator(
AppPageIndicator( count: _artifacts.length,
count: _artifacts.length, controller: _controller,
controller: _controller, semanticPageTitle: $strings.artifactsSemanticArtifact,
semanticPageTitle: $strings.artifactsSemanticArtifact, ),
), ],
], ),
), ),
); );
} }

View File

@ -22,7 +22,7 @@ class _CarouselItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppBtn.basic( return AppBtn.basic(
semanticLabel: '${artifact.title} ${artifact.date}', semanticLabel: 'Explore artifact details',
onPressed: onPressed, onPressed: onPressed,
pressEffect: false, pressEffect: false,
child: _ImagePreview( child: _ImagePreview(

View File

@ -116,7 +116,7 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
CustomScrollView( CustomScrollView(
primary: false, primary: false,
controller: _scroller, controller: _scroller,
cacheExtent: 500, cacheExtent: 1000,
slivers: [ slivers: [
/// Invisible padding at the top of the list, so the illustration shows through the btm /// Invisible padding at the top of the list, so the illustration shows through the btm
SliverToBoxAdapter( SliverToBoxAdapter(

View File

@ -28,35 +28,37 @@ class _ScrollingContent extends StatelessWidget {
final String dropChar = value.substring(0, 1); final String dropChar = value.substring(0, 1);
final textScale = MediaQuery.of(context).textScaleFactor; final textScale = MediaQuery.of(context).textScaleFactor;
final double dropCapWidth = StringUtils.measure(dropChar, dropStyle).width * textScale; final double dropCapWidth = StringUtils.measure(dropChar, dropStyle).width * textScale;
final bool skipCaps = !localeLogic.isEnglish || MediaQuery.of(context).accessibleNavigation; final bool skipCaps = !localeLogic.isEnglish;
return Semantics( return Semantics(
label: value, label: value,
child: !skipCaps child: ExcludeSemantics(
? DropCapText( child: !skipCaps
_fixNewlines(value).substring(1), ? DropCapText(
dropCap: DropCap( _fixNewlines(value).substring(1),
width: dropCapWidth, dropCap: DropCap(
height: $styles.text.body.fontSize! * $styles.text.body.height! * 2, width: dropCapWidth,
child: Transform.translate( height: $styles.text.body.fontSize! * $styles.text.body.height! * 2,
offset: Offset(0, bodyStyle.fontSize! * (bodyStyle.height! - 1) - 2), child: Transform.translate(
child: Text( offset: Offset(0, bodyStyle.fontSize! * (bodyStyle.height! - 1) - 2),
dropChar, child: Text(
overflow: TextOverflow.visible, dropChar,
style: $styles.text.dropCase.copyWith( overflow: TextOverflow.visible,
color: $styles.colors.accent1, style: $styles.text.dropCase.copyWith(
height: 1, color: $styles.colors.accent1,
height: 1,
),
), ),
), ),
), ),
), style: $styles.text.body,
style: $styles.text.body, dropCapPadding: EdgeInsets.only(right: 6),
dropCapPadding: EdgeInsets.only(right: 6), dropCapStyle: $styles.text.dropCase.copyWith(
dropCapStyle: $styles.text.dropCase.copyWith( color: $styles.colors.accent1,
color: $styles.colors.accent1, height: 1,
height: 1, ),
), )
) : Text(value, style: bodyStyle),
: Text(value, style: bodyStyle), ),
); );
} }

View File

@ -28,7 +28,7 @@ class _AnimatedArrowButton extends StatelessWidget {
onPressed: onTap, onPressed: onTap,
child: SizedBox( child: SizedBox(
height: 80, height: 80,
width: 50, width: 250,
child: Animate( child: Animate(
effects: [ effects: [
CustomEffect(builder: _buildOpacityTween, duration: duration, curve: Curves.easeOut), CustomEffect(builder: _buildOpacityTween, duration: duration, curve: Curves.easeOut),

View File

@ -127,11 +127,10 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
/// Wonders Illustrations /// Wonders Illustrations
MergeSemantics( MergeSemantics(
child: Semantics( child: Semantics(
header: true,
image: true,
liveRegion: true, liveRegion: true,
onIncrease: () => _setPageIndex(_wonderIndex + 1), onIncrease: () => _setPageIndex(_wonderIndex + 1),
onDecrease: () => _setPageIndex(_wonderIndex - 1), onDecrease: () => _setPageIndex(_wonderIndex - 1),
onTap: () => _showDetailsPage(),
child: PageView.builder( child: PageView.builder(
controller: _pageController, controller: _pageController,
onPageChanged: _handlePageViewChanged, onPageChanged: _handlePageViewChanged,
@ -170,29 +169,29 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
LightText( LightText(
child: IgnorePointer( child: IgnorePointer(
ignoringSemantics: false, ignoringSemantics: false,
child: MergeSemantics( child: Transform.translate(
child: Transform.translate( offset: Offset(0, 30),
offset: Offset(0, 30), child: Column(
child: Column( children: [
children: [ ExcludeSemantics(
// Hide the title when the menu is open for visual polish // Hide the title when the menu is open for visual polish
AnimatedOpacity( child: AnimatedOpacity(
opacity: _isMenuOpen ? 0 : 1, opacity: _isMenuOpen ? 0 : 1,
duration: $styles.times.fast, duration: $styles.times.fast,
child: WonderTitleText(currentWonder, enableShadows: true), child: WonderTitleText(currentWonder, enableShadows: true),
), ),
Gap($styles.insets.md), ),
AppPageIndicator( Gap($styles.insets.md),
count: _numWonders, AppPageIndicator(
controller: _pageController, count: _numWonders,
color: $styles.colors.white, controller: _pageController,
dotSize: 8, color: $styles.colors.white,
onDotPressed: _handlePageIndicatorDotPressed, dotSize: 8,
semanticPageTitle: $strings.homeSemanticWonder, onDotPressed: _handlePageIndicatorDotPressed,
), semanticPageTitle: $strings.homeSemanticWonder,
Gap($styles.insets.md), ),
], Gap($styles.insets.md),
), ],
), ),
), ),
), ),
@ -200,24 +199,22 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
/// Animated arrow and background /// Animated arrow and background
/// Wrap in a container that is full-width to make it easier to find for screen readers /// Wrap in a container that is full-width to make it easier to find for screen readers
MergeSemantics( Container(
child: Container( width: double.infinity,
width: double.infinity, alignment: Alignment.center,
alignment: Alignment.center,
/// Lose state of child objects when index changes, this will re-run all the animated switcher and the arrow anim /// Lose state of child objects when index changes, this will re-run all the animated switcher and the arrow anim
key: ValueKey(_wonderIndex), key: ValueKey(_wonderIndex),
child: Stack( child: Stack(
children: [ children: [
/// Expanding rounded rect that grows in height as user swipes up /// Expanding rounded rect that grows in height as user swipes up
Positioned.fill( Positioned.fill(
child: _buildSwipeableArrowBg(), child: _buildSwipeableArrowBg(),
), ),
/// Arrow Btn that fades in and out /// Arrow Btn that fades in and out
_AnimatedArrowButton(onTap: _showDetailsPage, semanticTitle: currentWonder.title), _AnimatedArrowButton(onTap: _showDetailsPage, semanticTitle: currentWonder.title),
], ],
),
), ),
), ),
Gap($styles.insets.md), Gap($styles.insets.md),
@ -233,14 +230,11 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
duration: $styles.times.fast, duration: $styles.times.fast,
opacity: _isMenuOpen ? 0 : 1, opacity: _isMenuOpen ? 0 : 1,
child: MergeSemantics( child: MergeSemantics(
child: Semantics( child: CircleIconBtn(
sortKey: OrdinalSortKey(0), icon: AppIcons.menu,
child: CircleIconBtn( onPressed: _handleOpenMenuPressed,
icon: AppIcons.menu, semanticLabel: $strings.homeSemanticOpenMain,
onPressed: _handleOpenMenuPressed, ).safe(),
semanticLabel: $strings.homeSemanticOpenMain,
).safe(),
),
), ),
), ),
), ),

View File

@ -35,7 +35,7 @@ packages:
name: archive name: archive
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.3.1" version: "3.3.0"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -238,7 +238,7 @@ packages:
name: file name: file
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.4" version: "6.1.2"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@ -588,7 +588,7 @@ packages:
name: material_color_utilities name: material_color_utilities
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0" version: "0.1.5"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -978,7 +978,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.1" version: "1.9.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -1027,7 +1027,7 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.13" version: "0.4.12"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@ -1125,14 +1125,14 @@ packages:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.3" version: "2.1.2"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "9.3.0" version: "9.0.0"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:
@ -1190,5 +1190,5 @@ packages:
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
sdks: sdks:
dart: ">=2.18.0-146.0.dev <3.0.0" dart: ">=2.17.1 <3.0.0"
flutter: ">=3.0.0" flutter: ">=3.0.0"

View File

@ -1,7 +1,7 @@
name: wonders name: wonders
description: Explore the famous wonders of the world. description: Explore the famous wonders of the world.
publish_to: "none" publish_to: "none"
version: 1.9.4 version: 1.9.5
environment: environment:
sdk: ">=2.16.0 <3.0.0" sdk: ">=2.16.0 <3.0.0"

View File

@ -1,3 +1,8 @@
# 1.9.5
- Improved a11y for Home Screen and Artifact Carousel
- Polished collection icons
- Added hero support to fullscreen Artifact Details
# 1.9.4 # 1.9.4
- Fix tappable arrow on home page - Fix tappable arrow on home page
- Reduce text size for collapsing pull quote - Reduce text size for collapsing pull quote