More Functions

- User Profile page available
- Search user avavilable
- Tested Message page
- Tested Contact page
This commit is contained in:
Linloir 2022-10-14 12:32:19 +08:00
parent f93a95d141
commit 0bb71791da
No known key found for this signature in database
GPG Key ID: 58EEB209A0F2C366
25 changed files with 832 additions and 247 deletions

View File

@ -1,34 +1,54 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 14:03:16
* @LastEditTime : 2022-10-13 16:29:57
* @LastEditTime : 2022-10-14 11:58:34
* @Description :
*/
import 'package:flutter/material.dart';
import 'package:tcp_client/repositories/common_models/userinfo.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tcp_client/common/username/username.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';
class ChatPage extends StatelessWidget {
const ChatPage({
required this.userInfo,
required this.userRepository,
required this.localServiceRepository,
required this.tcpRepository,
required this.userID,
super.key
});
final UserInfo userInfo;
final int userID;
final UserRepository userRepository;
final LocalServiceRepository localServiceRepository;
final TCPRepository tcpRepository;
static Route<void> route({required UserInfo userInfo}) => MaterialPageRoute<void>(builder: (context) => ChatPage(userInfo: userInfo,));
static Route<void> route({
required UserRepository userRepository,
required LocalServiceRepository localServiceRepository,
required TCPRepository tcpRepository,
required int userID
}) => MaterialPageRoute<void>(builder: (context) => ChatPage(
userID: userID,
userRepository: userRepository,
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository,
));
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
userInfo.userName,
style: const TextStyle(
return RepositoryProvider<UserRepository>.value(
value: userRepository,
child: Scaffold(
appBar: AppBar(
title: UserNameText(
userid: userID,
fontWeight: FontWeight.bold,
fontSize: 18
),
)
)
),
),
);
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 21:49:53
* @LastEditTime : 2022-10-13 22:17:17
* @LastEditTime : 2022-10-14 10:32:50
* @Description :
*/
@ -14,9 +14,14 @@ 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});
const UserAvatar({
required this.userid,
this.size = 48,
super.key
});
final int userid;
final double size;
@override
Widget build(BuildContext context) {
@ -28,16 +33,18 @@ class UserAvatar extends StatelessWidget {
builder: (context, state) {
if(state.userInfo.avatarEncoded == null) {
return Container(
width: size,
height: size,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.grey[800],
color: Colors.grey[600],
borderRadius: BorderRadius.circular(5.0),
boxShadow: [BoxShadow(blurRadius: 10.0, color: Colors.grey[850]!.withOpacity(0.15))]
),
child: Text(
state.userInfo.userName[0],
state.userInfo.userName[0].toUpperCase(),
style: TextStyle(
fontSize: 20,
fontSize: 24 * (size / 48),
fontWeight: FontWeight.w300,
color: Colors.white,
shadows: [Shadow(blurRadius: 5.0, color: Colors.white.withOpacity(0.15))]
@ -47,6 +54,8 @@ class UserAvatar extends StatelessWidget {
}
else {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
boxShadow: [BoxShadow(blurRadius: 10.0, color: Colors.grey[850]!.withOpacity(0.15))]

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 22:05:12
* @LastEditTime : 2022-10-13 22:21:42
* @LastEditTime : 2022-10-14 12:10:46
* @Description :
*/
@ -15,11 +15,15 @@ class UserNameText extends StatelessWidget {
const UserNameText({
required this.userid,
this.fontWeight = FontWeight.normal,
this.fontSize = 18,
this.alignment = Alignment.centerLeft,
super.key
});
final int userid;
final FontWeight fontWeight;
final double fontSize;
final Alignment alignment;
@override
Widget build(BuildContext context) {
@ -29,11 +33,14 @@ class UserNameText extends StatelessWidget {
userRepository: context.read<UserRepository>()
),
builder: (context, state) {
return Text(
state.userInfo.userName,
style: TextStyle(
fontSize: 18,
fontWeight: fontWeight
return Align(
alignment: alignment,
child: Text(
state.userInfo.userName,
style: TextStyle(
fontSize: fontSize,
fontWeight: fontWeight
),
),
);
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 11:05:08
* @LastEditTime : 2022-10-13 23:02:55
* @LastEditTime : 2022-10-14 10:52:26
* @Description :
*/
@ -13,6 +13,7 @@ import 'package:tcp_client/home/view/contact_page/contact_page.dart';
import 'package:tcp_client/home/view/contact_page/cubit/contact_cubit.dart';
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/profile_page/profile_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';
@ -20,18 +21,22 @@ import 'package:tcp_client/search/search_page.dart';
class HomePage extends StatelessWidget {
const HomePage({
required this.userID,
required this.localServiceRepository,
required this.tcpRepository,
super.key
});
final int userID;
final LocalServiceRepository localServiceRepository;
final TCPRepository tcpRepository;
static Route<void> route({
required int userID,
required LocalServiceRepository localServiceRepository,
required TCPRepository tcpRepository
}) => MaterialPageRoute<void>(builder: (context) => HomePage(
userID: userID,
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository,
));
@ -70,16 +75,20 @@ class HomePage extends StatelessWidget {
),
)
],
child: HomePageView(),
child: HomePageView(userID: userID,),
),
);
}
}
class HomePageView extends StatelessWidget {
HomePageView({super.key});
HomePageView({
required this.userID,
super.key
});
final PageController _controller = PageController();
final int userID;
@override
Widget build(BuildContext context) {
@ -122,9 +131,10 @@ class HomePageView extends StatelessWidget {
builder:(context, state) => PageView(
controller: _controller,
onPageChanged: (value) => context.read<HomeCubit>().switchPage(HomePagePosition.fromValue(value)),
children: const [
MessagePage(),
ContactPage()
children: [
const MessagePage(),
const ContactPage(),
MyProfilePage(userID: userID)
],
),
),

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-12 23:36:07
* @LastEditTime : 2022-10-13 22:59:25
* @LastEditTime : 2022-10-14 11:45:35
* @Description :
*/
@ -19,7 +19,6 @@ class ContactPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: BlocBuilder<ContactCubit, ContactState>(
builder: (context, state) {
return AzListView(

View File

@ -1,10 +1,12 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 14:01:45
* @LastEditTime : 2022-10-13 14:50:34
* @LastEditTime : 2022-10-14 11:49:50
* @Description :
*/
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tcp_client/home/view/contact_page/cubit/contact_state.dart';
@ -18,11 +20,15 @@ class ContactCubit extends Cubit<ContactState> {
required this.localServiceRepository,
required this.tcpRepository
}): super(ContactState.empty()) {
tcpRepository.responseStreamBroadcast.listen(_onResponse);
subscription = tcpRepository.responseStreamBroadcast.listen(_onResponse);
updateContacts();
timer = Timer.periodic(const Duration(seconds: 5), (timer) {updateContacts();});
}
LocalServiceRepository localServiceRepository;
TCPRepository tcpRepository;
final LocalServiceRepository localServiceRepository;
final TCPRepository tcpRepository;
late final StreamSubscription subscription;
late final Timer timer;
void _onResponse(TCPResponse response) {
switch(response.type) {
@ -42,4 +48,12 @@ class ContactCubit extends Cubit<ContactState> {
Future<void> updateContacts() async {
tcpRepository.pushRequest(FetchContactRequest(token: (await SharedPreferences.getInstance()).getInt('token')));
}
//Override dispose to cancel the subscription
@override
Future<void> close() {
subscription.cancel();
timer.cancel();
return super.close();
}
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 14:02:00
* @LastEditTime : 2022-10-13 22:26:07
* @LastEditTime : 2022-10-14 11:59:48
* @Description :
*/
@ -10,10 +10,15 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tcp_client/chat/chat_page.dart';
import 'package:tcp_client/common/avatar/avatar.dart';
import 'package:tcp_client/common/username/username.dart';
import 'package:tcp_client/home/cubit/home_cubit.dart';
import 'package:tcp_client/home/cubit/home_state.dart';
import 'package:tcp_client/home/view/message_page/cubit/msg_list_cubit.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/tcp_repository.dart';
import 'package:tcp_client/repositories/user_repository/user_repository.dart';
class ContactTile extends StatelessWidget {
const ContactTile({
@ -31,59 +36,35 @@ class ContactTile extends StatelessWidget {
children: [
InkWell(
onTap: () {
Navigator.of(context).push(ChatPage.route(userInfo: userInfo));
Navigator.of(context).push(ChatPage.route(
userRepository: context.read<UserRepository>(),
localServiceRepository: context.read<LocalServiceRepository>(),
tcpRepository: context.read<TCPRepository>(),
userID: userInfo.userID
));
context.read<MessageListCubit>().addEmptyMessageOf(targetUser: userInfo.userID);
context.read<HomeCubit>().switchPage(HomePagePosition.message);
},
),
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(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: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12.0
),
child: Text(
userInfo.userName,
style: const TextStyle(
fontSize: 18.0
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 16,
),
child: Row(
children: [
UserAvatar(userid: userInfo.userID),
const SizedBox(width: 12,),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12.0
),
),
)
),
],
child: UserNameText(userid: userInfo.userID,)
)
),
],
),
),
],
),

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 11:05:18
* @LastEditTime : 2022-10-13 22:59:10
* @LastEditTime : 2022-10-14 11:45:45
* @Description :
*/
@ -19,7 +19,6 @@ class MessagePage extends StatelessWidget {
return BlocBuilder<MessageListCubit, MessageListState>(
builder: (context, state) {
return Container(
color: Colors.blue,
child: ListView.separated(
itemBuilder: (context, index) {
return MessageTile(

View File

@ -1,17 +1,22 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 13:17:52
* @LastEditTime : 2022-10-13 22:23:31
* @LastEditTime : 2022-10-14 12:07:32
* @Description :
*/
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tcp_client/chat/chat_page.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';
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';
class MessageTile extends StatelessWidget {
const MessageTile({
@ -25,90 +30,105 @@ class MessageTile extends StatelessWidget {
@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),
// 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(
children: [
return IntrinsicHeight(
child: Stack(
fit: StackFit.expand,
children: [
InkWell(
onTap: () {
Navigator.of(context).push(ChatPage.route(
userRepository: context.read<UserRepository>(),
localServiceRepository: context.read<LocalServiceRepository>(),
tcpRepository: context.read<TCPRepository>(),
userID: userID
));
},
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 24.0
),
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
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: 16,),
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,
),
),
)
],
),
),
if(message != null)
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,
child: Align(
alignment: Alignment.topCenter,
child: Text(
getTimeStamp(message!.timeStamp)
),
),
)
],
),
),
if(message != null)
Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 0
),
child: Align(
alignment: Alignment.topCenter,
child: Text(
getTimeStamp(message!.timeStamp)
),
),
),
],
),
],
),
),
],
),
);
}

View File

@ -0,0 +1,48 @@
/*
* @Author : Linloir
* @Date : 2022-10-14 10:54:57
* @LastEditTime : 2022-10-14 11:44:12
* @Description :
*/
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tcp_client/home/view/profile_page/cubit/log_out_state.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 LogOutCubit extends Cubit<LogOutStatus> {
LogOutCubit({
required this.tcpRepository
}): super(LogOutStatus.none) {
subscription = tcpRepository.responseStreamBroadcast.listen(_onResponse);
}
final TCPRepository tcpRepository;
late final StreamSubscription subscription;
void _onResponse(TCPResponse response) {
if(response.type == TCPResponseType.logout) {
if(response.status == TCPResponseStatus.ok) {
emit(LogOutStatus.done);
}
else {
emit(LogOutStatus.none);
}
}
}
void onLogout() async {
tcpRepository.pushRequest(LogoutRequest(token: (await SharedPreferences.getInstance()).getInt('token')));
}
//Override dispose to cancel the subscription
@override
Future<void> close() {
subscription.cancel();
return super.close();
}
}

View File

@ -0,0 +1,8 @@
/*
* @Author : Linloir
* @Date : 2022-10-14 10:54:38
* @LastEditTime : 2022-10-14 10:56:04
* @Description :
*/
enum LogOutStatus { none, processing, done }

View File

@ -1,6 +1,84 @@
/*
* @Author : Linloir
* @Date : 2022-10-12 23:36:12
* @LastEditTime : 2022-10-12 23:36:57
* @LastEditTime : 2022-10-14 12:10:34
* @Description :
*/
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tcp_client/common/avatar/avatar.dart';
import 'package:tcp_client/common/username/username.dart';
import 'package:tcp_client/home/view/profile_page/cubit/log_out_cubit.dart';
import 'package:tcp_client/home/view/profile_page/cubit/log_out_state.dart';
import 'package:tcp_client/home/view/profile_page/view/log_out_button.dart';
import 'package:tcp_client/login/login_page.dart';
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart';
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
class MyProfilePage extends StatelessWidget {
const MyProfilePage({
required this.userID,
super.key
});
static Route<void> route({
required int userID,
}) => MaterialPageRoute<void>(builder: (context) => MyProfilePage(
userID: userID,
));
final int userID;
@override
Widget build(BuildContext context) {
return BlocProvider<LogOutCubit>(
create: (context) => LogOutCubit(tcpRepository: context.read<TCPRepository>()),
child: BlocListener<LogOutCubit, LogOutStatus>(
listenWhen: (previous, current) => current == LogOutStatus.done || (previous == LogOutStatus.processing && current == LogOutStatus.none),
listener: (context, state) {
if(state == LogOutStatus.none) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Log out failed'))
);
}
else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Logged out'))
);
Future.delayed(const Duration(seconds: 1)).then((_) {
Navigator.of(context).pushAndRemoveUntil(LoginPage.route(
localServiceRepository: context.read<LocalServiceRepository>(),
tcpRepository: context.read<TCPRepository>()
), (route) => false);
});
}
},
child: BlocBuilder<LogOutCubit, LogOutStatus>(
builder: (context, state) {
return Container(
padding: const EdgeInsets.all(60),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
UserAvatar(userid: userID, size: 96,),
const SizedBox(height: 48,),
UserNameText(
userid: userID,
fontWeight: FontWeight.bold,
fontSize: 22,
alignment: Alignment.center,
),
const SizedBox(height: 48,),
const LogoutButton(),
],
),
);
},
),
),
);
}
}

