- 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
169 lines
5.9 KiB
Dart
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,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|