sojorn/sojorn_app/lib/widgets/composer/composer_toolbar.dart
2026-02-15 00:33:24 -06:00

156 lines
4.8 KiB
Dart

import 'package:flutter/material.dart';
import '../../theme/app_theme.dart';
import '../../theme/tokens.dart';
/// Keyboard-attached toolbar for the composer with attachments, formatting, topic, and counter.
class ComposerToolbar extends StatelessWidget {
final VoidCallback onAddMedia;
final VoidCallback onToggleBold;
final VoidCallback onToggleItalic;
final VoidCallback onToggleChain;
final VoidCallback? onToggleNsfw;
final VoidCallback? onSelectTtl;
final bool isBold;
final bool isItalic;
final bool allowChain;
final bool isNsfw;
final bool ttlOverrideActive;
final String? ttlLabel;
final int characterCount;
final int maxCharacters;
final bool isUploadingImage;
final int remainingChars;
const ComposerToolbar({
super.key,
required this.onAddMedia,
required this.onToggleBold,
required this.onToggleItalic,
required this.onToggleChain,
this.onToggleNsfw,
this.onSelectTtl,
this.isBold = false,
this.isItalic = false,
this.allowChain = true,
this.isNsfw = false,
this.ttlOverrideActive = false,
this.ttlLabel,
this.characterCount = 0,
this.maxCharacters = 500,
this.isUploadingImage = false,
this.remainingChars = 500,
});
@override
Widget build(BuildContext context) {
final isOverLimit = remainingChars < 0;
final showNumber = remainingChars <= 20;
Color ringColor;
if (isOverLimit) {
ringColor = AppTheme.error;
} else if (remainingChars <= 20) {
ringColor = AppTheme.warning;
} else {
ringColor = AppTheme.brightNavy;
}
return Row(
children: [
IconButton(
onPressed: isUploadingImage ? null : onAddMedia,
icon: isUploadingImage
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: Icon(
Icons.add_photo_alternate_outlined,
color: AppTheme.navyText.withValues(alpha: 0.75),
),
tooltip: 'Add media',
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: onToggleBold,
icon: Icon(
Icons.format_bold,
color: isBold ? AppTheme.brightNavy : AppTheme.navyText.withValues(alpha: 0.5),
),
tooltip: 'Bold',
),
IconButton(
onPressed: onToggleItalic,
icon: Icon(
Icons.format_italic,
color: isItalic ? AppTheme.brightNavy : AppTheme.navyText.withValues(alpha: 0.5),
),
tooltip: 'Italic',
),
IconButton(
onPressed: onToggleChain,
icon: Icon(
Icons.link,
color: allowChain ? AppTheme.brightNavy : AppTheme.navyText.withValues(alpha: 0.4),
),
tooltip: allowChain ? 'Allow chain' : 'Chain disabled',
),
if (onToggleNsfw != null)
IconButton(
onPressed: onToggleNsfw,
icon: Icon(
Icons.visibility_off_outlined,
color: isNsfw ? AppTheme.nsfwWarningIcon : AppTheme.navyText.withValues(alpha: 0.4),
),
tooltip: isNsfw ? 'Marked as NSFW' : 'Mark as NSFW',
),
if (onSelectTtl != null)
IconButton(
onPressed: onSelectTtl,
icon: Icon(
Icons.timer_outlined,
color: ttlOverrideActive
? AppTheme.brightNavy
: AppTheme.navyText.withValues(alpha: 0.5),
),
tooltip: ttlLabel != null ? 'Post duration: $ttlLabel' : 'Post duration',
),
],
),
const Spacer(),
SizedBox(
width: 24,
height: 24,
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
value: (characterCount / maxCharacters).clamp(0, 1),
strokeWidth: 2.5,
backgroundColor: AppTheme.queenPink.withValues(alpha: 0.2),
valueColor: AlwaysStoppedAnimation<Color>(ringColor),
),
),
if (showNumber)
Text(
remainingChars.clamp(-99, 99).toString(),
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w700,
color: ringColor,
),
),
],
),
),
],
);
}
}