import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import '../../shared/theme/theme.dart'; import '../../shared/l10n/l10n.dart'; import '../../shared/widgets/buzz_loading_indicator.dart'; import 'channel.dart'; import 'channel_management_provider.dart'; import 'channel_mutes/channel_mutes_provider.dart'; class ManageChannelSheet extends HookConsumerWidget { final Channel channel; const ManageChannelSheet({super.key, required this.channel}); @override Widget build(BuildContext context, WidgetRef ref) { final canvasAsync = ref.watch(channelCanvasProvider(channel.id)); final isEditingCanvas = useState(false); final isSavingCanvas = useState(false); final isBusy = useState(false); final actionError = useState(null); final canvasController = useTextEditingController(); useEffect(() { final canvas = canvasAsync.asData?.value; if (!isEditingCanvas.value) { canvasController.text = canvas?.content ?? ''; } return null; }, [canvasAsync.asData?.value.content, isEditingCanvas.value]); final mutesState = ref.watch(channelMutesProvider); final isMuted = mutesState.store.channels[channel.id]?.muted == true; final canJoin = channel.visibility == 'open' && !channel.isArchived && !channel.isMember && !channel.isDm; final canLeave = channel.isMember && !channel.isArchived && !channel.isDm; final canEditCanvas = channel.isMember && !channel.isArchived; Future joinChannel() async { if (isBusy.value) return; isBusy.value = true; actionError.value = null; try { await ref.read(channelActionsProvider).joinChannel(channel.id); if (context.mounted) { Navigator.of(context).pop(false); } } catch (error) { actionError.value = error.toString(); } finally { isBusy.value = false; } } Future leaveChannel() async { if (isBusy.value) return; isBusy.value = true; actionError.value = null; try { await ref.read(channelActionsProvider).leaveChannel(channel.id); if (context.mounted) { Navigator.of(context).pop(true); } } catch (error) { actionError.value = error.toString(); } finally { isBusy.value = false; } } Future saveCanvas() async { if (isSavingCanvas.value) { return; } isSavingCanvas.value = true; actionError.value = null; try { await ref .read(channelActionsProvider) .setCanvas( channelId: channel.id, content: canvasController.text.trim(), ); if (context.mounted) { isEditingCanvas.value = false; } } catch (error) { actionError.value = error.toString(); } finally { isSavingCanvas.value = false; } } return Padding( padding: EdgeInsets.fromLTRB( Grid.gutter, 0, Grid.gutter, MediaQuery.viewInsetsOf(context).bottom + Grid.xs, ), child: SafeArea( top: false, child: ListView( shrinkWrap: true, children: [ Text( context.l10n.manageChannel, style: context.textTheme.titleMedium, ), const SizedBox(height: Grid.xxs), Text( context.l10n.basicManagement(channel.name), style: context.textTheme.bodySmall?.copyWith( color: context.colors.onSurfaceVariant, ), ), if (actionError.value case final error?) ...[ const SizedBox(height: Grid.xs), Text( error, style: context.textTheme.bodySmall?.copyWith( color: context.colors.error, ), ), ], const SizedBox(height: Grid.xs), SwitchListTile( title: Text(context.l10n.muteChannel), subtitle: Text(context.l10n.suppressNotifications), secondary: const Icon(LucideIcons.bellOff), contentPadding: EdgeInsets.zero, value: isMuted, onChanged: (value) { if (value) { ref .read(channelMutesProvider.notifier) .muteChannel(channel.id); } else { ref .read(channelMutesProvider.notifier) .unmuteChannel(channel.id); } }, ), if (canJoin || canLeave) ...[ const SizedBox(height: Grid.xs), Wrap( spacing: Grid.xxs, children: [ if (canJoin) FilledButton.tonal( onPressed: isBusy.value ? null : joinChannel, child: Text( isBusy.value ? context.l10n.joining : context.l10n.joinChannel, ), ), if (canLeave) OutlinedButton( onPressed: isBusy.value ? null : leaveChannel, child: Text( isBusy.value ? context.l10n.leaving : context.l10n.leaveChannelAction, ), ), ], ), ], const SizedBox(height: Grid.sm), Text(context.l10n.context, style: context.textTheme.labelLarge), const SizedBox(height: Grid.xxs), _ContextCard( label: context.l10n.description, value: channel.description, emptyLabel: context.l10n.noDescription, ), const SizedBox(height: Grid.xxs), _ContextCard( label: context.l10n.topic, value: channel.topic, emptyLabel: context.l10n.noTopic, ), const SizedBox(height: Grid.xxs), _ContextCard( label: context.l10n.purpose, value: channel.purpose, emptyLabel: context.l10n.noPurpose, ), if (!channel.isDm) ...[ const SizedBox(height: Grid.sm), Text(context.l10n.canvas, style: context.textTheme.labelLarge), const SizedBox(height: Grid.xxs), canvasAsync.when( data: (canvas) { if (isEditingCanvas.value) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextField( controller: canvasController, maxLines: 8, minLines: 6, decoration: InputDecoration( hintText: context.l10n.canvasHint, ), ), const SizedBox(height: Grid.xxs), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: isSavingCanvas.value ? null : () { isEditingCanvas.value = false; canvasController.text = canvas.content ?? ''; }, child: Text(context.l10n.cancel), ), const SizedBox(width: Grid.half), FilledButton( onPressed: isSavingCanvas.value ? null : saveCanvas, child: Text( isSavingCanvas.value ? context.l10n.saving : context.l10n.saveCanvas, ), ), ], ), ], ); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: double.infinity, padding: const EdgeInsets.all(Grid.xs), decoration: BoxDecoration( color: context.colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(Radii.md), ), child: Text( canvas.content?.trim().isNotEmpty == true ? canvas.content! : context.l10n.noCanvas, style: context.textTheme.bodyMedium?.copyWith( color: context.colors.onSurfaceVariant, ), ), ), const SizedBox(height: Grid.xxs), Align( alignment: Alignment.centerRight, child: FilledButton.tonal( onPressed: canEditCanvas ? () => isEditingCanvas.value = true : null, child: Text( canvas.content?.trim().isNotEmpty == true ? context.l10n.editCanvas : context.l10n.createCanvas, ), ), ), ], ); }, loading: () => Center( child: BuzzLoadingIndicator( size: 40, semanticLabel: context.l10n.loadingChannelDetails, ), ), error: (error, _) => Text( error.toString(), style: context.textTheme.bodySmall?.copyWith( color: context.colors.error, ), ), ), ], ], ), ), ); } } class _ContextCard extends StatelessWidget { final String label; final String? value; final String emptyLabel; const _ContextCard({ required this.label, required this.value, required this.emptyLabel, }); @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.all(Grid.xs), decoration: BoxDecoration( color: context.colors.surfaceContainerHighest, borderRadius: BorderRadius.circular(Radii.md), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: context.textTheme.labelSmall?.copyWith( color: context.colors.onSurfaceVariant, fontWeight: FontWeight.w600, ), ), const SizedBox(height: Grid.half), Text( value?.trim().isNotEmpty == true ? value!.trim() : emptyLabel, style: context.textTheme.bodyMedium?.copyWith( color: context.colors.onSurfaceVariant, ), ), ], ), ); } }