Files
buzz/mobile/lib/shared/widgets/buzz_action_tile.dart
klopez4212 aeb741fd31 Polish mobile channel navigation and message sends (#6488)
## Summary
- align iOS back controls and affected titles across channel details,
settings, and pairing
- make channel star and mute actions reflect their state immediately
- animate locally sent channel, thread, and DM messages from behind the
composer with a 300ms ease-out

## Testing
- `just mobile-check`
- full mobile test suite (1,573 tests)

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
2026-08-21 19:13:16 +01:00

108 lines
3.1 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../theme/theme.dart';
import 'buzz_loading_indicator.dart';
/// Equal-width icon action used by profile and profile-adjacent surfaces.
class BuzzActionTile extends StatelessWidget {
/// Creates an action tile with an optional loading state.
const BuzzActionTile({
super.key,
required this.icon,
required this.label,
required this.onTap,
this.iconWidget,
this.isEnabled = true,
this.isLoading = false,
this.loadingSemanticLabel,
this.iconColor,
});
/// Icon shown when the tile is not loading.
final IconData? icon;
/// Optional custom icon widget shown instead of [icon].
final Widget? iconWidget;
/// Label shown below the icon.
final String label;
/// Called when the tile is tapped.
final VoidCallback onTap;
/// Whether the tile accepts taps.
final bool isEnabled;
/// Whether to show a loading indicator instead of [icon].
final bool isLoading;
/// Accessibility label for the loading indicator.
final String? loadingSemanticLabel;
/// Optional icon tint used to communicate a selected action state.
final Color? iconColor;
@override
Widget build(BuildContext context) {
final canTap = isEnabled && !isLoading;
void handleTap() {
unawaited(HapticFeedback.lightImpact());
onTap();
}
return Semantics(
button: true,
enabled: canTap,
label: isLoading ? loadingSemanticLabel ?? label : label,
onTap: canTap ? handleTap : null,
excludeSemantics: true,
child: GestureDetector(
onTap: canTap ? handleTap : null,
excludeFromSemantics: true,
behavior: HitTestBehavior.opaque,
child: Opacity(
opacity: isEnabled ? 1 : 0.5,
child: Container(
width: double.infinity,
constraints: const BoxConstraints(minHeight: 68 + (Grid.xxs * 2)),
padding: const EdgeInsets.symmetric(vertical: Grid.xxs),
decoration: BoxDecoration(
color: context.colors.surfaceContainerHighest,
borderRadius: BorderRadius.circular(Radii.dialog),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (isLoading)
BuzzLoadingIndicator(
size: 22,
color: context.colors.onSurface,
semanticLabel: loadingSemanticLabel ?? label,
)
else
iconWidget ??
Icon(
icon,
size: 22,
color: iconColor ?? context.colors.onSurface,
),
const SizedBox(height: Grid.xxs),
Text(
label,
textAlign: TextAlign.center,
style: context.textTheme.labelMedium?.copyWith(
color: context.colors.onSurface,
),
),
],
),
),
),
),
);
}
}