More Codes

- Contacts page (untested)
This commit is contained in:
Linloir 2022-10-13 17:10:48 +08:00
parent 0f714c2633
commit 43cb79f6ab
No known key found for this signature in database
GPG Key ID: 58EEB209A0F2C366
25 changed files with 541 additions and 51 deletions

View File

@ -1,6 +1,35 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 14:03:16
* @LastEditTime : 2022-10-13 14:03:16
* @LastEditTime : 2022-10-13 16:29:57
* @Description :
*/
import 'package:flutter/material.dart';
import 'package:tcp_client/repositories/common_models/userinfo.dart';
class ChatPage extends StatelessWidget {
const ChatPage({
required this.userInfo,
super.key
});
final UserInfo userInfo;
static Route<void> route({required UserInfo userInfo}) => MaterialPageRoute<void>(builder: (context) => ChatPage(userInfo: userInfo,));
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
userInfo.userName,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18
),
)
),
);
}
}

View File

@ -1,6 +1,25 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 14:02:28
* @LastEditTime : 2022-10-13 14:02:28
* @LastEditTime : 2022-10-13 16:43:49
* @Description :
*/
import 'package:bloc/bloc.dart';
import 'package:tcp_client/home/cubit/home_state.dart';
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart';
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
class HomeCubit extends Cubit<HomeState> {
HomeCubit({
required this.localServiceRepository,
required this.tcpRepository,
}): super(const HomeState(page: HomePagePosition.message));
final LocalServiceRepository localServiceRepository;
final TCPRepository tcpRepository;
void switchPage(HomePagePosition newPage) {
emit(state.copyWith(page: newPage));
}
}

View File

@ -1,6 +1,38 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 14:02:24
* @LastEditTime : 2022-10-13 14:02:24
* @LastEditTime : 2022-10-13 16:55:05
* @Description :
*/
import 'package:equatable/equatable.dart';
enum HomePagePosition {
message(0),
contact(1),
profile(2);
const HomePagePosition(int value): _value = value;
final int _value;
final List<String> _literals = const ['Messages', 'Contacts', 'Me'];
int get value => _value;
String get literal => _literals[value];
//Construct the enum type by value
factory HomePagePosition.fromValue(int value) {
return HomePagePosition.values.firstWhere((element) => element._value == value, orElse: () => HomePagePosition.message);
}
}
class HomeState extends Equatable {
final HomePagePosition page;
const HomeState({required this.page});
HomeState copyWith({HomePagePosition? page}) {
return HomeState(page: page ?? this.page);
}
@override
List<Object> get props => [page];
}

View File

@ -1,19 +1,84 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 11:05:08
* @LastEditTime : 2022-10-12 11:03:13
* @LastEditTime : 2022-10-13 16:55:48
* @Description :
*/
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/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/repositories/local_service_repository/local_service_repository.dart';
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
HomePage({
required this.localServiceRepository,
required this.tcpRepository,
super.key
});
static Route<void> route() => MaterialPageRoute<void>(builder: (context) => const HomePage());
final LocalServiceRepository localServiceRepository;
final TCPRepository tcpRepository;
final PageController _controller = PageController();
static Route<void> route({
required LocalServiceRepository localServiceRepository,
required TCPRepository tcpRepository
}) => MaterialPageRoute<void>(builder: (context) => HomePage(
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository,
));
@override
Widget build(BuildContext context) {
return const Scaffold();
return MultiBlocProvider(
providers: [
BlocProvider<MessageListCubit>(
create: (context) => MessageListCubit(
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository
),
),
BlocProvider<ContactCubit>(
create: (context) => ContactCubit(
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository
),
),
BlocProvider<HomeCubit>(
create: (context) => HomeCubit(
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository
),
)
],
child: BlocListener<HomeCubit, HomeState>(
listenWhen:(previous, current) => current.page != previous.page,
listener: (context, state) {
_controller.animateToPage(
state.page.value,
duration: const Duration(milliseconds: 375),
curve: Curves.easeInOutCubicEmphasized
);
},
child: Scaffold(
body: PageView(
controller: _controller,
children: const [
MessagePage(),
ContactPage()
],
),
),
)
);
}
}

View File

@ -0,0 +1,6 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 16:37:30
* @LastEditTime : 2022-10-13 16:37:30
* @Description :
*/

