diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index ca4969522..559ddf468 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -2189,19 +2189,21 @@ Widget buildRemoteBlock({required Widget child, WhetherUseRemoteBlock? use}) { )); } -Widget unreadMessageCountBuilder(RxInt? count) { +Widget unreadMessageCountBuilder(RxInt? count, + {double? size, double? fontSize, double? marginLeft}) { return Obx(() => Offstage( offstage: !((count?.value ?? 0) > 0), child: Container( - width: 16, - height: 16, + width: size ?? 16, + height: size ?? 16, decoration: BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), child: Center( child: Text("${count?.value ?? 0}", - maxLines: 1, style: TextStyle(color: Colors.white, fontSize: 10)), + maxLines: 1, + style: TextStyle(color: Colors.white, fontSize: fontSize ?? 10)), ), - ).marginOnly(left: 4))); + ).marginOnly(left: marginLeft ?? 4))); } diff --git a/flutter/lib/common/widgets/chat_page.dart b/flutter/lib/common/widgets/chat_page.dart index c9b5292c2..a79e673d5 100644 --- a/flutter/lib/common/widgets/chat_page.dart +++ b/flutter/lib/common/widgets/chat_page.dart @@ -29,20 +29,36 @@ class ChatPage extends StatelessWidget implements PageShape { final appBarActions = [ PopupMenuButton<MessageKey>( tooltip: "", - icon: Icon(Icons.group), + icon: Stack( + children: [ + Icon(Icons.group), + Positioned( + top: 0, + right: 0, + child: unreadMessageCountBuilder(gFFI.chatModel.mobileUnreadSum, + marginLeft: 0, size: 12, fontSize: 8)) + ], + ), itemBuilder: (context) { // only mobile need [appBarActions], just bind gFFI.chatModel final chatModel = gFFI.chatModel; return chatModel.messages.entries.map((entry) { final id = entry.key; final user = entry.value.chatUser; + final client = gFFI.serverModel.clients + .firstWhereOrNull((e) => e.id == id.connId); return PopupMenuItem<MessageKey>( child: Row( children: [ - Icon(id.isOut ? Icons.call_made : Icons.call_received, + Icon( + id.isOut + ? Icons.call_made_rounded + : Icons.call_received_rounded, color: MyTheme.accent) .marginOnly(right: 6), Text("${user.firstName} ${user.id}"), + if (client != null) + unreadMessageCountBuilder(client.unreadChatMessageCount) ], ), value: id, @@ -72,6 +88,11 @@ class ChatPage extends StatelessWidget implements PageShape { messages: chatModel .messages[chatModel.currentKey]?.chatMessages ?? [], + readOnly: type == ChatPageType.mobileMain && + (chatModel.currentKey.connId == + ChatModel.clientModeID || + gFFI.serverModel.clients.every( + (e) => e.id != chatModel.currentKey.connId)), inputOptions: InputOptions( focusNode: chatModel.inputNode, textController: chatModel.textController, @@ -147,6 +168,11 @@ class ChatPage extends StatelessWidget implements PageShape { padding: EdgeInsets.all(12), child: Row( children: [ + Icon( + chatModel.currentKey.isOut + ? Icons.call_made_rounded + : Icons.call_received_rounded, + color: MyTheme.accent), Icon(Icons.account_circle, color: MyTheme.accent80), SizedBox(width: 5), Text( diff --git a/flutter/lib/mobile/pages/home_page.dart b/flutter/lib/mobile/pages/home_page.dart index 308651d23..61674807a 100644 --- a/flutter/lib/mobile/pages/home_page.dart +++ b/flutter/lib/mobile/pages/home_page.dart @@ -22,6 +22,7 @@ class HomePage extends StatefulWidget { class _HomePageState extends State<HomePage> { var _selectedIndex = 0; + int get selectedIndex => _selectedIndex; final List<PageShape> _pages = []; void refreshPages() { @@ -82,6 +83,8 @@ class _HomePageState extends State<HomePage> { gFFI.chatModel.hideChatWindowOverlay(); } _selectedIndex = index; + gFFI.chatModel + .mobileClearClientUnread(gFFI.chatModel.currentKey.connId); }), ), body: _pages.elementAt(_selectedIndex), diff --git a/flutter/lib/models/chat_model.dart b/flutter/lib/models/chat_model.dart index 9ac65148b..a50a7deb1 100644 --- a/flutter/lib/models/chat_model.dart +++ b/flutter/lib/models/chat_model.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; +import 'package:flutter_hbb/mobile/pages/home_page.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart'; @@ -67,6 +68,7 @@ class ChatModel with ChangeNotifier { Rx<VoiceCallStatus> get voiceCallStatus => _voiceCallStatus; TextEditingController textController = TextEditingController(); + RxInt mobileUnreadSum = 0.obs; @override void dispose() { @@ -304,6 +306,7 @@ class ChatModel with ChangeNotifier { _currentKey = key; notifyListeners(); } + mobileClearClientUnread(key.connId); } receive(int id, String text) async { @@ -383,12 +386,22 @@ class ChatModel with ChangeNotifier { } else { parent.target?.serverModel.jumpTo(id); } + } else { + if (HomePage.homeKey.currentState?.selectedIndex != 1 || + _currentKey.peerId != client.peerId) { + client.unreadChatMessageCount.value += 1; + mobileUpdateUnreadSum(); + } } chatUser = ChatUser(id: client.peerId, firstName: client.name); } - _currentKey = messagekey; - insertMessage(_currentKey, + insertMessage(messagekey, ChatMessage(text: text, user: chatUser, createdAt: DateTime.now())); + if (id == clientModeID || _currentKey.peerId.isEmpty) { + // Invalid + _currentKey = messagekey; + mobileClearClientUnread(messagekey.connId); + } notifyListeners(); } @@ -427,6 +440,32 @@ class ChatModel with ChangeNotifier { _messages[key] = old; } } + if (_currentKey == key) { + _currentKey = key; // hash != assign + } + } + + void mobileUpdateUnreadSum() { + if (!isMobile) return; + var sum = 0; + parent.target?.serverModel.clients + .map((e) => sum += e.unreadChatMessageCount.value) + .toList(); + Future.delayed(Duration.zero, () { + mobileUnreadSum.value = sum; + }); + } + + void mobileClearClientUnread(int id) { + if (!isMobile) return; + final client = parent.target?.serverModel.clients + .firstWhereOrNull((client) => client.id == id); + if (client != null) { + Future.delayed(Duration.zero, () { + client.unreadChatMessageCount.value = 0; + mobileUpdateUnreadSum(); + }); + } } close() { diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 97f0e706c..3f5fb43f9 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/main.dart'; +import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:get/get.dart'; import 'package:wakelock/wakelock.dart'; @@ -474,6 +475,8 @@ class ServerModel with ChangeNotifier { cmHiddenTimer = null; }); } + parent.target?.chatModel + .updateConnIdOfKey(MessageKey(client.peerId, client.id)); } void showLoginDialog(Client client) {