This commit is contained in:
Linloir 2022-10-18 11:37:00 +08:00
parent e7f690159c
commit 84fe935fcd
No known key found for this signature in database
GPG Key ID: 58EEB209A0F2C366
17 changed files with 168 additions and 93 deletions

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 14:03:56
* @LastEditTime : 2022-10-17 17:43:55
* @LastEditTime : 2022-10-18 11:25:04
* @Description :
*/
@ -9,7 +9,6 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:open_file/open_file.dart';
import 'package:path/path.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tcp_client/chat/cubit/chat_state.dart';
import 'package:tcp_client/chat/model/chat_history.dart';
@ -158,7 +157,6 @@ class ChatCubit extends Cubit<ChatState> {
response as ForwardMessageResponse;
if(response.message.senderID == userID || response.message.recieverID == userID) {
// Message storage will be handled by home bloc listener
// addMessage(response.message);
//Emit new state
var newHistory = ChatHistory(
message: response.message,

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-14 21:57:05
* @LastEditTime : 2022-10-14 22:55:16
* @LastEditTime : 2022-10-18 11:25:09
* @Description :
*/
@ -12,8 +12,6 @@ import 'package:tcp_client/chat/cubit/chat_cubit.dart';
import 'package:tcp_client/chat/view/input_box/cubit/input_state.dart';
import 'package:tcp_client/chat/view/input_box/model/input.dart';
import 'package:tcp_client/repositories/common_models/message.dart';
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart';
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
class MessageInputCubit extends Cubit<MessageInputState> {
MessageInputCubit({

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-14 17:54:30
* @LastEditTime : 2022-10-15 00:27:39
* @LastEditTime : 2022-10-18 11:25:12
* @Description :
*/
@ -16,7 +16,6 @@ import 'package:tcp_client/chat/view/input_box/cubit/input_cubit.dart';
import 'package:tcp_client/chat/view/input_box/cubit/input_state.dart';
import 'package:tcp_client/chat/view/input_box/model/input.dart';
import 'package:tcp_client/repositories/common_models/message.dart';
import 'package:tcp_client/repositories/tcp_repository/models/tcp_request.dart';
class InputBox extends StatelessWidget {
InputBox({super.key});
@ -29,9 +28,11 @@ class InputBox extends StatelessWidget {
create:(context) => MessageInputCubit(
chatCubit: context.read<ChatCubit>()
),
child: SizedBox(
height: 64,
child: Container(
// height: 64,
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: BlocListener<MessageInputCubit, MessageInputState>(
@ -39,16 +40,29 @@ class InputBox extends StatelessWidget {
listener: (context, state) {
_controller.clear();
},
child: Container(
constraints: const BoxConstraints(maxHeight: 120),
child: Builder(
builder: (context) => TextField(
controller: _controller,
onChanged: (value) {
context.read<MessageInputCubit>().onInputChange(MessageInput.dirty(value));
},
maxLines: null,
decoration: const InputDecoration(
border: UnderlineInputBorder(
borderSide: BorderSide(
width: 1.0
)
),
hintText: 'Input message here'
),
),
),
)
),
),
const SizedBox(width: 8.0,),
IconButton(
onPressed: () {
var chatCubit = context.read<ChatCubit>();
@ -66,8 +80,9 @@ class InputBox extends StatelessWidget {
}
});
},
icon: const Icon(Icons.attach_file_rounded)
icon: Icon(Icons.attach_file_rounded, color: Colors.grey[700],)
),
const SizedBox(width: 8.0,),
BlocBuilder<MessageInputCubit, MessageInputState>(
builder:(context, state) {
return IconButton(
@ -75,6 +90,7 @@ class InputBox extends StatelessWidget {
context.read<MessageInputCubit>().onSubmission();
} : null,
icon: const Icon(Icons.send_rounded),
color: state.status == FormzStatus.valid ? Colors.blue : Colors.grey[400],
);
},
)

View File

@ -1,14 +1,17 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 14:02:28
* @LastEditTime : 2022-10-17 17:00:45
* @LastEditTime : 2022-10-17 19:26:13
* @Description :
*/
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/cupertino.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/models/tcp_response.dart';
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
class HomeCubit extends Cubit<HomeState> {
@ -20,11 +23,13 @@ class HomeCubit extends Cubit<HomeState> {
pageController.addListener(() {
emit(state.copyWith(page: HomePagePosition.fromValue((pageController.page ?? 0).round())));
});
subscription = tcpRepository.responseStreamBroadcast.listen(_onTCPResponse);
}
final LocalServiceRepository localServiceRepository;
final TCPRepository tcpRepository;
final PageController pageController;
late final StreamSubscription subscription;
void switchPage(HomePagePosition newPage) {
pageController.animateToPage(
@ -33,4 +38,28 @@ class HomeCubit extends Cubit<HomeState> {
curve: Curves.easeInOutCubicEmphasized
);
}
void _onTCPResponse(TCPResponse response) {
switch(response.type) {
case TCPResponseType.forwardMessage: {
response as ForwardMessageResponse;
localServiceRepository.storeMessages([response.message]);
break;
}
case TCPResponseType.fetchMessage: {
response as FetchMessageResponse;
localServiceRepository.storeMessages(response.messages);
break;
}
default: {
break;
}
}
}
@override
Future<void> close() {
subscription.cancel();
return super.close();
}
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-12 23:36:07
* @LastEditTime : 2022-10-17 17:35:05
* @LastEditTime : 2022-10-18 11:25:18
* @Description :
*/
@ -18,8 +18,7 @@ class ContactPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: BlocBuilder<ContactCubit, ContactState>(
return BlocBuilder<ContactCubit, ContactState>(
builder: (context, state) {
var indexedData = state.indexedData;
return AzListView(
@ -50,7 +49,6 @@ class ContactPage extends StatelessWidget {
},
);
},
),
);
}
}

View File

@ -1,12 +1,10 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 14:02:00
* @LastEditTime : 2022-10-17 17:19:50
* @LastEditTime : 2022-10-18 11:25: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';

View File

@ -1,13 +1,12 @@
/*
* @Author : Linloir
* @Date : 2022-10-12 23:48:54
* @LastEditTime : 2022-10-15 01:01:48
* @LastEditTime : 2022-10-18 11:25:36
* @Description :
*/
import 'package:equatable/equatable.dart';
import 'package:tcp_client/repositories/common_models/message.dart';
import 'package:tcp_client/repositories/common_models/userinfo.dart';
class MessageInfo extends Equatable {
final Message? message;

View File

@ -1,19 +1,16 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 13:17:52
* @LastEditTime : 2022-10-17 17:18:22
* @LastEditTime : 2022-10-18 11:25:40
* @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';
@ -110,6 +107,8 @@ class MessageTile extends StatelessWidget {
child: IgnorePointer(
child: Text(
message?.contentDecoded ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
),

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-14 10:54:57
* @LastEditTime : 2022-10-14 11:44:12
* @LastEditTime : 2022-10-17 20:51:45
* @Description :
*/
@ -36,6 +36,7 @@ class LogOutCubit extends Cubit<LogOutStatus> {
}
void onLogout() async {
emit(LogOutStatus.processing);
tcpRepository.pushRequest(LogoutRequest(token: (await SharedPreferences.getInstance()).getInt('token')));
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-12 09:56:04
* @LastEditTime : 2022-10-14 16:47:04
* @LastEditTime : 2022-10-17 21:11:30
* @Description :
*/
@ -22,7 +22,11 @@ class InitializationCubit extends Cubit<InitializationState> {
TCPRepository? tcpRepository;
LocalServiceRepository? localServiceRepository;
Future(() async {
localServiceRepository = await LocalServiceRepository.create(databaseFilePath: '${(await getApplicationDocumentsDirectory()).path}/.data/database.db');
localServiceRepository = await LocalServiceRepository.create(
databaseFilePath: '${
(await getApplicationDocumentsDirectory()).path
}/LChatClient/.data/database.db'
);
}).then((_) {
emit(state.copyWith(
databaseStatus: InitializationStatus.done,

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 10:56:02
* @LastEditTime : 2022-10-17 13:01:35
* @LastEditTime : 2022-10-18 11:27:15
* @Description : Local Service Repository
*/
@ -133,20 +133,30 @@ class LocalServiceRepository {
}
Future<List<Message>> findMessages({required String pattern}) async {
if(pattern.isEmpty) {
return [];
}
// Obtain shared preferences.
final pref = await SharedPreferences.getInstance();
// Get user info from preferences
var currentUserID = pref.getInt('userid');
var alikeMessages = await _database.query(
var rawMessages = await _database.query(
'msgs',
where: 'userid = ? or targetid = ?',
where: '(userid = ? or targetid = ?)',
whereArgs: [
currentUserID, currentUserID
],
orderBy: 'timestamp desc',
limit: 100
);
return alikeMessages.map((e) => Message.fromJSONObject(jsonObject: e)).toList();
List<Message> alikeMessages = [];
for(var rawMessage in rawMessages) {
var message = Message.fromJSONObject(jsonObject: rawMessage);
if(message.contentDecoded.contains(pattern)) {
alikeMessages.add(message);
}
}
return alikeMessages;
}
//Find the most recent message of given users
@ -266,7 +276,7 @@ class LocalServiceRepository {
conflictAlgorithm: ConflictAlgorithm.replace
);
} catch (err) {
print(err);
//TODO: Log the err
}
//Clear temp file
tempFile.file.delete();
@ -318,7 +328,19 @@ class LocalServiceRepository {
}
Future<UserInfo?> fetchUserInfoViaUsername({required String username}) async {
var result = await _database.query(
'users',
where: 'username = ?',
whereArgs: [
username
]
);
if(result.isEmpty) {
return null;
}
else {
return UserInfo.fromJSONObject(jsonObject: result[0]);
}
}
Future<Message?> fetchMessage({required String msgmd5}) async {

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-11 09:42:05
* @LastEditTime : 2022-10-15 16:50:16
* @LastEditTime : 2022-10-18 11:27:46
* @Description : TCP repository
*/
@ -25,13 +25,26 @@ class TCPRepository {
required String remoteAddress,
required int remotePort
}): _socket = socket, _remoteAddress = remoteAddress, _remotePort = remotePort {
_socket.listen(_pullResponse);
Future(() async {
try{
await for(var response in _socket) {
_pullResponse(response);
}
} catch(e) {
_socket.close();
}
// _responseRawStreamController.close();
// _payloadPullStreamController.close();
// _payloadRawStreamController.close();
// _responseStreamController.close();
// _requestStreamController.close();
});
//This future never ends, would that be bothersome?
Future(() async {
await for(var request in _requestStreamController.stream) {
await _socket.addStream(request.stream);
}
});
}).onError((error, stackTrace) {_socket.close();});
Future(() async {
var responseQueue = StreamQueue(_responseRawStreamController.stream);
var payloadQueue = StreamQueue(_payloadRawStreamController.stream);
@ -42,7 +55,7 @@ class TCPRepository {
}
responseQueue.cancel();
payloadQueue.cancel();
});
}).onError((error, stackTrace) {_socket.close();});
}
static Future<TCPRepository> create({
@ -75,6 +88,9 @@ class TCPRepository {
//Byte length for subsequent data of the json object
int payloadLength = 0;
//Temp filename counter
int _fileCounter = 0;
//Construct a stream which emits events on intact requests
final StreamController<List<int>> _responseRawStreamController = StreamController();
final StreamController<File> _payloadRawStreamController = StreamController();
@ -147,9 +163,11 @@ class TCPRepository {
Future(() async {
var documentDirectory = await getApplicationDocumentsDirectory();
//Create temp file to read payload (might be huge)
Directory('${documentDirectory.path}/ChatClient').createSync();
Directory('${documentDirectory.path}/ChatClient/.tmp').createSync();
var tempFile = File('${documentDirectory.path}/ChatClient/.tmp/${DateTime.now().microsecondsSinceEpoch}')..createSync();
Directory('${documentDirectory.path}/LChatClient').createSync();
Directory('${documentDirectory.path}/LChatClient/.tmp').createSync();
var tempFile = File('${documentDirectory.path}/LChatClient/.tmp/${DateTime.now().microsecondsSinceEpoch}$_fileCounter')..createSync();
_fileCounter += 1;
_fileCounter %= 10;
await for(var data in payloadPullStream) {
await tempFile.writeAsBytes(data, mode: FileMode.append, flush: true);
}
@ -342,10 +360,5 @@ class TCPRepository {
void dispose() async {
await _socket.flush();
await _socket.close();
_responseRawStreamController.close();
_payloadPullStreamController.close();
_payloadRawStreamController.close();
_responseStreamController.close();
_requestStreamController.close();
}
}

View File

@ -1,7 +1,7 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 21:41:49
* @LastEditTime : 2022-10-14 10:38:31
* @LastEditTime : 2022-10-17 22:23:46
* @Description :
*/
@ -36,6 +36,7 @@ class HistoryTile extends StatelessWidget {
const SizedBox(width: 12,),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
@ -51,6 +52,8 @@ class HistoryTile extends StatelessWidget {
),
child: Text(
message.contentDecoded,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
),

View File

@ -1,19 +1,16 @@
/*
* @Author : Linloir
* @Date : 2022-10-13 21:41:41
* @LastEditTime : 2022-10-14 12:04:26
* @LastEditTime : 2022-10-18 11:28:17
* @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';

View File

@ -7,7 +7,7 @@ project(runner LANGUAGES CXX)
set(BINARY_NAME "tcp_client")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.tcp_client")
set(APPLICATION_ID "com.linloir.tcp_client")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.

View File

@ -4,7 +4,7 @@ project(tcp_client LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "tcp_client")
set(BINARY_NAME "LChatClient")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.

View File

@ -89,13 +89,13 @@ BEGIN
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.example" "\0"
VALUE "FileDescription", "tcp_client" "\0"
VALUE "CompanyName", "com.linloir" "\0"
VALUE "FileDescription", "LChatClient" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "tcp_client" "\0"
VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0"
VALUE "OriginalFilename", "tcp_client.exe" "\0"
VALUE "ProductName", "tcp_client" "\0"
VALUE "InternalName", "LChatClient" "\0"
VALUE "LegalCopyright", "Copyright (C) 2022 com.linloir. All rights reserved." "\0"
VALUE "OriginalFilename", "LCatClient.exe" "\0"
VALUE "ProductName", "LCatClient" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END