mirror of
https://github.com/Linloir/Simple-TCP-Client.git
synced 2025-12-17 00:38:11 +08:00
More Codes
- Search Page (ugly)
This commit is contained in:
parent
43cb79f6ab
commit
f93a95d141
69
lib/common/avatar/avatar.dart
Normal file
69
lib/common/avatar/avatar.dart
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 21:49:53
|
||||
* @LastEditTime : 2022-10-13 22:17:17
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:tcp_client/common/avatar/cubit/avatar_cubit.dart';
|
||||
import 'package:tcp_client/common/avatar/cubit/avatar_state.dart';
|
||||
import 'package:tcp_client/repositories/user_repository/user_repository.dart';
|
||||
|
||||
class UserAvatar extends StatelessWidget {
|
||||
const UserAvatar({required this.userid, super.key});
|
||||
|
||||
final int userid;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<AvatarCubit, AvatarState>(
|
||||
bloc: AvatarCubit(
|
||||
userid: userid,
|
||||
userRepository: context.read<UserRepository>()
|
||||
),
|
||||
builder: (context, state) {
|
||||
if(state.userInfo.avatarEncoded == null) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[800],
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
boxShadow: [BoxShadow(blurRadius: 10.0, color: Colors.grey[850]!.withOpacity(0.15))]
|
||||
),
|
||||
child: Text(
|
||||
state.userInfo.userName[0],
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w300,
|
||||
color: Colors.white,
|
||||
shadows: [Shadow(blurRadius: 5.0, color: Colors.white.withOpacity(0.15))]
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
boxShadow: [BoxShadow(blurRadius: 10.0, color: Colors.grey[850]!.withOpacity(0.15))]
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
child: OverflowBox(
|
||||
alignment: Alignment.center,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
child: Image.memory(base64.decode(state.userInfo.avatarEncoded!)),
|
||||
),
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
28
lib/common/avatar/cubit/avatar_cubit.dart
Normal file
28
lib/common/avatar/cubit/avatar_cubit.dart
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 21:50:14
|
||||
* @LastEditTime : 2022-10-13 22:03:01
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:tcp_client/common/avatar/cubit/avatar_state.dart';
|
||||
import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
||||
import 'package:tcp_client/repositories/user_repository/user_repository.dart';
|
||||
|
||||
class AvatarCubit extends Cubit<AvatarState> {
|
||||
AvatarCubit({
|
||||
required int userid,
|
||||
required this.userRepository
|
||||
}): super(AvatarState(userInfo: userRepository.getUserInfo(userid: userid))) {
|
||||
userRepository.userInfoStreamBroadcast.listen(onFetchedUserInfo);
|
||||
}
|
||||
|
||||
final UserRepository userRepository;
|
||||
|
||||
void onFetchedUserInfo(UserInfo userInfo) {
|
||||
if(userInfo.userID == state.userInfo.userID) {
|
||||
emit(AvatarState(userInfo: userInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
20
lib/common/avatar/cubit/avatar_state.dart
Normal file
20
lib/common/avatar/cubit/avatar_state.dart
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 21:50:07
|
||||
* @LastEditTime : 2022-10-13 22:02:11
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
||||
|
||||
class AvatarState extends Equatable {
|
||||
const AvatarState({
|
||||
required this.userInfo,
|
||||
});
|
||||
|
||||
final UserInfo userInfo;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [userInfo.userID, userInfo.avatarEncoded];
|
||||
}
|
||||
28
lib/common/username/cubit/username_cubit.dart
Normal file
28
lib/common/username/cubit/username_cubit.dart
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 21:50:14
|
||||
* @LastEditTime : 2022-10-13 22:05:52
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:tcp_client/common/username/cubit/username_state.dart';
|
||||
import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
||||
import 'package:tcp_client/repositories/user_repository/user_repository.dart';
|
||||
|
||||
class UsernameCubit extends Cubit<UsernameState> {
|
||||
UsernameCubit({
|
||||
required int userid,
|
||||
required this.userRepository
|
||||
}): super(UsernameState(userInfo: userRepository.getUserInfo(userid: userid))) {
|
||||
userRepository.userInfoStreamBroadcast.listen(onFetchedUserInfo);
|
||||
}
|
||||
|
||||
final UserRepository userRepository;
|
||||
|
||||
void onFetchedUserInfo(UserInfo userInfo) {
|
||||
if(userInfo.userID == state.userInfo.userID) {
|
||||
emit(UsernameState(userInfo: userInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
20
lib/common/username/cubit/username_state.dart
Normal file
20
lib/common/username/cubit/username_state.dart
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 21:50:07
|
||||
* @LastEditTime : 2022-10-13 22:05:43
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
||||
|
||||
class UsernameState extends Equatable {
|
||||
const UsernameState({
|
||||
required this.userInfo,
|
||||
});
|
||||
|
||||
final UserInfo userInfo;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [userInfo.userID, userInfo.avatarEncoded];
|
||||
}
|
||||
42
lib/common/username/username.dart
Normal file
42
lib/common/username/username.dart
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 22:05:12
|
||||
* @LastEditTime : 2022-10-13 22:21:42
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:tcp_client/common/username/cubit/username_cubit.dart';
|
||||
import 'package:tcp_client/common/username/cubit/username_state.dart';
|
||||
import 'package:tcp_client/repositories/user_repository/user_repository.dart';
|
||||
|
||||
class UserNameText extends StatelessWidget {
|
||||
const UserNameText({
|
||||
required this.userid,
|
||||
this.fontWeight = FontWeight.normal,
|
||||
super.key
|
||||
});
|
||||
|
||||
final int userid;
|
||||
final FontWeight fontWeight;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<UsernameCubit, UsernameState>(
|
||||
bloc: UsernameCubit(
|
||||
userid: userid,
|
||||
userRepository: context.read<UserRepository>()
|
||||
),
|
||||
builder: (context, state) {
|
||||
return Text(
|
||||
state.userInfo.userName,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: fontWeight
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 14:02:28
|
||||
* @LastEditTime : 2022-10-13 16:43:49
|
||||
* @LastEditTime : 2022-10-13 23:02:04
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 11:05:08
|
||||
* @LastEditTime : 2022-10-13 16:55:48
|
||||
* @LastEditTime : 2022-10-13 23:02:55
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -15,9 +15,11 @@ import 'package:tcp_client/home/view/message_page/cubit/msg_list_cubit.dart';
|
||||
import 'package:tcp_client/home/view/message_page/mesage_page.dart';
|
||||
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart';
|
||||
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
|
||||
import 'package:tcp_client/repositories/user_repository/user_repository.dart';
|
||||
import 'package:tcp_client/search/search_page.dart';
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
HomePage({
|
||||
const HomePage({
|
||||
required this.localServiceRepository,
|
||||
required this.tcpRepository,
|
||||
super.key
|
||||
@ -26,8 +28,6 @@ class HomePage extends StatelessWidget {
|
||||
final LocalServiceRepository localServiceRepository;
|
||||
final TCPRepository tcpRepository;
|
||||
|
||||
final PageController _controller = PageController();
|
||||
|
||||
static Route<void> route({
|
||||
required LocalServiceRepository localServiceRepository,
|
||||
required TCPRepository tcpRepository
|
||||
@ -38,47 +38,98 @@ class HomePage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
return MultiRepositoryProvider(
|
||||
providers: [
|
||||
BlocProvider<MessageListCubit>(
|
||||
create: (context) => MessageListCubit(
|
||||
localServiceRepository: localServiceRepository,
|
||||
tcpRepository: tcpRepository
|
||||
),
|
||||
),
|
||||
BlocProvider<ContactCubit>(
|
||||
create: (context) => ContactCubit(
|
||||
RepositoryProvider<UserRepository>(
|
||||
create: (context) => UserRepository(
|
||||
localServiceRepository: localServiceRepository,
|
||||
tcpRepository: tcpRepository
|
||||
),
|
||||
),
|
||||
BlocProvider<HomeCubit>(
|
||||
create: (context) => HomeCubit(
|
||||
localServiceRepository: localServiceRepository,
|
||||
tcpRepository: tcpRepository
|
||||
),
|
||||
)
|
||||
RepositoryProvider<LocalServiceRepository>.value(value: localServiceRepository),
|
||||
RepositoryProvider<TCPRepository>.value(value: tcpRepository),
|
||||
],
|
||||
child: BlocListener<HomeCubit, HomeState>(
|
||||
listenWhen:(previous, current) => current.page != previous.page,
|
||||
listener: (context, state) {
|
||||
_controller.animateToPage(
|
||||
state.page.value,
|
||||
duration: const Duration(milliseconds: 375),
|
||||
curve: Curves.easeInOutCubicEmphasized
|
||||
);
|
||||
},
|
||||
child: Scaffold(
|
||||
body: PageView(
|
||||
controller: _controller,
|
||||
children: const [
|
||||
MessagePage(),
|
||||
ContactPage()
|
||||
],
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<MessageListCubit>(
|
||||
create: (context) => MessageListCubit(
|
||||
localServiceRepository: localServiceRepository,
|
||||
tcpRepository: tcpRepository
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
BlocProvider<ContactCubit>(
|
||||
create: (context) => ContactCubit(
|
||||
localServiceRepository: localServiceRepository,
|
||||
tcpRepository: tcpRepository
|
||||
),
|
||||
),
|
||||
BlocProvider<HomeCubit>(
|
||||
create: (context) => HomeCubit(
|
||||
localServiceRepository: localServiceRepository,
|
||||
tcpRepository: tcpRepository
|
||||
),
|
||||
)
|
||||
],
|
||||
child: HomePageView(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomePageView extends StatelessWidget {
|
||||
HomePageView({super.key});
|
||||
|
||||
final PageController _controller = PageController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<HomeCubit, HomeState>(
|
||||
listenWhen:(previous, current) => current.page != previous.page,
|
||||
listener: (context, state) {
|
||||
_controller.animateToPage(
|
||||
state.page.value,
|
||||
duration: const Duration(milliseconds: 375),
|
||||
curve: Curves.easeInOutCubicEmphasized
|
||||
);
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: BlocBuilder<HomeCubit, HomeState>(
|
||||
builder: (context, state) {
|
||||
return Text(
|
||||
state.page.literal,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.search_rounded),
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(SearchPage.route(
|
||||
localServiceRepository: context.read<LocalServiceRepository>(),
|
||||
tcpRepository: context.read<TCPRepository>(),
|
||||
userRepository: context.read<UserRepository>()
|
||||
));
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: BlocBuilder<HomeCubit, HomeState>(
|
||||
builder:(context, state) => PageView(
|
||||
controller: _controller,
|
||||
onPageChanged: (value) => context.read<HomeCubit>().switchPage(HomePagePosition.fromValue(value)),
|
||||
children: const [
|
||||
MessagePage(),
|
||||
ContactPage()
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 23:36:07
|
||||
* @LastEditTime : 2022-10-13 16:10:57
|
||||
* @LastEditTime : 2022-10-13 22:59:25
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -18,36 +18,39 @@ class ContactPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ContactCubit, ContactState>(
|
||||
builder: (context, state) {
|
||||
return AzListView(
|
||||
data: state.indexedData,
|
||||
itemCount: state.contacts.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ContactTile(
|
||||
userInfo: state.contacts[index],
|
||||
);
|
||||
},
|
||||
physics: const BouncingScrollPhysics(),
|
||||
susItemBuilder: (context, index) {
|
||||
return Container(
|
||||
height: 40,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
color: Colors.grey[200],
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
ContactModel(userInfo: state.contacts[index]).getSuspensionTag(),
|
||||
softWrap: false,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: Colors.grey[700],
|
||||
return Container(
|
||||
color: Colors.green,
|
||||
child: BlocBuilder<ContactCubit, ContactState>(
|
||||
builder: (context, state) {
|
||||
return AzListView(
|
||||
data: state.indexedData,
|
||||
itemCount: state.contacts.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ContactTile(
|
||||
userInfo: state.contacts[index],
|
||||
);
|
||||
},
|
||||
physics: const BouncingScrollPhysics(),
|
||||
susItemBuilder: (context, index) {
|
||||
return Container(
|
||||
height: 40,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
color: Colors.grey[200],
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
ContactModel(userInfo: state.contacts[index]).getSuspensionTag(),
|
||||
softWrap: false,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 14:02:00
|
||||
* @LastEditTime : 2022-10-13 16:44:03
|
||||
* @LastEditTime : 2022-10-13 22:26:07
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -32,7 +32,7 @@ class ContactTile extends StatelessWidget {
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(ChatPage.route(userInfo: userInfo));
|
||||
context.read<MessageListCubit>().addEmptyMessageOf(user: userInfo);
|
||||
context.read<MessageListCubit>().addEmptyMessageOf(targetUser: userInfo.userID);
|
||||
context.read<HomeCubit>().switchPage(HomePagePosition.message);
|
||||
},
|
||||
),
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 23:38:31
|
||||
* @LastEditTime : 2022-10-13 16:12:53
|
||||
* @LastEditTime : 2022-10-13 22:27:29
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -25,11 +25,11 @@ class MessageListCubit extends Cubit<MessageListState> {
|
||||
final LocalServiceRepository localServiceRepository;
|
||||
final TCPRepository tcpRepository;
|
||||
|
||||
void addEmptyMessageOf({required UserInfo user}) {
|
||||
if(state.messageList.any((element) => element.userInfo.userID == user.userID)) {
|
||||
void addEmptyMessageOf({required int targetUser}) {
|
||||
if(state.messageList.any((element) => element.targetUser == targetUser)) {
|
||||
return;
|
||||
}
|
||||
var newList = [MessageInfo(userInfo: user)];
|
||||
var newList = [MessageInfo(targetUser: targetUser)];
|
||||
emit(MessageListState(messageList: newList..addAll(state.messageList)));
|
||||
}
|
||||
|
||||
@ -50,12 +50,9 @@ class MessageListCubit extends Cubit<MessageListState> {
|
||||
//therefore only insert to map if the target user does not exist
|
||||
if(!addedUserSet.contains(targetUser)) {
|
||||
addedUserSet.add(targetUser);
|
||||
var targetUserInfo = await localServiceRepository.fetchUserInfoViaID(userid: targetUser);
|
||||
//TODO: Maybe need to add API in tcp repository to fetch user info via id
|
||||
targetUserInfo ??= UserInfo(userid: targetUser, username: targetUser.toString());
|
||||
//Create message info
|
||||
latestMessages.add(MessageInfo(
|
||||
userInfo: targetUserInfo,
|
||||
targetUser: targetUser,
|
||||
message: message
|
||||
));
|
||||
}
|
||||
@ -74,12 +71,9 @@ class MessageListCubit extends Cubit<MessageListState> {
|
||||
var targetUser = response.message.senderID == curUser ?
|
||||
response.message.recieverID :
|
||||
response.message.senderID;
|
||||
var targetUserInfo = await localServiceRepository.fetchUserInfoViaID(userid: targetUser);
|
||||
//TODO: Maybe need to add API in tcp repository to fetch user info via id
|
||||
targetUserInfo ??= UserInfo(userid: targetUser, username: targetUser.toString());
|
||||
emit(state.updateWithSingle(
|
||||
messageInfo: MessageInfo(
|
||||
userInfo: targetUserInfo,
|
||||
targetUser: targetUser,
|
||||
message: response.message
|
||||
)
|
||||
));
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 23:37:49
|
||||
* @LastEditTime : 2022-10-13 11:09:54
|
||||
* @LastEditTime : 2022-10-13 22:24:22
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -22,7 +22,7 @@ class MessageListState extends Equatable {
|
||||
}) {
|
||||
var newList = <MessageInfo>[messageInfo];
|
||||
for(var msgInfo in messageList) {
|
||||
if(msgInfo.userInfo.userID == messageInfo.userInfo.userID) {
|
||||
if(msgInfo.targetUser == messageInfo.targetUser) {
|
||||
continue;
|
||||
}
|
||||
newList.add(msgInfo);
|
||||
@ -41,11 +41,11 @@ class MessageListState extends Equatable {
|
||||
insertListIndex < orderedNewMessages.length &&
|
||||
origListIndex < messageList.length
|
||||
) {
|
||||
if(addedUsers.contains(orderedNewMessages[insertListIndex].userInfo.userID)) {
|
||||
if(addedUsers.contains(orderedNewMessages[insertListIndex].targetUser)) {
|
||||
insertListIndex += 1;
|
||||
continue;
|
||||
}
|
||||
if(addedUsers.contains(messageList[origListIndex].userInfo.userID)) {
|
||||
if(addedUsers.contains(messageList[origListIndex].targetUser)) {
|
||||
origListIndex += 1;
|
||||
continue;
|
||||
}
|
||||
@ -54,35 +54,35 @@ class MessageListState extends Equatable {
|
||||
(orderedNewMessages[insertListIndex].message?.timeStamp ?? 0)
|
||||
) {
|
||||
newList.add(messageList[origListIndex]);
|
||||
addedUsers.add(messageList[origListIndex].userInfo.userID);
|
||||
addedUsers.add(messageList[origListIndex].targetUser);
|
||||
origListIndex += 1;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
newList.add(orderedNewMessages[insertListIndex]);
|
||||
addedUsers.add(orderedNewMessages[insertListIndex].userInfo.userID);
|
||||
addedUsers.add(orderedNewMessages[insertListIndex].targetUser);
|
||||
insertListIndex += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
//Add the messages left
|
||||
while(origListIndex < messageList.length) {
|
||||
if(addedUsers.contains(messageList[origListIndex].userInfo.userID)) {
|
||||
if(addedUsers.contains(messageList[origListIndex].targetUser)) {
|
||||
origListIndex += 1;
|
||||
continue;
|
||||
}
|
||||
newList.add(messageList[origListIndex]);
|
||||
addedUsers.add(messageList[origListIndex].userInfo.userID);
|
||||
addedUsers.add(messageList[origListIndex].targetUser);
|
||||
origListIndex += 1;
|
||||
continue;
|
||||
}
|
||||
while(insertListIndex < orderedNewMessages.length) {
|
||||
if(addedUsers.contains(orderedNewMessages[insertListIndex].userInfo.userID)) {
|
||||
if(addedUsers.contains(orderedNewMessages[insertListIndex].targetUser)) {
|
||||
origListIndex += 1;
|
||||
continue;
|
||||
}
|
||||
newList.add(orderedNewMessages[insertListIndex]);
|
||||
addedUsers.add(orderedNewMessages[insertListIndex].userInfo.userID);
|
||||
addedUsers.add(orderedNewMessages[insertListIndex].targetUser);
|
||||
origListIndex += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 11:05:18
|
||||
* @LastEditTime : 2022-10-13 16:11:24
|
||||
* @LastEditTime : 2022-10-13 22:59:10
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -18,19 +18,22 @@ class MessagePage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<MessageListCubit, MessageListState>(
|
||||
builder: (context, state) {
|
||||
return ListView.separated(
|
||||
itemBuilder: (context, index) {
|
||||
return MessageTile(
|
||||
userInfo: state.messageList[index].userInfo,
|
||||
message: state.messageList[index].message,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const Divider(
|
||||
height: 0.5,
|
||||
);
|
||||
},
|
||||
itemCount: state.messageList.length
|
||||
return Container(
|
||||
color: Colors.blue,
|
||||
child: ListView.separated(
|
||||
itemBuilder: (context, index) {
|
||||
return MessageTile(
|
||||
userID: state.messageList[index].targetUser,
|
||||
message: state.messageList[index].message,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const Divider(
|
||||
height: 0.5,
|
||||
);
|
||||
},
|
||||
itemCount: state.messageList.length
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 23:48:54
|
||||
* @LastEditTime : 2022-10-12 23:50:17
|
||||
* @LastEditTime : 2022-10-13 22:24:01
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -11,13 +11,13 @@ import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
||||
|
||||
class MessageInfo extends Equatable {
|
||||
final Message? message;
|
||||
final UserInfo userInfo;
|
||||
final int targetUser;
|
||||
|
||||
const MessageInfo({
|
||||
this.message,
|
||||
required this.userInfo
|
||||
required this.targetUser
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message?.contentmd5, userInfo];
|
||||
List<Object?> get props => [message?.contentmd5, targetUser];
|
||||
}
|
||||
|
||||
@ -1,24 +1,26 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 13:17:52
|
||||
* @LastEditTime : 2022-10-13 14:57:14
|
||||
* @LastEditTime : 2022-10-13 22:23:31
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tcp_client/common/avatar/avatar.dart';
|
||||
import 'package:tcp_client/common/username/username.dart';
|
||||
import 'package:tcp_client/repositories/common_models/message.dart';
|
||||
import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
||||
|
||||
class MessageTile extends StatelessWidget {
|
||||
const MessageTile({
|
||||
required this.userInfo,
|
||||
required this.userID,
|
||||
this.message,
|
||||
super.key
|
||||
});
|
||||
|
||||
final UserInfo userInfo;
|
||||
final int userID;
|
||||
final Message? message;
|
||||
|
||||
@override
|
||||
@ -33,37 +35,38 @@ class MessageTile extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if(userInfo.avatarEncoded != null && userInfo.avatarEncoded!.isEmpty)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
border: Border.all(
|
||||
color: Colors.grey[700]!,
|
||||
width: 1.0
|
||||
)
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
child: OverflowBox(
|
||||
alignment: Alignment.center,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
child: Image.memory(base64Decode(userInfo.avatarEncoded!)),
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
if(userInfo.avatarEncoded == null || userInfo.avatarEncoded!.isEmpty)
|
||||
Container(
|
||||
color: Colors.grey,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
border: Border.all(
|
||||
color: Colors.grey[700]!,
|
||||
width: 1.0
|
||||
)
|
||||
),
|
||||
),
|
||||
UserAvatar(userid: userID),
|
||||
// if(userInfo.avatarEncoded != null && userInfo.avatarEncoded!.isEmpty)
|
||||
// Container(
|
||||
// decoration: BoxDecoration(
|
||||
// borderRadius: BorderRadius.circular(5.0),
|
||||
// border: Border.all(
|
||||
// color: Colors.grey[700]!,
|
||||
// width: 1.0
|
||||
// )
|
||||
// ),
|
||||
// child: ClipRRect(
|
||||
// borderRadius: BorderRadius.circular(5.0),
|
||||
// child: OverflowBox(
|
||||
// alignment: Alignment.center,
|
||||
// child: FittedBox(
|
||||
// fit: BoxFit.fitWidth,
|
||||
// child: Image.memory(base64Decode(userInfo.avatarEncoded!)),
|
||||
// ),
|
||||
// )
|
||||
// ),
|
||||
// ),
|
||||
// if(userInfo.avatarEncoded == null || userInfo.avatarEncoded!.isEmpty)
|
||||
// Container(
|
||||
// color: Colors.grey,
|
||||
// decoration: BoxDecoration(
|
||||
// borderRadius: BorderRadius.circular(5.0),
|
||||
// border: Border.all(
|
||||
// color: Colors.grey[700]!,
|
||||
// width: 1.0
|
||||
// )
|
||||
// ),
|
||||
// ),
|
||||
const SizedBox(width: 12,),
|
||||
Expanded(
|
||||
child: Column(
|
||||
@ -73,13 +76,7 @@ class MessageTile extends StatelessWidget {
|
||||
vertical: 8.0,
|
||||
horizontal: 0
|
||||
),
|
||||
child: Text(
|
||||
userInfo.userName,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
child: UserNameText(userid: userID, fontWeight: FontWeight.bold,)
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 09:44:03
|
||||
* @LastEditTime : 2022-10-12 13:56:56
|
||||
* @LastEditTime : 2022-10-13 23:04:49
|
||||
* @Description : Abstract TCP request class
|
||||
*/
|
||||
|
||||
@ -108,10 +108,19 @@ class LogoutRequest extends TCPRequest {
|
||||
}
|
||||
|
||||
class GetProfileRequest extends TCPRequest {
|
||||
const GetProfileRequest({required int? token}): super(type: TCPRequestType.profile, token: token);
|
||||
const GetProfileRequest({
|
||||
required int userid,
|
||||
required int? token
|
||||
}): _userid = userid, super(type: TCPRequestType.profile, token: token);
|
||||
|
||||
final int _userid;
|
||||
|
||||
int get userid => _userid;
|
||||
|
||||
@override
|
||||
Map<String, Object?> get body => {};
|
||||
Map<String, Object?> get body => {
|
||||
'userid': _userid
|
||||
};
|
||||
}
|
||||
|
||||
class ModifyPasswordRequest extends TCPRequest {
|
||||
@ -205,14 +214,15 @@ class FetchFileRequest extends TCPRequest {
|
||||
class SearchUserRequest extends TCPRequest {
|
||||
final String _username;
|
||||
|
||||
const SearchUserRequest({required String username, required int? token}): _username = username, super(type: TCPRequestType.searchUser, token: token);
|
||||
SearchUserRequest({required String username, required int? token}):
|
||||
_username = base64.encode(utf8.encode(username)), super(type: TCPRequestType.searchUser, token: token);
|
||||
|
||||
@override
|
||||
Map<String, Object?> get body => {
|
||||
'username': _username,
|
||||
};
|
||||
|
||||
String get username => _username;
|
||||
String get username => utf8.decode(base64.decode(_username));
|
||||
}
|
||||
|
||||
class AddContactRequest extends TCPRequest {
|
||||
|
||||
79
lib/repositories/user_repository/user_repository.dart
Normal file
79
lib/repositories/user_repository/user_repository.dart
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 20:18:14
|
||||
* @LastEditTime : 2022-10-13 21:26:16
|
||||
* @Description : Repository to cache user info
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
||||
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart';
|
||||
import 'package:tcp_client/repositories/tcp_repository/models/tcp_request.dart';
|
||||
import 'package:tcp_client/repositories/tcp_repository/models/tcp_response.dart';
|
||||
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
|
||||
|
||||
class UserRepository {
|
||||
final Map<int, UserInfo> users = {};
|
||||
final LocalServiceRepository localServiceRepository;
|
||||
final TCPRepository tcpRepository;
|
||||
|
||||
final StreamController<UserInfo> _userInfoStreamController = StreamController();
|
||||
Stream<UserInfo>? _userInfoStreamBroadcast;
|
||||
Stream<UserInfo> get userInfoStreamBroadcast {
|
||||
_userInfoStreamBroadcast ??= _userInfoStreamController.stream.asBroadcastStream();
|
||||
return _userInfoStreamBroadcast!;
|
||||
}
|
||||
|
||||
UserRepository({
|
||||
required this.localServiceRepository,
|
||||
required this.tcpRepository
|
||||
}) {
|
||||
tcpRepository.responseStreamBroadcast.listen(_onResponse);
|
||||
}
|
||||
|
||||
Future<void> _onResponse(TCPResponse response) async {
|
||||
if(response.type == TCPResponseType.profile && response.status == TCPResponseStatus.ok) {
|
||||
response as GetProfileResponse;
|
||||
users.update(response.userInfo!.userID, (value) => response.userInfo!, ifAbsent: () => response.userInfo!);
|
||||
_userInfoStreamController.add(response.userInfo!);
|
||||
localServiceRepository.storeUserInfo(userInfo: response.userInfo!);
|
||||
}
|
||||
}
|
||||
|
||||
//Fetch user info
|
||||
//1. Check if the user info is in the map
|
||||
// if so, return the user, otherwise consult the database
|
||||
//2. If the database has the user info
|
||||
// add it to the map and stream, otherwise consult the tcp repository
|
||||
//3. Pass the control to tcp response handler
|
||||
UserInfo getUserInfo({required int userid}) {
|
||||
if(users.containsKey(userid)) {
|
||||
return users[userid]!;
|
||||
}
|
||||
Future<UserInfo?>(() async {
|
||||
//Consult the database for info
|
||||
return await localServiceRepository.fetchUserInfoViaID(userid: userid);
|
||||
}).then((userInfo) async {
|
||||
if(userInfo == null) {
|
||||
//Consult the tcp server for info
|
||||
tcpRepository.pushRequest(GetProfileRequest(
|
||||
userid: userid,
|
||||
token: (await SharedPreferences.getInstance()).getInt('token')
|
||||
));
|
||||
}
|
||||
else {
|
||||
//Add to map
|
||||
users.update(userid, (value) => userInfo, ifAbsent: () => userInfo);
|
||||
//Push to stream
|
||||
_userInfoStreamController.add(userInfo);
|
||||
}
|
||||
});
|
||||
//Return a mock userinfo
|
||||
return UserInfo(
|
||||
userid: userid,
|
||||
username: userid.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,78 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 17:09:25
|
||||
* @LastEditTime : 2022-10-13 17:09:26
|
||||
* @LastEditTime : 2022-10-13 23:25:20
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart';
|
||||
import 'package:tcp_client/repositories/tcp_repository/models/tcp_request.dart';
|
||||
import 'package:tcp_client/repositories/tcp_repository/models/tcp_response.dart';
|
||||
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
|
||||
import 'package:tcp_client/search/cubit/search_state.dart';
|
||||
import 'package:tcp_client/search/model/history_result.dart';
|
||||
import 'package:tcp_client/search/model/user_result.dart';
|
||||
|
||||
class SearchCubit extends Cubit<SearchState> {
|
||||
SearchCubit({
|
||||
required this.localServiceRepository,
|
||||
required this.tcpRepository
|
||||
}): super(const SearchState.empty()) {
|
||||
subscription = tcpRepository.responseStreamBroadcast.listen(_onResponse);
|
||||
}
|
||||
|
||||
final LocalServiceRepository localServiceRepository;
|
||||
final TCPRepository tcpRepository;
|
||||
late final StreamSubscription subscription;
|
||||
|
||||
void onKeyChanged(String newKey) {
|
||||
EasyDebounce.debounce(
|
||||
'Search',
|
||||
const Duration(milliseconds: 500),
|
||||
() => _performSearch(newKey)
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _performSearch(String newKey) async {
|
||||
tcpRepository.pushRequest(SearchUserRequest(
|
||||
username: newKey,
|
||||
token: (await SharedPreferences.getInstance()).getInt('token')
|
||||
));
|
||||
var histories = await localServiceRepository.findMessages(pattern: newKey);
|
||||
var currentUserID = (await SharedPreferences.getInstance()).getInt('userid');
|
||||
var historyResults = histories.map((msg) {
|
||||
var targetID = msg.senderID == currentUserID ? msg.recieverID : msg.senderID;
|
||||
return HistorySearchResult(contact: targetID, message: msg);
|
||||
}).toList();
|
||||
emit(state.copyWith(historyResults: historyResults));
|
||||
}
|
||||
|
||||
Future<void> _onResponse(TCPResponse response) async {
|
||||
switch(response.type) {
|
||||
case TCPResponseType.searchUser: {
|
||||
response as SearchUserResponse;
|
||||
//TODO: Maybe server search should be ambigious
|
||||
var userInfo = response.userInfo;
|
||||
emit(state.copyWith(
|
||||
userResults: [
|
||||
if(userInfo != null) UserSearchResult(userInfo: userInfo)
|
||||
]
|
||||
));
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
//Override dispose to cancel the subscription
|
||||
@override
|
||||
Future<void> close() {
|
||||
subscription.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,34 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 17:08:56
|
||||
* @LastEditTime : 2022-10-13 17:08:57
|
||||
* @LastEditTime : 2022-10-13 17:42:33
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:tcp_client/search/model/history_result.dart';
|
||||
import 'package:tcp_client/search/model/user_result.dart';
|
||||
|
||||
class SearchState extends Equatable {
|
||||
const SearchState({
|
||||
required this.historyResults,
|
||||
required this.userResults
|
||||
});
|
||||
const SearchState.empty(): historyResults = const [], userResults = const [];
|
||||
|
||||
final List<HistorySearchResult> historyResults;
|
||||
final List<UserSearchResult> userResults;
|
||||
|
||||
SearchState copyWith({
|
||||
List<HistorySearchResult>? historyResults,
|
||||
List<UserSearchResult>? userResults
|
||||
}) {
|
||||
return SearchState(
|
||||
historyResults: historyResults ?? this.historyResults,
|
||||
userResults: userResults ?? this.userResults
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [historyResults, userResults];
|
||||
}
|
||||
|
||||
22
lib/search/model/history_result.dart
Normal file
22
lib/search/model/history_result.dart
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 17:30:26
|
||||
* @LastEditTime : 2022-10-13 21:28:46
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:tcp_client/repositories/common_models/message.dart';
|
||||
|
||||
class HistorySearchResult extends Equatable {
|
||||
final int contact;
|
||||
final Message message;
|
||||
|
||||
const HistorySearchResult({
|
||||
required this.contact,
|
||||
required this.message
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [contact, message.contentmd5];
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 17:09:46
|
||||
* @LastEditTime : 2022-10-13 17:09:46
|
||||
* @Description :
|
||||
*/
|
||||
18
lib/search/model/user_result.dart
Normal file
18
lib/search/model/user_result.dart
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 17:30:36
|
||||
* @LastEditTime : 2022-10-13 22:39:25
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
||||
|
||||
class UserSearchResult extends Equatable {
|
||||
final UserInfo userInfo;
|
||||
|
||||
const UserSearchResult({required this.userInfo});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [userInfo.userID];
|
||||
}
|
||||
@ -1,21 +1,106 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 17:04:12
|
||||
* @LastEditTime : 2022-10-13 17:08:13
|
||||
* @LastEditTime : 2022-10-13 23:24:13
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart';
|
||||
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
|
||||
import 'package:tcp_client/repositories/user_repository/user_repository.dart';
|
||||
import 'package:tcp_client/search/cubit/search_cubit.dart';
|
||||
import 'package:tcp_client/search/cubit/search_state.dart';
|
||||
import 'package:tcp_client/search/view/history_tile.dart';
|
||||
import 'package:tcp_client/search/view/search_bar.dart';
|
||||
import 'package:tcp_client/search/view/user_tile.dart';
|
||||
|
||||
class SearchPage extends StatelessWidget {
|
||||
const SearchPage({super.key});
|
||||
const SearchPage({
|
||||
required this.localServiceRepository,
|
||||
required this.tcpRepository,
|
||||
required this.userRepository,
|
||||
super.key
|
||||
});
|
||||
|
||||
final LocalServiceRepository localServiceRepository;
|
||||
final TCPRepository tcpRepository;
|
||||
final UserRepository userRepository;
|
||||
|
||||
static Route<void> route({
|
||||
required LocalServiceRepository localServiceRepository,
|
||||
required TCPRepository tcpRepository,
|
||||
required UserRepository userRepository
|
||||
}) => MaterialPageRoute<void>(builder: (context) => SearchPage(
|
||||
localServiceRepository: localServiceRepository,
|
||||
tcpRepository: tcpRepository,
|
||||
userRepository: userRepository,
|
||||
));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const SearchBar(),
|
||||
return RepositoryProvider<UserRepository>.value(
|
||||
value: userRepository,
|
||||
child: BlocProvider<SearchCubit>(
|
||||
create: (context) => SearchCubit(
|
||||
localServiceRepository: localServiceRepository,
|
||||
tcpRepository: tcpRepository
|
||||
),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const SearchBar(),
|
||||
),
|
||||
body: BlocBuilder<SearchCubit, SearchState>(
|
||||
builder:(context, state) {
|
||||
return ListView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
children: [
|
||||
if(state.userResults.isNotEmpty)
|
||||
...[
|
||||
const SizedBox(height: 16.0,),
|
||||
const Text(
|
||||
'Users',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8.0,),
|
||||
...state.userResults.map((e) => UserTile(userInfo: e.userInfo))
|
||||
],
|
||||
if(state.historyResults.isNotEmpty)
|
||||
...[
|
||||
const SizedBox(height: 16.0,),
|
||||
const Text(
|
||||
'Histories',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8.0,),
|
||||
...state.historyResults.map((e) => HistoryTile(
|
||||
userID: e.contact,
|
||||
message: e.message
|
||||
))
|
||||
],
|
||||
if(state.historyResults.isEmpty && state.userResults.isEmpty)
|
||||
const Align(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'No result found',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
98
lib/search/view/history_tile.dart
Normal file
98
lib/search/view/history_tile.dart
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 21:41:49
|
||||
* @LastEditTime : 2022-10-13 22:31:37
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tcp_client/common/avatar/avatar.dart';
|
||||
import 'package:tcp_client/common/username/username.dart';
|
||||
import 'package:tcp_client/repositories/common_models/message.dart';
|
||||
|
||||
class HistoryTile extends StatelessWidget {
|
||||
const HistoryTile({
|
||||
required this.userID,
|
||||
required this.message,
|
||||
super.key
|
||||
});
|
||||
|
||||
final int userID;
|
||||
final Message message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16.0,
|
||||
horizontal: 24.0
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
UserAvatar(userid: userID),
|
||||
const SizedBox(width: 12,),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 0
|
||||
),
|
||||
child: UserNameText(userid: userID, fontWeight: FontWeight.bold,)
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4.0
|
||||
),
|
||||
child: Text(
|
||||
message.contentDecoded,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 0
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Text(
|
||||
getTimeStamp(message.timeStamp)
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String getTimeStamp(int timeStamp) {
|
||||
var date = DateTime.fromMillisecondsSinceEpoch(timeStamp);
|
||||
var weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||
//If date is today, return time
|
||||
if(date.day == DateTime.now().day) {
|
||||
return '${date.hour}:${date.minute}';
|
||||
}
|
||||
//If date is yesterday, return 'yesterday'
|
||||
if(date.day == DateTime.now().day - 1) {
|
||||
return 'yesterday';
|
||||
}
|
||||
//If date is within this week, return the weekday in english
|
||||
if(date.weekday < DateTime.now().weekday) {
|
||||
return weekdays[date.weekday - 1];
|
||||
}
|
||||
//Otherwise return the date in english
|
||||
return '${date.month}/${date.day}';
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 17:06:52
|
||||
* @LastEditTime : 2022-10-13 17:06:53
|
||||
* @LastEditTime : 2022-10-13 21:35:11
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:tcp_client/search/cubit/search_cubit.dart';
|
||||
|
||||
class SearchBar extends StatelessWidget {
|
||||
const SearchBar({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return
|
||||
return TextField(
|
||||
onChanged: (value) {
|
||||
context.read<SearchCubit>().onKeyChanged(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
73
lib/search/view/user_tile.dart
Normal file
73
lib/search/view/user_tile.dart
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 21:41:41
|
||||
* @LastEditTime : 2022-10-13 23:26:46
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
||||
|
||||
class UserTile extends StatelessWidget {
|
||||
const UserTile({
|
||||
required this.userInfo,
|
||||
super.key
|
||||
});
|
||||
|
||||
final UserInfo userInfo;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
if(userInfo.avatarEncoded != null && userInfo.avatarEncoded!.isEmpty)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
border: Border.all(
|
||||
color: Colors.grey[700]!,
|
||||
width: 1.0
|
||||
)
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
child: OverflowBox(
|
||||
alignment: Alignment.center,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
child: Image.memory(base64.decode(userInfo.avatarEncoded!)),
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
if(userInfo.avatarEncoded == null || userInfo.avatarEncoded!.isEmpty)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey,
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
border: Border.all(
|
||||
color: Colors.grey[700]!,
|
||||
width: 1.0
|
||||
)
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12,),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0
|
||||
),
|
||||
child: Text(
|
||||
userInfo.userName,
|
||||
style: const TextStyle(
|
||||
fontSize: 18.0
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -71,6 +71,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
easy_debounce:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: easy_debounce
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.2+1"
|
||||
equatable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@ -53,6 +53,7 @@ dependencies:
|
||||
pull_to_refresh: ^2.0.0
|
||||
azlistview: ^2.0.0
|
||||
lpinyin: ^2.0.3
|
||||
easy_debounce: ^2.0.2+1
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user