Files
tatuchat/lib/pages/chat/chat_attachment_picker.dart
Welton Moura 5a72c71cfc
Some checks failed
Main Deploy Workflow / deploy_web (push) Has been cancelled
Main Deploy Workflow / deploy_playstore_internal (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
feat: menu de contexto em bottom sheet, painel de anexos, perfil e feed do mural
- Salas (chat, canais, murais e salas de espacos) abrem bottom sheet com grid de acoes
- Botao + da barra de digitacao abre painel de anexos em grid de 3 colunas que empurra a caixa de texto
- Modal do perfil permite editar nome/status e ler QR code (mobile)
- Alterar status atualiza localmente via extensao do SDK matrix
- Mural pre-carrega historico para exibir todas as midias em dispositivos sem cache
2026-09-21 14:30:47 -03:00

169 lines
5.9 KiB
Dart

// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'package:tatchat/config/themes.dart';
import 'package:tatchat/l10n/l10n.dart';
import 'package:tatchat/pages/chat/chat.dart';
import 'package:tatchat/utils/platform_infos.dart';
import 'package:material_ui/material_ui.dart';
class ChatAttachmentPicker extends StatelessWidget {
final ChatController controller;
const ChatAttachmentPicker(this.controller, {super.key});
static const double _itemSpacing = 8;
static const double _handleSize = 4;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final l10n = L10n.of(context);
final mobile = PlatformInfos.isMobile;
final items = <(IconData, String, AddPopupMenuActions)>[
if (mobile)
(
Icons.gps_fixed_outlined,
l10n.shareLocation,
AddPopupMenuActions.location,
),
(Icons.poll_outlined, l10n.startPoll, AddPopupMenuActions.poll),
if (mobile)
(
Icons.videocam_outlined,
l10n.recordAVideo,
AddPopupMenuActions.videoCamera,
),
if (mobile)
(
Icons.camera_alt_outlined,
l10n.takeAPhoto,
AddPopupMenuActions.photoCamera,
),
(Icons.image_outlined, l10n.openGallery, AddPopupMenuActions.media),
(Icons.attachment_outlined, l10n.sendFile, AddPopupMenuActions.file),
];
if (items.isEmpty) return const SizedBox.shrink();
final rows = <List<(IconData, String, AddPopupMenuActions)>>[];
for (var i = 0; i < items.length; i += 3) {
rows.add(items.sublist(i, i + 3 > items.length ? items.length : i + 3));
}
return ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
child: AnimatedSize(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
alignment: Alignment.bottomCenter,
child: controller.showAttachmentPicker
? Material(
color: theme.colorScheme.surfaceContainer,
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 6, 12, 12),
child: Column(
mainAxisSize: .min,
children: [
Container(
width: 32,
height: _handleSize,
decoration: BoxDecoration(
color: theme.colorScheme.outlineVariant,
borderRadius: BorderRadius.circular(_handleSize / 2),
),
),
const SizedBox(height: 12),
Column(
mainAxisSize: .min,
children: [
for (final (i, row) in rows.indexed) ...[
if (i > 0) const SizedBox(height: _itemSpacing),
Row(
children: [
for (final item in row) ...[
if (row.indexOf(item) > 0)
const SizedBox(width: _itemSpacing),
Expanded(
child: AspectRatio(
aspectRatio: 1,
child: _AttachmentItem(
icon: item.$1,
label: item.$2,
onTap: () {
controller.hideAttachmentPicker();
controller
.onAddPopupMenuButtonSelected(
item.$3,
);
},
),
),
),
],
if (row.length < 3)
for (var i = 0; i < 3 - row.length; i++) ...[
const SizedBox(width: _itemSpacing),
const Expanded(child: SizedBox()),
],
],
),
],
],
),
],
),
),
)
: const SizedBox.shrink(),
),
);
}
}
class _AttachmentItem extends StatelessWidget {
final IconData icon;
final String label;
final VoidCallback onTap;
const _AttachmentItem({
required this.icon,
required this.label,
required this.onTap,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Material(
color: theme.colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(20),
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: onTap,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 30, color: theme.colorScheme.onSecondaryContainer),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: theme.textTheme.labelMedium?.copyWith(
color: theme.colorScheme.onSecondaryContainer,
),
),
),
],
),
),
);
}
}