From ba452c7fd8675a4e67eff5a71ca8d5ee0814646d Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Fri, 21 Oct 2022 23:09:46 +0800 Subject: [PATCH 1/7] Basic Adaption - Change Database Dependence from sqflite-comon-ffi to sqflite --- lib/main.dart | 76 +------------------ .../local_service_repository.dart | 37 ++------- 2 files changed, 8 insertions(+), 105 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 2a7b03f..bc9b026 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,53 +1,20 @@ /* * @Author : Linloir * @Date : 2022-10-10 08:04:53 - * @LastEditTime : 2022-10-20 23:11:45 + * @LastEditTime : 2022-10-21 22:49:14 * @Description : */ -import 'package:easy_debounce/easy_debounce.dart'; import 'package:flutter/gestures.dart'; 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'; -import 'package:window_manager/window_manager.dart'; void main() async { - sqfliteFfiInit(); - - //The code below is for desktop platforms only------------------------- - WidgetsFlutterBinding.ensureInitialized(); - - // Must add this line. - await windowManager.ensureInitialized(); - - //Get preferred window size - var pref = await SharedPreferences.getInstance(); - var width = pref.getDouble('windowWidth'); - var height = pref.getDouble('windowHeight'); - var posX = pref.getDouble('windowPosX'); - var posY = pref.getDouble('windowPosY'); - WindowOptions windowOptions = WindowOptions( - size: Size(width ?? 800, height ?? 600), - backgroundColor: Colors.transparent, - skipTaskbar: false, - titleBarStyle: TitleBarStyle.normal - ); - await windowManager.waitUntilReadyToShow(windowOptions, () async { - if(posX != null && posY != null) { - await windowManager.setPosition(Offset(posX, posY)); - } - await windowManager.show(); - await windowManager.focus(); - }); - - //--------------------------------------------------------------------- - runApp(const MyApp()); } @@ -58,44 +25,7 @@ class MyApp extends StatefulWidget { State createState() => MyAppState(); } -class MyAppState extends State with WindowListener { - // This widget is the root of your application. - @override - void initState() { - windowManager.addListener(this); - super.initState(); - } - - @override - void onWindowMove() { - EasyDebounce.debounce( - 'WindowMove', - const Duration(milliseconds: 50), - () async { - var pref = await SharedPreferences.getInstance(); - var pos = await windowManager.getPosition(); - pref.setDouble('windowPosX', pos.dx); - pref.setDouble('windowPosY', pos.dy); - } - ); - super.onWindowMove(); - } - - @override - void onWindowResize() { - EasyDebounce.debounce( - 'WindowResize', - const Duration(milliseconds: 50), - () async { - var pref = await SharedPreferences.getInstance(); - var size = await windowManager.getSize(); - pref.setDouble('windowWidth', size.width); - pref.setDouble('windowHeight', size.height); - } - ); - super.onWindowResize(); - } - +class MyAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( @@ -119,7 +49,7 @@ class SplashPage extends StatelessWidget { return BlocProvider( create: (context) { return InitializationCubit( - serverAddress: '127.0.0.1', + serverAddress: 'chat.linloir.cn', serverPort: 20706 ); }, diff --git a/lib/repositories/local_service_repository/local_service_repository.dart b/lib/repositories/local_service_repository/local_service_repository.dart index be76788..2d44b06 100644 --- a/lib/repositories/local_service_repository/local_service_repository.dart +++ b/lib/repositories/local_service_repository/local_service_repository.dart @@ -1,27 +1,19 @@ /* * @Author : Linloir * @Date : 2022-10-11 10:56:02 - * @LastEditTime : 2022-10-20 17:24:26 + * @LastEditTime : 2022-10-21 22:47:38 * @Description : Local Service Repository */ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; - -import 'package:convert/convert.dart'; -import 'package:crypto/crypto.dart'; import 'package:file_picker/file_picker.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.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/models/local_file.dart'; -//Windows platform -import 'package:sqflite_common/sqlite_api.dart'; -import 'package:sqflite_common_ffi/sqflite_ffi.dart'; -//Android platform -// import 'package:sqflite/sqflite.dart'; +import 'package:sqflite/sqflite.dart'; class LocalServiceRepository { late final Database _database; @@ -91,20 +83,11 @@ class LocalServiceRepository { UserInfo? currentUser, required String databaseFilePath }) async { - //Windows platform - var database = await databaseFactoryFfi.openDatabase( + var database = await openDatabase( databaseFilePath, - options: OpenDatabaseOptions( - version: 1, - onCreate: _onDatabaseCreate - ) + version: 1, + onCreate: _onDatabaseCreate ); - //Android platform - // var database = await openDatabase( - // databaseFilePath, - // version: 1, - // onCreate: _onDatabaseCreate - // ); return LocalServiceRepository._internal(database: database); } @@ -116,16 +99,6 @@ class LocalServiceRepository { ); if (filePickResult == null) return null; var file = File(filePickResult.files.single.path!); - // var md5Output = AccumulatorSink(); - // ByteConversionSink md5Input = md5.startChunkedConversion(md5Output); - // await for(var bytes in file.openRead()) { - // md5Input.add(bytes); - // } - // md5Input.close(); - // return LocalFile( - // file: file, - // filemd5: md5Output.events.single.toString() - // ); return file; } From ba59d234842c63155d675cb7aa5bd1624787a55a Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Sat, 22 Oct 2022 00:35:46 +0800 Subject: [PATCH 2/7] Improvements: - Fetch message indicator when startup --- lib/home/cubit/home_cubit.dart | 39 +++++++++++- lib/home/cubit/home_state.dart | 13 ++-- lib/home/home_page.dart | 62 +++++++++++++++---- .../message_page/cubit/msg_list_cubit.dart | 7 +-- 4 files changed, 98 insertions(+), 23 deletions(-) diff --git a/lib/home/cubit/home_cubit.dart b/lib/home/cubit/home_cubit.dart index d24e67e..a605076 100644 --- a/lib/home/cubit/home_cubit.dart +++ b/lib/home/cubit/home_cubit.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-13 14:02:28 - * @LastEditTime : 2022-10-17 19:26:13 + * @LastEditTime : 2022-10-21 23:31:43 * @Description : */ @@ -9,8 +9,10 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:flutter/cupertino.dart'; +import 'package:shared_preferences/shared_preferences.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_request.dart'; import 'package:tcp_client/repositories/tcp_repository/models/tcp_response.dart'; import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart'; @@ -19,11 +21,41 @@ class HomeCubit extends Cubit { required this.localServiceRepository, required this.tcpRepository, required this.pageController - }): super(const HomeState(page: HomePagePosition.message)) { + }): super(const HomeState(page: HomePagePosition.message, status: HomePageStatus.initializing)) { pageController.addListener(() { emit(state.copyWith(page: HomePagePosition.fromValue((pageController.page ?? 0).round()))); }); subscription = tcpRepository.responseStreamBroadcast.listen(_onTCPResponse); + Future(() async { + // var cloned = await tcpRepository.clone(); + // cloned.pushRequest(FetchMessageRequest( + // token: (await SharedPreferences.getInstance()).getInt('token') + // )); + // await for(var response in cloned.responseStreamBroadcast) { + // if(response.type == TCPResponseType.fetchMessage) { + // if(response.status == TCPResponseStatus.ok) { + // response as FetchMessageResponse; + // localServiceRepository.storeMessages(response.messages); + // break; + // } + // } + // } + // cloned.dispose(); + tcpRepository.pushRequest(FetchMessageRequest( + token: (await SharedPreferences.getInstance()).getInt('token') + )); + await for(var response in tcpRepository.responseStreamBroadcast) { + if(response.type == TCPResponseType.fetchMessage) { + if(response.status == TCPResponseStatus.ok) { + response as FetchMessageResponse; + localServiceRepository.storeMessages(response.messages); + break; + } + } + } + }).then((_) { + emit(state.copyWith(status: HomePageStatus.done)); + }); } final LocalServiceRepository localServiceRepository; @@ -40,6 +72,9 @@ class HomeCubit extends Cubit { } void _onTCPResponse(TCPResponse response) { + if(response.status == TCPResponseStatus.err) { + return; + } switch(response.type) { case TCPResponseType.forwardMessage: { response as ForwardMessageResponse; diff --git a/lib/home/cubit/home_state.dart b/lib/home/cubit/home_state.dart index b01f524..64fec37 100644 --- a/lib/home/cubit/home_state.dart +++ b/lib/home/cubit/home_state.dart @@ -1,12 +1,14 @@ /* * @Author : Linloir * @Date : 2022-10-13 14:02:24 - * @LastEditTime : 2022-10-13 16:55:05 + * @LastEditTime : 2022-10-21 23:28:33 * @Description : */ import 'package:equatable/equatable.dart'; +enum HomePageStatus { initializing, done } + enum HomePagePosition { message(0), contact(1), @@ -26,13 +28,14 @@ enum HomePagePosition { class HomeState extends Equatable { final HomePagePosition page; + final HomePageStatus status; - const HomeState({required this.page}); + const HomeState({required this.page, required this.status}); - HomeState copyWith({HomePagePosition? page}) { - return HomeState(page: page ?? this.page); + HomeState copyWith({HomePagePosition? page, HomePageStatus? status}) { + return HomeState(page: page ?? this.page, status: status ?? this.status); } @override - List get props => [page]; + List get props => [page, status]; } diff --git a/lib/home/home_page.dart b/lib/home/home_page.dart index 89835ab..a9465af 100644 --- a/lib/home/home_page.dart +++ b/lib/home/home_page.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-11 11:05:08 - * @LastEditTime : 2022-10-19 11:08:50 + * @LastEditTime : 2022-10-21 23:56:24 * @Description : */ @@ -119,16 +119,56 @@ class HomePageView extends StatelessWidget { ) ], ), - body: Center( - child: BlocBuilder( - builder:(context, state) => PageView( - controller: context.read().pageController, - children: [ - MessagePage(), - const ContactPage(), - MyProfilePage(userID: userID) - ], - ), + body: BlocBuilder( + builder:(context, state) => Stack( + children: [ + Positioned.fill( + child: Center( + child: PageView( + controller: context.read().pageController, + children: [ + MessagePage(), + const ContactPage(), + MyProfilePage(userID: userID) + ], + ), + ), + ), + if(state.status == HomePageStatus.initializing) + Positioned.fill( + child: AbsorbPointer( + child: Center( + child: Container( + decoration: BoxDecoration( + color: Colors.grey[800]!.withOpacity(0.5), + borderRadius: BorderRadius.circular(8.0) + ), + height: 200, + width: 200, + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + CircularProgressIndicator( + color: Colors.white, + strokeWidth: 4.0, + ), + SizedBox(height: 16.0,), + Text( + 'Fetching Messages', + style: TextStyle( + color: Colors.white, + fontSize: 18.0 + ), + ) + ], + ), + ), + ), + ), + ), + ] ), ), bottomNavigationBar: BlocBuilder( diff --git a/lib/home/view/message_page/cubit/msg_list_cubit.dart b/lib/home/view/message_page/cubit/msg_list_cubit.dart index 08fe392..73db5ec 100644 --- a/lib/home/view/message_page/cubit/msg_list_cubit.dart +++ b/lib/home/view/message_page/cubit/msg_list_cubit.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-12 23:38:31 - * @LastEditTime : 2022-10-19 18:02:22 + * @LastEditTime : 2022-10-21 23:14:02 * @Description : */ @@ -40,10 +40,7 @@ class MessageListCubit extends Cubit { } } return msgList; - }).then((msgList) => emit(state.updateWithList(orderedNewMessages: msgList))) - .then((_) async => tcpRepository.pushRequest(FetchMessageRequest( - token: (await SharedPreferences.getInstance()).getInt('token')) - )); + }).then((msgList) => emit(state.updateWithList(orderedNewMessages: msgList))); } final LocalServiceRepository localServiceRepository; From a758929b46782245e2d2274845178514a2651c9a Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Sat, 22 Oct 2022 17:54:11 +0800 Subject: [PATCH 3/7] Improvements: - Auto Reconnect --- lib/home/cubit/home_cubit.dart | 6 +- .../local_service_repository.dart | 129 +++++++++++++++++- .../tcp_repository/tcp_repository.dart | 80 +++++++++-- 3 files changed, 190 insertions(+), 25 deletions(-) diff --git a/lib/home/cubit/home_cubit.dart b/lib/home/cubit/home_cubit.dart index a605076..467dc2c 100644 --- a/lib/home/cubit/home_cubit.dart +++ b/lib/home/cubit/home_cubit.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-13 14:02:28 - * @LastEditTime : 2022-10-21 23:31:43 + * @LastEditTime : 2022-10-22 01:20:14 * @Description : */ @@ -47,8 +47,8 @@ class HomeCubit extends Cubit { await for(var response in tcpRepository.responseStreamBroadcast) { if(response.type == TCPResponseType.fetchMessage) { if(response.status == TCPResponseStatus.ok) { - response as FetchMessageResponse; - localServiceRepository.storeMessages(response.messages); + // response as FetchMessageResponse; + // localServiceRepository.storeMessages(response.messages); break; } } diff --git a/lib/repositories/local_service_repository/local_service_repository.dart b/lib/repositories/local_service_repository/local_service_repository.dart index 2d44b06..b115559 100644 --- a/lib/repositories/local_service_repository/local_service_repository.dart +++ b/lib/repositories/local_service_repository/local_service_repository.dart @@ -1,12 +1,15 @@ /* * @Author : Linloir * @Date : 2022-10-11 10:56:02 - * @LastEditTime : 2022-10-21 22:47:38 + * @LastEditTime : 2022-10-22 01:22:58 * @Description : Local Service Repository */ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; +import 'package:convert/convert.dart'; +import 'package:crypto/crypto.dart'; import 'package:file_picker/file_picker.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -54,6 +57,22 @@ class LocalServiceRepository { ); ''' ); + await txn.execute( + ''' + create table msgimgs ( + msgmd5 text primary key, + imgmd5 text not null + ); + ''' + ); + await txn.execute( + ''' + create table imgs ( + imgmd5 text primary key, + dir text not null + ); + ''' + ); }); // await db.execute( // ''' @@ -105,11 +124,25 @@ class LocalServiceRepository { Future storeMessages(List messages) async { await _database.transaction((txn) async { for(var message in messages) { - await txn.insert( - 'msgs', - message.jsonObject, - conflictAlgorithm: ConflictAlgorithm.replace - ); + if(message.type == MessageType.image) { + //store image first + storeImage( + image: base64.decode(message.contentDecoded), + msgmd5: message.contentmd5 + ); + await txn.insert( + 'msgs', + message.jsonObject..['content'] = "", + conflictAlgorithm: ConflictAlgorithm.replace + ); + } + else { + await txn.insert( + 'msgs', + message.jsonObject, + conflictAlgorithm: ConflictAlgorithm.replace + ); + } } }); } @@ -135,6 +168,20 @@ class LocalServiceRepository { for(var rawMessage in rawMessages) { var message = Message.fromJSONObject(jsonObject: rawMessage); if(message.contentDecoded.toLowerCase().contains(pattern.toLowerCase())) { + //Since history page does not show message + //There is no need to fetch message here + // if(message.type == MessageType.image) { + // var image = await fetchImage(msgmd5: message.contentmd5); + // if(image != null) { + // alikeMessages.add(message.copyWith( + // content: base64.encode(image), + // )); + // continue; + // } + // else { + // //TODO: do something + // } + // } alikeMessages.add(message); } } @@ -160,6 +207,8 @@ class LocalServiceRepository { messages.add([]); } else { + //Since message page does not show message + //There is no need to fetch message here messages.add([Message.fromJSONObject(jsonObject: queryResult[0])]); } } @@ -185,7 +234,18 @@ class LocalServiceRepository { limit: num, offset: position ); - return queryResult.map((e) => Message.fromJSONObject(jsonObject: e)).toList(); + List messages = []; + for(var result in queryResult) { + var message = Message.fromJSONObject(jsonObject: result); + if(message.type == MessageType.image) { + var image = await fetchImage(msgmd5: message.contentmd5); + if(image != null) { + message = message.copyWith(content: base64.encode(image)); + } + } + messages.add(message); + } + return messages; } Future findFile({required String filemd5, required String fileName}) async { @@ -340,4 +400,59 @@ class LocalServiceRepository { return null; } } + + Future storeImage({required List image, required String msgmd5}) async { + var md5Output = AccumulatorSink(); + ByteConversionSink md5Input = md5.startChunkedConversion(md5Output); + md5Input.add(image); + md5Input.close(); + var imagemd5 = md5Output.events.single.toString(); + //Write to image library + var documentPath = (await getApplicationDocumentsDirectory()).path; + await Directory('$documentPath/LChatClient/imgs').create(); + var permanentFilePath = '$documentPath/LChatClient/imgs/$imagemd5'; + var imageFile = await File(permanentFilePath).create(); + imageFile.writeAsBytes(image); + await _database.transaction((txn) async { + txn.insert( + 'msgimgs', + { + 'msgmd5': msgmd5, + 'imgmd5': imagemd5 + }, + conflictAlgorithm: ConflictAlgorithm.replace + ); + txn.insert( + 'imgs', + { + 'imgmd5': imagemd5, + 'dir': permanentFilePath + }, + conflictAlgorithm: ConflictAlgorithm.replace + ); + }); + } + + Future?> fetchImage({required String msgmd5}) async { + var imageQueryResult = await _database.query( + 'msgimgs natural join imgs', + where: 'msgimgs.msgmd5 = ?', + whereArgs: [ + msgmd5 + ], + columns: [ + 'imgs.dir as dir' + ] + ); + if(imageQueryResult.isEmpty) { + return null; + } + var path = imageQueryResult[0]['dir'] as String; + var image = File(path); + if(!await image.exists()) { + return null; + } + var imageContent = await image.readAsBytes(); + return imageContent; + } } diff --git a/lib/repositories/tcp_repository/tcp_repository.dart b/lib/repositories/tcp_repository/tcp_repository.dart index f39e185..5b1600a 100644 --- a/lib/repositories/tcp_repository/tcp_repository.dart +++ b/lib/repositories/tcp_repository/tcp_repository.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-11 09:42:05 - * @LastEditTime : 2022-10-19 10:41:43 + * @LastEditTime : 2022-10-22 17:46:28 * @Description : TCP repository */ @@ -26,13 +26,25 @@ class TCPRepository { required int remotePort }): _socket = socket, _remoteAddress = remoteAddress, _remotePort = remotePort { Future(() async { - try{ - await for(var response in _socket) { - _pullResponse(response); - await Future.delayed(const Duration(microseconds: 0)); + while(true) { + try{ + await for(var response in _socket!) { + _pullResponse(response); + await Future.delayed(const Duration(microseconds: 0)); + } + break; + } catch(e) { + _socket?.close(); + _socket = null; + while(true) { + try{ + _socket = await Socket.connect(remoteAddress, remotePort); + break; + } catch (e) { + continue; + } + } } - } catch(e) { - _socket.close(); } // _responseRawStreamController.close(); // _payloadPullStreamController.close(); @@ -42,10 +54,40 @@ class TCPRepository { }); //This future never ends, would that be bothersome? Future(() async { - await for(var request in _requestStreamController.stream) { - await _socket.addStream(request.stream); + TCPRequest? failedRequest; + while(true) { + try{ + if(failedRequest != null) { + await Future.doWhile(() async { + await Future.delayed(const Duration(microseconds: 0)); + return _socket == null; + }); + await _socket!.addStream(failedRequest.stream); + } + await for(var request in _requestStreamController.stream) { + failedRequest = request; + await Future.doWhile(() async { + await Future.delayed(const Duration(microseconds: 0)); + return _socket == null; + }); + await _socket!.addStream(request.stream); + failedRequest = null; + } + break; + } catch (e) { + _socket?.close(); + _socket = null; + while(true) { + try{ + _socket = await Socket.connect(remoteAddress, remotePort); + break; + } catch (e) { + continue; + } + } + } } - }).onError((error, stackTrace) {_socket.close();}); + }); Future(() async { var responseQueue = StreamQueue(_responseRawStreamController.stream); var payloadQueue = StreamQueue(_payloadRawStreamController.stream); @@ -56,14 +98,22 @@ class TCPRepository { } responseQueue.cancel(); payloadQueue.cancel(); - }).onError((error, stackTrace) {_socket.close();}); + }).onError((error, stackTrace) {_socket?.close();}); } static Future create({ required String serverAddress, required int serverPort }) async { - var socket = await Socket.connect(serverAddress, serverPort); + Socket socket; + while(true) { + try{ + socket = await Socket.connect(serverAddress, serverPort); + break; + } catch (e) { + continue; + } + } return TCPRepository._internal( socket: socket, remoteAddress: serverAddress, @@ -78,7 +128,7 @@ class TCPRepository { ); } - final Socket _socket; + Socket? _socket; final String _remoteAddress; final int _remotePort; @@ -359,7 +409,7 @@ class TCPRepository { } void dispose() async { - await _socket.flush(); - await _socket.close(); + await _socket?.flush(); + await _socket?.close(); } } \ No newline at end of file From da53d222612214657b99da1446b102609c9cb0df Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Sat, 22 Oct 2022 21:27:17 +0800 Subject: [PATCH 4/7] New Feature: - Add API ACKFETCH to acknowledge fetch message done --- lib/home/cubit/home_cubit.dart | 35 +++++++++++-------- lib/main.dart | 4 +-- .../tcp_repository/models/tcp_request.dart | 14 +++++++- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/lib/home/cubit/home_cubit.dart b/lib/home/cubit/home_cubit.dart index 467dc2c..d697a8d 100644 --- a/lib/home/cubit/home_cubit.dart +++ b/lib/home/cubit/home_cubit.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-13 14:02:28 - * @LastEditTime : 2022-10-22 01:20:14 + * @LastEditTime : 2022-10-22 21:08:39 * @Description : */ @@ -44,17 +44,15 @@ class HomeCubit extends Cubit { tcpRepository.pushRequest(FetchMessageRequest( token: (await SharedPreferences.getInstance()).getInt('token') )); - await for(var response in tcpRepository.responseStreamBroadcast) { - if(response.type == TCPResponseType.fetchMessage) { - if(response.status == TCPResponseStatus.ok) { - // response as FetchMessageResponse; - // localServiceRepository.storeMessages(response.messages); - break; - } - } - } - }).then((_) { - emit(state.copyWith(status: HomePageStatus.done)); + // await for(var response in tcpRepository.responseStreamBroadcast) { + // if(response.type == TCPResponseType.fetchMessage) { + // if(response.status == TCPResponseStatus.ok) { + // // response as FetchMessageResponse; + // // localServiceRepository.storeMessages(response.messages); + // break; + // } + // } + // } }); } @@ -71,19 +69,26 @@ class HomeCubit extends Cubit { ); } - void _onTCPResponse(TCPResponse response) { + void _onTCPResponse(TCPResponse response) async { if(response.status == TCPResponseStatus.err) { return; } switch(response.type) { case TCPResponseType.forwardMessage: { response as ForwardMessageResponse; - localServiceRepository.storeMessages([response.message]); + await localServiceRepository.storeMessages([response.message]); break; } case TCPResponseType.fetchMessage: { response as FetchMessageResponse; - localServiceRepository.storeMessages(response.messages); + await localServiceRepository.storeMessages(response.messages); + emit(state.copyWith(status: HomePageStatus.done)); + if(response.messages.isNotEmpty) { + tcpRepository.pushRequest(AckFetchRequest( + timeStamp: response.messages[0].timeStamp, + token: (await SharedPreferences.getInstance()).getInt('token') + )); + } break; } default: { diff --git a/lib/main.dart b/lib/main.dart index bc9b026..fbaee2f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-10 08:04:53 - * @LastEditTime : 2022-10-21 22:49:14 + * @LastEditTime : 2022-10-22 20:43:39 * @Description : */ import 'package:flutter/gestures.dart'; @@ -49,7 +49,7 @@ class SplashPage extends StatelessWidget { return BlocProvider( create: (context) { return InitializationCubit( - serverAddress: 'chat.linloir.cn', + serverAddress: '192.168.43.155', serverPort: 20706 ); }, diff --git a/lib/repositories/tcp_repository/models/tcp_request.dart b/lib/repositories/tcp_repository/models/tcp_request.dart index 77274be..c6bd13e 100644 --- a/lib/repositories/tcp_repository/models/tcp_request.dart +++ b/lib/repositories/tcp_repository/models/tcp_request.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-11 09:44:03 - * @LastEditTime : 2022-10-18 14:45:20 + * @LastEditTime : 2022-10-22 21:01:35 * @Description : Abstract TCP request class */ @@ -24,6 +24,7 @@ enum TCPRequestType { modifyProfile ('MODIFYPROFILE'), //Modify user profile sendMessage ('SENDMSG'), //Send message fetchMessage ('FETCHMSG'), //Fetch message + ackFetch ('ACKFETCH'), //Acknowledge message fetch findFile ('FINDFILE'), //Find file by md5 before transmitting the file fetchFile ('FETCHFILE'), //Fetch file and file md5 by message md5 searchUser ('SEARCHUSR'), //Search username and userid by username @@ -243,4 +244,15 @@ class FetchContactRequest extends TCPRequest { @override Map get body => {}; +} + +class AckFetchRequest extends TCPRequest { + final int _timeStamp; + + const AckFetchRequest({required int timeStamp, required int? token}): _timeStamp = timeStamp, super(type: TCPRequestType.ackFetch, token: token); + + @override + Map get body => { + 'timestamp': _timeStamp + }; } \ No newline at end of file From 5594bd6ba7f7ed1f6e840dbe1607ac6563d16bfb Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Sun, 23 Oct 2022 10:32:46 +0800 Subject: [PATCH 5/7] Improvements: - Improve display paddings, sizes & experiences on remote platforms --- lib/chat/chat_page.dart | 4 +- lib/chat/cubit/chat_cubit.dart | 2 +- lib/chat/view/common/file_box.dart | 25 ++++--- lib/chat/view/common/image_box.dart | 4 +- lib/chat/view/common/text_box.dart | 23 ++++--- lib/chat/view/history_tile.dart | 6 +- lib/chat/view/in_message_box.dart | 68 +++++++++---------- lib/chat/view/input_box/input_box.dart | 12 ++-- lib/chat/view/out_message_box.dart | 68 +++++++++---------- .../view/message_page/view/message_tile.dart | 6 +- lib/login/login_page.dart | 4 +- lib/login/view/login_form.dart | 18 ++++- lib/main.dart | 4 +- lib/register/register_page.dart | 4 +- lib/register/view/register_form.dart | 18 ++++- lib/search/search_page.dart | 8 +-- lib/search/view/history_tile.dart | 10 +-- lib/search/view/user_tile.dart | 6 +- 18 files changed, 163 insertions(+), 127 deletions(-) diff --git a/lib/chat/chat_page.dart b/lib/chat/chat_page.dart index d247041..5f0e723 100644 --- a/lib/chat/chat_page.dart +++ b/lib/chat/chat_page.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-13 14:03:16 - * @LastEditTime : 2022-10-20 10:52:30 + * @LastEditTime : 2022-10-22 21:30:04 * @Description : */ @@ -88,7 +88,7 @@ class ChatPage extends StatelessWidget { //Return history tile return Padding( padding: const EdgeInsets.symmetric( - horizontal: 24, + horizontal: 16, vertical: 8 ), child: HistoryTile( diff --git a/lib/chat/cubit/chat_cubit.dart b/lib/chat/cubit/chat_cubit.dart index b10d666..def71ff 100644 --- a/lib/chat/cubit/chat_cubit.dart +++ b/lib/chat/cubit/chat_cubit.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-13 14:03:56 - * @LastEditTime : 2022-10-20 11:04:40 + * @LastEditTime : 2022-10-22 22:46:18 * @Description : */ diff --git a/lib/chat/view/common/file_box.dart b/lib/chat/view/common/file_box.dart index 18dc34d..87bbfaf 100644 --- a/lib/chat/view/common/file_box.dart +++ b/lib/chat/view/common/file_box.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-14 17:07:13 - * @LastEditTime : 2022-10-18 15:44:34 + * @LastEditTime : 2022-10-22 23:31:19 * @Description : */ @@ -35,7 +35,10 @@ class FileBox extends StatelessWidget { ); }, child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.symmetric( + horizontal: 0.0, + vertical: 6.0 + ), child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -43,38 +46,38 @@ class FileBox extends StatelessWidget { child: history.status == ChatHistoryStatus.none || history.status == ChatHistoryStatus.done ? Icon( Icons.file_present_rounded, - size: 24, + size: 20, color: history.type == ChatHistoryType.income ? Colors.blue[800] : Colors.white.withOpacity(0.8), ) : history.status == ChatHistoryStatus.failed ? Icon( Icons.refresh_rounded, - size: 24, + size: 20, color: history.type == ChatHistoryType.income ? Colors.red[800] : Colors.white.withOpacity(0.8), ) : history.status == ChatHistoryStatus.processing ? SizedBox( - height: 18.0, - width: 18.0, + height: 16.0, + width: 16.0, child: LoadingIndicator( indicatorType: Indicator.ballPulseSync, colors: [Colors.white.withOpacity(0.8)], ), ) : SizedBox( - height: 18.0, - width: 18.0, + height: 16.0, + width: 16.0, child: CircularProgressIndicator( color: history.type == ChatHistoryType.income ? Colors.blue[800] : Colors.white.withOpacity(0.8), - strokeWidth: 3, + strokeWidth: 2.5, ), ) ), - const SizedBox(width: 18.0,), + const SizedBox(width: 16.0,), Flexible( child: Text( history.message.contentDecoded, softWrap: true, style: TextStyle( - fontSize: 20.0, + fontSize: 16.0, color: history.type == ChatHistoryType.income ? Colors.grey[900] : Colors.white ), ), diff --git a/lib/chat/view/common/image_box.dart b/lib/chat/view/common/image_box.dart index 7ff9618..7b253d4 100644 --- a/lib/chat/view/common/image_box.dart +++ b/lib/chat/view/common/image_box.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-14 17:04:20 - * @LastEditTime : 2022-10-20 13:47:29 + * @LastEditTime : 2022-10-22 23:05:24 * @Description : */ @@ -25,7 +25,7 @@ class ImageBox extends StatelessWidget { child: Stack( children: [ Container( - constraints: const BoxConstraints(maxWidth: 500, maxHeight: 200), + constraints: const BoxConstraints(maxWidth: 200, maxHeight: 150), child: history.preCachedImage ?? Image.memory(base64Decode(history.message.contentDecoded)), ), Material( diff --git a/lib/chat/view/common/text_box.dart b/lib/chat/view/common/text_box.dart index ebb52fc..f9cd767 100644 --- a/lib/chat/view/common/text_box.dart +++ b/lib/chat/view/common/text_box.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-14 17:04:12 - * @LastEditTime : 2022-10-15 10:53:28 + * @LastEditTime : 2022-10-22 23:06:38 * @Description : */ @@ -25,7 +25,10 @@ class TextBox extends StatelessWidget { return InkWell( onTap: (){}, child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.symmetric( + horizontal: 0.0, + vertical: 6.0 + ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, @@ -35,14 +38,14 @@ class TextBox extends StatelessWidget { if(history.status == ChatHistoryStatus.sending) ...[ SizedBox( - height: 15.0, - width: 15.0, + height: 12.0, + width: 12.0, child: CircularProgressIndicator( color: Colors.white.withOpacity(0.5), strokeWidth: 2.0, ), ), - const SizedBox(width: 16.0,), + const SizedBox(width: 12.0,), ], if(history.status == ChatHistoryStatus.failed) ...[ @@ -53,7 +56,7 @@ class TextBox extends StatelessWidget { child: Icon( Icons.error_rounded, color: Colors.white.withOpacity(0.5), - size: 20, + size: 18, ), onTap: () async { context.read().tcpRepository.pushRequest(SendMessageRequest( @@ -64,16 +67,16 @@ class TextBox extends StatelessWidget { ), ), ), - const SizedBox(width: 8.0,), + const SizedBox(width: 6.0,), ], if(history.status == ChatHistoryStatus.done) ...[ Icon( Icons.check_rounded, color: Colors.white.withOpacity(0.5), - size: 20, + size: 18, ), - const SizedBox(width: 12.0,), + const SizedBox(width: 8.0,), ], ], Flexible( @@ -81,7 +84,7 @@ class TextBox extends StatelessWidget { history.message.contentDecoded, softWrap: true, style: TextStyle( - fontSize: 18, + fontSize: 16, color: history.type == ChatHistoryType.income ? Colors.grey[900] : Colors.white ), ), diff --git a/lib/chat/view/history_tile.dart b/lib/chat/view/history_tile.dart index 8ac3554..3472052 100644 --- a/lib/chat/view/history_tile.dart +++ b/lib/chat/view/history_tile.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-13 14:03:45 - * @LastEditTime : 2022-10-15 10:52:30 + * @LastEditTime : 2022-10-22 21:30:26 * @Description : */ @@ -34,7 +34,7 @@ class HistoryTile extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: [ - UserAvatar(userid: history.message.senderID), + UserAvatar(userid: history.message.senderID, size: 42,), const SizedBox(width: 16.0,), Flexible( child: InMessageBox(history: history) @@ -58,7 +58,7 @@ class HistoryTile extends StatelessWidget { child: OutMessageBox(history: history), ), const SizedBox(width: 16.0,), - UserAvatar(userid: history.message.senderID), + UserAvatar(userid: history.message.senderID, size: 42,), ], ) ), diff --git a/lib/chat/view/in_message_box.dart b/lib/chat/view/in_message_box.dart index 334b936..5cef6cd 100644 --- a/lib/chat/view/in_message_box.dart +++ b/lib/chat/view/in_message_box.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-14 13:49:47 - * @LastEditTime : 2022-10-20 13:56:20 + * @LastEditTime : 2022-10-23 10:07:48 * @Description : */ @@ -42,37 +42,37 @@ class InMessageBox extends StatelessWidget { ), boxShadow: [BoxShadow(blurRadius: 5.0, color: Colors.grey.withOpacity(0.3))] ), - child: ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8.0), - topRight: Radius.circular(8.0), - bottomLeft: Radius.zero, - bottomRight: Radius.circular(8.0) - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if(history.message.type == MessageType.file) - FileBox(history: history), - if(history.message.type == MessageType.image) - ImageBox(history: history), - if(history.message.type == MessageType.plaintext) - TextBox(history: history), - if(history.message.type != MessageType.image) - ...[ - const SizedBox(height: 4.0,), - Text( - _getTimeStamp(history.message.timeStamp), - style: TextStyle( - fontSize: 12, - color: Colors.grey[400], - ), - ) - ] - ], - ), - ) + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if(history.message.type == MessageType.file) + FileBox(history: history), + if(history.message.type == MessageType.image) + ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(8.0), + topRight: Radius.circular(8.0), + bottomLeft: Radius.zero, + bottomRight: Radius.circular(8.0) + ), + child: ImageBox(history: history), + ), + if(history.message.type == MessageType.plaintext) + TextBox(history: history), + if(history.message.type != MessageType.image) + ...[ + const SizedBox(height: 4.0,), + Text( + _getTimeStamp(history.message.timeStamp), + style: TextStyle( + fontSize: 12, + color: Colors.grey[400], + ), + ) + ] + ], + ), ), if(history.message.type == MessageType.image) ...[ @@ -96,9 +96,9 @@ class InMessageBox extends StatelessWidget { if(date.day == DateTime.now().day) { return '${date.hour}:${date.minute.toString().padLeft(2, '0')}'; } - //If date is yesterday, return 'yesterday' + //If date is yda, return 'yda' if(date.day == DateTime.now().day - 1) { - return 'yesterday ${date.hour}:${date.minute.toString().padLeft(2, '0')}'; + return 'yda ${date.hour}:${date.minute.toString().padLeft(2, '0')}'; } //If date is within this week, return the weekday in english if(date.weekday < DateTime.now().weekday) { diff --git a/lib/chat/view/input_box/input_box.dart b/lib/chat/view/input_box/input_box.dart index 5c09050..3e62218 100644 --- a/lib/chat/view/input_box/input_box.dart +++ b/lib/chat/view/input_box/input_box.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-14 17:54:30 - * @LastEditTime : 2022-10-20 11:18:48 + * @LastEditTime : 2022-10-23 10:13:22 * @Description : */ @@ -34,7 +34,12 @@ class InputBox extends StatelessWidget { ), child: Container( // height: 64, - padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0), + padding: const EdgeInsets.only( + left: 16.0, + right: 4.0, + top: 16.0, + bottom: 16.0 + ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -79,7 +84,6 @@ class InputBox extends StatelessWidget { ), ), ), - const SizedBox(width: 8.0,), IconButton( onPressed: () { var chatCubit = context.read(); @@ -98,7 +102,6 @@ class InputBox extends StatelessWidget { }, icon: Icon(Icons.attach_file_rounded, color: Colors.grey[700],) ), - const SizedBox(width: 8.0,), IconButton( onPressed: () { var chatCubit = context.read(); @@ -116,7 +119,6 @@ class InputBox extends StatelessWidget { }, icon: Icon(Icons.photo_rounded, color: Colors.grey[700],) ), - const SizedBox(width: 8.0,), BlocBuilder( builder:(context, state) { return IconButton( diff --git a/lib/chat/view/out_message_box.dart b/lib/chat/view/out_message_box.dart index 937a240..fb91095 100644 --- a/lib/chat/view/out_message_box.dart +++ b/lib/chat/view/out_message_box.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-14 13:49:28 - * @LastEditTime : 2022-10-19 23:47:20 + * @LastEditTime : 2022-10-23 10:07:32 * @Description : */ @@ -42,37 +42,37 @@ class OutMessageBox extends StatelessWidget { ), boxShadow: [BoxShadow(blurRadius: 5.0, color: Colors.grey.withOpacity(0.2))] ), - child: ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8.0), - topRight: Radius.circular(8.0), - bottomLeft: Radius.circular(8.0), - bottomRight: Radius.zero - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - if(history.message.type == MessageType.file) - FileBox(history: history), - if(history.message.type == MessageType.image) - ImageBox(history: history), - if(history.message.type == MessageType.plaintext) - TextBox(history: history), - if(history.message.type != MessageType.image) - ...[ - const SizedBox(height: 4.0,), - Text( - _getTimeStamp(history.message.timeStamp), - style: TextStyle( - fontSize: 12, - color: Colors.grey[200], - ), - ) - ], - ], - ), - ) + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + if(history.message.type == MessageType.file) + FileBox(history: history), + if(history.message.type == MessageType.image) + ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(8.0), + topRight: Radius.circular(8.0), + bottomLeft: Radius.circular(8.0), + bottomRight: Radius.zero + ), + child: ImageBox(history: history), + ), + if(history.message.type == MessageType.plaintext) + TextBox(history: history), + if(history.message.type != MessageType.image) + ...[ + const SizedBox(height: 4.0,), + Text( + _getTimeStamp(history.message.timeStamp), + style: TextStyle( + fontSize: 12, + color: Colors.grey[200], + ), + ) + ], + ], + ), ), if(history.message.type == MessageType.image) ...[ @@ -96,9 +96,9 @@ class OutMessageBox extends StatelessWidget { if(date.day == DateTime.now().day) { return '${date.hour}:${date.minute.toString().padLeft(2, '0')}'; } - //If date is yesterday, return 'yesterday' + //If date is yda, return 'yda' if(date.day == DateTime.now().day - 1) { - return 'yesterday ${date.hour}:${date.minute.toString().padLeft(2, '0')}'; + return 'yda ${date.hour}:${date.minute.toString().padLeft(2, '0')}'; } //If date is within this week, return the weekday in english if(date.weekday < DateTime.now().weekday) { diff --git a/lib/home/view/message_page/view/message_tile.dart b/lib/home/view/message_page/view/message_tile.dart index 73e58a1..12b331c 100644 --- a/lib/home/view/message_page/view/message_tile.dart +++ b/lib/home/view/message_page/view/message_tile.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-13 13:17:52 - * @LastEditTime : 2022-10-20 00:52:14 + * @LastEditTime : 2022-10-23 10:09:09 * @Description : */ @@ -150,9 +150,9 @@ class MessageTile extends StatelessWidget { if(date.day == DateTime.now().day) { return '${date.hour}:${date.minute.toString().padLeft(2, '0')}'; } - //If date is yesterday, return 'yesterday' + //If date is yda, return 'yda' if(date.day == DateTime.now().day - 1) { - return 'yesterday'; + return 'yda ${date.hour}:${date.minute.toString().padLeft(2, '0')}'; } //If date is within this week, return the weekday in english if(date.weekday < DateTime.now().weekday) { diff --git a/lib/login/login_page.dart b/lib/login/login_page.dart index abb266d..91b1cca 100644 --- a/lib/login/login_page.dart +++ b/lib/login/login_page.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-12 15:06:30 - * @LastEditTime : 2022-10-20 20:55:07 + * @LastEditTime : 2022-10-23 10:15:11 * @Description : */ @@ -81,7 +81,7 @@ class LoginPage extends StatelessWidget { Expanded( flex: 6, child: SizedBox( - width: MediaQuery.of(context).size.width * 0.5, + width: MediaQuery.of(context).size.width * 0.7, child: const LoginPanel() ) ), diff --git a/lib/login/view/login_form.dart b/lib/login/view/login_form.dart index 69470d4..285b5b7 100644 --- a/lib/login/view/login_form.dart +++ b/lib/login/view/login_form.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-12 16:29:25 - * @LastEditTime : 2022-10-20 20:43:51 + * @LastEditTime : 2022-10-23 10:28:05 * @Description : */ @@ -44,6 +44,11 @@ class UsernameInput extends StatelessWidget { onChanged: (username) { context.read().onUsernameChange(Username.dirty(username)); }, + textInputAction: TextInputAction.next, + textCapitalization: TextCapitalization.none, + autocorrect: false, + enableIMEPersonalizedLearning: false, + enableSuggestions: false, decoration: InputDecoration( labelText: 'Username', errorText: state.username.invalid ? 'Invalid username' : null @@ -60,12 +65,21 @@ class PasswordInput extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( - buildWhen: (previous, current) => previous.password != current.password, + buildWhen: (previous, current) => previous.password != current.password || previous.status != current.status, builder: (context, state) { return TextField( onChanged: (password) { context.read().onPasswordChange(Password.dirty(password)); }, + onEditingComplete: () { + if( + state.status == FormzStatus.valid || + state.status == FormzStatus.submissionFailure + ) { + context.read().onSubmission(); + } + }, + textInputAction: TextInputAction.done, obscureText: true, decoration: InputDecoration( labelText: 'Password', diff --git a/lib/main.dart b/lib/main.dart index fbaee2f..de26e9d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-10 08:04:53 - * @LastEditTime : 2022-10-22 20:43:39 + * @LastEditTime : 2022-10-23 10:32:16 * @Description : */ import 'package:flutter/gestures.dart'; @@ -49,7 +49,7 @@ class SplashPage extends StatelessWidget { return BlocProvider( create: (context) { return InitializationCubit( - serverAddress: '192.168.43.155', + serverAddress: '127.0.0.1', serverPort: 20706 ); }, diff --git a/lib/register/register_page.dart b/lib/register/register_page.dart index a2d9c2e..077300a 100644 --- a/lib/register/register_page.dart +++ b/lib/register/register_page.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-12 17:36:38 - * @LastEditTime : 2022-10-20 20:55:17 + * @LastEditTime : 2022-10-23 10:15:31 * @Description : */ /* @@ -82,7 +82,7 @@ class RegisterPage extends StatelessWidget { listenWhen: (previous, current) => previous.status != current.status, child: Center( child: SizedBox( - width: MediaQuery.of(context).size.width * 0.5, + width: MediaQuery.of(context).size.width * 0.7, child: Column( mainAxisSize: MainAxisSize.max, children: [ diff --git a/lib/register/view/register_form.dart b/lib/register/view/register_form.dart index e73c27f..92f9cc8 100644 --- a/lib/register/view/register_form.dart +++ b/lib/register/view/register_form.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-12 16:29:25 - * @LastEditTime : 2022-10-20 20:54:22 + * @LastEditTime : 2022-10-23 10:28:12 * @Description : */ @@ -44,6 +44,11 @@ class UsernameInput extends StatelessWidget { onChanged: (username) { context.read().onUsernameChange(Username.dirty(username)); }, + textInputAction: TextInputAction.next, + textCapitalization: TextCapitalization.none, + autocorrect: false, + enableIMEPersonalizedLearning: false, + enableSuggestions: false, decoration: InputDecoration( labelText: 'Username', errorText: state.username.invalid ? 'Invalid username' : null @@ -60,12 +65,21 @@ class PasswordInput extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( - buildWhen: (previous, current) => previous.password != current.password, + buildWhen: (previous, current) => previous.password != current.password || previous.status != current.status, builder: (context, state) { return TextField( onChanged: (password) { context.read().onPasswordChange(Password.dirty(password)); }, + onEditingComplete: () { + if( + state.status == FormzStatus.valid || + state.status == FormzStatus.submissionFailure + ) { + context.read().onSubmission(); + } + }, + textInputAction: TextInputAction.done, obscureText: true, decoration: InputDecoration( labelText: 'Password', diff --git a/lib/search/search_page.dart b/lib/search/search_page.dart index 75e38fa..7fd58d3 100644 --- a/lib/search/search_page.dart +++ b/lib/search/search_page.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-13 17:04:12 - * @LastEditTime : 2022-10-14 10:38:44 + * @LastEditTime : 2022-10-23 10:30:41 * @Description : */ @@ -61,7 +61,7 @@ class SearchPage extends StatelessWidget { const SizedBox(height: 16.0,), const Padding( padding: EdgeInsets.symmetric( - horizontal: 36.0, + horizontal: 24.0, vertical: 8.0 ), child: Text( @@ -80,7 +80,7 @@ class SearchPage extends StatelessWidget { const SizedBox(height: 16.0,), const Padding( padding: EdgeInsets.symmetric( - horizontal: 36.0, + horizontal: 24.0, vertical: 8.0 ), child: Text( @@ -102,7 +102,7 @@ class SearchPage extends StatelessWidget { const SizedBox(height: 16.0,), const Padding( padding: EdgeInsets.symmetric( - horizontal: 36.0, + horizontal: 24.0, vertical: 8.0 ), child: Text( diff --git a/lib/search/view/history_tile.dart b/lib/search/view/history_tile.dart index 174acf9..11cfb21 100644 --- a/lib/search/view/history_tile.dart +++ b/lib/search/view/history_tile.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-13 21:41:49 - * @LastEditTime : 2022-10-17 22:23:46 + * @LastEditTime : 2022-10-23 10:30:06 * @Description : */ @@ -24,8 +24,8 @@ class HistoryTile extends StatelessWidget { Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric( - vertical: 16.0, - horizontal: 36.0 + vertical: 8.0, + horizontal: 24.0 ), child: IntrinsicHeight( child: Row( @@ -87,9 +87,9 @@ class HistoryTile extends StatelessWidget { if(date.day == DateTime.now().day) { return '${date.hour}:${date.minute}'; } - //If date is yesterday, return 'yesterday' + //If date is yda, return 'yda' if(date.day == DateTime.now().day - 1) { - return 'yesterday'; + return 'yda ${date.hour}:${date.minute.toString().padLeft(2, '0')}'; } //If date is within this week, return the weekday in english if(date.weekday < DateTime.now().weekday) { diff --git a/lib/search/view/user_tile.dart b/lib/search/view/user_tile.dart index 5a9dd92..8ec1027 100644 --- a/lib/search/view/user_tile.dart +++ b/lib/search/view/user_tile.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-13 21:41:41 - * @LastEditTime : 2022-10-18 11:28:17 + * @LastEditTime : 2022-10-23 10:30:24 * @Description : */ @@ -35,8 +35,8 @@ class UserTile extends StatelessWidget { }, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: 36, - vertical: 16, + horizontal: 24, + vertical: 8, ), child: Row( children: [ From 354fcb326338403549dac0852fa69e23876b7cbd Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Sun, 23 Oct 2022 10:56:50 +0800 Subject: [PATCH 6/7] Feature: - Image status indicator (sending, done, failed) --- lib/chat/view/common/image_box.dart | 6 ++- lib/chat/view/common/text_box.dart | 4 +- lib/chat/view/history_tile.dart | 58 ++++++++++++++++++++++++++++- lib/main.dart | 4 +- 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/lib/chat/view/common/image_box.dart b/lib/chat/view/common/image_box.dart index 7b253d4..65c805d 100644 --- a/lib/chat/view/common/image_box.dart +++ b/lib/chat/view/common/image_box.dart @@ -1,14 +1,18 @@ /* * @Author : Linloir * @Date : 2022-10-14 17:04:20 - * @LastEditTime : 2022-10-22 23:05:24 + * @LastEditTime : 2022-10-23 10:52:45 * @Description : */ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tcp_client/chat/cubit/chat_cubit.dart'; import 'package:tcp_client/chat/model/chat_history.dart'; +import 'package:tcp_client/repositories/tcp_repository/models/tcp_request.dart'; class ImageBox extends StatelessWidget { const ImageBox({ diff --git a/lib/chat/view/common/text_box.dart b/lib/chat/view/common/text_box.dart index f9cd767..47ac05b 100644 --- a/lib/chat/view/common/text_box.dart +++ b/lib/chat/view/common/text_box.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-14 17:04:12 - * @LastEditTime : 2022-10-22 23:06:38 + * @LastEditTime : 2022-10-23 10:49:14 * @Description : */ @@ -67,7 +67,7 @@ class TextBox extends StatelessWidget { ), ), ), - const SizedBox(width: 6.0,), + const SizedBox(width: 8.0,), ], if(history.status == ChatHistoryStatus.done) ...[ diff --git a/lib/chat/view/history_tile.dart b/lib/chat/view/history_tile.dart index 3472052..470ee1b 100644 --- a/lib/chat/view/history_tile.dart +++ b/lib/chat/view/history_tile.dart @@ -1,15 +1,20 @@ /* * @Author : Linloir * @Date : 2022-10-13 14:03:45 - * @LastEditTime : 2022-10-22 21:30:26 + * @LastEditTime : 2022-10-23 10:55:42 * @Description : */ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tcp_client/chat/cubit/chat_cubit.dart'; import 'package:tcp_client/chat/model/chat_history.dart'; import 'package:tcp_client/chat/view/in_message_box.dart'; import 'package:tcp_client/chat/view/out_message_box.dart'; import 'package:tcp_client/common/avatar/avatar.dart'; +import 'package:tcp_client/repositories/common_models/message.dart'; +import 'package:tcp_client/repositories/tcp_repository/models/tcp_request.dart'; class HistoryTile extends StatelessWidget { const HistoryTile({ @@ -55,7 +60,56 @@ class HistoryTile extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ Flexible( - child: OutMessageBox(history: history), + child: history.message.type == MessageType.image ? Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if(history.type == ChatHistoryType.outcome && history.status == ChatHistoryStatus.sending) + ...[ + SizedBox( + height: 12.0, + width: 12.0, + child: CircularProgressIndicator( + color: Colors.grey.withOpacity(0.5), + strokeWidth: 2.0, + ), + ), + const SizedBox(width: 12.0,), + ], + if(history.type == ChatHistoryType.outcome && history.status == ChatHistoryStatus.done) + ...[ + Icon( + Icons.check_rounded, + color: Colors.grey.withOpacity(0.5), + size: 18, + ), + const SizedBox(width: 8.0,), + ], + if(history.type == ChatHistoryType.outcome && history.status == ChatHistoryStatus.failed) + ...[ + ClipOval( + child: Material( + color: Colors.transparent, + child: InkWell( + child: Icon( + Icons.error_rounded, + color: Colors.white.withOpacity(0.5), + size: 18, + ), + onTap: () async { + context.read().tcpRepository.pushRequest(SendMessageRequest( + message: history.message, + token: (await SharedPreferences.getInstance()).getInt('token') + )); + }, + ), + ), + ), + const SizedBox(width: 8.0,), + ], + OutMessageBox(history: history), + ], + ) : OutMessageBox(history: history), ), const SizedBox(width: 16.0,), UserAvatar(userid: history.message.senderID, size: 42,), diff --git a/lib/main.dart b/lib/main.dart index de26e9d..0acc010 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-10 08:04:53 - * @LastEditTime : 2022-10-23 10:32:16 + * @LastEditTime : 2022-10-23 10:44:13 * @Description : */ import 'package:flutter/gestures.dart'; @@ -49,7 +49,7 @@ class SplashPage extends StatelessWidget { return BlocProvider( create: (context) { return InitializationCubit( - serverAddress: '127.0.0.1', + serverAddress: 'chat.linloir.cn', serverPort: 20706 ); }, From 3544e5ab3ecd539a0d80ee94ea2bd1fe00980e88 Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Sun, 23 Oct 2022 11:36:49 +0800 Subject: [PATCH 7/7] Feature: - Image Viewer --- lib/chat/view/common/image_box.dart | 53 +++++++++++++++++++++++++---- lib/main.dart | 4 +-- pubspec.lock | 14 ++++---- pubspec.yaml | 3 +- 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/lib/chat/view/common/image_box.dart b/lib/chat/view/common/image_box.dart index 65c805d..a73a6b3 100644 --- a/lib/chat/view/common/image_box.dart +++ b/lib/chat/view/common/image_box.dart @@ -1,18 +1,15 @@ /* * @Author : Linloir * @Date : 2022-10-14 17:04:20 - * @LastEditTime : 2022-10-23 10:52:45 + * @LastEditTime : 2022-10-23 11:35:10 * @Description : */ import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:tcp_client/chat/cubit/chat_cubit.dart'; +import 'package:photo_view/photo_view.dart'; import 'package:tcp_client/chat/model/chat_history.dart'; -import 'package:tcp_client/repositories/tcp_repository/models/tcp_request.dart'; class ImageBox extends StatelessWidget { const ImageBox({ @@ -30,13 +27,55 @@ class ImageBox extends StatelessWidget { children: [ Container( constraints: const BoxConstraints(maxWidth: 200, maxHeight: 150), - child: history.preCachedImage ?? Image.memory(base64Decode(history.message.contentDecoded)), + child: Hero( + tag: history.message.contentmd5, + child: history.preCachedImage ?? Image.memory(base64Decode(history.message.contentDecoded)), + ), ), Material( color: Colors.transparent, child: InkWell( splashColor: Colors.white.withOpacity(0.1), - onTap: (){}, + onTap: (){ + var image = history.preCachedImage?.image ?? Image.memory(base64.decode(history.message.contentDecoded)).image; + Navigator.of(context).push(MaterialPageRoute( + builder:(context) { + return Scaffold( + body: Stack( + children: [ + Positioned.fill( + child: PhotoView( + heroAttributes: PhotoViewHeroAttributes( + tag: history.message.contentmd5 + ), + imageProvider: image, + minScale: PhotoViewComputedScale.contained, + ) + ), + Positioned.fill( + child: SafeArea( + child: Align( + alignment: Alignment.topRight, + child: IconButton( + icon: Icon( + Icons.close_rounded, + shadows: [ + Shadow(blurRadius: 8.0, color: Colors.white.withOpacity(0.5)) + ], + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + ), + ), + ], + ), + ); + }, + )); + }, ) ), ] diff --git a/lib/main.dart b/lib/main.dart index 0acc010..8972f9d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-10 08:04:53 - * @LastEditTime : 2022-10-23 10:44:13 + * @LastEditTime : 2022-10-23 11:36:04 * @Description : */ import 'package:flutter/gestures.dart'; @@ -49,7 +49,7 @@ class SplashPage extends StatelessWidget { return BlocProvider( create: (context) { return InitializationCubit( - serverAddress: 'chat.linloir.cn', + serverAddress: '127.0.0.1', serverPort: 20706 ); }, diff --git a/pubspec.lock b/pubspec.lock index 29f91e3..a84ea90 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -78,13 +78,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.0.2+1" - easy_image_viewer: - dependency: "direct main" - description: - name: easy_image_viewer - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.0" equatable: dependency: "direct main" description: @@ -291,6 +284,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.3" + photo_view: + dependency: "direct main" + description: + name: photo_view + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.14.0" platform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9acf638..75dd8fc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,7 +57,8 @@ dependencies: easy_debounce: ^2.0.2+1 path: ^1.8.2 window_manager: ^0.2.7 - easy_image_viewer: ^1.1.0 + # easy_image_viewer: ^1.1.0 + photo_view: ^0.14.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons.