Merge pull request #2879 from krille-chan/krille/fix-sending-indicator

fix: UX feedback for sending events
This commit is contained in:
Krille-chan 2026-04-19 11:08:07 +09:00 • committed by GitHub
commit 27125f8762
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 182 additions and 145 deletions

View file

@ -463,14 +463,17 @@ class ChatController extends State<ChatPageWithRoom>
scrollUpBannerEventId = eventId;
});
bool firstUpdateReceived = false;
String? animateInEventId;
void _insert(int index) {
if (index > 0) return;
animateInEventId = timeline?.events.firstOrNull?.eventId;
}
void updateView() {
if (!mounted) return;
setReadMarker();
setState(() {
firstUpdateReceived = true;
});
setState(() {});
}
Future<void>? loadTimelineFuture;
@ -487,6 +490,7 @@ class ChatController extends State<ChatPageWithRoom>
timeline?.cancelSubscriptions();
timeline = await room.getTimeline(
onUpdate: updateView,
onInsert: _insert,
eventContextId: eventContextId,
);
} catch (e, s) {

View file

@ -117,9 +117,7 @@ class ChatEventList extends StatelessWidget {
// The message at this index:
final event = events[i];
final animateIn =
event.eventId == timeline.events.first.eventId &&
controller.firstUpdateReceived;
final animateIn = event.eventId == controller.animateInEventId;
final nextEvent = i + 1 < events.length ? events[i + 1] : null;
final previousEvent = i > 0 ? events[i - 1] : null;

View file

@ -203,6 +203,8 @@ class Message extends StatelessWidget {
final enterThread = this.enterThread;
final sender = event.senderFromMemoryOrFallback;
final fileSendingStatus = event.fileSendingStatus;
return _AnimateIn(
animateIn: animateIn,
child: Center(
@ -316,9 +318,33 @@ class Message extends StatelessWidget {
height: 16,
child: event.status == EventStatus.error
? const Icon(Icons.error, color: Colors.red)
: event.fileSendingStatus != null
? const CircularProgressIndicator.adaptive(
: fileSendingStatus != null
? Stack(
children: [
Center(
child: switch (fileSendingStatus) {
FileSendingStatus
.generatingThumbnail =>
Icon(
Icons.compress_outlined,
size: 14,
),
FileSendingStatus.encrypting =>
Icon(
Icons.lock_outlined,
size: 14,
),
FileSendingStatus.uploading =>
Icon(
Icons.upload_outlined,
size: 14,
),
},
),
const CircularProgressIndicator.adaptive(
strokeWidth: 1,
),
],
)
: null,
),
@ -430,6 +456,14 @@ class Message extends StatelessWidget {
HapticFeedback.heavyImpact();
onSelect(event);
},
child: AnimatedOpacity(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
opacity:
event.status.isSending ||
event.type == EventTypes.Encrypted
? 0.5
: 1,
child: Container(
decoration: BoxDecoration(
color: noBubble
@ -478,7 +512,8 @@ class Message extends StatelessWidget {
.inReplyToEventId() ??
'\$fake_event_id',
content: {
'msgtype': 'm.text',
'msgtype':
'm.text',
'body': '...',
},
senderId:
@ -486,8 +521,8 @@ class Message extends StatelessWidget {
type:
'm.room.message',
room: event.room,
status:
EventStatus.sent,
status: EventStatus
.sent,
originServerTs:
DateTime.now(),
);
@ -499,8 +534,10 @@ class Message extends StatelessWidget {
top: 8,
),
child: Material(
color: Colors.transparent,
borderRadius: ReplyContent
color:
Colors.transparent,
borderRadius:
ReplyContent
.borderRadius,
child: InkWell(
borderRadius:
@ -516,7 +553,8 @@ class Message extends StatelessWidget {
replyEvent,
ownMessage:
ownMessage,
timeline: timeline,
timeline:
timeline,
),
),
),
@ -539,7 +577,8 @@ class Message extends StatelessWidget {
RelationshipTypes.edit,
))
Padding(
padding: const EdgeInsets.only(
padding:
const EdgeInsets.only(
bottom: 8.0,
left: 16.0,
right: 16.0,
@ -577,6 +616,7 @@ class Message extends StatelessWidget {
),
),
),
),
Align(
alignment: ownMessage
? Alignment.bottomRight
@ -957,15 +997,10 @@ class __AnimateInState extends State<_AnimateIn> {
});
});
}
return AnimatedOpacity(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
opacity: _animationFinished ? 1 : 0,
child: AnimatedSize(
return AnimatedSize(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
child: _animationFinished ? widget.child : const SizedBox.shrink(),
),
);
}
}