fix: salas removidas de vez, paginação no mural, mídia em largura total e teclado nos comentários

This commit is contained in:
2026-09-14 17:15:52 -03:00
parent 8927e80cce
commit 9d69e833bd
8 changed files with 234 additions and 134 deletions

View File

@@ -85,19 +85,53 @@ class _MuralChatViewState extends State<MuralChatView> {
.toList();
bool _descriptionExpanded = false;
bool _loadingHistory = false;
bool _historyEnded = false;
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_getTimeline();
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollController.dispose();
timeline?.cancelSubscriptions();
super.dispose();
}
void _onScroll() {
if (_historyEnded) return;
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
_loadMore();
}
}
Future<void> _loadMore() async {
final timeline = this.timeline;
if (timeline == null || _loadingHistory || timeline.isRequestingHistory) {
return;
}
_loadingHistory = true;
setState(() {});
final eventCountBefore = timeline.events.length;
try {
await timeline.requestHistory();
} catch (e, s) {
Logs().w('Failed to load more mural posts', e, s);
} finally {
if (timeline.events.length <= eventCountBefore) {
_historyEnded = true;
}
_loadingHistory = false;
if (mounted) setState(() {});
}
}
void updateView() {
if (!mounted) return;
setState(() {});
@@ -105,7 +139,12 @@ class _MuralChatViewState extends State<MuralChatView> {
Future<void> _getTimeline() async {
timeline?.cancelSubscriptions();
timeline = await room.getTimeline(onUpdate: updateView);
_loadingHistory = false;
_historyEnded = false;
timeline = await room.getTimeline(
onUpdate: updateView,
limit: Room.defaultHistoryCount * 3,
);
if (!mounted) return;
setState(() {});
}
@@ -220,8 +259,12 @@ class _MuralChatViewState extends State<MuralChatView> {
useSafeArea: true,
isScrollControlled: true,
backgroundColor: theme.colorScheme.surface,
builder: (context) => FractionallySizedBox(
heightFactor: 0.92,
builder: (context) => ConstrainedBox(
constraints: BoxConstraints(
maxHeight:
MediaQuery.viewInsetsOf(context).bottom +
(MediaQuery.sizeOf(context).height * 0.92),
),
child: _PostViewer(
key: Key('post_viewer_${event.eventId}'),
event: event,
@@ -294,6 +337,7 @@ class _MuralChatViewState extends State<MuralChatView> {
),
)
: GridView.builder(
controller: _scrollController,
padding: const EdgeInsets.all(2),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
@@ -301,8 +345,17 @@ class _MuralChatViewState extends State<MuralChatView> {
mainAxisSpacing: 2,
crossAxisSpacing: 2,
),
itemCount: events.length,
itemCount: events.length + (_loadingHistory ? 1 : 0),
itemBuilder: (context, i) {
if (i >= events.length) {
return const Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
);
}
final event = events[i];
return _MediaCell(
event: event,
@@ -714,9 +767,8 @@ class _PostViewerState extends State<_PostViewer> {
Future<void> _forwardPost() async {
await showScaffoldDialog(
context: context,
builder: (context) => ShareScaffoldDialog(
items: [ContentShareItem(event.content.copy())],
),
builder: (context) =>
ShareScaffoldDialog(items: [ContentShareItem(event.content.copy())]),
);
}
@@ -780,9 +832,7 @@ class _PostViewerState extends State<_PostViewer> {
e.relationshipEventId == event.eventId,
)
.toList();
allComments.sort(
(a, b) => a.originServerTs.compareTo(b.originServerTs),
);
allComments.sort((a, b) => a.originServerTs.compareTo(b.originServerTs));
final nodeMap = <String, _CommentNode>{};
final roots = <_CommentNode>[];
@@ -799,8 +849,7 @@ class _PostViewerState extends State<_PostViewer> {
nodeMap.containsKey(current)) {
if (!visited.add(current)) break;
final node = nodeMap[current]!;
final parentId =
node.event.inReplyToEventId(includingFallback: false);
final parentId = node.event.inReplyToEventId(includingFallback: false);
if (parentId == null ||
parentId == event.eventId ||
!nodeMap.containsKey(parentId)) {
@@ -1116,22 +1165,22 @@ class _PostViewerState extends State<_PostViewer> {
child: isVideo
? EventVideoPlayer(event)
: hasImageDimensions
? AspectRatio(
aspectRatio: imgW / imgH,
child: MxcImage(
key: ValueKey(event.eventId),
event: event,
fit: BoxFit.cover,
isThumbnail: false,
),
)
: MxcImage(
key: ValueKey(event.eventId),
event: event,
width: double.infinity,
fit: BoxFit.contain,
isThumbnail: false,
),
? AspectRatio(
aspectRatio: imgW / imgH,
child: MxcImage(
key: ValueKey(event.eventId),
event: event,
fit: BoxFit.cover,
isThumbnail: false,
),
)
: MxcImage(
key: ValueKey(event.eventId),
event: event,
width: double.infinity,
fit: BoxFit.contain,
isThumbnail: false,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
@@ -1226,20 +1275,21 @@ class _PostViewerState extends State<_PostViewer> {
),
)
else
...commentTree.map((node) => _CommentTile(
node: node,
depth: 0,
timeline: widget.timeline,
currentUserId: widget.room.client.userID,
onReply: (comment) => setState(
() => _replyingTo = comment,
),
onToggleReaction: _toggleCommentReaction,
onShowReactionPicker: _showCommentReactionPicker,
onOpenProfile: _openProfile,
onDelete: _deleteComment,
onReport: _reportComment,
)),
...commentTree.map(
(node) => _CommentTile(
node: node,
depth: 0,
timeline: widget.timeline,
currentUserId: widget.room.client.userID,
onReply: (comment) =>
setState(() => _replyingTo = comment),
onToggleReaction: _toggleCommentReaction,
onShowReactionPicker: _showCommentReactionPicker,
onOpenProfile: _openProfile,
onDelete: _deleteComment,
onReport: _reportComment,
),
),
],
),
),
@@ -1458,8 +1508,7 @@ class _CommentTile extends StatelessWidget {
return Padding(
padding: const EdgeInsets.only(right: 4),
child: GestureDetector(
onTap: () =>
onToggleReaction(comment, entry.key),
onTap: () => onToggleReaction(comment, entry.key),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
@@ -1469,13 +1518,12 @@ class _CommentTile extends StatelessWidget {
color: isMyReaction
? theme.colorScheme.primaryContainer
: theme
.colorScheme
.surfaceContainerHighest,
.colorScheme
.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
border: isMyReaction
? Border.all(
color:
theme.colorScheme.primary,
color: theme.colorScheme.primary,
width: 1,
)
: null,