View File

@ -1,6 +1,53 @@
/*
* @Author : Linloir
* @Date : 2022-10-12 23:36:07
* @LastEditTime : 2022-10-12 23:36:08
* @LastEditTime : 2022-10-13 16:10:57
* @Description :
*/
import 'package:azlistview/azlistview.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tcp_client/home/view/contact_page/cubit/contact_cubit.dart';
import 'package:tcp_client/home/view/contact_page/cubit/contact_state.dart';
import 'package:tcp_client/home/view/contact_page/models/contact_model.dart';
import 'package:tcp_client/home/view/contact_page/view/contact_tile.dart';
class ContactPage extends StatelessWidget {
const ContactPage({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<ContactCubit, ContactState>(
builder: (context, state) {
return AzListView(
data: state.indexedData,
itemCount: state.contacts.length,
itemBuilder: (context, index) {
return ContactTile(
userInfo: state.contacts[index],
);
},
physics: const BouncingScrollPhysics(),
susItemBuilder: (context, index) {
return Container(
height: 40,
width: MediaQuery.of(context).size.width,
padding: const EdgeInsets.only(left: 16.0),
color: Colors.grey[200],
alignment: Alignment.centerLeft,
child: Text(
ContactModel(userInfo: state.contacts[index]).getSuspensionTag(),
softWrap: false,
style: TextStyle(
fontSize: 14.0,
color: Colors.grey[700],
),
),
);
},
);
},
);
}
}

View File

@ -1,6 +1,45 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 14:01:45
* @LastEditTime : 2022-10-13 14:01:46
* @LastEditTime : 2022-10-13 14:50:34
* @Description :
*/
import 'package:bloc/bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tcp_client/home/view/contact_page/cubit/contact_state.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 ContactCubit extends Cubit<ContactState> {
ContactCubit({
required this.localServiceRepository,
required this.tcpRepository
}): super(ContactState.empty()) {
tcpRepository.responseStreamBroadcast.listen(_onResponse);
}
LocalServiceRepository localServiceRepository;
TCPRepository tcpRepository;
void _onResponse(TCPResponse response) {
switch(response.type) {
case TCPResponseType.fetchContact: {
response as FetchContactResponse;
emit(ContactState(
contacts: response.addedContacts,
pending: response.pendingContacts,
requesting: response.requestingContacts
));
break;
}
default: break;
}
}
Future<void> updateContacts() async {
tcpRepository.pushRequest(FetchContactRequest(token: (await SharedPreferences.getInstance()).getInt('token')));
}
}

View File

@ -1,6 +1,35 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 14:01:39
* @LastEditTime : 2022-10-13 14:01:40
* @LastEditTime : 2022-10-13 15:51:23
* @Description :
*/
import 'package:azlistview/azlistview.dart';
import 'package:equatable/equatable.dart';
import 'package:tcp_client/home/view/contact_page/models/contact_model.dart';
import 'package:tcp_client/repositories/common_models/userinfo.dart';
class ContactState extends Equatable {
final List<UserInfo> contacts;
final List<UserInfo> pending;
final List<UserInfo> requesting;
const ContactState({
required this.contacts,
required this.pending,
required this.requesting
});
static ContactState empty() => const ContactState(contacts: [], pending: [], requesting: []);
List<ISuspensionBean> get indexedData {
var indexedList = contacts.map((e) => ContactModel(userInfo: e)).toList();
SuspensionUtil.sortListBySuspensionTag(indexedList);
SuspensionUtil.setShowSuspensionStatus(indexedList);
return indexedList;
}
@override
List<Object> get props => contacts;
}

View File

@ -0,0 +1,26 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 15:34:08
* @LastEditTime : 2022-10-13 15:41:24
* @Description :
*/
import 'package:azlistview/azlistview.dart';
import 'package:lpinyin/lpinyin.dart';
import 'package:tcp_client/repositories/common_models/userinfo.dart';
class ContactModel extends ISuspensionBean {
final UserInfo userInfo;
ContactModel({required this.userInfo});
@override
String getSuspensionTag() {
var pinyin = PinyinHelper.getPinyinE(userInfo.userName);
var tag = pinyin.substring(0, 1);
if(!RegExp('[A-Z]').hasMatch(tag)) {
tag = '#';
}
return tag;
}
}

View File

