mirror of
https://github.com/Linloir/Simple-TCP-Client.git
synced 2025-12-16 16:28:12 +08:00
More Codes
- Splash page - Login page - Register page
This commit is contained in:
parent
d3a5a32fdb
commit
7b89d8ce14
2
.gitignore
vendored
2
.gitignore
vendored
@ -42,3 +42,5 @@ app.*.map.json
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
.tmp/
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 11:05:08
|
||||
* @LastEditTime : 2022-10-11 11:05:08
|
||||
* @Description :
|
||||
*/
|
||||
19
lib/home/home_page.dart
Normal file
19
lib/home/home_page.dart
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 11:05:08
|
||||
* @LastEditTime : 2022-10-12 11:03:13
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
static Route<void> route() => MaterialPageRoute<void>(builder: (context) => const HomePage());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold();
|
||||
}
|
||||
}
|
||||
68
lib/initialization/cubit/initialization_cubit.dart
Normal file
68
lib/initialization/cubit/initialization_cubit.dart
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 09:56:04
|
||||
* @LastEditTime : 2022-10-12 17:54:35
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tcp_client/initialization/cubit/initialization_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 InitializationCubit extends Cubit<InitializationState> {
|
||||
InitializationCubit({
|
||||
required String serverAddress,
|
||||
required int serverPort
|
||||
}): super(const InitializationState.init()) {
|
||||
TCPRepository? tcpRepository;
|
||||
LocalServiceRepository? localServiceRepository;
|
||||
Future(() async {
|
||||
localServiceRepository = await LocalServiceRepository.create(databaseFilePath: '${(await getApplicationDocumentsDirectory()).path}/.data/database.db');
|
||||
}).then((_) {
|
||||
emit(state.copyWith(
|
||||
databaseStatus: InitializationStatus.done,
|
||||
localServiceRepository: localServiceRepository
|
||||
));
|
||||
});
|
||||
Future(() async {
|
||||
tcpRepository = await TCPRepository.create(serverAddress: serverAddress, serverPort: serverPort);
|
||||
}).then((_) {
|
||||
emit(state.copyWith(
|
||||
mainSocketStatus: InitializationStatus.done,
|
||||
tcpRepository: tcpRepository
|
||||
));
|
||||
});
|
||||
Future(() async {
|
||||
var tempConnection = await TCPRepository.create(serverAddress: serverAddress, serverPort: serverPort);
|
||||
var pref = await SharedPreferences.getInstance();
|
||||
var tokenid = pref.getInt('token');
|
||||
tempConnection.pushRequest(CheckStateRequest(token: tokenid));
|
||||
await for(var response in tempConnection.responseStreamBroadcast) {
|
||||
if(response.type == TCPResponseType.token) {
|
||||
pref.setInt('token', (response as SetTokenReponse).token);
|
||||
}
|
||||
else if(response.type == TCPResponseType.checkState) {
|
||||
if(response.status == TCPResponseStatus.ok) {
|
||||
pref.setInt('userid', (response as CheckStateResponse).userInfo!.userID);
|
||||
}
|
||||
else {
|
||||
pref.remove('userid');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
tempConnection.dispose();
|
||||
}).then((_) {
|
||||
emit(state.copyWith(
|
||||
tokenStatus: InitializationStatus.done
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
InitializationCubit.failed(): super(const InitializationState.init());
|
||||
}
|
||||
57
lib/initialization/cubit/initialization_state.dart
Normal file
57
lib/initialization/cubit/initialization_state.dart
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 09:57:48
|
||||
* @LastEditTime : 2022-10-12 13:59:14
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart';
|
||||
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
|
||||
|
||||
enum InitializationStatus { pending, done }
|
||||
|
||||
class InitializationState extends Equatable {
|
||||
const InitializationState({
|
||||
required this.databaseStatus,
|
||||
required this.mainSocketStatus,
|
||||
required this.tokenStatus,
|
||||
this.localServiceRepository,
|
||||
this.tcpRepository
|
||||
});
|
||||
|
||||
const InitializationState.init(): this(
|
||||
databaseStatus: InitializationStatus.pending,
|
||||
mainSocketStatus: InitializationStatus.pending,
|
||||
tokenStatus: InitializationStatus.pending
|
||||
);
|
||||
|
||||
final InitializationStatus databaseStatus;
|
||||
final InitializationStatus mainSocketStatus;
|
||||
final InitializationStatus tokenStatus;
|
||||
final LocalServiceRepository? localServiceRepository;
|
||||
final TCPRepository? tcpRepository;
|
||||
|
||||
InitializationState copyWith({
|
||||
InitializationStatus? databaseStatus,
|
||||
InitializationStatus? mainSocketStatus,
|
||||
InitializationStatus? tokenStatus,
|
||||
LocalServiceRepository? localServiceRepository,
|
||||
TCPRepository? tcpRepository
|
||||
}) {
|
||||
return InitializationState(
|
||||
databaseStatus: databaseStatus ?? this.databaseStatus,
|
||||
mainSocketStatus: mainSocketStatus ?? this.mainSocketStatus,
|
||||
tokenStatus: tokenStatus ?? this.tokenStatus,
|
||||
localServiceRepository: localServiceRepository ?? this.localServiceRepository,
|
||||
tcpRepository: tcpRepository ?? this.tcpRepository
|
||||
);
|
||||
}
|
||||
|
||||
bool get isDone => databaseStatus == InitializationStatus.done &&
|
||||
mainSocketStatus == InitializationStatus.done &&
|
||||
tokenStatus == InitializationStatus.done;
|
||||
|
||||
@override
|
||||
List<Object> get props => [databaseStatus, mainSocketStatus, tokenStatus];
|
||||
}
|
||||
151
lib/initialization/initialization_page.dart
Normal file
151
lib/initialization/initialization_page.dart
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 09:53:48
|
||||
* @LastEditTime : 2022-10-12 14:53:19
|
||||
* @Description : Splash page before main TCP connection and database is ready
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:loading_indicator/loading_indicator.dart';
|
||||
import 'package:tcp_client/initialization/cubit/initialization_cubit.dart';
|
||||
import 'package:tcp_client/initialization/cubit/initialization_state.dart';
|
||||
|
||||
class InitializePage extends StatelessWidget {
|
||||
const InitializePage({super.key});
|
||||
|
||||
static Route<void> route({
|
||||
required String serverAddress,
|
||||
required int serverPort,
|
||||
required String databasePath
|
||||
}) {
|
||||
return MaterialPageRoute<void>(builder: (context) => const InitializePage());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 48, maxHeight: 48),
|
||||
child: const LoadingIndicator(
|
||||
indicatorType: Indicator.ballScale,
|
||||
colors: [Colors.grey],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12,),
|
||||
BlocBuilder<InitializationCubit, InitializationState>(
|
||||
builder:(context, state) {
|
||||
if(state.databaseStatus == InitializationStatus.done) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.check_rounded,
|
||||
color: Colors.green,
|
||||
),
|
||||
SizedBox(width: 6,),
|
||||
Text('Database initialized.')
|
||||
],
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
SizedBox(
|
||||
height: 12,
|
||||
width: 12,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.grey,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 6,),
|
||||
Text('Database initializing...')
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8,),
|
||||
BlocBuilder<InitializationCubit, InitializationState>(
|
||||
builder:(context, state) {
|
||||
if(state.mainSocketStatus == InitializationStatus.done) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.check_rounded,
|
||||
color: Colors.green,
|
||||
),
|
||||
SizedBox(width: 6,),
|
||||
Text('TCP connection initialized.')
|
||||
],
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
SizedBox(
|
||||
height: 12,
|
||||
width: 12,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.grey,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 6,),
|
||||
Text('TCP connection initializing...')
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8,),
|
||||
BlocBuilder<InitializationCubit, InitializationState>(
|
||||
builder:(context, state) {
|
||||
if(state.tokenStatus == InitializationStatus.done) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.check_rounded,
|
||||
color: Colors.green,
|
||||
),
|
||||
SizedBox(width: 6,),
|
||||
Text('Device status verified.')
|
||||
],
|
||||
);
|
||||
}
|
||||
else {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
SizedBox(
|
||||
height: 12,
|
||||
width: 12,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.grey,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 6,),
|
||||
Text('Verifying device login status...')
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
78
lib/login/bloc/login_cubit.dart
Normal file
78
lib/login/bloc/login_cubit.dart
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 15:38:07
|
||||
* @LastEditTime : 2022-10-12 17:36:53
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:formz/formz.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tcp_client/login/bloc/login_state.dart';
|
||||
import 'package:tcp_client/login/models/password.dart';
|
||||
import 'package:tcp_client/login/models/username.dart';
|
||||
import 'package:tcp_client/repositories/common_models/useridentity.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 LoginCubit extends Cubit<LoginState> {
|
||||
LoginCubit({
|
||||
required this.localServiceRepository,
|
||||
required this.tcpRepository
|
||||
}): super(const LoginState());
|
||||
|
||||
final LocalServiceRepository localServiceRepository;
|
||||
final TCPRepository tcpRepository;
|
||||
|
||||
void onPasswordChange(Password password) {
|
||||
emit(state.copyWith(
|
||||
status: Formz.validate([state.username, password]),
|
||||
password: password
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> onUsernameChange(Username username) async {
|
||||
emit(state.copyWith(
|
||||
status: Formz.validate([username, state.password]),
|
||||
username: username,
|
||||
));
|
||||
var userinfo = await localServiceRepository.fetchUserInfoViaUsername(username: username.value);
|
||||
emit(state.copyWith(
|
||||
avatar: userinfo?.avatarEncoded
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> onSubmission() async {
|
||||
if(state.status.isValidated) {
|
||||
emit(state.copyWith(
|
||||
status: FormzStatus.submissionInProgress
|
||||
));
|
||||
tcpRepository.pushRequest(LoginRequest(
|
||||
identity: UserIdentity(
|
||||
username: state.username.value,
|
||||
password: state.password.value
|
||||
),
|
||||
token: (await SharedPreferences.getInstance()).getInt('token')
|
||||
));
|
||||
await for(var response in tcpRepository.responseStreamBroadcast) {
|
||||
if(response.type == TCPResponseType.login) {
|
||||
if(response.status == TCPResponseStatus.ok) {
|
||||
var pref = await SharedPreferences.getInstance();
|
||||
pref.setInt('userid', (response as LoginResponse).userInfo!.userID);
|
||||
emit(state.copyWith(
|
||||
status: FormzStatus.submissionSuccess
|
||||
));
|
||||
}
|
||||
else {
|
||||
emit(state.copyWith(
|
||||
status: FormzStatus.submissionFailure
|
||||
));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
lib/login/bloc/login_state.dart
Normal file
43
lib/login/bloc/login_state.dart
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 15:38:13
|
||||
* @LastEditTime : 2022-10-12 16:24:42
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:formz/formz.dart';
|
||||
import 'package:tcp_client/login/models/password.dart';
|
||||
import 'package:tcp_client/login/models/username.dart';
|
||||
|
||||
class LoginState extends Equatable {
|
||||
final Username username;
|
||||
final Password password;
|
||||
final String avatar;
|
||||
|
||||
final FormzStatus status;
|
||||
|
||||
const LoginState({
|
||||
this.status = FormzStatus.pure,
|
||||
this.username = const Username.pure(),
|
||||
this.password = const Password.pure(),
|
||||
this.avatar = ""
|
||||
});
|
||||
|
||||
LoginState copyWith({
|
||||
FormzStatus? status,
|
||||
Username? username,
|
||||
Password? password,
|
||||
String? avatar
|
||||
}) {
|
||||
return LoginState(
|
||||
status: status ?? this.status,
|
||||
username: username ?? this.username,
|
||||
password: password ?? this.password,
|
||||
avatar: avatar ?? this.avatar
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, username, password, avatar];
|
||||
}
|
||||
122
lib/login/login_page.dart
Normal file
122
lib/login/login_page.dart
Normal file
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 15:06:30
|
||||
* @LastEditTime : 2022-10-12 18:03:29
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:formz/formz.dart';
|
||||
import 'package:tcp_client/home/home_page.dart';
|
||||
import 'package:tcp_client/login/bloc/login_cubit.dart';
|
||||
import 'package:tcp_client/login/bloc/login_state.dart';
|
||||
import 'package:tcp_client/login/view/login_form.dart';
|
||||
import 'package:tcp_client/register/register_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 LoginPage extends StatelessWidget {
|
||||
const LoginPage({
|
||||
required this.localServiceRepository,
|
||||
required this.tcpRepository,
|
||||
super.key
|
||||
});
|
||||
|
||||
static Route<void> route({
|
||||
required LocalServiceRepository localServiceRepository,
|
||||
required TCPRepository tcpRepository
|
||||
}) => MaterialPageRoute<void>(builder: (context) => LoginPage(
|
||||
localServiceRepository: localServiceRepository,
|
||||
tcpRepository: tcpRepository
|
||||
));
|
||||
|
||||
final LocalServiceRepository localServiceRepository;
|
||||
final TCPRepository tcpRepository;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LoginCubit(
|
||||
localServiceRepository: localServiceRepository,
|
||||
tcpRepository: tcpRepository
|
||||
),
|
||||
child: Scaffold(
|
||||
body: BlocListener<LoginCubit, LoginState>(
|
||||
listener:(context, state) {
|
||||
if(state.status == FormzStatus.submissionFailure) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Login Failed'))
|
||||
);
|
||||
}
|
||||
else if(state.status == FormzStatus.submissionSuccess) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Login Successed'))
|
||||
);
|
||||
Future.delayed(const Duration(seconds: 1)).then((_) {
|
||||
Navigator.of(context).pushReplacement(HomePage.route());
|
||||
});
|
||||
}
|
||||
},
|
||||
listenWhen: (previous, current) => previous.status != current.status,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(),
|
||||
),
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
child: const LoginPanel()
|
||||
)
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('Does not have an account?'),
|
||||
const SizedBox(width: 8,),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).push(RegisterPage.route(localServiceRepository: localServiceRepository, tcpRepository: tcpRepository)),
|
||||
style: ButtonStyle(
|
||||
overlayColor: MaterialStateProperty.all(Colors.transparent),
|
||||
foregroundColor: MaterialStateProperty.all(Colors.blue[800])
|
||||
),
|
||||
child: const Text('Register'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoginPanel extends StatelessWidget {
|
||||
const LoginPanel({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
LoginForm()
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
20
lib/login/models/password.dart
Normal file
20
lib/login/models/password.dart
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 15:37:29
|
||||
* @LastEditTime : 2022-10-12 15:46:38
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:formz/formz.dart';
|
||||
|
||||
enum PasswordValidationError { empty }
|
||||
|
||||
class Password extends FormzInput<String, PasswordValidationError> {
|
||||
const Password.pure() : super.pure('');
|
||||
const Password.dirty([super.value = '']) : super.dirty();
|
||||
|
||||
@override
|
||||
PasswordValidationError? validator(String? value) {
|
||||
return value?.isNotEmpty == true ? null : PasswordValidationError.empty;
|
||||
}
|
||||
}
|
||||
20
lib/login/models/username.dart
Normal file
20
lib/login/models/username.dart
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 15:37:34
|
||||
* @LastEditTime : 2022-10-12 15:45:11
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:formz/formz.dart';
|
||||
|
||||
enum UsernameValidationError { empty }
|
||||
|
||||
class Username extends FormzInput<String, UsernameValidationError> {
|
||||
const Username.pure(): super.pure('');
|
||||
const Username.dirty([super.value = '']): super.dirty();
|
||||
|
||||
@override
|
||||
UsernameValidationError? validator(String? value) {
|
||||
return value?.isNotEmpty == true ? null : UsernameValidationError.empty;
|
||||
}
|
||||
}
|
||||
6
lib/login/view/avatar_widget.dart
Normal file
6
lib/login/view/avatar_widget.dart
Normal file
@ -0,0 +1,6 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 15:37:19
|
||||
* @LastEditTime : 2022-10-12 17:10:58
|
||||
* @Description :
|
||||
*/
|
||||
129
lib/login/view/login_form.dart
Normal file
129
lib/login/view/login_form.dart
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 16:29:25
|
||||
* @LastEditTime : 2022-10-12 17:31:23
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:formz/formz.dart';
|
||||
import 'package:tcp_client/login/bloc/login_cubit.dart';
|
||||
import 'package:tcp_client/login/bloc/login_state.dart';
|
||||
import 'package:tcp_client/login/models/password.dart';
|
||||
import 'package:tcp_client/login/models/username.dart';
|
||||
|
||||
class LoginForm extends StatelessWidget {
|
||||
const LoginForm({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
UsernameInput(),
|
||||
SizedBox(height: 8,),
|
||||
PasswordInput(),
|
||||
SizedBox(height: 28,),
|
||||
SubmitButton()
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UsernameInput extends StatelessWidget {
|
||||
const UsernameInput({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<LoginCubit, LoginState>(
|
||||
buildWhen: (previous, current) => previous.username != current.username,
|
||||
builder: (context, state) {
|
||||
return TextField(
|
||||
onChanged: (username) {
|
||||
context.read<LoginCubit>().onUsernameChange(Username.dirty(username));
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Username',
|
||||
errorText: state.username.invalid ? 'Invalid username' : null
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PasswordInput extends StatelessWidget {
|
||||
const PasswordInput({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<LoginCubit, LoginState>(
|
||||
buildWhen: (previous, current) => previous.password != current.password,
|
||||
builder: (context, state) {
|
||||
return TextField(
|
||||
onChanged: (password) {
|
||||
context.read<LoginCubit>().onPasswordChange(Password.dirty(password));
|
||||
},
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password',
|
||||
errorText: state.password.invalid ? 'Invalid password' : null
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SubmitButton extends StatelessWidget {
|
||||
const SubmitButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<LoginCubit, LoginState>(
|
||||
buildWhen: (previous, current) => previous.status != current.status,
|
||||
builder: (context, state) {
|
||||
return SizedBox(
|
||||
height: 40.0,
|
||||
width: 90.0,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
shape: MaterialStateProperty.all(const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0)))),
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
switch(states) {
|
||||
case {MaterialState.disabled}:
|
||||
return Colors.grey;
|
||||
case {MaterialState.pressed}:
|
||||
return Colors.blue[800];
|
||||
default:
|
||||
return Colors.blue[700];
|
||||
}
|
||||
}),
|
||||
overlayColor: MaterialStateProperty.all(Colors.blue[900]!.withOpacity(0.2))
|
||||
),
|
||||
onPressed: state.status == FormzStatus.submissionInProgress ? null : () {
|
||||
context.read<LoginCubit>().onSubmission();
|
||||
},
|
||||
child: state.status == FormzStatus.submissionInProgress ?
|
||||
const SizedBox(
|
||||
height: 14.0,
|
||||
width: 14.0,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
strokeWidth: 3,
|
||||
),
|
||||
) :
|
||||
const Text(
|
||||
'LOGIN',
|
||||
style: TextStyle(
|
||||
color: Colors.white
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
131
lib/main.dart
131
lib/main.dart
@ -1,5 +1,18 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-10 08:04:53
|
||||
* @LastEditTime : 2022-10-12 17:55:07
|
||||
* @Description :
|
||||
*/
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:tcp_client/home/home_page.dart';
|
||||
import 'package:tcp_client/initialization/cubit/initialization_cubit.dart';
|
||||
import 'package:tcp_client/initialization/cubit/initialization_state.dart';
|
||||
import 'package:tcp_client/initialization/initialization_page.dart';
|
||||
import 'package:tcp_client/login/login_page.dart';
|
||||
|
||||
void main() {
|
||||
sqfliteFfiInit();
|
||||
@ -15,103 +28,43 @@ class MyApp extends StatelessWidget {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
// This is the theme of your application.
|
||||
//
|
||||
// Try running your application with "flutter run". You'll see the
|
||||
// application has a blue toolbar. Then, without quitting the app, try
|
||||
// changing the primarySwatch below to Colors.green and then invoke
|
||||
// "hot reload" (press "r" in the console where you ran "flutter run",
|
||||
// or simply save your changes to "hot reload" in a Flutter IDE).
|
||||
// Notice that the counter didn't reset back to zero; the application
|
||||
// is not restarted.
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
||||
home: const SplashPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning
|
||||
// that it has a State object (defined below) that contains fields that affect
|
||||
// how it looks.
|
||||
|
||||
// This class is the configuration for the state. It holds the values (in this
|
||||
// case the title) provided by the parent (in this case the App widget) and
|
||||
// used by the build method of the State. Fields in a Widget subclass are
|
||||
// always marked "final".
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
int _counter = 0;
|
||||
|
||||
void _incrementCounter() {
|
||||
setState(() {
|
||||
// This call to setState tells the Flutter framework that something has
|
||||
// changed in this State, which causes it to rerun the build method below
|
||||
// so that the display can reflect the updated values. If we changed
|
||||
// _counter without calling setState(), then the build method would not be
|
||||
// called again, and so nothing would appear to happen.
|
||||
_counter++;
|
||||
});
|
||||
}
|
||||
class SplashPage extends StatelessWidget {
|
||||
const SplashPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This method is rerun every time setState is called, for instance as done
|
||||
// by the _incrementCounter method above.
|
||||
//
|
||||
// The Flutter framework has been optimized to make rerunning build methods
|
||||
// fast, so that you can just rebuild anything that needs updating rather
|
||||
// than having to individually change instances of widgets.
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: Center(
|
||||
// Center is a layout widget. It takes a single child and positions it
|
||||
// in the middle of the parent.
|
||||
child: Column(
|
||||
// Column is also a layout widget. It takes a list of children and
|
||||
// arranges them vertically. By default, it sizes itself to fit its
|
||||
// children horizontally, and tries to be as tall as its parent.
|
||||
//
|
||||
// Invoke "debug painting" (press "p" in the console, choose the
|
||||
// "Toggle Debug Paint" action from the Flutter Inspector in Android
|
||||
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
|
||||
// to see the wireframe for each widget.
|
||||
//
|
||||
// Column has various properties to control how it sizes itself and
|
||||
// how it positions its children. Here we use mainAxisAlignment to
|
||||
// center the children vertically; the main axis here is the vertical
|
||||
// axis because Columns are vertical (the cross axis would be
|
||||
// horizontal).
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You have pushed the button this many times:',
|
||||
),
|
||||
Text(
|
||||
'$_counter',
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _incrementCounter,
|
||||
tooltip: 'Increment',
|
||||
child: const Icon(Icons.add),
|
||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
||||
return BlocProvider<InitializationCubit>(
|
||||
create: (context) {
|
||||
return InitializationCubit(
|
||||
serverAddress: '127.0.0.1',
|
||||
serverPort: 20706
|
||||
);
|
||||
},
|
||||
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) {
|
||||
Navigator.of(context).pushReplacement(HomePage.route());
|
||||
}
|
||||
else {
|
||||
Navigator.of(context).pushReplacement(LoginPage.route(
|
||||
localServiceRepository: state.localServiceRepository!,
|
||||
tcpRepository: state.tcpRepository!
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
child: const InitializePage(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
78
lib/register/bloc/register_cubit.dart
Normal file
78
lib/register/bloc/register_cubit.dart
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 15:38:07
|
||||
* @LastEditTime : 2022-10-12 17:52:03
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:formz/formz.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tcp_client/register/bloc/register_state.dart';
|
||||
import 'package:tcp_client/register/models/password.dart';
|
||||
import 'package:tcp_client/register/models/username.dart';
|
||||
import 'package:tcp_client/repositories/common_models/useridentity.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 RegisterCubit extends Cubit<RegisterState> {
|
||||
RegisterCubit({
|
||||
required this.localServiceRepository,
|
||||
required this.tcpRepository
|
||||
}): super(const RegisterState());
|
||||
|
||||
final LocalServiceRepository localServiceRepository;
|
||||
final TCPRepository tcpRepository;
|
||||
|
||||
void onPasswordChange(Password password) {
|
||||
emit(state.copyWith(
|
||||
status: Formz.validate([state.username, password]),
|
||||
password: password
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> onUsernameChange(Username username) async {
|
||||
emit(state.copyWith(
|
||||
status: Formz.validate([username, state.password]),
|
||||
username: username,
|
||||
));
|
||||
var userinfo = await localServiceRepository.fetchUserInfoViaUsername(username: username.value);
|
||||
emit(state.copyWith(
|
||||
avatar: userinfo?.avatarEncoded
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> onSubmission() async {
|
||||
if(state.status.isValidated) {
|
||||
emit(state.copyWith(
|
||||
status: FormzStatus.submissionInProgress
|
||||
));
|
||||
tcpRepository.pushRequest(RegisterRequest(
|
||||
identity: UserIdentity(
|
||||
username: state.username.value,
|
||||
password: state.password.value
|
||||
),
|
||||
token: (await SharedPreferences.getInstance()).getInt('token')
|
||||
));
|
||||
await for(var response in tcpRepository.responseStreamBroadcast) {
|
||||
if(response.type == TCPResponseType.register) {
|
||||
if(response.status == TCPResponseStatus.ok) {
|
||||
var pref = await SharedPreferences.getInstance();
|
||||
pref.setInt('userid', (response as RegisterResponse).userInfo!.userID);
|
||||
emit(state.copyWith(
|
||||
status: FormzStatus.submissionSuccess
|
||||
));
|
||||
}
|
||||
else {
|
||||
emit(state.copyWith(
|
||||
status: FormzStatus.submissionFailure
|
||||
));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
lib/register/bloc/register_state.dart
Normal file
43
lib/register/bloc/register_state.dart
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 15:38:13
|
||||
* @LastEditTime : 2022-10-12 17:40:39
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:formz/formz.dart';
|
||||
import 'package:tcp_client/register/models/password.dart';
|
||||
import 'package:tcp_client/register/models/username.dart';
|
||||
|
||||
class RegisterState extends Equatable {
|
||||
final Username username;
|
||||
final Password password;
|
||||
final String avatar;
|
||||
|
||||
final FormzStatus status;
|
||||
|
||||
const RegisterState({
|
||||
this.status = FormzStatus.pure,
|
||||
this.username = const Username.pure(),
|
||||
this.password = const Password.pure(),
|
||||
this.avatar = ""
|
||||
});
|
||||
|
||||
RegisterState copyWith({
|
||||
FormzStatus? status,
|
||||
Username? username,
|
||||
Password? password,
|
||||
String? avatar
|
||||
}) {
|
||||
return RegisterState(
|
||||
status: status ?? this.status,
|
||||
username: username ?? this.username,
|
||||
password: password ?? this.password,
|
||||
avatar: avatar ?? this.avatar
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, username, password, avatar];
|
||||
}
|
||||
20
lib/register/models/password.dart
Normal file
20
lib/register/models/password.dart
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 15:37:29
|
||||
* @LastEditTime : 2022-10-12 15:46:38
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:formz/formz.dart';
|
||||
|
||||
enum PasswordValidationError { empty }
|
||||
|
||||
class Password extends FormzInput<String, PasswordValidationError> {
|
||||
const Password.pure() : super.pure('');
|
||||
const Password.dirty([super.value = '']) : super.dirty();
|
||||
|
||||
@override
|
||||
PasswordValidationError? validator(String? value) {
|
||||
return value?.isNotEmpty == true ? null : PasswordValidationError.empty;
|
||||
}
|
||||
}
|
||||
20
lib/register/models/username.dart
Normal file
20
lib/register/models/username.dart
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 15:37:34
|
||||
* @LastEditTime : 2022-10-12 15:45:11
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:formz/formz.dart';
|
||||
|
||||
enum UsernameValidationError { empty }
|
||||
|
||||
class Username extends FormzInput<String, UsernameValidationError> {
|
||||
const Username.pure(): super.pure('');
|
||||
const Username.dirty([super.value = '']): super.dirty();
|
||||
|
||||
@override
|
||||
UsernameValidationError? validator(String? value) {
|
||||
return value?.isNotEmpty == true ? null : UsernameValidationError.empty;
|
||||
}
|
||||
}
|
||||
114
lib/register/register_page.dart
Normal file
114
lib/register/register_page.dart
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 17:36:38
|
||||
* @LastEditTime : 2022-10-12 17:50:42
|
||||
* @Description :
|
||||
*/
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 15:06:30
|
||||
* @LastEditTime : 2022-10-12 17:34:10
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:formz/formz.dart';
|
||||
import 'package:tcp_client/home/home_page.dart';
|
||||
import 'package:tcp_client/register/bloc/register_cubit.dart';
|
||||
import 'package:tcp_client/register/bloc/register_state.dart';
|
||||
import 'package:tcp_client/register/view/register_form.dart';
|
||||
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart';
|
||||
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
|
||||
|
||||
class RegisterPage extends StatelessWidget {
|
||||
const RegisterPage({
|
||||
required this.localServiceRepository,
|
||||
required this.tcpRepository,
|
||||
super.key
|
||||
});
|
||||
|
||||
static Route<void> route({
|
||||
required LocalServiceRepository localServiceRepository,
|
||||
required TCPRepository tcpRepository
|
||||
}) => MaterialPageRoute<void>(builder: (context) => RegisterPage(
|
||||
localServiceRepository: localServiceRepository,
|
||||
tcpRepository: tcpRepository
|
||||
));
|
||||
|
||||
final LocalServiceRepository localServiceRepository;
|
||||
final TCPRepository tcpRepository;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => RegisterCubit(
|
||||
localServiceRepository: localServiceRepository,
|
||||
tcpRepository: tcpRepository
|
||||
),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.blue[800],
|
||||
),
|
||||
body: BlocListener<RegisterCubit, RegisterState>(
|
||||
listener:(context, state) {
|
||||
if(state.status == FormzStatus.submissionFailure) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Register Failed'))
|
||||
);
|
||||
}
|
||||
else if(state.status == FormzStatus.submissionSuccess) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Register Successed'))
|
||||
);
|
||||
Future.delayed(const Duration(seconds: 1)).then((_) {
|
||||
Navigator.of(context).pushReplacement(HomePage.route());
|
||||
});
|
||||
}
|
||||
},
|
||||
listenWhen: (previous, current) => previous.status != current.status,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(),
|
||||
),
|
||||
const Expanded(
|
||||
flex: 6,
|
||||
child: RegisterPanel()
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RegisterPanel extends StatelessWidget {
|
||||
const RegisterPanel({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
RegisterForm()
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
129
lib/register/view/register_form.dart
Normal file
129
lib/register/view/register_form.dart
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 16:29:25
|
||||
* @LastEditTime : 2022-10-12 17:44:33
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:formz/formz.dart';
|
||||
import 'package:tcp_client/register/bloc/register_cubit.dart';
|
||||
import 'package:tcp_client/register/bloc/register_state.dart';
|
||||
import 'package:tcp_client/register/models/password.dart';
|
||||
import 'package:tcp_client/register/models/username.dart';
|
||||
|
||||
class RegisterForm extends StatelessWidget {
|
||||
const RegisterForm({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
UsernameInput(),
|
||||
SizedBox(height: 8,),
|
||||
PasswordInput(),
|
||||
SizedBox(height: 28,),
|
||||
SubmitButton()
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UsernameInput extends StatelessWidget {
|
||||
const UsernameInput({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<RegisterCubit, RegisterState>(
|
||||
buildWhen: (previous, current) => previous.username != current.username,
|
||||
builder: (context, state) {
|
||||
return TextField(
|
||||
onChanged: (username) {
|
||||
context.read<RegisterCubit>().onUsernameChange(Username.dirty(username));
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Username',
|
||||
errorText: state.username.invalid ? 'Invalid username' : null
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PasswordInput extends StatelessWidget {
|
||||
const PasswordInput({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<RegisterCubit, RegisterState>(
|
||||
buildWhen: (previous, current) => previous.password != current.password,
|
||||
builder: (context, state) {
|
||||
return TextField(
|
||||
onChanged: (password) {
|
||||
context.read<RegisterCubit>().onPasswordChange(Password.dirty(password));
|
||||
},
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password',
|
||||
errorText: state.password.invalid ? 'Invalid password' : null
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SubmitButton extends StatelessWidget {
|
||||
const SubmitButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<RegisterCubit, RegisterState>(
|
||||
buildWhen: (previous, current) => previous.status != current.status,
|
||||
builder: (context, state) {
|
||||
return SizedBox(
|
||||
height: 40.0,
|
||||
width: 100.0,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
shape: MaterialStateProperty.all(const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0)))),
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
switch(states) {
|
||||
case {MaterialState.disabled}:
|
||||
return Colors.grey;
|
||||
case {MaterialState.pressed}:
|
||||
return Colors.blue[800];
|
||||
default:
|
||||
return Colors.blue[700];
|
||||
}
|
||||
}),
|
||||
overlayColor: MaterialStateProperty.all(Colors.blue[900]!.withOpacity(0.2))
|
||||
),
|
||||
onPressed: state.status == FormzStatus.submissionInProgress ? null : () {
|
||||
context.read<RegisterCubit>().onSubmission();
|
||||
},
|
||||
child: state.status == FormzStatus.submissionInProgress ?
|
||||
const SizedBox(
|
||||
height: 14.0,
|
||||
width: 14.0,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
strokeWidth: 3,
|
||||
),
|
||||
) :
|
||||
const Text(
|
||||
'REGISTER',
|
||||
style: TextStyle(
|
||||
color: Colors.white
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 10:56:02
|
||||
* @LastEditTime : 2022-10-11 23:49:16
|
||||
* @LastEditTime : 2022-10-12 15:35:30
|
||||
* @Description : Local Service Repository
|
||||
*/
|
||||
|
||||
@ -74,8 +74,7 @@ class LocalServiceRepository {
|
||||
var file = File(filePickResult.files.single.path!);
|
||||
return LocalFile(
|
||||
file: file,
|
||||
filemd5: md5.convert(await file.readAsBytes()).toString(),
|
||||
ext: file.path.substring(file.path.lastIndexOf('.'))
|
||||
filemd5: md5.convert(await file.readAsBytes()).toString()
|
||||
);
|
||||
}
|
||||
|
||||
@ -157,7 +156,7 @@ class LocalServiceRepository {
|
||||
return queryResult.map((e) => Message.fromJSONObject(jsonObject: e)).toList();
|
||||
}
|
||||
|
||||
Future<File?> findFile({required String filemd5}) async {
|
||||
Future<File?> findFile({required String filemd5, required String fileName}) async {
|
||||
var directory = await _database.query(
|
||||
'files',
|
||||
where: 'filemd5 = ?',
|
||||
@ -173,7 +172,25 @@ class LocalServiceRepository {
|
||||
//Try if the file exists
|
||||
var file = File(filePath);
|
||||
if(await file.exists()) {
|
||||
return file;
|
||||
//Copy to desired file path
|
||||
var pref = await SharedPreferences.getInstance();
|
||||
var userID = pref.getInt('userid');
|
||||
var documentPath = (await getApplicationDocumentsDirectory()).path;
|
||||
var fileBaseName = fileName.substring(0, fileName.lastIndexOf('.'));
|
||||
var fileExt = fileName.substring(fileName.lastIndexOf('.'));
|
||||
var duplicate = 0;
|
||||
//Rename target file
|
||||
await Directory('$documentPath/files').create();
|
||||
await Directory('$documentPath/files/$userID').create();
|
||||
var targetFilePath = '$documentPath/files/$userID/$fileBaseName$fileExt';
|
||||
var targetFile = File(targetFilePath);
|
||||
while(await targetFile.exists()) {
|
||||
duplicate += 1;
|
||||
targetFilePath = '$documentPath/files/$userID/$fileBaseName($duplicate)$fileExt';
|
||||
targetFile = File(targetFilePath);
|
||||
}
|
||||
targetFile = await file.copy(targetFilePath);
|
||||
return targetFile;
|
||||
}
|
||||
else {
|
||||
//Delete all linked files
|
||||
@ -184,6 +201,7 @@ class LocalServiceRepository {
|
||||
filemd5
|
||||
]
|
||||
);
|
||||
//TODO: maybe throw some error here?
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -194,7 +212,9 @@ class LocalServiceRepository {
|
||||
}) async {
|
||||
//Write to file library
|
||||
var documentPath = (await getApplicationDocumentsDirectory()).path;
|
||||
var permanentFilePath = '$documentPath/files/${tempFile.filemd5}${tempFile.ext}';
|
||||
await Directory('$documentPath/files').create();
|
||||
await Directory('$documentPath/files/.lib').create();
|
||||
var permanentFilePath = '$documentPath/files/.lib/${tempFile.filemd5}';
|
||||
await tempFile.file.copy(permanentFilePath);
|
||||
await _database.insert(
|
||||
'files',
|
||||
@ -234,7 +254,7 @@ class LocalServiceRepository {
|
||||
_userInfoChangeStreamController.add(userInfo);
|
||||
}
|
||||
|
||||
Future<UserInfo?> fetchUserInfo({required userid}) async {
|
||||
Future<UserInfo?> fetchUserInfoViaID({required int userid}) async {
|
||||
var targetUser = await _database.query(
|
||||
'users',
|
||||
where: 'userid = ?',
|
||||
@ -247,4 +267,8 @@ class LocalServiceRepository {
|
||||
return UserInfo.fromJSONObject(jsonObject: targetUser[0]);
|
||||
}
|
||||
}
|
||||
|
||||
Future<UserInfo?> fetchUserInfoViaUsername({required String username}) async {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 10:55:36
|
||||
* @LastEditTime : 2022-10-11 22:54:59
|
||||
* @LastEditTime : 2022-10-12 09:19:50
|
||||
* @Description : Local File Model
|
||||
*/
|
||||
|
||||
@ -12,9 +12,8 @@ import 'package:equatable/equatable.dart';
|
||||
class LocalFile extends Equatable {
|
||||
final File file;
|
||||
final String filemd5;
|
||||
final String ext;
|
||||
|
||||
const LocalFile({required this.file, required this.filemd5, required this.ext});
|
||||
const LocalFile({required this.file, required this.filemd5});
|
||||
|
||||
@override
|
||||
List<Object> get props => [filemd5];
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 09:44:03
|
||||
* @LastEditTime : 2022-10-11 16:55:08
|
||||
* @LastEditTime : 2022-10-12 13:56:56
|
||||
* @Description : Abstract TCP request class
|
||||
*/
|
||||
|
||||
@ -43,12 +43,12 @@ enum TCPRequestType {
|
||||
|
||||
abstract class TCPRequest {
|
||||
final TCPRequestType _type;
|
||||
final int _token;
|
||||
final int? _token;
|
||||
|
||||
const TCPRequest({required TCPRequestType type, required int token}): _type = type, _token = token;
|
||||
const TCPRequest({required TCPRequestType type, required int? token}): _type = type, _token = token;
|
||||
|
||||
TCPRequestType get type => _type;
|
||||
int get token => _token;
|
||||
int? get token => _token;
|
||||
|
||||
Map<String, Object?> get body;
|
||||
|
||||
@ -56,7 +56,7 @@ abstract class TCPRequest {
|
||||
return jsonEncode({
|
||||
'request': type.value,
|
||||
'body': body,
|
||||
'token': token
|
||||
'tokenid': token
|
||||
});
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ abstract class TCPRequest {
|
||||
}
|
||||
|
||||
class CheckStateRequest extends TCPRequest {
|
||||
const CheckStateRequest({required int token}): super(type: TCPRequestType.checkState, token: token);
|
||||
const CheckStateRequest({required int? token}): super(type: TCPRequestType.checkState, token: token);
|
||||
|
||||
@override
|
||||
Map<String, Object?> get body => {};
|
||||
@ -81,7 +81,7 @@ class RegisterRequest extends TCPRequest {
|
||||
|
||||
RegisterRequest({
|
||||
required UserIdentity identity,
|
||||
required token
|
||||
required int? token
|
||||
}): _identity = identity, super(type: TCPRequestType.register, token: token);
|
||||
|
||||
@override
|
||||
@ -93,7 +93,7 @@ class LoginRequest extends TCPRequest {
|
||||
|
||||
LoginRequest({
|
||||
required UserIdentity identity,
|
||||
required token
|
||||
required int? token
|
||||
}): _identity = identity, super(type: TCPRequestType.login, token: token);
|
||||
|
||||
@override
|
||||
@ -101,14 +101,14 @@ class LoginRequest extends TCPRequest {
|
||||
}
|
||||
|
||||
class LogoutRequest extends TCPRequest {
|
||||
const LogoutRequest({required int token}): super(type: TCPRequestType.logout, token: token);
|
||||
const LogoutRequest({required int? token}): super(type: TCPRequestType.logout, token: token);
|
||||
|
||||
@override
|
||||
Map<String, Object?> get body => {};
|
||||
}
|
||||
|
||||
class GetProfileRequest extends TCPRequest {
|
||||
const GetProfileRequest({required int token}): super(type: TCPRequestType.profile, token: token);
|
||||
const GetProfileRequest({required int? token}): super(type: TCPRequestType.profile, token: token);
|
||||
|
||||
@override
|
||||
Map<String, Object?> get body => {};
|
||||
@ -119,7 +119,7 @@ class ModifyPasswordRequest extends TCPRequest {
|
||||
|
||||
ModifyPasswordRequest({
|
||||
required UserIdentity identity,
|
||||
required token
|
||||
required int? token
|
||||
}): _identity = identity, super(type: TCPRequestType.modifyPassword, token: token);
|
||||
|
||||
@override
|
||||
@ -131,7 +131,7 @@ class ModifyProfileRequest extends TCPRequest {
|
||||
|
||||
const ModifyProfileRequest ({
|
||||
required UserInfo userInfo,
|
||||
required int token
|
||||
required int? token
|
||||
}): _userinfo = userInfo, super(type: TCPRequestType.modifyProfile, token: token);
|
||||
|
||||
@override
|
||||
@ -143,7 +143,7 @@ class SendMessageRequest extends TCPRequest {
|
||||
|
||||
SendMessageRequest({
|
||||
required Message message,
|
||||
required int token
|
||||
required int? token
|
||||
}):
|
||||
_message = message,
|
||||
super(type: TCPRequestType.sendMessage, token: token);
|
||||
@ -170,7 +170,7 @@ class SendMessageRequest extends TCPRequest {
|
||||
}
|
||||
|
||||
class FetchMessageRequest extends TCPRequest {
|
||||
const FetchMessageRequest({required int token}): super(type: TCPRequestType.fetchMessage, token: token);
|
||||
const FetchMessageRequest({required int? token}): super(type: TCPRequestType.fetchMessage, token: token);
|
||||
|
||||
@override
|
||||
Map<String, Object?> get body => {};
|
||||
@ -179,7 +179,7 @@ class FetchMessageRequest extends TCPRequest {
|
||||
class FindFileRequest extends TCPRequest {
|
||||
final LocalFile _file;
|
||||
|
||||
const FindFileRequest({required LocalFile file, required int token}): _file = file, super(type: TCPRequestType.findFile, token: token);
|
||||
const FindFileRequest({required LocalFile file, required int? token}): _file = file, super(type: TCPRequestType.findFile, token: token);
|
||||
|
||||
@override
|
||||
Map<String, Object?> get body => {
|
||||
@ -192,7 +192,7 @@ class FindFileRequest extends TCPRequest {
|
||||
class FetchFileRequest extends TCPRequest {
|
||||
final String _msgmd5;
|
||||
|
||||
const FetchFileRequest({required String msgmd5, required int token}): _msgmd5 = msgmd5, super(type: TCPRequestType.fetchFile, token: token);
|
||||
const FetchFileRequest({required String msgmd5, required int? token}): _msgmd5 = msgmd5, super(type: TCPRequestType.fetchFile, token: token);
|
||||
|
||||
@override
|
||||
Map<String, Object?> get body => {
|
||||
@ -205,7 +205,7 @@ class FetchFileRequest extends TCPRequest {
|
||||
class SearchUserRequest extends TCPRequest {
|
||||
final String _username;
|
||||
|
||||
const SearchUserRequest({required String username, required int token}): _username = username, super(type: TCPRequestType.searchUser, token: token);
|
||||
const SearchUserRequest({required String username, required int? token}): _username = username, super(type: TCPRequestType.searchUser, token: token);
|
||||
|
||||
@override
|
||||
Map<String, Object?> get body => {
|
||||
@ -218,7 +218,7 @@ class SearchUserRequest extends TCPRequest {
|
||||
class AddContactRequest extends TCPRequest {
|
||||
final int _userid;
|
||||
|
||||
const AddContactRequest({required int userid, required int token}): _userid = userid, super(type: TCPRequestType.addContact, token: token);
|
||||
const AddContactRequest({required int userid, required int? token}): _userid = userid, super(type: TCPRequestType.addContact, token: token);
|
||||
|
||||
@override
|
||||
Map<String, Object?> get body => {
|
||||
@ -229,7 +229,7 @@ class AddContactRequest extends TCPRequest {
|
||||
}
|
||||
|
||||
class FetchContactRequest extends TCPRequest {
|
||||
const FetchContactRequest({required int token}): super(type: TCPRequestType.fetchContact, token: token);
|
||||
const FetchContactRequest({required int? token}): super(type: TCPRequestType.fetchContact, token: token);
|
||||
|
||||
@override
|
||||
Map<String, Object?> get body => {};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 11:02:19
|
||||
* @LastEditTime : 2022-10-11 22:55:48
|
||||
* @LastEditTime : 2022-10-12 13:25:49
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -84,39 +84,39 @@ class SetTokenReponse extends TCPResponse {
|
||||
}
|
||||
|
||||
class CheckStateResponse extends TCPResponse {
|
||||
late final UserInfo _userInfo;
|
||||
late final UserInfo? _userInfo;
|
||||
|
||||
CheckStateResponse({
|
||||
required Map<String, Object?> jsonObject
|
||||
}): super(jsonObject: jsonObject) {
|
||||
_userInfo = UserInfo.fromJSONObject(jsonObject: jsonObject['body'] as Map<String, Object?>);
|
||||
_userInfo = jsonObject['body'] == null ? null : UserInfo.fromJSONObject(jsonObject: jsonObject['body'] as Map<String, Object?>);
|
||||
}
|
||||
|
||||
UserInfo get userInfo => _userInfo;
|
||||
UserInfo? get userInfo => _userInfo;
|
||||
}
|
||||
|
||||
class RegisterResponse extends TCPResponse {
|
||||
late final UserInfo _userInfo;
|
||||
late final UserInfo? _userInfo;
|
||||
|
||||
RegisterResponse({
|
||||
required Map<String, Object?> jsonObject
|
||||
}): super(jsonObject: jsonObject) {
|
||||
_userInfo = UserInfo.fromJSONObject(jsonObject: jsonObject['body'] as Map<String, Object?>);
|
||||
_userInfo = jsonObject['body'] == null ? null : UserInfo.fromJSONObject(jsonObject: jsonObject['body'] as Map<String, Object?>);
|
||||
}
|
||||
|
||||
UserInfo get userInfo => _userInfo;
|
||||
UserInfo? get userInfo => _userInfo;
|
||||
}
|
||||
|
||||
class LoginResponse extends TCPResponse {
|
||||
late final UserInfo _userInfo;
|
||||
late final UserInfo? _userInfo;
|
||||
|
||||
LoginResponse({
|
||||
required Map<String, Object?> jsonObject
|
||||
}): super(jsonObject: jsonObject) {
|
||||
_userInfo = UserInfo.fromJSONObject(jsonObject: jsonObject['body'] as Map<String, Object?>);
|
||||
_userInfo = jsonObject['body'] == null ? null : UserInfo.fromJSONObject(jsonObject: jsonObject['body'] as Map<String, Object?>);
|
||||
}
|
||||
|
||||
UserInfo get userInfo => _userInfo;
|
||||
UserInfo? get userInfo => _userInfo;
|
||||
}
|
||||
|
||||
class LogoutResponse extends TCPResponse {
|
||||
@ -126,15 +126,15 @@ class LogoutResponse extends TCPResponse {
|
||||
}
|
||||
|
||||
class GetProfileResponse extends TCPResponse {
|
||||
late final UserInfo _userInfo;
|
||||
late final UserInfo? _userInfo;
|
||||
|
||||
GetProfileResponse({
|
||||
required Map<String, Object?> jsonObject
|
||||
}): super(jsonObject: jsonObject) {
|
||||
_userInfo = UserInfo.fromJSONObject(jsonObject: jsonObject['body'] as Map<String, Object?>);
|
||||
_userInfo = jsonObject['body'] == null ? null : UserInfo.fromJSONObject(jsonObject: jsonObject['body'] as Map<String, Object?>);
|
||||
}
|
||||
|
||||
UserInfo get userInfo => _userInfo;
|
||||
UserInfo? get userInfo => _userInfo;
|
||||
}
|
||||
|
||||
class ModifyPasswordResponse extends TCPResponse {
|
||||
@ -144,15 +144,15 @@ class ModifyPasswordResponse extends TCPResponse {
|
||||
}
|
||||
|
||||
class ModifyProfileResponse extends TCPResponse {
|
||||
late final UserInfo _userInfo;
|
||||
late final UserInfo? _userInfo;
|
||||
|
||||
ModifyProfileResponse({
|
||||
required Map<String, Object?> jsonObject
|
||||
}): super(jsonObject: jsonObject) {
|
||||
_userInfo = UserInfo.fromJSONObject(jsonObject: jsonObject['body'] as Map<String, Object?>);
|
||||
_userInfo = jsonObject['body'] == null ? null : UserInfo.fromJSONObject(jsonObject: jsonObject['body'] as Map<String, Object?>);
|
||||
}
|
||||
|
||||
UserInfo get userInfo => _userInfo;
|
||||
UserInfo? get userInfo => _userInfo;
|
||||
}
|
||||
|
||||
class SendMessageResponse extends TCPResponse {
|
||||
@ -203,8 +203,7 @@ class FetchFileResponse extends TCPResponse {
|
||||
}): super(jsonObject: jsonObject) {
|
||||
_payload = LocalFile(
|
||||
file: payload.file,
|
||||
filemd5: payload.filemd5,
|
||||
ext: (jsonObject['body'] as Map<String, Object?>)['ext'] as String
|
||||
filemd5: payload.filemd5
|
||||
);
|
||||
}
|
||||
|
||||
@ -212,15 +211,15 @@ class FetchFileResponse extends TCPResponse {
|
||||
}
|
||||
|
||||
class SearchUserResponse extends TCPResponse {
|
||||
late final UserInfo _userInfo;
|
||||
late final UserInfo? _userInfo;
|
||||
|
||||
SearchUserResponse({
|
||||
required Map<String, Object?> jsonObject
|
||||
}): super(jsonObject: jsonObject) {
|
||||
_userInfo = UserInfo.fromJSONObject(jsonObject: jsonObject['body'] as Map<String, Object?>);
|
||||
_userInfo = jsonObject['body'] == null ? null : UserInfo.fromJSONObject(jsonObject: jsonObject['body'] as Map<String, Object?>);
|
||||
}
|
||||
|
||||
UserInfo get userInfo => _userInfo;
|
||||
UserInfo? get userInfo => _userInfo;
|
||||
}
|
||||
|
||||
class AddContactResponse extends TCPResponse {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 09:42:05
|
||||
* @LastEditTime : 2022-10-11 22:55:28
|
||||
* @LastEditTime : 2022-10-12 16:57:50
|
||||
* @Description : TCP repository
|
||||
*/
|
||||
|
||||
@ -9,15 +9,17 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tcp_client/repositories/common_models/message.dart';
|
||||
import 'package:tcp_client/repositories/local_service_repository/models/local_file.dart';
|
||||
import 'package:tcp_client/repositories/tcp_repository/models/tcp_request.dart';
|
||||
import 'package:tcp_client/repositories/tcp_repository/models/tcp_response.dart';
|
||||
|
||||
class TCPRepository {
|
||||
TCPRepository({
|
||||
TCPRepository._internal({
|
||||
required Socket socket,
|
||||
required String remoteAddress,
|
||||
required int remotePort
|
||||
@ -30,13 +32,30 @@ class TCPRepository {
|
||||
}
|
||||
});
|
||||
Future(() async {
|
||||
await for(var response in _responseRawStreamController.stream) {
|
||||
var payloadFile = await _payloadRawStreamController.stream.single;
|
||||
await _pushResponse(responseBytes: response, tempFile: payloadFile);
|
||||
var responseQueue = StreamQueue(_responseRawStreamController.stream);
|
||||
var payloadQueue = StreamQueue(_payloadRawStreamController.stream);
|
||||
while(await Future<bool>(() => !_responseRawStreamController.isClosed && !_payloadRawStreamController.isClosed)) {
|
||||
var response = await responseQueue.next;
|
||||
var payload = await payloadQueue.next;
|
||||
await _pushResponse(responseBytes: response, tempFile: payload);
|
||||
}
|
||||
responseQueue.cancel();
|
||||
payloadQueue.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
static Future<TCPRepository> create({
|
||||
required String serverAddress,
|
||||
required int serverPort
|
||||
}) async {
|
||||
var socket = await Socket.connect(serverAddress, serverPort);
|
||||
return TCPRepository._internal(
|
||||
socket: socket,
|
||||
remoteAddress: serverAddress,
|
||||
remotePort: serverPort
|
||||
);
|
||||
}
|
||||
|
||||
final Socket _socket;
|
||||
final String _remoteAddress;
|
||||
final int _remotePort;
|
||||
@ -67,8 +86,32 @@ class TCPRepository {
|
||||
//Provide a request stream for widgets to push to
|
||||
final StreamController<TCPRequest> _requestStreamController = StreamController();
|
||||
|
||||
Future<void> pushRequest(TCPRequest request) async {
|
||||
_requestStreamController.add(request);
|
||||
void pushRequest(TCPRequest request) {
|
||||
if(request.type == TCPRequestType.sendMessage) {
|
||||
request as SendMessageRequest;
|
||||
if(request.message.type == MessageType.file) {
|
||||
Future(() async {
|
||||
//Duplicate current socket
|
||||
Socket socket = await Socket.connect(_remoteAddress, _remotePort);
|
||||
TCPRepository duplicatedRepository = TCPRepository._internal(
|
||||
socket: socket,
|
||||
remoteAddress: _remoteAddress,
|
||||
remotePort: _remotePort
|
||||
);
|
||||
duplicatedRepository._requestStreamController.add(request);
|
||||
await for(var response in duplicatedRepository.responseStream) {
|
||||
if(response.type == TCPResponseType.sendMessage) {
|
||||
_responseStreamController.add(response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
duplicatedRepository.dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
_requestStreamController.add(request);
|
||||
}
|
||||
}
|
||||
|
||||
//Listen to the incoming stream and emits event whenever there is a intact request
|
||||
@ -86,16 +129,20 @@ class TCPRepository {
|
||||
//Clear the length indicator bytes
|
||||
buffer.removeRange(0, 8);
|
||||
//Create temp file to read payload (might be huge)
|
||||
var tempFile = File('${Directory.current.path}/.tmp/${DateTime.now().microsecondsSinceEpoch}')..createSync();
|
||||
Directory('${Directory.current.path}/.tmp').createSync();
|
||||
//Create a pull stream for payload file
|
||||
_payloadPullStreamController = StreamController();
|
||||
//Create a future that listens to the status of the payload transmission
|
||||
Future(() async {
|
||||
await for(var data in _payloadPullStreamController.stream) {
|
||||
await tempFile.writeAsBytes(data, mode: FileMode.append, flush: true);
|
||||
}
|
||||
_payloadRawStreamController.add(tempFile);
|
||||
});
|
||||
() {
|
||||
var payloadPullStream = _payloadPullStreamController.stream;
|
||||
var tempFile = File('${Directory.current.path}/.tmp/${DateTime.now().microsecondsSinceEpoch}')..createSync();
|
||||
Future(() async {
|
||||
await for(var data in payloadPullStream) {
|
||||
await tempFile.writeAsBytes(data, mode: FileMode.append, flush: true);
|
||||
}
|
||||
_payloadRawStreamController.add(tempFile);
|
||||
});
|
||||
}();
|
||||
}
|
||||
else {
|
||||
//Buffered data is not long enough
|
||||
@ -111,7 +158,6 @@ class TCPRepository {
|
||||
//Got intact request json
|
||||
//Emit request buffer through stream
|
||||
_responseRawStreamController.add(buffer.sublist(0, responseLength));
|
||||
_responseRawStreamController.close();
|
||||
//Remove proccessed buffer
|
||||
buffer.removeRange(0, responseLength);
|
||||
//Clear awaiting request length
|
||||
@ -156,6 +202,10 @@ class TCPRepository {
|
||||
required List<int> responseBytes,
|
||||
required File tempFile
|
||||
}) async {
|
||||
if(_responseStreamController.isClosed) {
|
||||
await tempFile.delete();
|
||||
return;
|
||||
}
|
||||
var responseJSON = String.fromCharCodes(responseBytes);
|
||||
var responseObject = jsonDecode(responseJSON);
|
||||
TCPResponseType responseType = TCPResponseType.fromValue(responseObject['response'] as String);
|
||||
@ -225,8 +275,7 @@ class TCPRepository {
|
||||
jsonObject: responseObject,
|
||||
payload: LocalFile(
|
||||
file: tempFile,
|
||||
filemd5: md5.convert(await tempFile.readAsBytes()).toString(),
|
||||
ext: ""
|
||||
filemd5: md5.convert(await tempFile.readAsBytes()).toString()
|
||||
)
|
||||
));
|
||||
break;
|
||||
@ -258,7 +307,7 @@ class TCPRepository {
|
||||
}) async {
|
||||
//Duplicate current socket
|
||||
Socket socket = await Socket.connect(_remoteAddress, _remotePort);
|
||||
TCPRepository duplicatedRepository = TCPRepository(
|
||||
TCPRepository duplicatedRepository = TCPRepository._internal(
|
||||
socket: socket,
|
||||
remoteAddress: _remoteAddress,
|
||||
remotePort: _remotePort
|
||||
@ -278,11 +327,11 @@ class TCPRepository {
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_socket.close();
|
||||
_responseRawStreamController.close();
|
||||
_payloadPullStreamController.close();
|
||||
_payloadRawStreamController.close();
|
||||
_responseStreamController.close();
|
||||
_requestStreamController.close();
|
||||
_socket.close();
|
||||
}
|
||||
}
|
||||
16
pubspec.lock
16
pubspec.lock
@ -2,7 +2,7 @@
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: async
|
||||
url: "https://pub.flutter-io.cn"
|
||||
@ -156,6 +156,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
loading_indicator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: loading_indicator
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -380,6 +387,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
stream_transform:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: stream_transform
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -46,6 +46,9 @@ dependencies:
|
||||
git:
|
||||
url: https://github.com/crazecoder/open_file
|
||||
shared_preferences: ^2.0.15
|
||||
loading_indicator: ^3.1.0
|
||||
async: ^2.9.0
|
||||
stream_transform: ^2.0.1
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user