refactor: action buttons and scroll to bottom ui/ux

This commit is contained in:
cogwheel0
2025-09-02 20:43:57 +05:30
parent ad4a0cc340
commit 3c082ffc9e
11 changed files with 241 additions and 137 deletions

View File

@@ -0,0 +1,103 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:conduit/shared/theme/theme_extensions.dart';
import 'package:conduit/core/services/platform_service.dart';
import 'package:conduit/core/services/settings_service.dart';
class ChatActionButton extends ConsumerStatefulWidget {
final IconData icon;
final String label;
final VoidCallback? onTap;
final EdgeInsetsGeometry padding;
final BorderRadius? borderRadius;
const ChatActionButton({
super.key,
required this.icon,
required this.label,
this.onTap,
this.padding = const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
this.borderRadius,
});
@override
ConsumerState<ChatActionButton> createState() => _ChatActionButtonState();
}
class _ChatActionButtonState extends ConsumerState<ChatActionButton> {
bool _pressed = false;
@override
Widget build(BuildContext context) {
final theme = context.conduitTheme;
final hapticEnabled = ref.read(hapticEnabledProvider);
final radius = widget.borderRadius ?? BorderRadius.circular(AppBorderRadius.lg);
final overlay = theme.buttonPrimary.withValues(alpha: 0.08);
return Tooltip(
message: widget.label,
waitDuration: const Duration(milliseconds: 600),
child: Semantics(
button: true,
label: widget.label,
child: AnimatedScale(
scale: _pressed ? 0.98 : 1.0,
duration: const Duration(milliseconds: 120),
curve: Curves.easeOutCubic,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: radius,
splashColor: overlay,
highlightColor: theme.textPrimary.withValues(alpha: 0.06),
onHighlightChanged: (v) => setState(() => _pressed = v),
onTap: widget.onTap == null
? null
: () {
PlatformService.hapticFeedbackWithSettings(
type: HapticType.selection,
hapticEnabled: hapticEnabled,
);
widget.onTap!();
},
child: Ink(
decoration: BoxDecoration(
color: theme.textPrimary.withValues(alpha: 0.04),
borderRadius: radius,
border: Border.all(
color: theme.textPrimary.withValues(alpha: 0.08),
width: BorderWidth.regular,
),
),
child: Padding(
padding: widget.padding,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
widget.icon,
size: IconSize.sm,
color: theme.textPrimary.withValues(alpha: 0.8),
),
const SizedBox(width: Spacing.xs),
Text(
widget.label,
style: TextStyle(
fontSize: AppTypography.labelMedium,
color: theme.textPrimary.withValues(alpha: 0.8),
fontWeight: FontWeight.w500,
letterSpacing: 0.2,
),
),
],
),
),
),
),
),
),
),
);
}
}

View File

@@ -43,7 +43,7 @@ class ConduitMarkdownConfig {
LinkConfig(
style: TextStyle(
color: theme.buttonPrimary,
decoration: TextDecoration.underline,
decoration: TextDecoration.none,
),
onTap: (url) async {
if (await canLaunchUrlString(url)) {

View File

@@ -0,0 +1,40 @@
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
typedef OnWidgetSizeChange = void Function(Size size);
class MeasureSize extends SingleChildRenderObjectWidget {
final OnWidgetSizeChange onChange;
const MeasureSize({super.key, required this.onChange, required Widget child})
: super(child: child);
@override
RenderObject createRenderObject(BuildContext context) {
return _MeasureSizeRenderObject(onChange);
}
@override
void updateRenderObject(
BuildContext context, covariant _MeasureSizeRenderObject renderObject) {
renderObject.onChange = onChange;
}
}
class _MeasureSizeRenderObject extends RenderProxyBox {
_MeasureSizeRenderObject(this.onChange);
OnWidgetSizeChange onChange;
Size? _oldSize;
@override
void performLayout() {
super.performLayout();
Size? newSize = child?.size;
if (_oldSize == newSize || newSize == null) return;
_oldSize = newSize;
WidgetsBinding.instance.addPostFrameCallback((_) {
onChange(newSize);
});
}
}