fix: Improve dPad navigation buttons (#605)

This commit is contained in:
PartyDonut 2025-11-11 20:00:58 +01:00 committed by GitHub
parent e9f32f522b
commit 493f40645c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 91 additions and 61 deletions

View file

@ -14,6 +14,7 @@ import 'package:fladder/screens/shared/animated_fade_size.dart';
import 'package:fladder/screens/shared/fladder_logo.dart'; import 'package:fladder/screens/shared/fladder_logo.dart';
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
import 'package:fladder/widgets/keyboard/slide_in_keyboard.dart'; import 'package:fladder/widgets/keyboard/slide_in_keyboard.dart';
import 'package:fladder/widgets/navigation_scaffold/components/adaptive_fab.dart';
import 'package:fladder/widgets/navigation_scaffold/components/fladder_app_bar.dart'; import 'package:fladder/widgets/navigation_scaffold/components/fladder_app_bar.dart';
@RoutePage() @RoutePage()
@ -53,20 +54,22 @@ class _LoginPageState extends ConsumerState<LoginScreen> {
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
spacing: 16, spacing: 16,
children: [ children: [
if (!AdaptiveLayout.of(context).isDesktop) if (AdaptiveLayout.of(context).isDesktop)
FloatingActionButton( AdaptiveFab(
context: context,
key: const Key("edit_user_button"), key: const Key("edit_user_button"),
heroTag: "edit_user_button", heroTag: "edit_user_button",
backgroundColor: editUsersMode ? Theme.of(context).colorScheme.errorContainer : null, backgroundColor: editUsersMode ? Theme.of(context).colorScheme.errorContainer : null,
child: const Icon(IconsaxPlusLinear.edit_2), child: const Icon(IconsaxPlusLinear.edit_2),
onPressed: () => setState(() => editUsersMode = !editUsersMode), onPressed: () => setState(() => editUsersMode = !editUsersMode),
), ).normal,
FloatingActionButton( AdaptiveFab(
context: context,
key: const Key("new_user_button"), key: const Key("new_user_button"),
heroTag: "new_user_button", heroTag: "new_user_button",
child: const Icon(IconsaxPlusLinear.add_square), child: const Icon(IconsaxPlusLinear.add_square),
onPressed: () => ref.read(authProvider.notifier).addNewUser(), onPressed: () => ref.read(authProvider.notifier).addNewUser(),
), ).normal,
], ],
), ),
_ => null, _ => null,

View file

@ -4,8 +4,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:iconsax_plus/iconsax_plus.dart'; import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:fladder/screens/shared/flat_button.dart';
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/sticky_header_text.dart'; import 'package:fladder/widgets/shared/ensure_visible.dart';
class ExpandingOverview extends ConsumerStatefulWidget { class ExpandingOverview extends ConsumerStatefulWidget {
final String text; final String text;
@ -29,58 +31,68 @@ class _ExpandingOverviewState extends ConsumerState<ExpandingOverview> {
final color = Theme.of(context).colorScheme.onSurface; final color = Theme.of(context).colorScheme.onSurface;
const int maxLength = 200; const int maxLength = 200;
final bool canExpand = widget.text.length > maxLength; final bool canExpand = widget.text.length > maxLength;
return AnimatedSize( final isDpad = AdaptiveLayout.inputDeviceOf(context) == InputDevice.dPad;
duration: const Duration(milliseconds: 250), return FlatButton(
alignment: Alignment.topCenter, onTap: canExpand && isDpad ? toggleState : null,
child: Column( onFocusChange: (value) {
crossAxisAlignment: CrossAxisAlignment.start, if (value) {
mainAxisSize: MainAxisSize.min, context.ensureVisible();
children: [ }
StickyHeaderText( },
label: context.localized.overview, child: AnimatedSize(
), duration: const Duration(milliseconds: 250),
ShaderMask( alignment: Alignment.topCenter,
shaderCallback: (bounds) => LinearGradient( child: Column(
begin: Alignment.topCenter, crossAxisAlignment: CrossAxisAlignment.start,
end: Alignment.bottomCenter, mainAxisSize: MainAxisSize.min,
stops: const [0, 1], children: [
colors: [ Text(
color, context.localized.overview,
color.withValues( style: Theme.of(context).textTheme.titleLarge,
alpha: !canExpand
? 1
: expanded
? 1
: 0),
],
).createShader(bounds),
child: HtmlWidget(
widget.text.substring(0, !expanded ? maxLength.clamp(0, widget.text.length) : widget.text.length - 1),
textStyle: Theme.of(context).textTheme.bodyLarge,
), ),
), ShaderMask(
if (canExpand) ...{ shaderCallback: (bounds) => LinearGradient(
const SizedBox(height: 16), begin: Alignment.topCenter,
Align( end: Alignment.bottomCenter,
alignment: Alignment.center, stops: const [0, 1],
child: Transform.translate( colors: [
offset: Offset(0, expanded ? 0 : -15), color,
child: AnimatedSwitcher( color.withValues(
duration: const Duration(milliseconds: 250), alpha: !canExpand
child: expanded ? 1
? IconButton.filledTonal( : expanded
onPressed: toggleState, ? 1
icon: const Icon(IconsaxPlusLinear.arrow_up_1), : 0),
) ],
: IconButton.filledTonal( ).createShader(bounds),
onPressed: toggleState, child: HtmlWidget(
icon: const Icon(IconsaxPlusLinear.arrow_down), widget.text.substring(0, !expanded ? maxLength.clamp(0, widget.text.length) : widget.text.length - 1),
), textStyle: Theme.of(context).textTheme.bodyLarge,
),
), ),
), ),
}, if (canExpand && !isDpad) ...{
], const SizedBox(height: 16),
Align(
alignment: Alignment.center,
child: Transform.translate(
offset: Offset(0, expanded ? 0 : -15),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
child: expanded
? IconButton.filledTonal(
onPressed: toggleState,
icon: const Icon(IconsaxPlusLinear.arrow_up_1),
)
: IconButton.filledTonal(
onPressed: toggleState,
icon: const Icon(IconsaxPlusLinear.arrow_down),
),
),
),
),
},
],
),
), ),
); );
} }

View file

@ -3,23 +3,37 @@ import 'package:flutter/material.dart';
class AdaptiveFab { class AdaptiveFab {
final BuildContext context; final BuildContext context;
final String title; final String title;
final String? heroTag;
final Widget child; final Widget child;
final Function() onPressed; final Function() onPressed;
final Color? backgroundColor;
final Key? key; final Key? key;
AdaptiveFab({ AdaptiveFab({
required this.context, required this.context,
this.title = '', this.title = '',
this.heroTag,
required this.child, required this.child,
required this.onPressed, required this.onPressed,
this.key, this.key,
this.backgroundColor,
}); });
FloatingActionButton get normal { Widget get normal {
return FloatingActionButton( return Hero(
key: key, tag: heroTag ?? UniqueKey(),
onPressed: onPressed, child: IconButton.filledTonal(
tooltip: title, iconSize: 26,
child: child, key: key,
tooltip: title,
onPressed: onPressed,
style: IconButton.styleFrom(
backgroundColor: backgroundColor,
),
icon: Padding(
padding: const EdgeInsets.all(8.0),
child: child,
),
),
); );
} }
@ -34,6 +48,7 @@ class AdaptiveFab {
onPressed: onPressed, onPressed: onPressed,
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
backgroundColor: backgroundColor,
), ),
child: Row( child: Row(
spacing: 16, spacing: 16,