websocket apply/add/delete friend v1

main
htylight 2023-08-11 23:02:31 +08:00
parent f75cccd28e
commit 5da249e4fc
24 changed files with 302 additions and 265 deletions

View File

@ -16,7 +16,16 @@ class ChatSetting {
@HiveField(3, defaultValue: false)
bool isHideMsg;
ChatSetting(this.contactId, this.isTop, this.isOpen, this.isHideMsg);
@HiveField(4)
DateTime? latestDateTime;
ChatSetting(
this.contactId,
this.isTop,
this.isOpen,
this.isHideMsg,
this.latestDateTime,
);
}
@HiveType(typeId: 1)

View File

@ -21,13 +21,14 @@ class ChatSettingAdapter extends TypeAdapter<ChatSetting> {
fields[1] == null ? false : fields[1] as bool,
fields[2] == null ? true : fields[2] as bool,
fields[3] == null ? false : fields[3] as bool,
fields[4] as DateTime?,
);
}
@override
void write(BinaryWriter writer, ChatSetting obj) {
writer
..writeByte(4)
..writeByte(5)
..writeByte(0)
..write(obj.contactId)
..writeByte(1)
@ -35,7 +36,9 @@ class ChatSettingAdapter extends TypeAdapter<ChatSetting> {
..writeByte(2)
..write(obj.isOpen)
..writeByte(3)
..write(obj.isHideMsg);
..write(obj.isHideMsg)
..writeByte(4)
..write(obj.latestDateTime);
}
@override

View File

@ -15,7 +15,7 @@ void initDatabase() async {
Box<ChatSetting> chatSettingBox =
await Hive.openBox<ChatSetting>('chat_setting');
final openedChats = chatSettingBox.values.where((element) => element.isOpen);
for (var chatBox in openedChats) {
@ -53,11 +53,12 @@ void openNewMessageBox(String contactId) async {
final encryptionKeyUint8List = await getEncryptKey();
var chatSettingBox = Hive.box<ChatSetting>('chat_setting');
chatSettingBox.add(ChatSetting(contactId, false, true, false));
chatSettingBox
.add(ChatSetting(contactId, false, true, false, DateTime.now()));
await Hive.openLazyBox(
'message_$contactId',
encryptionCipher: HiveAesCipher(encryptionKeyUint8List),
compactionStrategy: (entries, deletedEntries) => entries > 200,
);
}
}

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:together_mobile/common/theme.dart';
import 'package:together_mobile/database/init_database.dart';
import 'package:together_mobile/database/hive_database.dart';
import 'package:together_mobile/router/router.dart';
import 'package:together_mobile/models/init_get_it.dart';

View File

@ -11,7 +11,6 @@ class Apply {
applicant = json['applicant'];
recipient = json['recipient'];
hello = json['hello'];
createdAt = json['createdAt'];
groupChatId = json['groupChatId'];
setting = json['setting'];
}
@ -33,13 +32,22 @@ class ApplyList extends ChangeNotifier {
void removeAt(int index) {
applyList.removeAt(index);
applicantIds.removeAt(index);
count--;
notifyListeners();
}
void addJson(Map<String, dynamic> json) {
Apply apply = Apply.fromJson(json);
int index = applicantIds.indexOf(json['applicant']);
if (index != -1) {
applyList.removeAt(index);
count--;
}
applyList.add(apply);
applicantIds.add(json['applicant']);
count++;
notifyListeners();
}
void clean() {

View File

@ -53,7 +53,7 @@ class Contact extends ChangeNotifier {
}
void addFriend(String friendId, Map friendSetting) {
friends[friendId] = FriendSetting.fromJson(friendSetting);
friends.addAll({friendId: FriendSetting.fromJson(friendSetting)});
notifyListeners();
}
@ -121,7 +121,13 @@ class ContactAccountProfile extends ChangeNotifier {
return friendId.isNotEmpty ? (true, friendId) : (false, '');
}
void addAccountProfile(String friendId, Map<String, dynamic> json) {
friends.addAll({friendId: FriendAccountProfile.fromJson(json)});
notifyListeners();
}
void removeFriend(String friendId) {
friends.remove(friendId);
notifyListeners();
}
}

View File

@ -13,6 +13,14 @@ class UserAccount extends ChangeNotifier {
email = data['email']!;
}
Map<String, String> toMap() {
return {
'id': id,
'username': username,
'email': email,
};
}
void updateUsername(String newUsername) {
username = newUsername;
}
@ -51,6 +59,18 @@ class UserProfile extends ChangeNotifier {
isInitialised = true;
}
Map<String, String> toMap() {
return {
'nickname': nickname,
'gender': gender,
'birthday': birthday,
'location': location,
'status': status,
'sign': sign,
'avatar': avatar,
};
}
void updateBasic(Map<String, String> newBasic) {
nickname = newBasic['nickname']!;
location = newBasic['location']!;

View File

@ -4,6 +4,9 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:together_mobile/database/box_type.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:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/status.dart' as status;
@ -42,6 +45,12 @@ class WebSocketManager extends ChangeNotifier {
switch (data['event']) {
case 'one-to-one-chat':
receiveFriendMsg(data);
case 'apply-friend':
receiveApplyFriend(data);
case 'friend-added':
receiveFriendAdded(data);
case 'friend-deleted':
receiveFriendDeleted(data);
}
}
@ -130,24 +139,58 @@ void receiveFriendMsg(Map<String, dynamic> msg) async {
}
Box<ChatSetting> chatSettingBox = Hive.box<ChatSetting>('chat_setting');
ChatSetting? chatSetting = chatSettingBox.get(senderId);
DateTime dateTime = DateTime.parse(msg['dateTime'] as String);
if (chatSetting == null) {
chatSettingBox.put(senderId, ChatSetting(senderId, false, true, false));
chatSettingBox.put(
senderId,
ChatSetting(
senderId,
false,
true,
false,
dateTime,
),
);
} else {
chatSetting.isOpen = true;
chatSetting.latestDateTime = dateTime;
chatSettingBox.put(senderId, chatSetting);
}
List<String> attachments = List.from(msg['attachments']);
messageTBox.add(
MessageT(
senderId,
msg['text'],
msg['type'],
DateTime.parse(msg['dateTime']),
DateTime.parse(msg['dateTime'] as String),
msg['isShowTime'],
msg['attachments'],
attachments,
),
);
}
void receiveApplyFriend(Map<String, dynamic> msg) {
getIt.get<ApplyList>().addJson(msg);
}
void receiveFriendAdded(Map<String, dynamic> msg) {
print('=================收到了申请好友通过事件==================');
print(msg);
print('=======================================');
getIt.get<Contact>().addFriend(msg['friendId'], msg['setting']);
getIt
.get<ContactAccountProfile>()
.addAccountProfile(msg['friendId'], msg['accountProfile']);
}
void receiveFriendDeleted(Map<String, dynamic> msg) {
print('=================收到了解除好友事件==================');
print(msg);
print('=======================================');
getIt.get<Contact>().removeFriend(msg['friendId']);
getIt.get<ContactAccountProfile>().removeFriend(msg['friendId']);
}

View File

@ -6,6 +6,7 @@ import 'package:hive_flutter/hive_flutter.dart';
import 'package:together_mobile/database/box_type.dart';
import 'package:together_mobile/models/websocket_model.dart';
import 'package:together_mobile/utils/app_dir.dart';
import 'package:together_mobile/utils/format_datetime.dart';
import 'components/chat_tile.dart';
import 'package:together_mobile/models/contact_model.dart';
import 'package:together_mobile/models/apply_list_model.dart';
@ -15,7 +16,7 @@ import 'package:together_mobile/request/contact.dart';
import 'package:together_mobile/models/user_model.dart';
import 'package:together_mobile/models/init_get_it.dart';
import 'package:together_mobile/request/user_profile.dart';
import 'package:together_mobile/database/init_database.dart';
import 'package:together_mobile/database/hive_database.dart';
class ChatScreen extends StatefulWidget {
const ChatScreen({super.key});
@ -139,14 +140,41 @@ class _ChatScreenState extends State<ChatScreen> {
itemCount: openedChat.length,
itemBuilder: (BuildContext context, int index) {
String contactId = openedChat[index].contactId;
String showedTime = formatTileDateTime(
openedChat[index].latestDateTime!,
);
return ValueListenableBuilder(
valueListenable:
Hive.box<MessageT>('message_$contactId')
.listenable(),
builder: (context, value, _) {
return ChatTile(
contactId: contactId,
);
builder: (context, messageTBox, _) {
int length = messageTBox.length;
if (length > 0) {
MessageT messageT =
messageTBox.getAt(length - 1)!;
return ChatTile(
index: index,
contactId: contactId,
senderId: messageT.senderId,
type: messageT.type,
text: messageT.text,
attachments: messageT.attachments,
dateTime: showedTime,
isShowTime: messageT.isShowTime,
);
} else {
return ChatTile(
index: index,
contactId: contactId,
senderId: '',
type: '',
text: '',
attachments: const [],
dateTime: showedTime,
isShowTime: false,
);
}
},
);
},

View File

@ -1,138 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:go_router/go_router.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:together_mobile/common/constants.dart';
import 'package:together_mobile/database/box_type.dart';
import 'package:together_mobile/models/contact_model.dart';
import 'package:together_mobile/models/init_get_it.dart';
import 'package:together_mobile/request/server.dart';
import 'badge_avatar.dart';
class ChatTile extends StatefulWidget {
const ChatTile({
super.key,
required this.contactId,
});
final String contactId;
@override
State<ChatTile> createState() => _ChatTileState();
}
class _ChatTileState extends State<ChatTile> {
Future<Map<String, dynamic>> _getLatestMsg() async {
LazyBox<MessageT> messageTBox =
Hive.lazyBox<MessageT>('message_${widget.contactId}');
int messageCount = messageTBox.length;
if (messageCount > 0) {
MessageT messageT = (await messageTBox.getAt(messageCount - 1))!;
return Future(() => {
'text': messageT.text,
'dateTime': messageT.dateTime,
'attachments': messageT.attachments,
});
} else {
return Future(() => {});
}
}
@override
Widget build(BuildContext context) {
return Slidable(
key: const ValueKey(0),
endActionPane: ActionPane(
motion: const BehindMotion(),
dismissible: DismissiblePane(
onDismissed: () {},
),
children: [
SlidableAction(
onPressed: (BuildContext context) {},
backgroundColor: kSecondaryColor,
foregroundColor: kContentColorDark,
icon: Icons.arrow_upward_rounded,
label: '置顶',
),
SlidableAction(
onPressed: (BuildContext context) {},
foregroundColor: kContentColorDark,
backgroundColor: kPrimaryColor,
icon: Icons.remove_red_eye,
label: '隐藏信息',
flex: 1,
),
],
),
child: ListTile(
// Must have a onTap callback or Ink won't work
onTap: () => context.goNamed(
'Message',
queryParameters: {'contactId': widget.contactId},
),
leading: getIt
.get<ContactAccountProfile>()
.friends[widget.contactId]!
.avatar
.isEmpty
? const BadgeAvatar(
count: 99,
radius: 25,
backgroundImage: AssetImage('assets/images/user_3.png'),
)
: BadgeAvatar(
count: 99,
radius: 25,
backgroundImage: CachedNetworkImageProvider(
'$avatarsUrl/${getIt.get<ContactAccountProfile>().friends[widget.contactId]!.avatar}',
),
),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
// friend remark or nickname
child: Text(
getIt
.get<Contact>()
.friends[widget.contactId]!
.friendRemark
.isEmpty
? getIt
.get<ContactAccountProfile>()
.friends[widget.contactId]!
.nickname
: getIt
.get<Contact>()
.friends[widget.contactId]!
.friendRemark,
overflow: TextOverflow.ellipsis,
),
),
// latest msg datetime
Text('10:13'),
],
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'How are you today, you look not very well, whats happended to you',
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Theme.of(context).textTheme.displayLarge?.color),
),
),
// Text('10:13'),
],
),
),
);
}
}

