fix group chat bubble bug: flash

main
htylight 2023-08-27 19:14:37 +08:00
parent 6367125961
commit 3f3f898d2c
6 changed files with 263 additions and 123 deletions

View File

@ -105,6 +105,15 @@ class Contact extends ChangeNotifier {
groupChatCount--; groupChatCount--;
notifyListeners(); notifyListeners();
} }
void clean() {
friends = {};
friendGroups = [];
defaultGroup = '';
groupChats = {};
friendCount = 0;
groupChatCount = 0;
}
} }
class FriendAccountProfile { class FriendAccountProfile {
@ -272,4 +281,10 @@ class ContactAccountProfile extends ChangeNotifier {
grouChatMemberProfiles.remove(groupChatId); grouChatMemberProfiles.remove(groupChatId);
notifyListeners(); notifyListeners();
} }
void clean() {
friends = {};
groupChats = {};
grouChatMemberProfiles = {};
}
} }

View File

@ -14,11 +14,17 @@ import 'package:together_mobile/models/user_model.dart';
import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/status.dart' as status; import 'package:web_socket_channel/status.dart' as status;
enum SocketStatus {
connected,
closed,
error,
}
class WebSocketManager extends ChangeNotifier { class WebSocketManager extends ChangeNotifier {
late Uri wsUrl; late Uri wsUrl;
late WebSocketChannel channel; late WebSocketChannel channel;
String id = ''; String id = '';
SocketStatus? socketStatus; SocketStatus socketStatus = SocketStatus.closed;
Timer? heartBeatTimer; Timer? heartBeatTimer;
Timer? serverTimer; Timer? serverTimer;
Timer? reconnectTimer; Timer? reconnectTimer;
@ -42,6 +48,20 @@ class WebSocketManager extends ChangeNotifier {
channel.stream.listen(onData, onError: onError, onDone: onDone); channel.stream.listen(onData, onError: onError, onDone: onDone);
} }
void disconnect() {
channel.sink.close();
wsUrl = Uri();
id = '';
socketStatus = SocketStatus.closed;
heartBeatTimer?.cancel();
serverTimer?.cancel();
reconnectTimer?.cancel();
heartBeatTimer = null;
serverTimer = null;
reconnectTimer = null;
reconnectTimes = 0;
}
void onData(jsonData) { void onData(jsonData) {
heartBeatInspect(); heartBeatInspect();
Map<String, dynamic> data = json.decode(jsonData); Map<String, dynamic> data = json.decode(jsonData);
@ -65,6 +85,9 @@ class WebSocketManager extends ChangeNotifier {
void onDone() { void onDone() {
print('websocket disconnected <$channel>'); print('websocket disconnected <$channel>');
if (socketStatus == SocketStatus.closed) {
return;
}
reconnect(); reconnect();
} }
@ -132,12 +155,6 @@ class WebSocketManager extends ChangeNotifier {
} }
} }
enum SocketStatus {
connected,
closed,
error,
}
void receiveFriendMsg(Map<String, dynamic> msg) async { void receiveFriendMsg(Map<String, dynamic> msg) async {
print('=================收到了好友信息事件=================='); print('=================收到了好友信息事件==================');
print(msg); print(msg);
@ -252,10 +269,10 @@ void receiveGroupChatMsg(Map<String, dynamic> msg) async {
if (chatSetting == null) { if (chatSetting == null) {
chatSettingBox.put( chatSettingBox.put(
senderId, groupChatId,
ChatSetting( ChatSetting(
groupChatId, groupChatId,
0, 1,
false, false,
true, true,
false, false,
@ -271,7 +288,6 @@ void receiveGroupChatMsg(Map<String, dynamic> msg) async {
} }
List<String> attachments = List.from(msg['attachments']); List<String> attachments = List.from(msg['attachments']);
messageTBox.add( messageTBox.add(
MessageT( MessageT(
senderId, senderId,

View File

@ -162,6 +162,7 @@ class _ChatScreenState extends State<ChatScreen> {
messageTBox.getAt(length - 1)!; messageTBox.getAt(length - 1)!;
if (openedChat[index].type == 0) { if (openedChat[index].type == 0) {
return FriendChatTile( return FriendChatTile(
key: ValueKey(contactId),
index: index, index: index,
contactId: contactId, contactId: contactId,
senderId: messageT.senderId, senderId: messageT.senderId,
@ -174,6 +175,7 @@ class _ChatScreenState extends State<ChatScreen> {
); );
} else { } else {
return GroupChatChatTile( return GroupChatChatTile(
key: ValueKey(contactId),
index: index, index: index,
contactId: contactId, contactId: contactId,
senderId: messageT.senderId, senderId: messageT.senderId,

View File

@ -37,12 +37,18 @@ class _GroupChatMessageBubbleState extends State<GroupChatMessageBubble> {
final List<Timer> _timerList = []; final List<Timer> _timerList = [];
Future<bool> _getMemberGroupChatProfile() async { Future<bool> _getMemberGroupChatProfile() async {
if (widget.senderId == getIt.get<UserAccount>().id || if (widget.senderId == getIt.get<UserAccount>().id) {
getIt // myself or already have profile
return Future(() => true);
}
if (getIt
.get<ContactAccountProfile>() .get<ContactAccountProfile>()
.grouChatMemberProfiles .grouChatMemberProfiles
.containsKey(widget.contactId) &&
getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget.contactId]!
.containsKey(widget.senderId)) { .containsKey(widget.senderId)) {
// myself or already have profile
return Future(() => true); return Future(() => true);
} else { } else {
Map<String, dynamic> res; Map<String, dynamic> res;
@ -136,50 +142,19 @@ class _GroupChatMessageBubbleState extends State<GroupChatMessageBubble> {
? FutureBuilder( ? FutureBuilder(
future: _getMemberGroupChatProfile(), future: _getMemberGroupChatProfile(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { return _showAvatar(snapshot, isFriend);
if (isFriend) {
return getIt
.get<ContactAccountProfile>()
.friends[widget.senderId]!
.avatar
.isEmpty
? CircleAvatar(
child:
Image.asset('assets/images/user_2.png'),
)
: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(
'$avatarsUrl/${getIt.get<ContactAccountProfile>().friends[widget.senderId]!.avatar}',
),
);
} else {
return getIt
.get<ContactAccountProfile>()
.groupChats[widget.senderId]!
.avatar
.isEmpty
? CircleAvatar(
child:
Image.asset('assets/images/user_2.png'),
)
: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(
'$avatarsUrl/${getIt.get<ContactAccountProfile>().groupChats[widget.senderId]!.avatar}',
),
);
}
} else {
return CircleAvatar(
child: Image.asset('assets/images/user_2.png'),
);
}
}, },
) )
: CircleAvatar( : getIt.get<UserProfile>().avatar.isNotEmpty
backgroundImage: CachedNetworkImageProvider( ? CircleAvatar(
'$avatarsUrl/${getIt.get<UserProfile>().avatar}', backgroundImage: CachedNetworkImageProvider(
), '$avatarsUrl/${getIt.get<UserProfile>().avatar}',
), ),
)
: const CircleAvatar(
backgroundImage:
AssetImage('assets/images/user_4.png'),
),
const SizedBox( const SizedBox(
width: 10, width: 10,
), ),
@ -190,8 +165,6 @@ class _GroupChatMessageBubbleState extends State<GroupChatMessageBubble> {
? CrossAxisAlignment.start ? CrossAxisAlignment.start
: CrossAxisAlignment.end, : CrossAxisAlignment.end,
children: [ children: [
// nickname
// used in group chat only
widget.senderId == getIt.get<UserAccount>().id widget.senderId == getIt.get<UserAccount>().id
? getIt ? getIt
.get<Contact>() .get<Contact>()
@ -208,70 +181,12 @@ class _GroupChatMessageBubbleState extends State<GroupChatMessageBubble> {
: FutureBuilder( : FutureBuilder(
future: _getMemberGroupChatProfile(), future: _getMemberGroupChatProfile(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { return _showName(snapshot, isFriend);
if (isFriend) {
return getIt
.get<Contact>()
.friends[widget.senderId]!
.friendRemark
.isEmpty
? getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget
.contactId]![widget.senderId]!
.remark
.isEmpty
? Text(
getIt
.get<ContactAccountProfile>()
.friends[widget.senderId]!
.nickname,
)
: Text(
getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[
widget.contactId]![
widget.senderId]!
.remark,
)
: Text(
getIt
.get<Contact>()
.friends[widget.senderId]!
.friendRemark,
);
} else {
return getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget
.contactId]![widget.senderId]!
.remark
.isEmpty
? Text(
getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget
.contactId]![widget.senderId]!
.nickname,
)
: Text(
getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget
.contactId]![widget.senderId]!
.remark,
);
}
} else {
return Text(widget.senderId);
}
}, },
), ),
const SizedBox( const SizedBox(
height: 5, height: 5,
), ),
if (widget.type == 'text/multipart') if (widget.type == 'text/multipart')
// message box // message box
Container( Container(
@ -347,4 +262,191 @@ class _GroupChatMessageBubbleState extends State<GroupChatMessageBubble> {
), ),
); );
} }
CircleAvatar _showAvatar(AsyncSnapshot snapshot, bool isFriend) {
if (snapshot.hasData) {
if (isFriend) {
String avatar =
getIt.get<ContactAccountProfile>().friends[widget.senderId]!.avatar;
return avatar.isEmpty
? CircleAvatar(
child: Image.asset('assets/images/user_4.png'),
)
: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(
'$avatarsUrl/$avatar',
),
);
} else {
String avatar = getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget.contactId]![widget.senderId]!
.avatar;
return avatar.isEmpty
? CircleAvatar(
child: Image.asset('assets/images/user_4.png'),
)
: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(
'$avatarsUrl/$avatar',
),
);
}
} else {
// fix the bug that when the future return, the avatar will flash
if (isFriend) {
String avatar =
getIt.get<ContactAccountProfile>().friends[widget.senderId]!.avatar;
return avatar.isEmpty
? CircleAvatar(
child: Image.asset('assets/images/user_4.png'),
)
: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(
'$avatarsUrl/$avatar',
),
);
} else {
if (getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles
.containsKey(widget.contactId)) {
if (getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget.contactId]!
.containsKey(widget.senderId)) {
String avatar = getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget.contactId]![widget.senderId]!
.avatar;
return avatar.isNotEmpty
? CircleAvatar(
backgroundImage: CachedNetworkImageProvider(
'$avatarsUrl/$avatar',
),
)
: const CircleAvatar(
backgroundImage: AssetImage(
'assets/images/user_4.png',
),
);
} else {
return const CircleAvatar(
backgroundImage: AssetImage(
'assets/images/user_4.png',
),
);
}
} else {
return const CircleAvatar(
backgroundImage: AssetImage(
'assets/images/user_4.png',
),
);
}
}
}
}
Text _showName(AsyncSnapshot snapshot, bool isFriend) {
if (snapshot.hasData) {
if (isFriend) {
String friendRemark =
getIt.get<Contact>().friends[widget.senderId]!.friendRemark;
String remarkInGroupChat = getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget.contactId]![widget.senderId]!
.remark;
return friendRemark.isEmpty
? remarkInGroupChat.isEmpty
? Text(
getIt
.get<ContactAccountProfile>()
.friends[widget.senderId]!
.nickname,
)
: Text(remarkInGroupChat)
: Text(friendRemark);
} else {
String remarkInGroupChat = getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget.contactId]![widget.senderId]!
.remark;
return remarkInGroupChat.isEmpty
? Text(
getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget.contactId]![widget.senderId]!
.nickname,
)
: Text(remarkInGroupChat);
}
} else {
// fix the bug that when the future return, the name will flash
if (isFriend) {
String friendRemark =
getIt.get<Contact>().friends[widget.senderId]!.friendRemark;
if (friendRemark.isNotEmpty) {
return Text(friendRemark);
} else {
if (getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles
.containsKey(widget.contactId) &&
getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget.contactId]!
.containsKey(widget.senderId)) {
String remarkInGroupChat = getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget.contactId]![widget.senderId]!
.remark;
String nickname = getIt
.get<ContactAccountProfile>()
.friends[widget.senderId]!
.nickname;
return remarkInGroupChat.isEmpty
? Text(nickname)
: Text(remarkInGroupChat);
} else {
return Text(widget.senderId.substring(0, 5));
}
}
} else {
if (getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles
.containsKey(widget.contactId)) {
if (getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget.contactId]!
.containsKey(widget.senderId)) {
String remarkInGroupChat = getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget.contactId]![widget.senderId]!
.remark;
String nickname = getIt
.get<ContactAccountProfile>()
.grouChatMemberProfiles[widget.contactId]![widget.senderId]!
.nickname;
return remarkInGroupChat.isNotEmpty
? Text(remarkInGroupChat)
: Text(nickname);
} else {
return Text(
widget.senderId.substring(0, 5),
);
}
} else {
return Text(
widget.senderId.substring(0, 5),
);
}
}
}
}
} }

View File

@ -2,9 +2,11 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:together_mobile/models/apply_list_model.dart'; import 'package:together_mobile/models/apply_list_model.dart';
import 'package:together_mobile/models/contact_model.dart';
import 'package:together_mobile/models/init_get_it.dart'; import 'package:together_mobile/models/init_get_it.dart';
import 'package:together_mobile/models/token_model.dart'; import 'package:together_mobile/models/token_model.dart';
import 'package:together_mobile/models/user_model.dart'; import 'package:together_mobile/models/user_model.dart';
import 'package:together_mobile/models/websocket_model.dart';
class SettingScreen extends StatelessWidget { class SettingScreen extends StatelessWidget {
const SettingScreen({super.key}); const SettingScreen({super.key});
@ -22,9 +24,12 @@ class SettingScreen extends StatelessWidget {
child: TextButton( child: TextButton(
onPressed: () async { onPressed: () async {
await getIt.get<Token>().clean(); await getIt.get<Token>().clean();
getIt.get<WebSocketManager>().disconnect();
getIt.get<UserAccount>().clean(); getIt.get<UserAccount>().clean();
getIt.get<UserProfile>().clean(); getIt.get<UserProfile>().clean();
getIt.get<ApplyList>().clean(); getIt.get<ApplyList>().clean();
getIt.get<Contact>().clean();
getIt.get<ContactAccountProfile>().clean();
// Hive.deleteFromDisk(); // Hive.deleteFromDisk();
Hive.close(); Hive.close();
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously

View File

@ -30,6 +30,13 @@ class _UsernameSigninBodyState extends State<UsernameSigninBody> {
'password': false, 'password': false,
}; };
@override
void dispose() {
usernameController.dispose();
passwordController.dispose();
super.dispose();
}
void _isInputError(String type, bool error) { void _isInputError(String type, bool error) {
setState(() { setState(() {
_isError[type] = error; _isError[type] = error;
@ -139,11 +146,4 @@ class _UsernameSigninBodyState extends State<UsernameSigninBody> {
CherryToast.error(title: const Text('用户名或密码错误')).show(context); CherryToast.error(title: const Text('用户名或密码错误')).show(context);
} }
} }
@override
void dispose() {
usernameController.dispose();
passwordController.dispose();
super.dispose();
}
} }