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
This commit is contained in:
168
lib/pages/chat/chat_attachment_picker.dart
Normal file
168
lib/pages/chat/chat_attachment_picker.dart
Normal file
@@ -0,0 +1,168 @@
|
||||
// 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user