Merge pull request #75 from gskinnerTeam/feature/focus_states
Add focus state to AppBtn
This commit is contained in:
commit
289edfcde4
@ -124,16 +124,33 @@ class AppBtn extends StatelessWidget {
|
||||
overlayColor: ButtonStyleButton.allOrNull<Color>(Colors.transparent), // disable default press effect
|
||||
shape: ButtonStyleButton.allOrNull<OutlinedBorder>(shape),
|
||||
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding ?? EdgeInsets.all($styles.insets.md)),
|
||||
|
||||
enableFeedback: enableFeedback,
|
||||
);
|
||||
|
||||
Widget button = TextButton(
|
||||
Widget button = _CustomFocusBuilder(
|
||||
builder: (context, focus) => Stack(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: onPressed,
|
||||
style: style,
|
||||
focusNode: focus,
|
||||
child: DefaultTextStyle(
|
||||
style: DefaultTextStyle.of(context).style.copyWith(color: textColor),
|
||||
child: content,
|
||||
),
|
||||
),
|
||||
if (focus.hasFocus)
|
||||
Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular($styles.corners.md),
|
||||
border: Border.all(color: $styles.colors.accent1, width: 3))),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
// add press effect:
|
||||
@ -180,3 +197,20 @@ class _ButtonPressEffectState extends State<_ButtonPressEffect> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomFocusBuilder extends StatefulWidget {
|
||||
const _CustomFocusBuilder({Key? key, required this.builder}) : super(key: key);
|
||||
final Widget Function(BuildContext context, FocusNode focus) builder;
|
||||
|
||||
@override
|
||||
State<_CustomFocusBuilder> createState() => _CustomFocusBuilderState();
|
||||
}
|
||||
|
||||
class _CustomFocusBuilderState extends State<_CustomFocusBuilder> {
|
||||
late final _focusNode = FocusNode()..addListener(() => setState(() {}));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.builder.call(context, _focusNode);
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ class TimelineEventCard extends StatelessWidget {
|
||||
|
||||
/// Text content
|
||||
Expanded(
|
||||
child: Text(text, style: $styles.text.body),
|
||||
child: Focus(child: Text(text, style: $styles.text.body)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -102,7 +102,7 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
|
||||
/// Scrolling content - Includes an invisible gap at the top, and then scrolls over the illustration
|
||||
TopCenter(
|
||||
child: SizedBox(
|
||||
//width: $styles.sizes.maxContentWidth1,
|
||||
child: FocusTraversalGroup(
|
||||
child: CustomScrollView(
|
||||
primary: false,
|
||||
controller: _scroller,
|
||||
@ -153,6 +153,7 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// Home Btn
|
||||
AnimatedBuilder(
|
||||
|
@ -20,7 +20,7 @@ class _ScrollingContent extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Text buildText(String value) => Text(_fixNewlines(value), style: $styles.text.body);
|
||||
Widget buildText(String value) => Focus(child: Text(_fixNewlines(value), style: $styles.text.body));
|
||||
|
||||
Widget buildDropCapText(String value) {
|
||||
final TextStyle dropStyle = $styles.text.dropCase;
|
||||
|
@ -38,7 +38,7 @@ class AboutDialogContent extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
double fontSize = $styles.text.bodySmall.fontSize!;
|
||||
double fontSize = $styles.text.body.fontSize!;
|
||||
fontSize *= MediaQuery.textScaleFactorOf(context);
|
||||
return SingleChildScrollView(
|
||||
child: Column(children: [
|
||||
|
@ -3,9 +3,11 @@ part of '../timeline_screen.dart';
|
||||
/// A vertically aligned stack of dots that represent global events
|
||||
/// The event closest to the [selectedYr] param will be visible selected
|
||||
class _EventMarkers extends StatefulWidget {
|
||||
const _EventMarkers(this.selectedYr, {Key? key, required this.onEventChanged}) : super(key: key);
|
||||
const _EventMarkers(this.selectedYr, {Key? key, required this.onEventChanged, required this.onMarkerPressed})
|
||||
: super(key: key);
|
||||
|
||||
final void Function(TimelineEvent? event) onEventChanged;
|
||||
final void Function(TimelineEvent event) onMarkerPressed;
|
||||
final int selectedYr;
|
||||
|
||||
@override
|
||||
@ -62,8 +64,12 @@ class _EventMarkersState extends State<_EventMarkers> {
|
||||
/// Create a marker for each event
|
||||
List<Widget> markers = timelineLogic.events.map((event) {
|
||||
double offsetY = _calculateOffsetY(event.year);
|
||||
return _EventMarker(offsetY,
|
||||
isSelected: event == selectedEvent, semanticLabel: '${event.year}: ${event.description}');
|
||||
return _EventMarker(
|
||||
offsetY,
|
||||
event: event,
|
||||
isSelected: event == selectedEvent,
|
||||
onPressed: widget.onMarkerPressed,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
/// Stack of fractionally positioned markers
|
||||
@ -110,11 +116,13 @@ class _EventMarker extends StatelessWidget {
|
||||
this.offset, {
|
||||
Key? key,
|
||||
required this.isSelected,
|
||||
required this.semanticLabel,
|
||||
required this.event,
|
||||
required this.onPressed,
|
||||
}) : super(key: key);
|
||||
final double offset;
|
||||
final TimelineEvent event;
|
||||
final bool isSelected;
|
||||
final String semanticLabel;
|
||||
final void Function(TimelineEvent event) onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -126,8 +134,9 @@ class _EventMarker extends StatelessWidget {
|
||||
height: 0,
|
||||
child: OverflowBox(
|
||||
maxHeight: 30,
|
||||
child: Semantics(
|
||||
label: semanticLabel,
|
||||
child: AppBtn.basic(
|
||||
semanticLabel: '${event.year}: ${event.description}',
|
||||
onPressed: () => onPressed(event),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: 30,
|
||||
|
@ -45,6 +45,11 @@ class _ScalingViewportState extends State<_ScrollingViewport> {
|
||||
AppHaptics.selectionClick();
|
||||
}
|
||||
|
||||
void _handleMarkerPressed(event) {
|
||||
final pos = controller.calculateScrollPosFromYear(event.year);
|
||||
controller.scroller.animateTo(pos, duration: $styles.times.med, curve: Curves.easeOutBack);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
@ -132,6 +137,7 @@ class _ScalingViewportState extends State<_ScrollingViewport> {
|
||||
builder: (_, __) => _EventMarkers(
|
||||
controller.calculateYearFromScrollPos(),
|
||||
onEventChanged: _handleEventMarkerChanged,
|
||||
onMarkerPressed: _handleMarkerPressed,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -4,11 +4,7 @@ class WonderDetailsTabMenu extends StatelessWidget {
|
||||
static double bottomPadding = 0;
|
||||
static double buttonInset = 12;
|
||||
|
||||
const WonderDetailsTabMenu(
|
||||
{Key? key,
|
||||
required this.tabController,
|
||||
this.showBg = false,
|
||||
required this.wonderType})
|
||||
const WonderDetailsTabMenu({Key? key, required this.tabController, this.showBg = false, required this.wonderType})
|
||||
: super(key: key);
|
||||
|
||||
final TabController tabController;
|
||||
@ -36,10 +32,7 @@ class WonderDetailsTabMenu extends StatelessWidget {
|
||||
),
|
||||
// Buttons
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: $styles.insets.sm,
|
||||
right: $styles.insets.xxs,
|
||||
bottom: bottomPadding),
|
||||
padding: EdgeInsets.only(left: $styles.insets.sm, right: $styles.insets.xxs, bottom: bottomPadding),
|
||||
// TabButtons are a Stack with a row of icon buttons, and an illustrated home button sitting on top.
|
||||
// The home buttons shows / hides itself based on `showHomeBtn`
|
||||
// The row contains an animated placeholder gap which makes room for the icon as it transitions in.
|
||||
@ -48,6 +41,7 @@ class WonderDetailsTabMenu extends StatelessWidget {
|
||||
children: [
|
||||
// Main tab btns + animated gap
|
||||
Center(
|
||||
child: FocusTraversalGroup(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -59,24 +53,17 @@ class WonderDetailsTabMenu extends StatelessWidget {
|
||||
borderSize: showBg ? 6 : 2,
|
||||
),
|
||||
_TabBtn(0, tabController,
|
||||
iconImg: 'editorial',
|
||||
label: $strings.wonderDetailsTabLabelInformation,
|
||||
color: iconColor),
|
||||
iconImg: 'editorial', label: $strings.wonderDetailsTabLabelInformation, color: iconColor),
|
||||
_TabBtn(1, tabController,
|
||||
iconImg: 'photos',
|
||||
label: $strings.wonderDetailsTabLabelImages,
|
||||
color: iconColor),
|
||||
iconImg: 'photos', label: $strings.wonderDetailsTabLabelImages, color: iconColor),
|
||||
_TabBtn(2, tabController,
|
||||
iconImg: 'artifacts',
|
||||
label: $strings.wonderDetailsTabLabelArtifacts,
|
||||
color: iconColor),
|
||||
iconImg: 'artifacts', label: $strings.wonderDetailsTabLabelArtifacts, color: iconColor),
|
||||
_TabBtn(3, tabController,
|
||||
iconImg: 'timeline',
|
||||
label: $strings.wonderDetailsTabLabelEvents,
|
||||
color: iconColor),
|
||||
iconImg: 'timeline', label: $strings.wonderDetailsTabLabelEvents, color: iconColor),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -87,11 +74,7 @@ class WonderDetailsTabMenu extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _WonderHomeBtn extends StatelessWidget {
|
||||
const _WonderHomeBtn(
|
||||
{Key? key,
|
||||
required this.size,
|
||||
required this.wonderType,
|
||||
required this.borderSize})
|
||||
const _WonderHomeBtn({Key? key, required this.size, required this.wonderType, required this.borderSize})
|
||||
: super(key: key);
|
||||
|
||||
final double size;
|
||||
@ -113,8 +96,7 @@ class _WonderHomeBtn extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(99),
|
||||
color: wonderType.fgColor,
|
||||
image: DecorationImage(
|
||||
image: AssetImage(wonderType.homeBtn), fit: BoxFit.fill),
|
||||
image: DecorationImage(image: AssetImage(wonderType.homeBtn), fit: BoxFit.fill),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -143,12 +125,9 @@ class _TabBtn extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool selected = tabController.index == index;
|
||||
final MaterialLocalizations localizations =
|
||||
MaterialLocalizations.of(context);
|
||||
final iconImgPath =
|
||||
'${ImagePaths.common}/tab-$iconImg${selected ? '-active' : ''}.png';
|
||||
String tabLabel = localizations.tabLabel(
|
||||
tabIndex: index + 1, tabCount: tabController.length);
|
||||
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
||||
final iconImgPath = '${ImagePaths.common}/tab-$iconImg${selected ? '-active' : ''}.png';
|
||||
String tabLabel = localizations.tabLabel(tabIndex: index + 1, tabCount: tabController.length);
|
||||
tabLabel = '$label: $tabLabel';
|
||||
final double btnWidth = (context.widthPx / 6).clamp(_minWidth, _maxWidth);
|
||||
return MergeSemantics(
|
||||
@ -157,15 +136,12 @@ class _TabBtn extends StatelessWidget {
|
||||
label: tabLabel,
|
||||
child: ExcludeSemantics(
|
||||
child: AppBtn.basic(
|
||||
padding: EdgeInsets.only(
|
||||
top: $styles.insets.md + $styles.insets.xs,
|
||||
bottom: $styles.insets.sm),
|
||||
padding: EdgeInsets.only(top: $styles.insets.md + $styles.insets.xs, bottom: $styles.insets.sm),
|
||||
onPressed: () => tabController.index = index,
|
||||
semanticLabel: label,
|
||||
minimumSize: Size(btnWidth, 0),
|
||||
// Image icon
|
||||
child: Image.asset(iconImgPath,
|
||||
height: 32, width: 32, color: selected ? null : color),
|
||||
child: Image.asset(iconImgPath, height: 32, width: 32, color: selected ? null : color),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -32,7 +32,7 @@ class _EventsListState extends State<_EventsList> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.blurOnScroll ? _buildScrollingListWithBlur() : _buildScrollingList();
|
||||
return FocusTraversalGroup(child: widget.blurOnScroll ? _buildScrollingListWithBlur() : _buildScrollingList());
|
||||
}
|
||||
|
||||
/// The actual content of the scrolling list
|
||||
|
@ -1,3 +1,7 @@
|
||||
# 2.0.12
|
||||
- Artifact search remembers open/closed panel state
|
||||
- Add focus state to all buttons
|
||||
|
||||
# 2.0.11
|
||||
- Fixed landscape not working on some tablets
|
||||
- Fixed app restarting when folded, or switching between multi-window mode.
|
||||
|
Loading…
x
Reference in New Issue
Block a user