@ -1,6 +1,92 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 14:02:00
* @LastEditTime : 2022-10-13 14:02:00
* @LastEditTime : 2022-10-13 16:44:03
* @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/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';
class ContactTile extends StatelessWidget {
const ContactTile({
required this.userInfo,
super.key
});
final UserInfo userInfo;
@override
Widget build(BuildContext context) {
return IntrinsicHeight(
child: Stack(
fit: StackFit.expand,
children: [
InkWell(
onTap: () {
Navigator.of(context).push(ChatPage.route(userInfo: userInfo));
context.read<MessageListCubit>().addEmptyMessageOf(user: userInfo);
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
),
),
)
),
],
),
],
),
);
}
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-12 23:38:31
* @LastEditTime : 2022-10-13 11:14:54
* @LastEditTime : 2022-10-13 16:12:53
* @Description :
*/
@ -19,13 +19,21 @@ class MessageListCubit extends Cubit<MessageListState> {
required this.localServiceRepository,
required this.tcpRepository
}): super(MessageListState.empty()) {
tcpRepository.responseStreamBroadcast.listen(onResponse);
tcpRepository.responseStreamBroadcast.listen(_onResponse);
}
final LocalServiceRepository localServiceRepository;
final TCPRepository tcpRepository;
Future<void> onResponse(TCPResponse response) async {
void addEmptyMessageOf({required UserInfo user}) {
if(state.messageList.any((element) => element.userInfo.userID == user.userID)) {
return;
}
var newList = [MessageInfo(userInfo: user)];
emit(MessageListState(messageList: newList..addAll(state.messageList)));
}
Future<void> _onResponse(TCPResponse response) async {
switch(response.type) {
case TCPResponseType.fetchMessage: {
response as FetchMessageResponse;

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 11:05:18
* @LastEditTime : 2022-10-13 13:58:43
* @LastEditTime : 2022-10-13 16:11:24
* @Description :
*/
@ -10,39 +10,29 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tcp_client/home/view/message_page/cubit/msg_list_cubit.dart';
import 'package:tcp_client/home/view/message_page/cubit/msg_list_state.dart';
import 'package:tcp_client/home/view/message_page/view/message_tile.dart';
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart';
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
class MessagePage extends StatelessWidget {
const MessagePage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider<MessageListCubit>(
create: (context) {
return MessageListCubit(
localServiceRepository: context.read<LocalServiceRepository>(),
tcpRepository: context.read<TCPRepository>()
return BlocBuilder<MessageListCubit, MessageListState>(
builder: (context, state) {
return ListView.separated(
itemBuilder: (context, index) {
return MessageTile(
userInfo: state.messageList[index].userInfo,
message: state.messageList[index].message,
);
},
separatorBuilder: (context, index) {
return const Divider(
height: 0.5,
);
},
itemCount: state.messageList.length
);
},
child: BlocBuilder<MessageListCubit, MessageListState>(
builder: (context, state) {
return ListView.separated(
itemBuilder: (context, index) {
return MessageTile(
userInfo: state.messageList[index].userInfo,
message: state.messageList[index].message,
);
},
separatorBuilder: (context, index) {
return const Divider(
height: 0.5,
);
},
itemCount: state.messageList.length
);
}
)
}
);
}
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 13:17:52
* @LastEditTime : 2022-10-13 14:00:12
* @LastEditTime : 2022-10-13 14:57:14
* @Description :
*/
@ -64,6 +64,7 @@ class MessageTile extends StatelessWidget {
)
),
),
const SizedBox(width: 12,),
Expanded(
child: Column(
children: [

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-12 15:06:30
* @LastEditTime : 2022-10-12 23:37:04
* @LastEditTime : 2022-10-13 16:46:06
* @Description :
*/
@ -54,7 +54,10 @@ class LoginPage extends StatelessWidget {
const SnackBar(content: Text('Login Successed'))
);
Future.delayed(const Duration(seconds: 1)).then((_) {
Navigator.of(context).pushReplacement(HomePage.route());
Navigator.of(context).pushReplacement(HomePage.route(
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository
));
});
}
},

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-10 08:04:53
* @LastEditTime : 2022-10-12 17:55:07
* @LastEditTime : 2022-10-13 16:46:33
* @Description :
*/
import 'package:flutter/material.dart';
@ -52,7 +52,10 @@ class SplashPage extends StatelessWidget {
if(state.isDone) {
Future.delayed(const Duration(seconds: 1)).then((_) async {
if((await SharedPreferences.getInstance()).getInt('userid') != null) {
Navigator.of(context).pushReplacement(HomePage.route());
Navigator.of(context).pushReplacement(HomePage.route(
localServiceRepository: state.localServiceRepository!,
tcpRepository: state.tcpRepository!
));
}
else {
Navigator.of(context).pushReplacement(LoginPage.route(

View File

@ -0,0 +1,6 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 17:04:44
* @LastEditTime : 2022-10-13 17:04:44
* @Description :
*/

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-12 17:36:38
* @LastEditTime : 2022-10-12 17:50:42
* @LastEditTime : 2022-10-13 16:45:44
* @Description :
*/
/*
@ -64,7 +64,10 @@ class RegisterPage extends StatelessWidget {
const SnackBar(content: Text('Register Successed'))
);
Future.delayed(const Duration(seconds: 1)).then((_) {
Navigator.of(context).pushReplacement(HomePage.route());
Navigator.of(context).pushReplacement(HomePage.route(
localServiceRepository: localServiceRepository,
tcpRepository: tcpRepository
));
});
}
},

View File

@ -1,10 +1,12 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 14:30:10
* @LastEditTime : 2022-10-11 15:39:13
* @LastEditTime : 2022-10-13 15:38:18
* @Description :
*/
import 'dart:convert';
import 'package:tcp_client/repositories/common_models/json_encodable.dart';
class UserInfo extends JSONEncodable {
@ -25,7 +27,7 @@ class UserInfo extends JSONEncodable {
required Map<String, Object?> jsonObject
}):
_userid = jsonObject['userid'] as int,
_username = jsonObject['username'] as String,
_username = utf8.decode(base64.decode(jsonObject['username'] as String)),
_avatar = jsonObject['avatar'] as String?;
int get userID => _userid;
@ -35,7 +37,7 @@ class UserInfo extends JSONEncodable {
@override
Map<String, Object?> get jsonObject => {
'userid': _userid,
'username': _username,
'username': base64.encode(utf8.encode(_username)),
'avatar': _avatar
};
}

View File

@ -0,0 +1,6 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 17:09:25
* @LastEditTime : 2022-10-13 17:09:26
* @Description :
*/

View File

@ -0,0 +1,6 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 17:08:56
* @LastEditTime : 2022-10-13 17:08:57
* @Description :
*/

View File

@ -0,0 +1,6 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 17:09:46
* @LastEditTime : 2022-10-13 17:09:46
* @Description :
*/

View File

@ -0,0 +1,22 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 17:04:12
* @LastEditTime : 2022-10-13 17:08:13
* @Description :
*/
import 'package:flutter/material.dart';
import 'package:tcp_client/search/view/search_bar.dart';
class SearchPage extends StatelessWidget {
const SearchPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const SearchBar(),
),
);
}
}

View File

@ -0,0 +1,17 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 17:06:52
* @LastEditTime : 2022-10-13 17:06:53
* @Description :
*/
import 'package:flutter/material.dart';
class SearchBar extends StatelessWidget {
const SearchBar({super.key});
@override
Widget build(BuildContext context) {
return
}
}

View File

@ -8,6 +8,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.9.0"
azlistview:
dependency: "direct main"
description:
name: azlistview
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
bloc:
dependency: "direct main"
description:
@ -125,6 +132,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.7"
flutter_slidable:
dependency: "direct main"
description:
name: flutter_slidable
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
@ -163,6 +177,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.0"
lpinyin:
dependency: "direct main"
description:
name: lpinyin
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.3"
matcher:
dependency: transitive
description:
@ -284,6 +305,20 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.0.3"
pull_to_refresh:
dependency: "direct main"
description:
name: pull_to_refresh
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
scrollable_positioned_list:
dependency: transitive
description:
name: scrollable_positioned_list
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.3"
shared_preferences:
dependency: "direct main"
description:

View File

@ -49,6 +49,10 @@ dependencies:
loading_indicator: ^3.1.0
async: ^2.9.0
stream_transform: ^2.0.1
flutter_slidable: ^2.0.0
pull_to_refresh: ^2.0.0
azlistview: ^2.0.0
lpinyin: ^2.0.3
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.