Compare commits

...

7 Commits

Author SHA1 Message Date
Shawn
9f3c38d1b5 Add focus element to event cards 2024-02-05 14:30:43 -07:00
Shawn
4bb389cec2 Rename events screen 2024-02-05 14:30:12 -07:00
Shawn
9903a96d96 Clear current focus when changing tabs 2024-02-05 14:29:44 -07:00
Shawn
6093a11c9a Cleanup 2024-02-05 14:29:12 -07:00
Shawn
f951eec66a Add safe area to homeBtn 2024-02-05 14:28:53 -07:00
Shawn
b527acba1c Use ExcludeFocus widget when IndexedStack child is not selected for better tab traversal with keyboard / screen reader. 2024-01-23 10:32:09 -07:00
Shawn
1bfecc0ba7 Rename Events screen 2024-01-23 10:27:22 -07:00
9 changed files with 69 additions and 58 deletions

View File

@ -39,7 +39,9 @@ class LazyIndexedStackState extends State<LazyIndexedStack> {
// Mark current index as active // Mark current index as active
_activated[widget.index] = true; _activated[widget.index] = true;
final children = List.generate(_activated.length, (i) { final children = List.generate(_activated.length, (i) {
return _activated[i] ? widget.children[i] : const SizedBox.shrink(); Widget child = _activated[i] ? widget.children[i] : const SizedBox.shrink();
bool isSelected = widget.index == i;
return ExcludeFocus(excluding: !isSelected, child: child);
}); });
return IndexedStack( return IndexedStack(
alignment: widget.alignment, alignment: widget.alignment,

View File

@ -10,39 +10,42 @@ class TimelineEventCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MergeSemantics( return Focus(
child: Padding( child: MergeSemantics(
padding: EdgeInsets.only(bottom: $styles.insets.sm), child: Padding(
child: DefaultTextColor( padding: EdgeInsets.only(bottom: $styles.insets.sm),
color: darkMode ? Colors.white : Colors.black, child: DefaultTextColor(
child: Container( color: darkMode ? Colors.white : Colors.black,
color: darkMode ? $styles.colors.greyStrong : $styles.colors.offWhite, child: Container(
padding: EdgeInsets.all($styles.insets.sm), color: darkMode ? $styles.colors.greyStrong : $styles.colors.offWhite,
child: IntrinsicHeight( padding: EdgeInsets.all($styles.insets.sm),
child: Row( child: IntrinsicHeight(
children: [ child: Row(
/// Date children: [
SizedBox( /// Date
width: 75, SizedBox(
child: Column( width: 75,
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Text('${year.abs()}', style: $styles.text.h3.copyWith(fontWeight: FontWeight.w400, height: 1)), children: [
Text(StringUtils.getYrSuffix(year), style: $styles.text.bodySmall), Text('${year.abs()}',
], style: $styles.text.h3.copyWith(fontWeight: FontWeight.w400, height: 1)),
Text(StringUtils.getYrSuffix(year), style: $styles.text.bodySmall),
],
),
), ),
),
/// Divider /// Divider
Container(width: 1, color: darkMode ? Colors.white : $styles.colors.black), Container(width: 1, color: darkMode ? Colors.white : $styles.colors.black),
Gap($styles.insets.sm), Gap($styles.insets.sm),
/// Text content /// Text content
Expanded( Expanded(
child: Focus(child: Text(text, style: $styles.text.body)), child: Text(text, style: $styles.text.body),
), ),
], ],
),
), ),
), ),
), ),

View File

@ -170,20 +170,22 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
), ),
/// Home Btn /// Home Btn
AnimatedBuilder( SafeArea(
animation: _scroller, child: AnimatedBuilder(
builder: (_, child) { animation: _scroller,
return AnimatedOpacity( builder: (_, child) {
opacity: _scrollPos.value > 0 ? 0 : 1, return AnimatedOpacity(
duration: $styles.times.med, opacity: _scrollPos.value > 0 ? 0 : 1,
child: child, duration: $styles.times.med,
); child: child,
}, );
child: Align( },
alignment: backBtnAlign, child: Align(
child: Padding( alignment: backBtnAlign,
padding: EdgeInsets.all($styles.insets.sm), child: Padding(
child: BackBtn(icon: AppIcons.north, onPressed: _handleBackPressed), padding: EdgeInsets.all($styles.insets.sm),
child: BackBtn(icon: AppIcons.north, onPressed: _handleBackPressed),
),
), ),
), ),
) )

View File

@ -98,6 +98,7 @@ class _PhotoGalleryState extends State<PhotoGallery> {
} }
} }
// Manually handle keyboard arrow-keys so we can control how the navigation behaves
bool _handleKeyDown(KeyDownEvent event) { bool _handleKeyDown(KeyDownEvent event) {
final key = event.logicalKey; final key = event.logicalKey;
Map<LogicalKeyboardKey, int> keyActions = { Map<LogicalKeyboardKey, int> keyActions = {
@ -112,20 +113,20 @@ class _PhotoGalleryState extends State<PhotoGallery> {
if (actionValue == null) return false; if (actionValue == null) return false;
int newIndex = _index + actionValue; int newIndex = _index + actionValue;
// Block actions along edges of the grid // Allow free movement across the grid of items but block actions that try and go outside the bounds
bool isRightSide = _index % _gridSize == _gridSize - 1; bool isRightSide = _index % _gridSize == _gridSize - 1;
bool isLeftSide = _index % _gridSize == 0; bool isLeftSide = _index % _gridSize == 0;
bool outOfBounds = newIndex < 0 || newIndex >= _imgCount; bool outOfRange = newIndex < 0 || newIndex >= _imgCount;
if ((isRightSide && key == LogicalKeyboardKey.arrowRight) || if ((isRightSide && key == LogicalKeyboardKey.arrowRight) ||
(isLeftSide && key == LogicalKeyboardKey.arrowLeft) || (isLeftSide && key == LogicalKeyboardKey.arrowLeft) ||
outOfBounds) { outOfRange) {
return false; return false;
} }
_setIndex(newIndex); _setIndex(newIndex);
return true; return true;
} }
/// Converts a swipe direction into a new index // Converts a swipe direction into a new index
void _handleSwipe(Offset dir) { void _handleSwipe(Offset dir) {
// Calculate new index, y swipes move by an entire row, x swipes move one index at a time // Calculate new index, y swipes move by an entire row, x swipes move one index at a time
int newIndex = _index; int newIndex = _index;

View File

@ -5,7 +5,7 @@ import 'package:wonders/ui/screens/artifact/artifact_carousel/artifact_carousel_
import 'package:wonders/ui/screens/editorial/editorial_screen.dart'; import 'package:wonders/ui/screens/editorial/editorial_screen.dart';
import 'package:wonders/ui/screens/photo_gallery/photo_gallery.dart'; import 'package:wonders/ui/screens/photo_gallery/photo_gallery.dart';
import 'package:wonders/ui/screens/wonder_details/wonder_details_tab_menu.dart'; import 'package:wonders/ui/screens/wonder_details/wonder_details_tab_menu.dart';
import 'package:wonders/ui/screens/wonder_events/wonder_events.dart'; import 'package:wonders/ui/screens/wonder_events/wonder_events_screen.dart';
class WonderDetailsScreen extends StatefulWidget with GetItStatefulWidgetMixin { class WonderDetailsScreen extends StatefulWidget with GetItStatefulWidgetMixin {
WonderDetailsScreen({Key? key, required this.type, this.tabIndex = 0}) : super(key: key); WonderDetailsScreen({Key? key, required this.type, this.tabIndex = 0}) : super(key: key);
@ -51,6 +51,7 @@ class _WonderDetailsScreenState extends State<WonderDetailsScreen>
void _handleTabTapped(int index) { void _handleTabTapped(int index) {
_tabController.index = index; _tabController.index = index;
WidgetsBinding.instance.focusManager.primaryFocus?.unfocus();
context.go(ScreenPaths.wonderDetails(widget.type, tabIndex: _tabController.index)); context.go(ScreenPaths.wonderDetails(widget.type, tabIndex: _tabController.index));
} }
@ -80,7 +81,7 @@ class _WonderDetailsScreenState extends State<WonderDetailsScreen>
WonderEditorialScreen(wonder, contentPadding: menuPadding), WonderEditorialScreen(wonder, contentPadding: menuPadding),
PhotoGallery(collectionId: wonder.unsplashCollectionId, wonderType: wonder.type), PhotoGallery(collectionId: wonder.unsplashCollectionId, wonderType: wonder.type),
ArtifactCarouselScreen(type: wonder.type, contentPadding: menuPadding), ArtifactCarouselScreen(type: wonder.type, contentPadding: menuPadding),
WonderEvents(type: widget.type, contentPadding: menuPadding), WonderEventsScreen(type: widget.type, contentPadding: menuPadding),
], ],
), ),

View File

@ -1,4 +1,4 @@
part of '../wonder_events.dart'; part of '../wonder_events_screen.dart';
class _EventsList extends StatefulWidget { class _EventsList extends StatefulWidget {
const _EventsList({ const _EventsList({

View File

@ -1,4 +1,4 @@
part of '../wonder_events.dart'; part of '../wonder_events_screen.dart';
class _TimelineBtn extends StatelessWidget { class _TimelineBtn extends StatelessWidget {
const _TimelineBtn({Key? key, required this.type, this.width}) : super(key: key); const _TimelineBtn({Key? key, required this.type, this.width}) : super(key: key);

View File

@ -1,4 +1,4 @@
part of '../wonder_events.dart'; part of '../wonder_events_screen.dart';
class _WonderImageWithTimeline extends StatelessWidget { class _WonderImageWithTimeline extends StatelessWidget {
const _WonderImageWithTimeline({Key? key, required this.data, required this.height}) : super(key: key); const _WonderImageWithTimeline({Key? key, required this.data, required this.height}) : super(key: key);
@ -47,7 +47,9 @@ class _WonderImageWithTimeline extends StatelessWidget {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? _fixLuminance(data.type.fgColor) : Colors.transparent, color: isSelected ? _fixLuminance(data.type.fgColor) : Colors.transparent,
border: isSelected ? Border.all(color: Colors.transparent) : Border.all(color: $styles.colors.greyMedium), border: isSelected
? Border.all(color: Colors.transparent)
: Border.all(color: $styles.colors.greyMedium),
borderRadius: BorderRadius.circular($styles.corners.md), borderRadius: BorderRadius.circular($styles.corners.md),
), ),
); );

View File

@ -18,15 +18,15 @@ part 'widgets/_events_list.dart';
part 'widgets/_timeline_btn.dart'; part 'widgets/_timeline_btn.dart';
part 'widgets/_wonder_image_with_timeline.dart'; part 'widgets/_wonder_image_with_timeline.dart';
class WonderEvents extends StatefulWidget { class WonderEventsScreen extends StatefulWidget {
const WonderEvents({Key? key, required this.type, this.contentPadding = EdgeInsets.zero}) : super(key: key); const WonderEventsScreen({Key? key, required this.type, this.contentPadding = EdgeInsets.zero}) : super(key: key);
final WonderType type; final WonderType type;
final EdgeInsets contentPadding; final EdgeInsets contentPadding;
@override @override
State<WonderEvents> createState() => _WonderEventsState(); State<WonderEventsScreen> createState() => _WonderEventsScreenState();
} }
class _WonderEventsState extends State<WonderEvents> { class _WonderEventsScreenState extends State<WonderEventsScreen> {
late final _data = wondersLogic.getData(widget.type); late final _data = wondersLogic.getData(widget.type);
final _eventsListKey = GlobalKey<_EventsListState>(); final _eventsListKey = GlobalKey<_EventsListState>();
double _scrollPos = 0; double _scrollPos = 0;