From 9910fc7a92b7acc0ab1177848b36989a76c969d5 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 10 Aug 2024 00:43:55 +0800 Subject: [PATCH] :sparkles: Channel content auto refresh after long time background activity --- lib/screens/channel/channel_chat.dart | 175 +++++++++++++++----------- lib/translations/en_us.dart | 3 + lib/translations/zh_cn.dart | 2 + 3 files changed, 109 insertions(+), 71 deletions(-) diff --git a/lib/screens/channel/channel_chat.dart b/lib/screens/channel/channel_chat.dart index 0661a54..667bcbe 100644 --- a/lib/screens/channel/channel_chat.dart +++ b/lib/screens/channel/channel_chat.dart @@ -20,7 +20,6 @@ import 'package:solian/widgets/app_bar_leading.dart'; import 'package:solian/widgets/app_bar_title.dart'; import 'package:solian/widgets/channel/channel_call_indicator.dart'; import 'package:solian/widgets/chat/call/chat_call_action.dart'; -import 'package:solian/widgets/chat/chat_event.dart'; import 'package:solian/widgets/chat/chat_event_list.dart'; import 'package:solian/widgets/chat/chat_message_input.dart'; import 'package:solian/widgets/current_state_action.dart'; @@ -39,7 +38,10 @@ class ChannelChatScreen extends StatefulWidget { State createState() => _ChannelChatScreenState(); } -class _ChannelChatScreenState extends State { +class _ChannelChatScreenState extends State + with WidgetsBindingObserver { + DateTime? _isOutOfSyncSince; + bool _isBusy = false; int? _accountId; @@ -123,20 +125,38 @@ class _ChannelChatScreenState extends State { }); } + void _keepUpdateWithServer() { + _getOngoingCall(); + _chatController.getEvents(_channel!, widget.realm); + setState(() => _isOutOfSyncSince = null); + } + Event? _messageToReplying; Event? _messageToEditing; - Widget buildHistoryBody(Event item, {bool isMerged = false}) { - return ChatEvent( - key: Key('m${item.uuid}'), - item: item, - isMerged: isMerged, - chatController: _chatController, - ); + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + switch (state) { + case AppLifecycleState.resumed: + if (_isOutOfSyncSince == null) break; + if (DateTime.now().difference(_isOutOfSyncSince!).inSeconds < 60) break; + _keepUpdateWithServer(); + break; + case AppLifecycleState.paused: + if (mounted) { + setState(() => _isOutOfSyncSince = DateTime.now()); + } + break; + default: + break; + } } @override void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + _accountId = Get.find().userProfile.value!['id']; _chatController = ChatEventController(); @@ -147,21 +167,10 @@ class _ChannelChatScreenState extends State { _chatController.getEvents(_channel!, widget.realm); _listenMessages(); }); - - super.initState(); } @override Widget build(BuildContext context) { - if (_isBusy || _channel == null) { - return Material( - color: Theme.of(context).colorScheme.surface, - child: const Center( - child: CircularProgressIndicator(), - ), - ); - } - String title = _channel?.name ?? 'loading'.tr; String? placeholder; @@ -185,7 +194,8 @@ class _ChannelChatScreenState extends State { actions: [ const BackgroundStateWidget(), Builder(builder: (context) { - if (_isBusy) return const SizedBox(); + if (_isBusy || _channel == null) return const SizedBox(); + return ChatCallButton( realm: _channel!.realm, channel: _channel!, @@ -195,6 +205,8 @@ class _ChannelChatScreenState extends State { IconButton( icon: const Icon(Icons.more_vert), onPressed: () { + if (_channel == null) return; + AppRouter.instance .pushNamed( 'channelDetail', @@ -219,66 +231,87 @@ class _ChannelChatScreenState extends State { ), ], ), - body: Column( - children: [ - if (_ongoingCall != null) - ChannelCallIndicator( - channel: _channel!, - ongoingCall: _ongoingCall!, + body: Builder(builder: (context) { + if (_isBusy || _channel == null) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + return Column( + children: [ + if (_ongoingCall != null) + ChannelCallIndicator( + channel: _channel!, + ongoingCall: _ongoingCall!, + ), + Expanded( + child: ChatEventList( + scope: widget.realm, + channel: _channel!, + chatController: _chatController, + onEdit: (item) { + setState(() => _messageToEditing = item); + }, + onReply: (item) { + setState(() => _messageToReplying = item); + }, + ), ), - Expanded( - child: ChatEventList( - scope: widget.realm, - channel: _channel!, - chatController: _chatController, - onEdit: (item) { - setState(() => _messageToEditing = item); - }, - onReply: (item) { - setState(() => _messageToReplying = item); - }, - ), - ), - Obx(() { - if (_chatController.isLoading.isTrue) { - return const LinearProgressIndicator().animate().slideY(); - } else { - return const SizedBox(); - } - }), - ClipRect( - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50), - child: SafeArea( - child: ChatMessageInput( - edit: _messageToEditing, - reply: _messageToReplying, - realm: widget.realm, - placeholder: placeholder, - channel: _channel!, - onSent: (Event item) { - setState(() { - _chatController.addPendingEvent(item); - }); - }, - onReset: () { - setState(() { - _messageToReplying = null; - _messageToEditing = null; - }); - }, + if (_isOutOfSyncSince != null) + ListTile( + tileColor: Theme.of(context).colorScheme.surfaceContainerLow, + leading: const Icon(Icons.history_toggle_off), + title: Text('messageOutOfSync'.tr), + subtitle: Text('messageOutOfSyncCaption'.tr), + onTap: _isBusy + ? null + : () { + _keepUpdateWithServer(); + }, + ), + Obx(() { + if (_chatController.isLoading.isTrue) { + return const LinearProgressIndicator().animate().slideY(); + } else { + return const SizedBox(); + } + }), + ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50), + child: SafeArea( + child: ChatMessageInput( + edit: _messageToEditing, + reply: _messageToReplying, + realm: widget.realm, + placeholder: placeholder, + channel: _channel!, + onSent: (Event item) { + setState(() { + _chatController.addPendingEvent(item); + }); + }, + onReset: () { + setState(() { + _messageToReplying = null; + _messageToEditing = null; + }); + }, + ), ), ), ), - ), - ], - ), + ], + ); + }), ); } @override void dispose() { _subscription?.cancel(); + WidgetsBinding.instance.removeObserver(this); super.dispose(); } } diff --git a/lib/translations/en_us.dart b/lib/translations/en_us.dart index 70c5ba4..ffa6281 100644 --- a/lib/translations/en_us.dart +++ b/lib/translations/en_us.dart @@ -370,4 +370,7 @@ const i18nEnglish = { 'callStatusDisconnected': 'Disconnected', 'callStatusConnecting': 'Connecting', 'callStatusReconnected': 'Reconnecting', + 'messageOutOfSync': 'May Out of Sync with Server', + 'messageOutOfSyncCaption': + 'Since the App has entered the background, there may be a time difference between the message list and the server. Click to Refresh.', }; diff --git a/lib/translations/zh_cn.dart b/lib/translations/zh_cn.dart index f071fbd..a187bbf 100644 --- a/lib/translations/zh_cn.dart +++ b/lib/translations/zh_cn.dart @@ -337,4 +337,6 @@ const i18nSimplifiedChinese = { 'callStatusDisconnected': '已断开', 'callStatusConnecting': '连接中', 'callStatusReconnected': '重连中', + 'messageOutOfSync': '消息可能与服务器脱节', + 'messageOutOfSyncCaption': '由于 App 进入后台,消息列表可能与服务器存在时差,点击刷新。', };