View File

@ -0,0 +1,43 @@
/*
* @Author : Linloir
* @Date : 2022-10-14 10:53:24
* @LastEditTime : 2022-10-14 11:12:22
* @Description :
*/
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tcp_client/home/view/profile_page/cubit/log_out_cubit.dart';
class LogoutButton extends StatelessWidget {
const LogoutButton({super.key});
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: () {
context.read<LogOutCubit>().onLogout();
},
style: ButtonStyle(
elevation: MaterialStateProperty.all(0),
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0))),
minimumSize: MaterialStateProperty.all(const Size(300, 0)),
backgroundColor: MaterialStateProperty.all(Colors.red[700]),
overlayColor: MaterialStateProperty.all(Colors.red[900]!.withOpacity(0.2)),
foregroundColor: MaterialStateProperty.all(Colors.white),
),
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 12
),
child: Text(
'Log Out',
style: TextStyle(
fontSize: 20
),
),
),
);
}
}

View File

@ -1,13 +1,14 @@
/*
* @Author : Linloir
* @Date : 2022-10-12 15:06:30
* @LastEditTime : 2022-10-13 16:46:06
* @LastEditTime : 2022-10-14 10:47:08
* @Description :
*/
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:formz/formz.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tcp_client/home/home_page.dart';
import 'package:tcp_client/login/cubit/login_cubit.dart';
import 'package:tcp_client/login/cubit/login_state.dart';
@ -53,8 +54,13 @@ class LoginPage extends StatelessWidget {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Login Successed'))
);
Future.delayed(const Duration(seconds: 1)).then((_) {
Future<int>(() async {
await Future.delayed(const Duration(seconds: 1));
var pref = await SharedPreferences.getInstance();
return pref.getInt('userid')!;
}).then((userID) {
Navigator.of(context).pushReplacement(HomePage.route(
userID: userID,
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository
));

View File

@ -1,9 +1,10 @@
/*
* @Author : Linloir
* @Date : 2022-10-10 08:04:53
* @LastEditTime : 2022-10-13 16:46:33
* @LastEditTime : 2022-10-14 11:45:01
* @Description :
*/
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -30,6 +31,9 @@ class MyApp extends StatelessWidget {
theme: ThemeData(
primarySwatch: Colors.blue,
),
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {PointerDeviceKind.mouse, PointerDeviceKind.touch, PointerDeviceKind.stylus, PointerDeviceKind.unknown},
),
home: const SplashPage(),
);
}
@ -50,9 +54,14 @@ class SplashPage extends StatelessWidget {
child: BlocListener<InitializationCubit, InitializationState>(
listener: (context, state) {
if(state.isDone) {
Future.delayed(const Duration(seconds: 1)).then((_) async {
if((await SharedPreferences.getInstance()).getInt('userid') != null) {
Future<int?>(() async {
await Future.delayed(const Duration(seconds: 1));
var pref = await SharedPreferences.getInstance();
return pref.getInt('userid');
}).then((userID) {
if(userID != null) {
Navigator.of(context).pushReplacement(HomePage.route(
userID: userID,
localServiceRepository: state.localServiceRepository!,
tcpRepository: state.tcpRepository!
));

View File

@ -0,0 +1,65 @@
/*
* @Author : Linloir
* @Date : 2022-10-14 08:54:32
* @LastEditTime : 2022-10-14 11:30:59
* @Description :
*/
import 'package:bloc/bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tcp_client/profile/cubit/user_profile_state.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 UserProfileCubit extends Cubit<UserProfileState> {
UserProfileCubit({
required this.userID,
required this.tcpRepository
}): super(const UserProfileState(status: ContactStatus.none)) {
updateContactStatus();
}
final int userID;
final TCPRepository tcpRepository;
Future<void> updateContactStatus() async {
if(userID == (await SharedPreferences.getInstance()).getInt('userid')) {
emit(const UserProfileState(status: ContactStatus.none));
}
var clonedTCPRepository = await tcpRepository.clone();
clonedTCPRepository.pushRequest(FetchContactRequest(token: (await SharedPreferences.getInstance()).getInt('token')));
await for(var response in clonedTCPRepository.responseStreamBroadcast) {
if(response.type == TCPResponseType.fetchContact) {
response as FetchContactResponse;
if(response.addedContacts.any((element) => element.userID == userID)) {
emit(const UserProfileState(status: ContactStatus.isContact));
}
else if(response.pendingContacts.any((element) => element.userID == userID)) {
emit(const UserProfileState(status: ContactStatus.pendingReply));
}
else {
emit(const UserProfileState(status: ContactStatus.notContact));
}
break;
}
}
clonedTCPRepository.dispose();
}
Future<void> addContact() async {
emit(const UserProfileState(status: ContactStatus.consulting));
var clonedTCPRepository = await tcpRepository.clone();
clonedTCPRepository.pushRequest(AddContactRequest(
userid: userID,
token: (await SharedPreferences.getInstance()).getInt('token')
));
await for(var response in clonedTCPRepository.responseStreamBroadcast) {
if(response.type == TCPResponseType.addContact) {
break;
}
}
clonedTCPRepository.dispose();
updateContactStatus();
}
}

View File

@ -0,0 +1,19 @@
/*
* @Author : Linloir
* @Date : 2022-10-14 08:54:25
* @LastEditTime : 2022-10-14 09:11:04
* @Description :
*/
import 'package:equatable/equatable.dart';
enum ContactStatus { isContact, pendingReply, notContact, consulting, none }
class UserProfileState extends Equatable {
final ContactStatus status;
const UserProfileState({required this.status});
@override
List<Object> get props => [status];
}

View File

@ -1,6 +1,89 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 17:04:44
* @LastEditTime : 2022-10-13 17:04:44
* @LastEditTime : 2022-10-14 12:11:26
* @Description :
*/
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tcp_client/common/avatar/avatar.dart';
import 'package:tcp_client/common/username/username.dart';
import 'package:tcp_client/profile/cubit/user_profile_cubit.dart';
import 'package:tcp_client/profile/view/add_contact_button.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';
class ProfilePage extends StatelessWidget {
const ProfilePage({
required this.userID,
required this.localServiceRepository,
required this.tcpRepository,
required this.userRepository,
super.key
});
static Route<void> route({
required int userID,
required LocalServiceRepository localServiceRepository,
required TCPRepository tcpRepository,
required UserRepository userRepository
}) => MaterialPageRoute<void>(builder: (context) => ProfilePage(
userID: userID,
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository,
userRepository: userRepository,
));
final int userID;
final LocalServiceRepository localServiceRepository;
final TCPRepository tcpRepository;
final UserRepository userRepository;
@override
Widget build(BuildContext context) {
return MultiRepositoryProvider(
providers: [
RepositoryProvider<UserRepository>.value(
value: userRepository,
),
RepositoryProvider<LocalServiceRepository>.value(
value: localServiceRepository,
),
RepositoryProvider<TCPRepository>.value(
value: tcpRepository,
)
],
child: BlocProvider<UserProfileCubit>(
create: (context) => UserProfileCubit(
userID: userID,
tcpRepository: tcpRepository,
),
child: Scaffold(
appBar: AppBar(),
body: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(60),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
UserAvatar(userid: userID, size: 96,),
const SizedBox(height: 48,),
UserNameText(
userid: userID,
fontWeight: FontWeight.bold,
fontSize: 22,
alignment: Alignment.center,
),
const SizedBox(height: 48,),
AddContactButton(userID: userID)
],
),
),
),
),
);
}
}

View File

@ -0,0 +1,142 @@
/*
* @Author : Linloir
* @Date : 2022-10-14 09:34:53
* @LastEditTime : 2022-10-14 12:00:52
* @Description :
*/
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tcp_client/chat/chat_page.dart';
import 'package:tcp_client/profile/cubit/user_profile_cubit.dart';
import 'package:tcp_client/profile/cubit/user_profile_state.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';
class AddContactButton extends StatelessWidget {
const AddContactButton({
required this.userID,
super.key
});
final int userID;
@override
Widget build(BuildContext context) {
return BlocBuilder<UserProfileCubit, UserProfileState>(
builder: (context, state) {
if(state.status == ContactStatus.isContact) {
return TextButton(
onPressed: () {
Navigator.of(context).push(ChatPage.route(
userRepository: context.read<UserRepository>(),
localServiceRepository: context.read<LocalServiceRepository>(),
tcpRepository: context.read<TCPRepository>(),
userID: userID
));
},
style: ButtonStyle(
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0))),
minimumSize: MaterialStateProperty.all(const Size(300, 0)),
backgroundColor: MaterialStateProperty.all(Colors.blue),
overlayColor: MaterialStateProperty.all(Colors.blue[800]!.withOpacity(0.2)),
foregroundColor: MaterialStateProperty.all(Colors.white),
),
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 12
),
child: Text(
'Chat',
style: TextStyle(
fontSize: 20
),
),
),
);
}
if(state.status == ContactStatus.pendingReply) {
return TextButton(
onPressed: null,
style: ButtonStyle(
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0))),
minimumSize: MaterialStateProperty.all(const Size(300, 0)),
backgroundColor: MaterialStateProperty.all(Colors.grey[400]),
overlayColor: MaterialStateProperty.all(Colors.grey[800]!.withOpacity(0.2)),
foregroundColor: MaterialStateProperty.all(Colors.grey[800]),
),
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 12
),
child: Text(
'Pending for Reply',
style: TextStyle(
fontSize: 20
),
),
),
);
}
else if(state.status == ContactStatus.notContact) {
return TextButton(
onPressed: () {
context.read<UserProfileCubit>().addContact();
},
style: ButtonStyle(
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0))),
minimumSize: MaterialStateProperty.all(const Size(300, 0)),
backgroundColor: MaterialStateProperty.all(Colors.blue),
overlayColor: MaterialStateProperty.all(Colors.blue[800]!.withOpacity(0.2)),
foregroundColor: MaterialStateProperty.all(Colors.white),
),
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 12
),
child: Text(
'Add Contact',
style: TextStyle(
fontSize: 20
),
),
),
);
}
else if(state.status == ContactStatus.consulting) {
return TextButton(
onPressed: null,
style: ButtonStyle(
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0))),
minimumSize: MaterialStateProperty.all(const Size(300, 0)),
backgroundColor: MaterialStateProperty.all(Colors.blue),
overlayColor: MaterialStateProperty.all(Colors.blue[800]!.withOpacity(0.2)),
foregroundColor: MaterialStateProperty.all(Colors.white),
),
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 12
),
child: SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2.0,
),
)
)
);
}
else {
return Container();
}
},
);
}
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-12 17:36:38
* @LastEditTime : 2022-10-13 16:45:44
* @LastEditTime : 2022-10-14 11:22:38
* @Description :
*/
/*
@ -14,6 +14,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:formz/formz.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tcp_client/home/home_page.dart';
import 'package:tcp_client/register/cubit/register_cubit.dart';
import 'package:tcp_client/register/cubit/register_state.dart';
@ -63,11 +64,16 @@ class RegisterPage extends StatelessWidget {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Register Successed'))
);
Future.delayed(const Duration(seconds: 1)).then((_) {
Navigator.of(context).pushReplacement(HomePage.route(
Future<int>(() async {
await Future.delayed(const Duration(seconds: 1));
var pref = await SharedPreferences.getInstance();
return pref.getInt('userid')!;
}).then((userID) {
Navigator.of(context).pushAndRemoveUntil(HomePage.route(
userID: userID,
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository
));
), (route) => false);
});
}
},

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 11:02:19
* @LastEditTime : 2022-10-12 13:25:49
* @LastEditTime : 2022-10-14 12:16:04
* @Description :
*/
@ -236,7 +236,11 @@ class FetchContactResponse extends TCPResponse {
FetchContactResponse({
required Map<String, Object?> jsonObject
}): super(jsonObject: jsonObject) {
var body = jsonObject['body'] as Map<String, Object?>;
var body = jsonObject['body'] as Map<String, Object?>? ?? {
'contacts': [],
'pending': [],
'requesting': []
};
var rawAddedContacts = body['contacts'] as List;
var rawPendingContacts = body['pending'] as List;
var rawRequestingContacts = body['requesting'] as List;

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 09:42:05
* @LastEditTime : 2022-10-12 23:35:10
* @LastEditTime : 2022-10-14 09:15:47
* @Description : TCP repository
*/
@ -56,6 +56,13 @@ class TCPRepository {
);
}
Future<TCPRepository> clone() async {
return await TCPRepository.create(
serverAddress: _remoteAddress,
serverPort: _remotePort
);
}
final Socket _socket;
final String _remoteAddress;
final int _remotePort;

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 17:04:12
* @LastEditTime : 2022-10-13 23:24:13
* @LastEditTime : 2022-10-14 10:38:44
* @Description :
*/
@ -59,11 +59,17 @@ class SearchPage extends StatelessWidget {
if(state.userResults.isNotEmpty)
...[
const SizedBox(height: 16.0,),
const Text(
'Users',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20
const Padding(
padding: EdgeInsets.symmetric(
horizontal: 36.0,
vertical: 8.0
),
child: Text(
'Users',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20
),
),
),
const SizedBox(height: 8.0,),
@ -72,11 +78,17 @@ class SearchPage extends StatelessWidget {
if(state.historyResults.isNotEmpty)
...[
const SizedBox(height: 16.0,),
const Text(
'Histories',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20
const Padding(
padding: EdgeInsets.symmetric(
horizontal: 36.0,
vertical: 8.0
),
child: Text(
'Histories',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20
),
),
),
const SizedBox(height: 8.0,),
@ -86,16 +98,22 @@ class SearchPage extends StatelessWidget {
))
],
if(state.historyResults.isEmpty && state.userResults.isEmpty)
const Align(
alignment: Alignment.center,
child: Text(
'No result found',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20
...[
const SizedBox(height: 16.0,),
const Padding(
padding: EdgeInsets.symmetric(
horizontal: 36.0,
vertical: 8.0
),
child: Text(
'No Results',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20
),
),
),
)
],
],
);
},

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 21:41:49
* @LastEditTime : 2022-10-13 22:31:37
* @LastEditTime : 2022-10-14 10:38:31
* @Description :
*/
@ -25,7 +25,7 @@ class HistoryTile extends StatelessWidget {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 16.0,
horizontal: 24.0
horizontal: 36.0
),
child: IntrinsicHeight(
child: Row(

View File

@ -1,14 +1,21 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 21:41:41
* @LastEditTime : 2022-10-13 23:26:46
* @LastEditTime : 2022-10-14 12:04:26
* @Description :
*/
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tcp_client/common/avatar/avatar.dart';
import 'package:tcp_client/common/username/username.dart';
import 'package:tcp_client/profile/user_profile_page.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/user_repository/user_repository.dart';
import 'package:tcp_client/search/cubit/search_cubit.dart';
class UserTile extends StatelessWidget {
const UserTile({
@ -20,54 +27,37 @@ class UserTile extends StatelessWidget {
@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
),
),
)
return InkWell(
onTap: () {
Navigator.of(context).push(ProfilePage.route(
userID: userInfo.userID,
localServiceRepository: context.read<SearchCubit>().localServiceRepository,
tcpRepository: context.read<SearchCubit>().tcpRepository,
userRepository: context.read<UserRepository>()
));
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 36,
vertical: 16,
),
],
child: Row(
children: [
UserAvatar(userid: userInfo.userID),
const SizedBox(width: 12,),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12.0
),
child: UserNameText(
userid: userInfo.userID,
)
)
),
],
),
),
);
}
}