mirror of
https://github.com/Linloir/Simple-TCP-Client.git
synced 2025-12-17 08:48: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
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 14:02:28
|
* @Date : 2022-10-13 14:02:28
|
||||||
* @LastEditTime : 2022-10-13 16:43:49
|
* @LastEditTime : 2022-10-13 23:02:04
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-11 11:05:08
|
* @Date : 2022-10-11 11:05:08
|
||||||
* @LastEditTime : 2022-10-13 16:55:48
|
* @LastEditTime : 2022-10-13 23:02:55
|
||||||
* @Description :
|
* @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/home/view/message_page/mesage_page.dart';
|
||||||
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.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/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 {
|
class HomePage extends StatelessWidget {
|
||||||
HomePage({
|
const HomePage({
|
||||||
required this.localServiceRepository,
|
required this.localServiceRepository,
|
||||||
required this.tcpRepository,
|
required this.tcpRepository,
|
||||||
super.key
|
super.key
|
||||||
@ -26,8 +28,6 @@ class HomePage extends StatelessWidget {
|
|||||||
final LocalServiceRepository localServiceRepository;
|
final LocalServiceRepository localServiceRepository;
|
||||||
final TCPRepository tcpRepository;
|
final TCPRepository tcpRepository;
|
||||||
|
|
||||||
final PageController _controller = PageController();
|
|
||||||
|
|
||||||
static Route<void> route({
|
static Route<void> route({
|
||||||
required LocalServiceRepository localServiceRepository,
|
required LocalServiceRepository localServiceRepository,
|
||||||
required TCPRepository tcpRepository
|
required TCPRepository tcpRepository
|
||||||
@ -38,47 +38,98 @@ class HomePage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return MultiRepositoryProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider<MessageListCubit>(
|
RepositoryProvider<UserRepository>(
|
||||||
create: (context) => MessageListCubit(
|
create: (context) => UserRepository(
|
||||||
localServiceRepository: localServiceRepository,
|
|
||||||
tcpRepository: tcpRepository
|
|
||||||
),
|
|
||||||
),
|
|
||||||
BlocProvider<ContactCubit>(
|
|
||||||
create: (context) => ContactCubit(
|
|
||||||
localServiceRepository: localServiceRepository,
|
localServiceRepository: localServiceRepository,
|
||||||
tcpRepository: tcpRepository
|
tcpRepository: tcpRepository
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider<HomeCubit>(
|
RepositoryProvider<LocalServiceRepository>.value(value: localServiceRepository),
|
||||||
create: (context) => HomeCubit(
|
RepositoryProvider<TCPRepository>.value(value: tcpRepository),
|
||||||
localServiceRepository: localServiceRepository,
|
|
||||||
tcpRepository: tcpRepository
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
child: BlocListener<HomeCubit, HomeState>(
|
child: MultiBlocProvider(
|
||||||
listenWhen:(previous, current) => current.page != previous.page,
|
providers: [
|
||||||
listener: (context, state) {
|
BlocProvider<MessageListCubit>(
|
||||||
_controller.animateToPage(
|
create: (context) => MessageListCubit(
|
||||||
state.page.value,
|
localServiceRepository: localServiceRepository,
|
||||||
duration: const Duration(milliseconds: 375),
|
tcpRepository: tcpRepository
|
||||||
curve: Curves.easeInOutCubicEmphasized
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Scaffold(
|
|
||||||
body: PageView(
|
|
||||||
controller: _controller,
|
|
||||||
children: const [
|
|
||||||
MessagePage(),
|
|
||||||
ContactPage()
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
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
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-12 23:36:07
|
* @Date : 2022-10-12 23:36:07
|
||||||
* @LastEditTime : 2022-10-13 16:10:57
|
* @LastEditTime : 2022-10-13 22:59:25
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -18,36 +18,39 @@ class ContactPage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<ContactCubit, ContactState>(
|
return Container(
|
||||||
builder: (context, state) {
|
color: Colors.green,
|
||||||
return AzListView(
|
child: BlocBuilder<ContactCubit, ContactState>(
|
||||||
data: state.indexedData,
|
builder: (context, state) {
|
||||||
itemCount: state.contacts.length,
|
return AzListView(
|
||||||
itemBuilder: (context, index) {
|
data: state.indexedData,
|
||||||
return ContactTile(
|
itemCount: state.contacts.length,
|
||||||
userInfo: state.contacts[index],
|
itemBuilder: (context, index) {
|
||||||
);
|
return ContactTile(
|
||||||
},
|
userInfo: state.contacts[index],
|
||||||
physics: const BouncingScrollPhysics(),
|
);
|
||||||
susItemBuilder: (context, index) {
|
},
|
||||||
return Container(
|
physics: const BouncingScrollPhysics(),
|
||||||
height: 40,
|
susItemBuilder: (context, index) {
|
||||||
width: MediaQuery.of(context).size.width,
|
return Container(
|
||||||
padding: const EdgeInsets.only(left: 16.0),
|
height: 40,
|
||||||
color: Colors.grey[200],
|
width: MediaQuery.of(context).size.width,
|
||||||
alignment: Alignment.centerLeft,
|
padding: const EdgeInsets.only(left: 16.0),
|
||||||
child: Text(
|
color: Colors.grey[200],
|
||||||
ContactModel(userInfo: state.contacts[index]).getSuspensionTag(),
|
alignment: Alignment.centerLeft,
|
||||||
softWrap: false,
|
child: Text(
|
||||||
style: TextStyle(
|
ContactModel(userInfo: state.contacts[index]).getSuspensionTag(),
|
||||||
fontSize: 14.0,
|
softWrap: false,
|
||||||
color: Colors.grey[700],
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: Colors.grey[700],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 14:02:00
|
* @Date : 2022-10-13 14:02:00
|
||||||
* @LastEditTime : 2022-10-13 16:44:03
|
* @LastEditTime : 2022-10-13 22:26:07
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
InkWell(
|
InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(ChatPage.route(userInfo: userInfo));
|
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);
|
context.read<HomeCubit>().switchPage(HomePagePosition.message);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-12 23:38:31
|
* @Date : 2022-10-12 23:38:31
|
||||||
* @LastEditTime : 2022-10-13 16:12:53
|
* @LastEditTime : 2022-10-13 22:27:29
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -25,11 +25,11 @@ class MessageListCubit extends Cubit<MessageListState> {
|
|||||||
final LocalServiceRepository localServiceRepository;
|
final LocalServiceRepository localServiceRepository;
|
||||||
final TCPRepository tcpRepository;
|
final TCPRepository tcpRepository;
|
||||||
|
|
||||||
void addEmptyMessageOf({required UserInfo user}) {
|
void addEmptyMessageOf({required int targetUser}) {
|
||||||
if(state.messageList.any((element) => element.userInfo.userID == user.userID)) {
|
if(state.messageList.any((element) => element.targetUser == targetUser)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var newList = [MessageInfo(userInfo: user)];
|
var newList = [MessageInfo(targetUser: targetUser)];
|
||||||
emit(MessageListState(messageList: newList..addAll(state.messageList)));
|
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
|
//therefore only insert to map if the target user does not exist
|
||||||
if(!addedUserSet.contains(targetUser)) {
|
if(!addedUserSet.contains(targetUser)) {
|
||||||
addedUserSet.add(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
|
//Create message info
|
||||||
latestMessages.add(MessageInfo(
|
latestMessages.add(MessageInfo(
|
||||||
userInfo: targetUserInfo,
|
targetUser: targetUser,
|
||||||
message: message
|
message: message
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -74,12 +71,9 @@ class MessageListCubit extends Cubit<MessageListState> {
|
|||||||
var targetUser = response.message.senderID == curUser ?
|
var targetUser = response.message.senderID == curUser ?
|
||||||
response.message.recieverID :
|
response.message.recieverID :
|
||||||
response.message.senderID;
|
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(
|
emit(state.updateWithSingle(
|
||||||
messageInfo: MessageInfo(
|
messageInfo: MessageInfo(
|
||||||
userInfo: targetUserInfo,
|
targetUser: targetUser,
|
||||||
message: response.message
|
message: response.message
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-12 23:37:49
|
* @Date : 2022-10-12 23:37:49
|
||||||
* @LastEditTime : 2022-10-13 11:09:54
|
* @LastEditTime : 2022-10-13 22:24:22
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ class MessageListState extends Equatable {
|
|||||||
}) {
|
}) {
|
||||||
var newList = <MessageInfo>[messageInfo];
|
var newList = <MessageInfo>[messageInfo];
|
||||||
for(var msgInfo in messageList) {
|
for(var msgInfo in messageList) {
|
||||||
if(msgInfo.userInfo.userID == messageInfo.userInfo.userID) {
|
if(msgInfo.targetUser == messageInfo.targetUser) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
newList.add(msgInfo);
|
newList.add(msgInfo);
|
||||||
@ -41,11 +41,11 @@ class MessageListState extends Equatable {
|
|||||||
insertListIndex < orderedNewMessages.length &&
|
insertListIndex < orderedNewMessages.length &&
|
||||||
origListIndex < messageList.length
|
origListIndex < messageList.length
|
||||||
) {
|
) {
|
||||||
if(addedUsers.contains(orderedNewMessages[insertListIndex].userInfo.userID)) {
|
if(addedUsers.contains(orderedNewMessages[insertListIndex].targetUser)) {
|
||||||
insertListIndex += 1;
|
insertListIndex += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(addedUsers.contains(messageList[origListIndex].userInfo.userID)) {
|
if(addedUsers.contains(messageList[origListIndex].targetUser)) {
|
||||||
origListIndex += 1;
|
origListIndex += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -54,35 +54,35 @@ class MessageListState extends Equatable {
|
|||||||
(orderedNewMessages[insertListIndex].message?.timeStamp ?? 0)
|
(orderedNewMessages[insertListIndex].message?.timeStamp ?? 0)
|
||||||
) {
|
) {
|
||||||
newList.add(messageList[origListIndex]);
|
newList.add(messageList[origListIndex]);
|
||||||
addedUsers.add(messageList[origListIndex].userInfo.userID);
|
addedUsers.add(messageList[origListIndex].targetUser);
|
||||||
origListIndex += 1;
|
origListIndex += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
newList.add(orderedNewMessages[insertListIndex]);
|
newList.add(orderedNewMessages[insertListIndex]);
|
||||||
addedUsers.add(orderedNewMessages[insertListIndex].userInfo.userID);
|
addedUsers.add(orderedNewMessages[insertListIndex].targetUser);
|
||||||
insertListIndex += 1;
|
insertListIndex += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Add the messages left
|
//Add the messages left
|
||||||
while(origListIndex < messageList.length) {
|
while(origListIndex < messageList.length) {
|
||||||
if(addedUsers.contains(messageList[origListIndex].userInfo.userID)) {
|
if(addedUsers.contains(messageList[origListIndex].targetUser)) {
|
||||||
origListIndex += 1;
|
origListIndex += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
newList.add(messageList[origListIndex]);
|
newList.add(messageList[origListIndex]);
|
||||||
addedUsers.add(messageList[origListIndex].userInfo.userID);
|
addedUsers.add(messageList[origListIndex].targetUser);
|
||||||
origListIndex += 1;
|
origListIndex += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
while(insertListIndex < orderedNewMessages.length) {
|
while(insertListIndex < orderedNewMessages.length) {
|
||||||
if(addedUsers.contains(orderedNewMessages[insertListIndex].userInfo.userID)) {
|
if(addedUsers.contains(orderedNewMessages[insertListIndex].targetUser)) {
|
||||||
origListIndex += 1;
|
origListIndex += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
newList.add(orderedNewMessages[insertListIndex]);
|
newList.add(orderedNewMessages[insertListIndex]);
|
||||||
addedUsers.add(orderedNewMessages[insertListIndex].userInfo.userID);
|
addedUsers.add(orderedNewMessages[insertListIndex].targetUser);
|
||||||
origListIndex += 1;
|
origListIndex += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-11 11:05:18
|
* @Date : 2022-10-11 11:05:18
|
||||||
* @LastEditTime : 2022-10-13 16:11:24
|
* @LastEditTime : 2022-10-13 22:59:10
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -18,19 +18,22 @@ class MessagePage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<MessageListCubit, MessageListState>(
|
return BlocBuilder<MessageListCubit, MessageListState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return ListView.separated(
|
return Container(
|
||||||
itemBuilder: (context, index) {
|
color: Colors.blue,
|
||||||
return MessageTile(
|
child: ListView.separated(
|
||||||
userInfo: state.messageList[index].userInfo,
|
itemBuilder: (context, index) {
|
||||||
message: state.messageList[index].message,
|
return MessageTile(
|
||||||
);
|
userID: state.messageList[index].targetUser,
|
||||||
},
|
message: state.messageList[index].message,
|
||||||
separatorBuilder: (context, index) {
|
);
|
||||||
return const Divider(
|
},
|
||||||
height: 0.5,
|
separatorBuilder: (context, index) {
|
||||||
);
|
return const Divider(
|
||||||
},
|
height: 0.5,
|
||||||
itemCount: state.messageList.length
|
);
|
||||||
|
},
|
||||||
|
itemCount: state.messageList.length
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-12 23:48:54
|
* @Date : 2022-10-12 23:48:54
|
||||||
* @LastEditTime : 2022-10-12 23:50:17
|
* @LastEditTime : 2022-10-13 22:24:01
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -11,13 +11,13 @@ import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
|||||||
|
|
||||||
class MessageInfo extends Equatable {
|
class MessageInfo extends Equatable {
|
||||||
final Message? message;
|
final Message? message;
|
||||||
final UserInfo userInfo;
|
final int targetUser;
|
||||||
|
|
||||||
const MessageInfo({
|
const MessageInfo({
|
||||||
this.message,
|
this.message,
|
||||||
required this.userInfo
|
required this.targetUser
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [message?.contentmd5, userInfo];
|
List<Object?> get props => [message?.contentmd5, targetUser];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +1,26 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 13:17:52
|
* @Date : 2022-10-13 13:17:52
|
||||||
* @LastEditTime : 2022-10-13 14:57:14
|
* @LastEditTime : 2022-10-13 22:23:31
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
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/message.dart';
|
||||||
import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
||||||
|
|
||||||
class MessageTile extends StatelessWidget {
|
class MessageTile extends StatelessWidget {
|
||||||
const MessageTile({
|
const MessageTile({
|
||||||
required this.userInfo,
|
required this.userID,
|
||||||
this.message,
|
this.message,
|
||||||
super.key
|
super.key
|
||||||
});
|
});
|
||||||
|
|
||||||
final UserInfo userInfo;
|
final int userID;
|
||||||
final Message? message;
|
final Message? message;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -33,37 +35,38 @@ class MessageTile extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
if(userInfo.avatarEncoded != null && userInfo.avatarEncoded!.isEmpty)
|
UserAvatar(userid: userID),
|
||||||
Container(
|
// if(userInfo.avatarEncoded != null && userInfo.avatarEncoded!.isEmpty)
|
||||||
decoration: BoxDecoration(
|
// Container(
|
||||||
borderRadius: BorderRadius.circular(5.0),
|
// decoration: BoxDecoration(
|
||||||
border: Border.all(
|
// borderRadius: BorderRadius.circular(5.0),
|
||||||
color: Colors.grey[700]!,
|
// border: Border.all(
|
||||||
width: 1.0
|
// color: Colors.grey[700]!,
|
||||||
)
|
// width: 1.0
|
||||||
),
|
// )
|
||||||
child: ClipRRect(
|
// ),
|
||||||
borderRadius: BorderRadius.circular(5.0),
|
// child: ClipRRect(
|
||||||
child: OverflowBox(
|
// borderRadius: BorderRadius.circular(5.0),
|
||||||
alignment: Alignment.center,
|
// child: OverflowBox(
|
||||||
child: FittedBox(
|
// alignment: Alignment.center,
|
||||||
fit: BoxFit.fitWidth,
|
// child: FittedBox(
|
||||||
child: Image.memory(base64Decode(userInfo.avatarEncoded!)),
|
// fit: BoxFit.fitWidth,
|
||||||
),
|
// child: Image.memory(base64Decode(userInfo.avatarEncoded!)),
|
||||||
)
|
// ),
|
||||||
),
|
// )
|
||||||
),
|
// ),
|
||||||
if(userInfo.avatarEncoded == null || userInfo.avatarEncoded!.isEmpty)
|
// ),
|
||||||
Container(
|
// if(userInfo.avatarEncoded == null || userInfo.avatarEncoded!.isEmpty)
|
||||||
color: Colors.grey,
|
// Container(
|
||||||
decoration: BoxDecoration(
|
// color: Colors.grey,
|
||||||
borderRadius: BorderRadius.circular(5.0),
|
// decoration: BoxDecoration(
|
||||||
border: Border.all(
|
// borderRadius: BorderRadius.circular(5.0),
|
||||||
color: Colors.grey[700]!,
|
// border: Border.all(
|
||||||
width: 1.0
|
// color: Colors.grey[700]!,
|
||||||
)
|
// width: 1.0
|
||||||
),
|
// )
|
||||||
),
|
// ),
|
||||||
|
// ),
|
||||||
const SizedBox(width: 12,),
|
const SizedBox(width: 12,),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -73,13 +76,7 @@ class MessageTile extends StatelessWidget {
|
|||||||
vertical: 8.0,
|
vertical: 8.0,
|
||||||
horizontal: 0
|
horizontal: 0
|
||||||
),
|
),
|
||||||
child: Text(
|
child: UserNameText(userid: userID, fontWeight: FontWeight.bold,)
|
||||||
userInfo.userName,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Padding(
|
Padding(
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-11 09:44:03
|
* @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
|
* @Description : Abstract TCP request class
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -108,10 +108,19 @@ class LogoutRequest extends TCPRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GetProfileRequest 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
|
@override
|
||||||
Map<String, Object?> get body => {};
|
Map<String, Object?> get body => {
|
||||||
|
'userid': _userid
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModifyPasswordRequest extends TCPRequest {
|
class ModifyPasswordRequest extends TCPRequest {
|
||||||
@ -205,14 +214,15 @@ class FetchFileRequest extends TCPRequest {
|
|||||||
class SearchUserRequest extends TCPRequest {
|
class SearchUserRequest extends TCPRequest {
|
||||||
final String _username;
|
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
|
@override
|
||||||
Map<String, Object?> get body => {
|
Map<String, Object?> get body => {
|
||||||
'username': _username,
|
'username': _username,
|
||||||
};
|
};
|
||||||
|
|
||||||
String get username => _username;
|
String get username => utf8.decode(base64.decode(_username));
|
||||||
}
|
}
|
||||||
|
|
||||||
class AddContactRequest extends TCPRequest {
|
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
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 17:09:25
|
* @Date : 2022-10-13 17:09:25
|
||||||
* @LastEditTime : 2022-10-13 17:09:26
|
* @LastEditTime : 2022-10-13 23:25:20
|
||||||
* @Description :
|
* @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
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 17:08:56
|
* @Date : 2022-10-13 17:08:56
|
||||||
* @LastEditTime : 2022-10-13 17:08:57
|
* @LastEditTime : 2022-10-13 17:42:33
|
||||||
* @Description :
|
* @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
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 17:04:12
|
* @Date : 2022-10-13 17:04:12
|
||||||
* @LastEditTime : 2022-10-13 17:08:13
|
* @LastEditTime : 2022-10-13 23:24:13
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
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/search_bar.dart';
|
||||||
|
import 'package:tcp_client/search/view/user_tile.dart';
|
||||||
|
|
||||||
class SearchPage extends StatelessWidget {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return RepositoryProvider<UserRepository>.value(
|
||||||
appBar: AppBar(
|
value: userRepository,
|
||||||
title: const SearchBar(),
|
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
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 17:06:52
|
* @Date : 2022-10-13 17:06:52
|
||||||
* @LastEditTime : 2022-10-13 17:06:53
|
* @LastEditTime : 2022-10-13 21:35:11
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
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 {
|
class SearchBar extends StatelessWidget {
|
||||||
const SearchBar({super.key});
|
const SearchBar({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
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:
|
equatable:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -53,6 +53,7 @@ dependencies:
|
|||||||
pull_to_refresh: ^2.0.0
|
pull_to_refresh: ^2.0.0
|
||||||
azlistview: ^2.0.0
|
azlistview: ^2.0.0
|
||||||
lpinyin: ^2.0.3
|
lpinyin: ^2.0.3
|
||||||
|
easy_debounce: ^2.0.2+1
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user