View File

@ -10,53 +10,31 @@ import 'package:together_mobile/database/box_type.dart';
import 'package:together_mobile/models/contact_model.dart';
import 'package:together_mobile/models/init_get_it.dart';
import 'package:together_mobile/request/server.dart';
import 'package:together_mobile/utils/format_datetime.dart';
import 'badge_avatar.dart';
class ChatTile extends StatefulWidget {
const ChatTile({
super.key,
required this.index,
required this.contactId,
required this.senderId,
required this.type,
required this.text,
required this.attachments,
required this.dateTime,
required this.isShowTime,
});
final String contactId;
final int index;
final String contactId, senderId, type, text, dateTime;
final List<String> attachments;
final bool isShowTime;
@override
State<ChatTile> createState() => _ChatTileState();
}
class _ChatTileState extends State<ChatTile> {
late Map<String, dynamic> _latestMsg;
@override
void initState() {
super.initState();
Box<MessageT> messageTBox =
Hive.box<MessageT>('message_${widget.contactId}');
int length = messageTBox.length;
if (length > 0) {
MessageT messageT = messageTBox.getAt(length - 1)!;
_latestMsg = {
'senderId': messageT.senderId,
'type': messageT.type,
'text': messageT.text,
'attachments': messageT.attachments,
'dateTime': formatMessageDateTime(messageT.dateTime),
'isShowTime': messageT.isShowTime,
};
} else {
_latestMsg = {
'senderId': '',
'type': '',
'text': '',
'attachments': [],
'dateTime': [],
'isShowTime': false,
};
}
}
@override
Widget build(BuildContext context) {
return Slidable(
@ -64,7 +42,12 @@ class _ChatTileState extends State<ChatTile> {
endActionPane: ActionPane(
motion: const BehindMotion(),
dismissible: DismissiblePane(
onDismissed: () {},
onDismissed: () {
Box<ChatSetting> chatSettingBox = Hive.box('chat_setting');
ChatSetting chatSetting = chatSettingBox.getAt(widget.index)!;
chatSetting.isOpen = false;
chatSettingBox.put(widget.contactId, chatSetting);
},
),
children: [
SlidableAction(
@ -131,7 +114,7 @@ class _ChatTileState extends State<ChatTile> {
),
// latest msg datetime
Text(
_latestMsg['dateTime']!,
widget.dateTime,
style: const TextStyle(
fontSize: 14,
color: kUnActivatedColor,
@ -144,7 +127,7 @@ class _ChatTileState extends State<ChatTile> {
children: [
Expanded(
child: Text(
_latestMsg['text'],
widget.text,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: kUnActivatedColor,
@ -152,7 +135,6 @@ class _ChatTileState extends State<ChatTile> {
),
),
),
// Text('10:13'),
],
),
),

View File

@ -5,7 +5,7 @@ import 'package:together_mobile/models/init_get_it.dart';
import 'friend_tile.dart';
class FriendGroup extends StatefulWidget {
class FriendGroup extends StatelessWidget {
const FriendGroup({
super.key,
required this.groupName,
@ -13,31 +13,26 @@ class FriendGroup extends StatefulWidget {
final String groupName;
@override
State<FriendGroup> createState() => _FriendGroupState();
}
class _FriendGroupState extends State<FriendGroup> {
@override
Widget build(BuildContext context) {
return ExpansionTile(
title: Text(widget.groupName),
title: Text(groupName),
trailing: Text(
getIt
.get<Contact>()
.filterGroupFriends(widget.groupName)
.filterGroupFriends(groupName)
.length
.toString(),
),
children: List.generate(
getIt.get<Contact>().filterGroupFriends(widget.groupName).length,
getIt.get<Contact>().filterGroupFriends(groupName).length,
(index) => FriendTile(
key: ValueKey(
getIt.get<Contact>().friends.keys.toList()[index],
),
friendId: getIt
.get<Contact>()
.filterGroupFriends(widget.groupName)
.filterGroupFriends(groupName)
.keys
.toList()[index],
),

View File

@ -1,16 +1,9 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:archive/archive_io.dart';
import 'package:together_mobile/models/apply_list_model.dart';
import 'package:together_mobile/models/init_get_it.dart';
import 'package:together_mobile/models/user_model.dart';
import 'package:together_mobile/request/apply.dart';
import 'package:together_mobile/screens/contact/contact_apply_screen/components/apply_list_tile.dart';
import 'package:together_mobile/utils/app_dir.dart';
class ApplyListScreen extends StatefulWidget {
const ApplyListScreen({super.key});
@ -28,30 +21,30 @@ class _ApplyListScreenState extends State<ApplyListScreen> {
Map<String, dynamic> res =
await getApplicantProfile(getIt.get<ApplyList>().applicantIds);
List<String> avatars = [];
// List<String> avatars = [];
for (var profile in res['data'].values) {
if (profile['avatar'] != null) {
String avatarPath = await getAvatarPath(profile['avatar']);
if (!File(avatarPath).existsSync()) {
avatars.add(profile['avatar']);
}
}
}
// for (var profile in res['data'].values) {
// if (profile['avatar'] != null) {
// String avatarPath = await getAvatarPath(profile['avatar']);
// if (!File(avatarPath).existsSync()) {
// avatars.add(profile['avatar']);
// }
// }
// }
if (avatars.isNotEmpty) {
Uint8List res = await downloadApplicantAvatars(avatars);
Directory tempDir = await getTemporaryDirectory();
Directory appDir = await getApplicationDocumentsDirectory();
String zipFilePath =
'${tempDir.path}/temp_avatars_${DateTime.now().millisecondsSinceEpoch}.zip';
File zipFile = await File(zipFilePath).writeAsBytes(res);
extractFileToDisk(
zipFilePath,
'${appDir.path}/${getIt.get<UserAccount>().id}/images/avatars',
);
await zipFile.delete();
}
// if (avatars.isNotEmpty) {
// Uint8List res = await downloadApplicantAvatars(avatars);
// Directory tempDir = await getTemporaryDirectory();
// Directory appDir = await getApplicationDocumentsDirectory();
// String zipFilePath =
// '${tempDir.path}/temp_avatars_${DateTime.now().millisecondsSinceEpoch}.zip';
// File zipFile = await File(zipFilePath).writeAsBytes(res);
// extractFileToDisk(
// zipFilePath,
// '${appDir.path}/${getIt.get<UserAccount>().id}/images/avatars',
// );
// await zipFile.delete();
// }
return Future(() => res['data']);
}

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
@ -10,6 +11,7 @@ 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/user_model.dart';
import 'package:together_mobile/models/websocket_model.dart';
import 'package:together_mobile/request/apply.dart';
import 'package:together_mobile/request/server.dart';
@ -215,15 +217,33 @@ class _ApplyBottomSheetState extends State<ApplyBottomSheet> {
if (res['code'] == 10600) {
// ignore: use_build_context_synchronously
CherryToast.success(
title: const Text(
'添加好友成功',
style: TextStyle(
color: kContentColorLight,
title: const Text(
'添加好友成功',
style: TextStyle(
color: kContentColorLight,
),
),
)).show(context);
).show(context);
final accountProfile = {};
accountProfile.addAll(getIt.get<UserAccount>().toMap());
accountProfile.addAll(getIt.get<UserProfile>().toMap());
getIt.get<WebSocketManager>().channel.sink.add(json.encode({
'event': 'friend-added',
'friendId': widget.apply.recipient,
'receiverId': widget.apply.applicant,
'setting': widget.apply.setting,
'accountProfile': accountProfile,
}));
getIt.get<ApplyList>().removeAt(widget.index);
widget.refreshCallback();
getIt.get<Contact>().addFriend(widget.apply.applicant!, recipientSetting);
getIt.get<ContactAccountProfile>().addAccountProfile(
widget.apply.applicant!,
widget.accountProfile,
);
widget.refreshCallback();
} else {
// ignore: use_build_context_synchronously
CherryToast.error(

View File

@ -1,5 +1,3 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cherry_toast/cherry_toast.dart';
import 'package:flutter/material.dart';

View File

@ -36,6 +36,10 @@ class _ContactScreenState extends State<ContactScreen> with GetItStateMixin {
List<String> friendGroups = watchOnly(
(Contact contact) => contact.friendGroups,
);
int applyCount = watchOnly(
(ApplyList applyList) => applyList.count,
);
int friendCount = friends.length;
// Create a localkey, use to generate the custom scroll view,
// or there will be a error: "Duplicate GlobalKey detected in widget tree."
@ -84,6 +88,7 @@ class _ContactScreenState extends State<ContactScreen> with GetItStateMixin {
SliverFixedExtentList(
delegate: SliverChildListDelegate(
[
// apply list
TextButton(
onPressed: () async {
// await getApplicantInfo(get<ApplyList>().applicantIds);
@ -96,13 +101,13 @@ class _ContactScreenState extends State<ContactScreen> with GetItStateMixin {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
badges.Badge(
showBadge: get<ApplyList>().count > 0,
showBadge: applyCount > 0,
badgeStyle: const badges.BadgeStyle(
badgeColor: kErrorColor,
elevation: 12,
),
badgeContent: Text(
get<ApplyList>().count.toString(),
applyCount.toString(),
style: TextStyle(
fontSize: 11,
color:
@ -169,20 +174,22 @@ class _ContactScreenState extends State<ContactScreen> with GetItStateMixin {
groupName: friendGroups[index],
);
} else if (_shows['allFriends']!) {
String friendId = friends.keys.toList()[index];
// print(friends);
return FriendTile(
key: ValueKey(
friends.keys.toList()[index],
friendId,
),
friendId: friends.keys.toList()[index],
friendId: friendId,
);
} else {
return GroupChatTile();
}
},
childCount: _shows['friendGroups']!
? get<Contact>().friendGroups.length
? friendGroups.length
: _shows['allFriends']!
? get<Contact>().friends.length
? friendCount
: get<Contact>().groupChats.length,
),
),

View File

@ -1,10 +1,10 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:together_mobile/common/constants.dart';
import 'package:together_mobile/screens/contact_add/contact_add_friend_screen/components/add_bottom_sheet.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:together_mobile/common/constants.dart';
import 'package:together_mobile/request/server.dart';
import 'package:together_mobile/screens/contact_add/contact_add_friend_screen/components/add_bottom_sheet.dart';
import 'package:together_mobile/screens/friend_profile/components/friend_profile_card.dart';
class AddFriendScreen extends StatelessWidget {
@ -43,8 +43,9 @@ class AddFriendScreen extends StatelessWidget {
radius: 60,
)
: CircleAvatar(
backgroundImage:
FileImage(File(accountProfile['avatar']!)),
backgroundImage: CachedNetworkImageProvider(
'$avatarsUrl/${accountProfile['avatar']!}',
),
radius: 60,
),
const Chip(

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
@ -8,6 +10,7 @@ import 'package:together_mobile/common/constants.dart';
import 'package:together_mobile/models/contact_model.dart';
import 'package:together_mobile/models/init_get_it.dart';
import 'package:together_mobile/models/user_model.dart';
import 'package:together_mobile/models/websocket_model.dart';
import 'package:together_mobile/request/apply.dart';
import 'package:together_mobile/request/server.dart';
@ -208,6 +211,23 @@ class _AddBottomSheetState extends State<AddBottomSheet> {
// ignore: use_build_context_synchronously
context.pop();
getIt.get<WebSocketManager>().channel.sink.add(
json.encode(
{
'event': 'apply-friend',
'relation': 0,
'applicant': getIt.get<UserAccount>().id,
'recipient': widget.friendId,
'hello': hello,
'groupChatId': '',
'setting': {
'friendRemark': friendRemark,
'friendGroup': _selectedGroup,
},
},
),
);
// ignore: use_build_context_synchronously
CherryToast.success(
title: const Text(

View File

@ -179,16 +179,14 @@ class _SearchNewScreenState extends State<SearchNewScreen> {
: res['data']['gender'] == 'man'
? ''
: '',
'avatar': res['data']['avatar'] == null
? ''
: await getAvatarPath(res['data']['avatar']),
'avatar': res['data']['avatar'] ?? '',
};
if (accountProfile['avatar']!.isNotEmpty) {
if (!File(accountProfile['avatar']!).existsSync()) {
var data = await downloadAvatar(res['data']['avatar']);
await File(accountProfile['avatar']!).writeAsBytes(data);
}
}
// if (accountProfile['avatar']!.isNotEmpty) {
// if (!File(accountProfile['avatar']!).existsSync()) {
// var data = await downloadAvatar(res['data']['avatar']);
// await File(accountProfile['avatar']!).writeAsBytes(data);
// }
// }
// ignore: use_build_context_synchronously
context.pushNamed('AddFriend', queryParameters: accountProfile);
}

View File

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'package:cherry_toast/cherry_toast.dart';
import 'package:flutter/material.dart';
@ -8,6 +9,7 @@ import 'package:together_mobile/common/constants.dart';
import 'package:together_mobile/models/init_get_it.dart';
import 'package:together_mobile/models/contact_model.dart';
import 'package:together_mobile/models/user_model.dart';
import 'package:together_mobile/models/websocket_model.dart';
import 'package:together_mobile/request/contact.dart';
class FriendSettingScreen extends StatefulWidget {
@ -355,6 +357,13 @@ class _FriendSettingScreenState extends State<FriendSettingScreen> {
'Contact',
queryParameters: {'deletedFriendId': widget.friendId},
);
getIt.get<WebSocketManager>().channel.sink.add(json.encode({
'event': 'friend-deleted',
'friendId': getIt.get<UserAccount>().id,
'receiverId': widget.friendId,
}));
// ignore: use_build_context_synchronously
CherryToast.success(
title: const Text(

View File

@ -1,6 +1,11 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:together_mobile/common/constants.dart';
import 'package:together_mobile/models/contact_model.dart';
import 'package:together_mobile/models/init_get_it.dart';
import 'package:together_mobile/models/user_model.dart';
import 'package:together_mobile/request/server.dart';
class MessageBubble extends StatelessWidget {
const MessageBubble({
@ -43,9 +48,25 @@ class MessageBubble extends StatelessWidget {
senderId == contactId ? TextDirection.ltr : TextDirection.rtl,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
child: Image.asset('assets/images/user_2.png'),
),
senderId == contactId
? getIt
.get<ContactAccountProfile>()
.friends[contactId]!
.avatar
.isEmpty
? CircleAvatar(
child: Image.asset('assets/images/user_2.png'),
)
: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(
'$avatarsUrl/${getIt.get<ContactAccountProfile>().friends[contactId]!.avatar}',
),
)
: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(
'$avatarsUrl/${getIt.get<UserProfile>().avatar}',
),
),
const SizedBox(
width: 10,
),

View File

@ -192,7 +192,7 @@ class _MessageInputBoxState extends State<MessageInputBox> {
if (chatSetting == null) {
_chatBox.put(
widget.contactId,
ChatSetting(widget.contactId, false, true, false),
ChatSetting(widget.contactId, false, true, false, now),
);
}

View File

@ -81,6 +81,17 @@ class _MessageScreenState extends State<MessageScreen> {
valueListenable: Hive.box<MessageT>('message_${widget.contactId}')
.listenable(),
builder: (context, value, _) {
Future.delayed(
const Duration(
milliseconds: 50,
),
() => _controller.animateTo(
_controller.position.maxScrollExtent,
duration: const Duration(milliseconds: 500),
curve: Curves.linear,
),
);
return ListView.builder(
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:together_mobile/models/apply_list_model.dart';
import 'package:together_mobile/models/init_get_it.dart';
import 'package:together_mobile/models/token_model.dart';
@ -24,6 +25,7 @@ class SettingScreen extends StatelessWidget {
getIt.get<UserAccount>().clean();
getIt.get<UserProfile>().clean();
getIt.get<ApplyList>().clean();
Hive.close();
// ignore: use_build_context_synchronously
context.go('/welcome');
},