Polish home page and carousel view
Tweak implementation of AppBtn semantics
This commit is contained in:
parent
44bd0d15fa
commit
662925cbfa
@ -50,6 +50,7 @@ class _AppPageIndicatorState extends State<AppPageIndicator> {
|
||||
return Semantics(
|
||||
container: true,
|
||||
liveRegion: true,
|
||||
readOnly: true,
|
||||
label: StringUtils.supplant($strings.appPageSemanticSwipe, {
|
||||
'{pageTitle}': widget.semanticPageTitle,
|
||||
'{count}': (value % (widget.count) + 1).toString(),
|
||||
|
@ -138,19 +138,14 @@ class AppBtn extends StatelessWidget {
|
||||
// add press effect:
|
||||
if (pressEffect) button = _ButtonPressEffect(button);
|
||||
|
||||
// add semantics:
|
||||
if (semanticLabel.isEmpty) {
|
||||
button = ExcludeSemantics(child: button);
|
||||
} else {
|
||||
button = Semantics(
|
||||
label: semanticLabel,
|
||||
button: true,
|
||||
container: true,
|
||||
child: button,
|
||||
);
|
||||
}
|
||||
|
||||
return button;
|
||||
// add semantics?
|
||||
if (semanticLabel.isEmpty) return button;
|
||||
return Semantics(
|
||||
label: semanticLabel,
|
||||
button: true,
|
||||
container: true,
|
||||
child: ExcludeSemantics(child: button),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,11 +28,13 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
|
||||
late PageController _controller;
|
||||
|
||||
double get _currentOffset {
|
||||
bool inited = _controller.hasClients && _controller.position.haveDimensions;
|
||||
return inited ? _controller.page! : _controller.initialPage * 1.0;
|
||||
bool hasOffset = _controller.hasClients && _controller.position.haveDimensions;
|
||||
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
|
||||
void initState() {
|
||||
@ -43,9 +45,11 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
|
||||
// start at a high offset so we can scroll backwards:
|
||||
initialPage: _artifacts.length * 9999,
|
||||
viewportFraction: 0.5,
|
||||
);
|
||||
)..addListener(_handleCarouselScroll);
|
||||
}
|
||||
|
||||
void _handleCarouselScroll() => _currentIndex.value = _currentOffset.round() % _artifacts.length;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
@ -74,20 +78,24 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
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 backdropWidth = w <= _maxElementWidth ? w : min(w * _partialElementWidth, _maxElementWidth);
|
||||
final double backdropHeight = math.min(context.heightPx * 0.65, _maxElementHeight);
|
||||
final bool small = backdropHeight / _maxElementHeight < 0.7;
|
||||
final HighlightData artifact = _artifacts[_currentIndex];
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(child: _BlurredImageBg(url: artifact.imageUrl)),
|
||||
Positioned.fill(
|
||||
child: _BlurredImageBg(url: _currentArtifact.imageUrl),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
SimpleHeader($strings.artifactsTitleArtifacts, showBackBtn: false, isTransparent: true),
|
||||
@ -113,27 +121,24 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
|
||||
child: PageView.builder(
|
||||
controller: _controller,
|
||||
clipBehavior: Clip.none,
|
||||
itemBuilder: (context, index) {
|
||||
bool isCurrentIndex = index % _artifacts.length == _currentIndex;
|
||||
return ExcludeSemantics(
|
||||
excluding: isCurrentIndex == false,
|
||||
child: MergeSemantics(
|
||||
child: Semantics(
|
||||
onIncrease: () => _handleArtifactTap(_currentIndex + 1),
|
||||
onDecrease: () => _handleArtifactTap(_currentIndex - 1),
|
||||
child: _CarouselItem(
|
||||
index: index,
|
||||
currentPage: _currentOffset,
|
||||
artifact: _artifacts[index % _artifacts.length],
|
||||
bottomPadding: backdropHeight,
|
||||
maxWidth: backdropWidth,
|
||||
maxHeight: backdropHeight,
|
||||
onPressed: () => _handleArtifactTap(index),
|
||||
),
|
||||
itemBuilder: (context, index) => AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (_, __) {
|
||||
bool isCurrentIndex = index % _artifacts.length == _currentIndex.value;
|
||||
return ExcludeSemantics(
|
||||
excluding: !isCurrentIndex,
|
||||
child: _CarouselItem(
|
||||
index: index,
|
||||
currentPage: _currentOffset,
|
||||
artifact: _artifacts[index % _artifacts.length],
|
||||
bottomPadding: backdropHeight,
|
||||
maxWidth: backdropWidth,
|
||||
maxHeight: backdropHeight,
|
||||
onPressed: () => _handleArtifactTap(index),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -148,7 +153,7 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Gap($styles.insets.md),
|
||||
_buildContent(context, artifact, backdropWidth, small),
|
||||
_buildContent(context, backdropWidth, small),
|
||||
Gap(small ? $styles.insets.sm : $styles.insets.md),
|
||||
AppBtn.from(
|
||||
text: $strings.artifactsButtonBrowse,
|
||||
@ -169,16 +174,20 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context, HighlightData artifact, double width, bool small) {
|
||||
return Container(
|
||||
width: width,
|
||||
padding: EdgeInsets.symmetric(horizontal: $styles.insets.sm),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IgnorePointer(
|
||||
child: ExcludeSemantics(
|
||||
Widget _buildContent(BuildContext context, double width, bool small) {
|
||||
return Semantics(
|
||||
onIncrease: () => _handleArtifactTap(_currentOffset.round() + 1),
|
||||
onDecrease: () => _handleArtifactTap(_currentOffset.round() - 1),
|
||||
liveRegion: true,
|
||||
child: Container(
|
||||
width: width,
|
||||
padding: EdgeInsets.symmetric(horizontal: $styles.insets.sm),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IgnorePointer(
|
||||
ignoringSemantics: false,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -187,7 +196,7 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
|
||||
alignment: Alignment.center,
|
||||
child: StaticTextScale(
|
||||
child: Text(
|
||||
artifact.title,
|
||||
_currentArtifact.title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: $styles.text.h2.copyWith(color: $styles.colors.black, height: 1.2),
|
||||
textAlign: TextAlign.center,
|
||||
@ -197,21 +206,21 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
|
||||
),
|
||||
if (!small) Gap($styles.insets.xxs),
|
||||
Text(
|
||||
artifact.date.isEmpty ? '--' : artifact.date,
|
||||
_currentArtifact.date.isEmpty ? '--' : _currentArtifact.date,
|
||||
style: $styles.text.body,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
).animate(key: ValueKey(artifact.artifactId)).fadeIn(),
|
||||
).animate(key: ValueKey(_currentArtifact.artifactId)).fadeIn(),
|
||||
),
|
||||
),
|
||||
Gap(small ? $styles.insets.xs : $styles.insets.sm),
|
||||
AppPageIndicator(
|
||||
count: _artifacts.length,
|
||||
controller: _controller,
|
||||
semanticPageTitle: $strings.artifactsSemanticArtifact,
|
||||
),
|
||||
],
|
||||
Gap(small ? $styles.insets.xs : $styles.insets.sm),
|
||||
AppPageIndicator(
|
||||
count: _artifacts.length,
|
||||
controller: _controller,
|
||||
semanticPageTitle: $strings.artifactsSemanticArtifact,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class _CarouselItem extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBtn.basic(
|
||||
semanticLabel: '${artifact.title} ${artifact.date}',
|
||||
semanticLabel: 'Explore artifact details',
|
||||
onPressed: onPressed,
|
||||
pressEffect: false,
|
||||
child: _ImagePreview(
|
||||
|
@ -116,7 +116,7 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
|
||||
CustomScrollView(
|
||||
primary: false,
|
||||
controller: _scroller,
|
||||
cacheExtent: 500,
|
||||
cacheExtent: 1000,
|
||||
slivers: [
|
||||
/// Invisible padding at the top of the list, so the illustration shows through the btm
|
||||
SliverToBoxAdapter(
|
||||
|
@ -28,35 +28,37 @@ class _ScrollingContent extends StatelessWidget {
|
||||
final String dropChar = value.substring(0, 1);
|
||||
final textScale = MediaQuery.of(context).textScaleFactor;
|
||||
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(
|
||||
label: value,
|
||||
child: !skipCaps
|
||||
? DropCapText(
|
||||
_fixNewlines(value).substring(1),
|
||||
dropCap: DropCap(
|
||||
width: dropCapWidth,
|
||||
height: $styles.text.body.fontSize! * $styles.text.body.height! * 2,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, bodyStyle.fontSize! * (bodyStyle.height! - 1) - 2),
|
||||
child: Text(
|
||||
dropChar,
|
||||
overflow: TextOverflow.visible,
|
||||
style: $styles.text.dropCase.copyWith(
|
||||
color: $styles.colors.accent1,
|
||||
height: 1,
|
||||
child: ExcludeSemantics(
|
||||
child: !skipCaps
|
||||
? DropCapText(
|
||||
_fixNewlines(value).substring(1),
|
||||
dropCap: DropCap(
|
||||
width: dropCapWidth,
|
||||
height: $styles.text.body.fontSize! * $styles.text.body.height! * 2,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, bodyStyle.fontSize! * (bodyStyle.height! - 1) - 2),
|
||||
child: Text(
|
||||
dropChar,
|
||||
overflow: TextOverflow.visible,
|
||||
style: $styles.text.dropCase.copyWith(
|
||||
color: $styles.colors.accent1,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
style: $styles.text.body,
|
||||
dropCapPadding: EdgeInsets.only(right: 6),
|
||||
dropCapStyle: $styles.text.dropCase.copyWith(
|
||||
color: $styles.colors.accent1,
|
||||
height: 1,
|
||||
),
|
||||
)
|
||||
: Text(value, style: bodyStyle),
|
||||
style: $styles.text.body,
|
||||
dropCapPadding: EdgeInsets.only(right: 6),
|
||||
dropCapStyle: $styles.text.dropCase.copyWith(
|
||||
color: $styles.colors.accent1,
|
||||
height: 1,
|
||||
),
|
||||
)
|
||||
: Text(value, style: bodyStyle),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ class _AnimatedArrowButton extends StatelessWidget {
|
||||
onPressed: onTap,
|
||||
child: SizedBox(
|
||||
height: 80,
|
||||
width: 50,
|
||||
width: 250,
|
||||
child: Animate(
|
||||
effects: [
|
||||
CustomEffect(builder: _buildOpacityTween, duration: duration, curve: Curves.easeOut),
|
||||
|
@ -127,11 +127,10 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
||||
/// Wonders Illustrations
|
||||
MergeSemantics(
|
||||
child: Semantics(
|
||||
header: true,
|
||||
image: true,
|
||||
liveRegion: true,
|
||||
onIncrease: () => _setPageIndex(_wonderIndex + 1),
|
||||
onDecrease: () => _setPageIndex(_wonderIndex - 1),
|
||||
onTap: () => _showDetailsPage(),
|
||||
child: PageView.builder(
|
||||
controller: _pageController,
|
||||
onPageChanged: _handlePageViewChanged,
|
||||
@ -170,29 +169,29 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
||||
LightText(
|
||||
child: IgnorePointer(
|
||||
ignoringSemantics: false,
|
||||
child: MergeSemantics(
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, 30),
|
||||
child: Column(
|
||||
children: [
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, 30),
|
||||
child: Column(
|
||||
children: [
|
||||
ExcludeSemantics(
|
||||
// Hide the title when the menu is open for visual polish
|
||||
AnimatedOpacity(
|
||||
child: AnimatedOpacity(
|
||||
opacity: _isMenuOpen ? 0 : 1,
|
||||
duration: $styles.times.fast,
|
||||
child: WonderTitleText(currentWonder, enableShadows: true),
|
||||
),
|
||||
Gap($styles.insets.md),
|
||||
AppPageIndicator(
|
||||
count: _numWonders,
|
||||
controller: _pageController,
|
||||
color: $styles.colors.white,
|
||||
dotSize: 8,
|
||||
onDotPressed: _handlePageIndicatorDotPressed,
|
||||
semanticPageTitle: $strings.homeSemanticWonder,
|
||||
),
|
||||
Gap($styles.insets.md),
|
||||
],
|
||||
),
|
||||
),
|
||||
Gap($styles.insets.md),
|
||||
AppPageIndicator(
|
||||
count: _numWonders,
|
||||
controller: _pageController,
|
||||
color: $styles.colors.white,
|
||||
dotSize: 8,
|
||||
onDotPressed: _handlePageIndicatorDotPressed,
|
||||
semanticPageTitle: $strings.homeSemanticWonder,
|
||||
),
|
||||
Gap($styles.insets.md),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -200,24 +199,22 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
||||
|
||||
/// Animated arrow and background
|
||||
/// Wrap in a container that is full-width to make it easier to find for screen readers
|
||||
MergeSemantics(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
alignment: Alignment.center,
|
||||
Container(
|
||||
width: double.infinity,
|
||||
alignment: Alignment.center,
|
||||
|
||||
/// Lose state of child objects when index changes, this will re-run all the animated switcher and the arrow anim
|
||||
key: ValueKey(_wonderIndex),
|
||||
child: Stack(
|
||||
children: [
|
||||
/// Expanding rounded rect that grows in height as user swipes up
|
||||
Positioned.fill(
|
||||
child: _buildSwipeableArrowBg(),
|
||||
),
|
||||
/// Lose state of child objects when index changes, this will re-run all the animated switcher and the arrow anim
|
||||
key: ValueKey(_wonderIndex),
|
||||
child: Stack(
|
||||
children: [
|
||||
/// Expanding rounded rect that grows in height as user swipes up
|
||||
Positioned.fill(
|
||||
child: _buildSwipeableArrowBg(),
|
||||
),
|
||||
|
||||
/// Arrow Btn that fades in and out
|
||||
_AnimatedArrowButton(onTap: _showDetailsPage, semanticTitle: currentWonder.title),
|
||||
],
|
||||
),
|
||||
/// Arrow Btn that fades in and out
|
||||
_AnimatedArrowButton(onTap: _showDetailsPage, semanticTitle: currentWonder.title),
|
||||
],
|
||||
),
|
||||
),
|
||||
Gap($styles.insets.md),
|
||||
@ -233,14 +230,11 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
||||
duration: $styles.times.fast,
|
||||
opacity: _isMenuOpen ? 0 : 1,
|
||||
child: MergeSemantics(
|
||||
child: Semantics(
|
||||
sortKey: OrdinalSortKey(0),
|
||||
child: CircleIconBtn(
|
||||
icon: AppIcons.menu,
|
||||
onPressed: _handleOpenMenuPressed,
|
||||
semanticLabel: $strings.homeSemanticOpenMain,
|
||||
).safe(),
|
||||
),
|
||||
child: CircleIconBtn(
|
||||
icon: AppIcons.menu,
|
||||
onPressed: _handleOpenMenuPressed,
|
||||
semanticLabel: $strings.homeSemanticOpenMain,
|
||||
).safe(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
16
pubspec.lock
16
pubspec.lock
@ -35,7 +35,7 @@ packages:
|
||||
name: archive
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
version: "3.3.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -238,7 +238,7 @@ packages:
|
||||
name: file
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
version: "6.1.2"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -588,7 +588,7 @@ packages:
|
||||
name: material_color_utilities
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.1.5"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -978,7 +978,7 @@ packages:
|
||||
name: source_span
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.9.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1027,7 +1027,7 @@ packages:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.13"
|
||||
version: "0.4.12"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1125,14 +1125,14 @@ packages:
|
||||
name: vector_math
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.2"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "9.3.0"
|
||||
version: "9.0.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1190,5 +1190,5 @@ packages:
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
sdks:
|
||||
dart: ">=2.18.0-146.0.dev <3.0.0"
|
||||
dart: ">=2.17.1 <3.0.0"
|
||||
flutter: ">=3.0.0"
|
||||
|
@ -1,7 +1,7 @@
|
||||
name: wonders
|
||||
description: Explore the famous wonders of the world.
|
||||
publish_to: "none"
|
||||
version: 1.9.4
|
||||
version: 1.9.5
|
||||
|
||||
environment:
|
||||
sdk: ">=2.16.0 <3.0.0"
|
||||
|
@ -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
|
||||
- Fix tappable arrow on home page
|
||||
- Reduce text size for collapsing pull quote
|
||||
|
Loading…
x
Reference in New Issue
Block a user