feat: menu de contexto em bottom sheet, painel de anexos, perfil e feed do mural
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

- 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:
2026-09-21 14:30:47 -03:00
parent 762178d9f9
commit 5a72c71cfc
14 changed files with 861 additions and 584 deletions

View File

@@ -88,6 +88,7 @@ class _MuralChatViewState extends State<MuralChatView> {
bool _descriptionExpanded = false;
bool _loadingHistory = false;
bool _historyEnded = false;
bool _initialLoading = false;
final ScrollController _scrollController = ScrollController();
@override
@@ -125,7 +126,8 @@ class _MuralChatViewState extends State<MuralChatView> {
} catch (e, s) {
Logs().w('Failed to load more mural posts', e, s);
} finally {
if (timeline.events.length <= eventCountBefore) {
if (!timeline.canRequestHistory ||
timeline.events.length <= eventCountBefore) {
_historyEnded = true;
}
_loadingHistory = false;
@@ -133,6 +135,25 @@ class _MuralChatViewState extends State<MuralChatView> {
}
}
/// Fetches older events from the server until enough media posts were
/// loaded or the entire history was consumed. This makes the mural behave
/// like a chat: after the initial sync only a handful of recent events are
/// in the timeline which usually contain very few media posts (most events
/// are thread comments), so we must actively fetch batches from the server
/// until the feed is properly populated.
Future<void> _fillInitialMedia() async {
var attempts = 0;
const maxAttempts = 20;
const targetMediaCount = 30;
while (mounted &&
!_historyEnded &&
_mediaEvents.length < targetMediaCount &&
attempts < maxAttempts) {
attempts++;
await _loadMore();
}
}
void updateView() {
if (!mounted) return;
setState(() {});
@@ -142,12 +163,16 @@ class _MuralChatViewState extends State<MuralChatView> {
timeline?.cancelSubscriptions();
_loadingHistory = false;
_historyEnded = false;
_initialLoading = true;
timeline = await room.getTimeline(
onUpdate: updateView,
limit: Room.defaultHistoryCount * 3,
);
if (!mounted) return;
setState(() {});
await _fillInitialMedia();
_initialLoading = false;
if (mounted) setState(() {});
}
Future<void> _sendFiles(List<XFile>? files) async {
@@ -318,7 +343,9 @@ class _MuralChatViewState extends State<MuralChatView> {
),
const Divider(height: 1),
Expanded(
child: events.isEmpty
child: _initialLoading
? const Center(child: CircularProgressIndicator.adaptive())
: events.isEmpty
? Center(
child: Padding(
padding: const EdgeInsets.all(24),