mirror of
https://github.com/Linloir/Simple-TCP-Client.git
synced 2025-12-19 01:38:12 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26f330f962 |
10
README.md
10
README.md
@ -4,8 +4,8 @@ This is a simple chat client based on Flutter
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 14:03:16
|
||||
* @LastEditTime : 2022-10-22 21:30:04
|
||||
* @LastEditTime : 2022-10-20 10:52:30
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -88,7 +88,7 @@ class ChatPage extends StatelessWidget {
|
||||
//Return history tile
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
horizontal: 24,
|
||||
vertical: 8
|
||||
),
|
||||
child: HistoryTile(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 14:03:56
|
||||
* @LastEditTime : 2022-10-22 22:46:18
|
||||
* @LastEditTime : 2022-10-20 11:04:40
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-14 17:07:13
|
||||
* @LastEditTime : 2022-10-22 23:31:19
|
||||
* @LastEditTime : 2022-10-18 15:44:34
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -35,10 +35,7 @@ class FileBox extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 0.0,
|
||||
vertical: 6.0
|
||||
),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -46,38 +43,38 @@ class FileBox extends StatelessWidget {
|
||||
child: history.status == ChatHistoryStatus.none || history.status == ChatHistoryStatus.done ?
|
||||
Icon(
|
||||
Icons.file_present_rounded,
|
||||
size: 20,
|
||||
size: 24,
|
||||
color: history.type == ChatHistoryType.income ? Colors.blue[800] : Colors.white.withOpacity(0.8),
|
||||
) : history.status == ChatHistoryStatus.failed ?
|
||||
Icon(
|
||||
Icons.refresh_rounded,
|
||||
size: 20,
|
||||
size: 24,
|
||||
color: history.type == ChatHistoryType.income ? Colors.red[800] : Colors.white.withOpacity(0.8),
|
||||
) : history.status == ChatHistoryStatus.processing ?
|
||||
SizedBox(
|
||||
height: 16.0,
|
||||
width: 16.0,
|
||||
height: 18.0,
|
||||
width: 18.0,
|
||||
child: LoadingIndicator(
|
||||
indicatorType: Indicator.ballPulseSync,
|
||||
colors: [Colors.white.withOpacity(0.8)],
|
||||
),
|
||||
) :
|
||||
SizedBox(
|
||||
height: 16.0,
|
||||
width: 16.0,
|
||||
height: 18.0,
|
||||
width: 18.0,
|
||||
child: CircularProgressIndicator(
|
||||
color: history.type == ChatHistoryType.income ? Colors.blue[800] : Colors.white.withOpacity(0.8),
|
||||
strokeWidth: 2.5,
|
||||
strokeWidth: 3,
|
||||
),
|
||||
)
|
||||
),
|
||||
const SizedBox(width: 16.0,),
|
||||
const SizedBox(width: 18.0,),
|
||||
Flexible(
|
||||
child: Text(
|
||||
history.message.contentDecoded,
|
||||
softWrap: true,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontSize: 20.0,
|
||||
color: history.type == ChatHistoryType.income ? Colors.grey[900] : Colors.white
|
||||
),
|
||||
),
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-14 17:04:20
|
||||
* @LastEditTime : 2022-10-23 11:35:10
|
||||
* @LastEditTime : 2022-10-20 13:47:29
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:tcp_client/chat/model/chat_history.dart';
|
||||
|
||||
class ImageBox extends StatelessWidget {
|
||||
@ -26,56 +25,14 @@ class ImageBox extends StatelessWidget {
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 200, maxHeight: 150),
|
||||
child: Hero(
|
||||
tag: history.message.contentmd5,
|
||||
child: history.preCachedImage ?? Image.memory(base64Decode(history.message.contentDecoded)),
|
||||
),
|
||||
constraints: const BoxConstraints(maxWidth: 500, maxHeight: 200),
|
||||
child: history.preCachedImage ?? Image.memory(base64Decode(history.message.contentDecoded)),
|
||||
),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
splashColor: Colors.white.withOpacity(0.1),
|
||||
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();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
));
|
||||
},
|
||||
onTap: (){},
|
||||
)
|
||||
),
|
||||
]
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-14 17:04:12
|
||||
* @LastEditTime : 2022-10-23 10:49:14
|
||||
* @LastEditTime : 2022-10-15 10:53:28
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -25,10 +25,7 @@ class TextBox extends StatelessWidget {
|
||||
return InkWell(
|
||||
onTap: (){},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 0.0,
|
||||
vertical: 6.0
|
||||
),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -38,14 +35,14 @@ class TextBox extends StatelessWidget {
|
||||
if(history.status == ChatHistoryStatus.sending)
|
||||
...[
|
||||
SizedBox(
|
||||
height: 12.0,
|
||||
width: 12.0,
|
||||
height: 15.0,
|
||||
width: 15.0,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white.withOpacity(0.5),
|
||||
strokeWidth: 2.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12.0,),
|
||||
const SizedBox(width: 16.0,),
|
||||
],
|
||||
if(history.status == ChatHistoryStatus.failed)
|
||||
...[
|
||||
@ -56,7 +53,7 @@ class TextBox extends StatelessWidget {
|
||||
child: Icon(
|
||||
Icons.error_rounded,
|
||||
color: Colors.white.withOpacity(0.5),
|
||||
size: 18,
|
||||
size: 20,
|
||||
),
|
||||
onTap: () async {
|
||||
context.read<ChatCubit>().tcpRepository.pushRequest(SendMessageRequest(
|
||||
@ -74,9 +71,9 @@ class TextBox extends StatelessWidget {
|
||||
Icon(
|
||||
Icons.check_rounded,
|
||||
color: Colors.white.withOpacity(0.5),
|
||||
size: 18,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8.0,),
|
||||
const SizedBox(width: 12.0,),
|
||||
],
|
||||
],
|
||||
Flexible(
|
||||
@ -84,7 +81,7 @@ class TextBox extends StatelessWidget {
|
||||
history.message.contentDecoded,
|
||||
softWrap: true,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontSize: 18,
|
||||
color: history.type == ChatHistoryType.income ? Colors.grey[900] : Colors.white
|
||||
),
|
||||
),
|
||||
|
||||
@ -1,20 +1,15 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 14:03:45
|
||||
* @LastEditTime : 2022-10-23 10:55:42
|
||||
* @LastEditTime : 2022-10-15 10:52:30
|
||||
* @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({
|
||||
@ -39,7 +34,7 @@ class HistoryTile extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
UserAvatar(userid: history.message.senderID, size: 42,),
|
||||
UserAvatar(userid: history.message.senderID),
|
||||
const SizedBox(width: 16.0,),
|
||||
Flexible(
|
||||
child: InMessageBox(history: history)
|
||||
@ -60,59 +55,10 @@ class HistoryTile extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Flexible(
|
||||
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<ChatCubit>().tcpRepository.pushRequest(SendMessageRequest(
|
||||
message: history.message,
|
||||
token: (await SharedPreferences.getInstance()).getInt('token')
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8.0,),
|
||||
],
|
||||
OutMessageBox(history: history),
|
||||
],
|
||||
) : OutMessageBox(history: history),
|
||||
child: OutMessageBox(history: history),
|
||||
),
|
||||
const SizedBox(width: 16.0,),
|
||||
UserAvatar(userid: history.message.senderID, size: 42,),
|
||||
UserAvatar(userid: history.message.senderID),
|
||||
],
|
||||
)
|
||||
),
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-14 13:49:47
|
||||
* @LastEditTime : 2022-10-23 10:07:48
|
||||
* @LastEditTime : 2022-10-20 13:56:20
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -42,37 +42,37 @@ class InMessageBox extends StatelessWidget {
|
||||
),
|
||||
boxShadow: [BoxShadow(blurRadius: 5.0, color: Colors.grey.withOpacity(0.3))]
|
||||
),
|
||||
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],
|
||||
),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
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],
|
||||
),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
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 yda, return 'yda'
|
||||
//If date is yesterday, return 'yesterday'
|
||||
if(date.day == DateTime.now().day - 1) {
|
||||
return 'yda ${date.hour}:${date.minute.toString().padLeft(2, '0')}';
|
||||
return 'yesterday ${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) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-14 17:54:30
|
||||
* @LastEditTime : 2022-10-23 10:13:22
|
||||
* @LastEditTime : 2022-10-20 11:18:48
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -34,12 +34,7 @@ class InputBox extends StatelessWidget {
|
||||
),
|
||||
child: Container(
|
||||
// height: 64,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16.0,
|
||||
right: 4.0,
|
||||
top: 16.0,
|
||||
bottom: 16.0
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
@ -84,6 +79,7 @@ class InputBox extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8.0,),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
var chatCubit = context.read<ChatCubit>();
|
||||
@ -102,6 +98,7 @@ 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<ChatCubit>();
|
||||
@ -119,6 +116,7 @@ class InputBox extends StatelessWidget {
|
||||
},
|
||||
icon: Icon(Icons.photo_rounded, color: Colors.grey[700],)
|
||||
),
|
||||
const SizedBox(width: 8.0,),
|
||||
BlocBuilder<MessageInputCubit, MessageInputState>(
|
||||
builder:(context, state) {
|
||||
return IconButton(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-14 13:49:28
|
||||
* @LastEditTime : 2022-10-23 10:07:32
|
||||
* @LastEditTime : 2022-10-19 23:47:20
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -42,37 +42,37 @@ class OutMessageBox extends StatelessWidget {
|
||||
),
|
||||
boxShadow: [BoxShadow(blurRadius: 5.0, color: Colors.grey.withOpacity(0.2))]
|
||||
),
|
||||
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],
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
),
|
||||
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],
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
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 yda, return 'yda'
|
||||
//If date is yesterday, return 'yesterday'
|
||||
if(date.day == DateTime.now().day - 1) {
|
||||
return 'yda ${date.hour}:${date.minute.toString().padLeft(2, '0')}';
|
||||
return 'yesterday ${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) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 14:02:28
|
||||
* @LastEditTime : 2022-10-22 21:08:39
|
||||
* @LastEditTime : 2022-10-17 19:26:13
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -9,10 +9,8 @@ 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';
|
||||
|
||||
@ -21,39 +19,11 @@ class HomeCubit extends Cubit<HomeState> {
|
||||
required this.localServiceRepository,
|
||||
required this.tcpRepository,
|
||||
required this.pageController
|
||||
}): super(const HomeState(page: HomePagePosition.message, status: HomePageStatus.initializing)) {
|
||||
}): super(const HomeState(page: HomePagePosition.message)) {
|
||||
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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
final LocalServiceRepository localServiceRepository;
|
||||
@ -69,26 +39,16 @@ class HomeCubit extends Cubit<HomeState> {
|
||||
);
|
||||
}
|
||||
|
||||
void _onTCPResponse(TCPResponse response) async {
|
||||
if(response.status == TCPResponseStatus.err) {
|
||||
return;
|
||||
}
|
||||
void _onTCPResponse(TCPResponse response) {
|
||||
switch(response.type) {
|
||||
case TCPResponseType.forwardMessage: {
|
||||
response as ForwardMessageResponse;
|
||||
await localServiceRepository.storeMessages([response.message]);
|
||||
localServiceRepository.storeMessages([response.message]);
|
||||
break;
|
||||
}
|
||||
case TCPResponseType.fetchMessage: {
|
||||
response as FetchMessageResponse;
|
||||
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')
|
||||
));
|
||||
}
|
||||
localServiceRepository.storeMessages(response.messages);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 14:02:24
|
||||
* @LastEditTime : 2022-10-21 23:28:33
|
||||
* @LastEditTime : 2022-10-13 16:55:05
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
enum HomePageStatus { initializing, done }
|
||||
|
||||
enum HomePagePosition {
|
||||
message(0),
|
||||
contact(1),
|
||||
@ -28,14 +26,13 @@ enum HomePagePosition {
|
||||
|
||||
class HomeState extends Equatable {
|
||||
final HomePagePosition page;
|
||||
final HomePageStatus status;
|
||||
|
||||
const HomeState({required this.page, required this.status});
|
||||
const HomeState({required this.page});
|
||||
|
||||
HomeState copyWith({HomePagePosition? page, HomePageStatus? status}) {
|
||||
return HomeState(page: page ?? this.page, status: status ?? this.status);
|
||||
HomeState copyWith({HomePagePosition? page}) {
|
||||
return HomeState(page: page ?? this.page);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [page, status];
|
||||
List<Object> get props => [page];
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 11:05:08
|
||||
* @LastEditTime : 2022-10-21 23:56:24
|
||||
* @LastEditTime : 2022-10-19 11:08:50
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -119,56 +119,16 @@ class HomePageView extends StatelessWidget {
|
||||
)
|
||||
],
|
||||
),
|
||||
body: BlocBuilder<HomeCubit, HomeState>(
|
||||
builder:(context, state) => Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Center(
|
||||
child: PageView(
|
||||
controller: context.read<HomeCubit>().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
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
body: Center(
|
||||
child: BlocBuilder<HomeCubit, HomeState>(
|
||||
builder:(context, state) => PageView(
|
||||
controller: context.read<HomeCubit>().pageController,
|
||||
children: [
|
||||
MessagePage(),
|
||||
const ContactPage(),
|
||||
MyProfilePage(userID: userID)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: BlocBuilder<HomeCubit, HomeState>(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 23:38:31
|
||||
* @LastEditTime : 2022-10-21 23:14:02
|
||||
* @LastEditTime : 2022-10-19 18:02:22
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -40,7 +40,10 @@ class MessageListCubit extends Cubit<MessageListState> {
|
||||
}
|
||||
}
|
||||
return msgList;
|
||||
}).then((msgList) => emit(state.updateWithList(orderedNewMessages: msgList)));
|
||||
}).then((msgList) => emit(state.updateWithList(orderedNewMessages: msgList)))
|
||||
.then((_) async => tcpRepository.pushRequest(FetchMessageRequest(
|
||||
token: (await SharedPreferences.getInstance()).getInt('token'))
|
||||
));
|
||||
}
|
||||
|
||||
final LocalServiceRepository localServiceRepository;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 13:17:52
|
||||
* @LastEditTime : 2022-10-23 10:09:09
|
||||
* @LastEditTime : 2022-10-20 00:52:14
|
||||
* @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 yda, return 'yda'
|
||||
//If date is yesterday, return 'yesterday'
|
||||
if(date.day == DateTime.now().day - 1) {
|
||||
return 'yda ${date.hour}:${date.minute.toString().padLeft(2, '0')}';
|
||||
return 'yesterday';
|
||||
}
|
||||
//If date is within this week, return the weekday in english
|
||||
if(date.weekday < DateTime.now().weekday) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 15:06:30
|
||||
* @LastEditTime : 2022-10-23 10:15:11
|
||||
* @LastEditTime : 2022-10-20 20:55:07
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -81,7 +81,7 @@ class LoginPage extends StatelessWidget {
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.7,
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
child: const LoginPanel()
|
||||
)
|
||||
),
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 16:29:25
|
||||
* @LastEditTime : 2022-10-23 10:28:05
|
||||
* @LastEditTime : 2022-10-20 20:43:51
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -44,11 +44,6 @@ class UsernameInput extends StatelessWidget {
|
||||
onChanged: (username) {
|
||||
context.read<LoginCubit>().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
|
||||
@ -65,21 +60,12 @@ class PasswordInput extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<LoginCubit, LoginState>(
|
||||
buildWhen: (previous, current) => previous.password != current.password || previous.status != current.status,
|
||||
buildWhen: (previous, current) => previous.password != current.password,
|
||||
builder: (context, state) {
|
||||
return TextField(
|
||||
onChanged: (password) {
|
||||
context.read<LoginCubit>().onPasswordChange(Password.dirty(password));
|
||||
},
|
||||
onEditingComplete: () {
|
||||
if(
|
||||
state.status == FormzStatus.valid ||
|
||||
state.status == FormzStatus.submissionFailure
|
||||
) {
|
||||
context.read<LoginCubit>().onSubmission();
|
||||
}
|
||||
},
|
||||
textInputAction: TextInputAction.done,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password',
|
||||
|
||||
@ -1,20 +1,53 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-10 08:04:53
|
||||
* @LastEditTime : 2022-10-23 11:36:04
|
||||
* @LastEditTime : 2022-10-20 23:11:45
|
||||
* @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());
|
||||
}
|
||||
|
||||
@ -25,7 +58,44 @@ class MyApp extends StatefulWidget {
|
||||
State<MyApp> createState() => MyAppState();
|
||||
}
|
||||
|
||||
class MyAppState extends State<MyApp> {
|
||||
class MyAppState extends State<MyApp> 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();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 17:36:38
|
||||
* @LastEditTime : 2022-10-23 10:15:31
|
||||
* @LastEditTime : 2022-10-20 20:55:17
|
||||
* @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.7,
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-12 16:29:25
|
||||
* @LastEditTime : 2022-10-23 10:28:12
|
||||
* @LastEditTime : 2022-10-20 20:54:22
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -44,11 +44,6 @@ class UsernameInput extends StatelessWidget {
|
||||
onChanged: (username) {
|
||||
context.read<RegisterCubit>().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
|
||||
@ -65,21 +60,12 @@ class PasswordInput extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<RegisterCubit, RegisterState>(
|
||||
buildWhen: (previous, current) => previous.password != current.password || previous.status != current.status,
|
||||
buildWhen: (previous, current) => previous.password != current.password,
|
||||
builder: (context, state) {
|
||||
return TextField(
|
||||
onChanged: (password) {
|
||||
context.read<RegisterCubit>().onPasswordChange(Password.dirty(password));
|
||||
},
|
||||
onEditingComplete: () {
|
||||
if(
|
||||
state.status == FormzStatus.valid ||
|
||||
state.status == FormzStatus.submissionFailure
|
||||
) {
|
||||
context.read<RegisterCubit>().onSubmission();
|
||||
}
|
||||
},
|
||||
textInputAction: TextInputAction.done,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Password',
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 10:56:02
|
||||
* @LastEditTime : 2022-10-22 01:22:58
|
||||
* @LastEditTime : 2022-10-20 17:24:26
|
||||
* @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';
|
||||
@ -16,7 +17,11 @@ 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';
|
||||
import 'package:sqflite/sqflite.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';
|
||||
|
||||
class LocalServiceRepository {
|
||||
late final Database _database;
|
||||
@ -57,22 +62,6 @@ 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(
|
||||
// '''
|
||||
@ -102,11 +91,20 @@ class LocalServiceRepository {
|
||||
UserInfo? currentUser,
|
||||
required String databaseFilePath
|
||||
}) async {
|
||||
var database = await openDatabase(
|
||||
//Windows platform
|
||||
var database = await databaseFactoryFfi.openDatabase(
|
||||
databaseFilePath,
|
||||
version: 1,
|
||||
onCreate: _onDatabaseCreate
|
||||
options: OpenDatabaseOptions(
|
||||
version: 1,
|
||||
onCreate: _onDatabaseCreate
|
||||
)
|
||||
);
|
||||
//Android platform
|
||||
// var database = await openDatabase(
|
||||
// databaseFilePath,
|
||||
// version: 1,
|
||||
// onCreate: _onDatabaseCreate
|
||||
// );
|
||||
return LocalServiceRepository._internal(database: database);
|
||||
}
|
||||
|
||||
@ -118,31 +116,27 @@ class LocalServiceRepository {
|
||||
);
|
||||
if (filePickResult == null) return null;
|
||||
var file = File(filePickResult.files.single.path!);
|
||||
// var md5Output = AccumulatorSink<Digest>();
|
||||
// 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;
|
||||
}
|
||||
|
||||
Future<void> storeMessages(List<Message> messages) async {
|
||||
await _database.transaction((txn) async {
|
||||
for(var message in messages) {
|
||||
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
|
||||
);
|
||||
}
|
||||
await txn.insert(
|
||||
'msgs',
|
||||
message.jsonObject,
|
||||
conflictAlgorithm: ConflictAlgorithm.replace
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -168,20 +162,6 @@ 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);
|
||||
}
|
||||
}
|
||||
@ -207,8 +187,6 @@ 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])]);
|
||||
}
|
||||
}
|
||||
@ -234,18 +212,7 @@ class LocalServiceRepository {
|
||||
limit: num,
|
||||
offset: position
|
||||
);
|
||||
List<Message> 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;
|
||||
return queryResult.map((e) => Message.fromJSONObject(jsonObject: e)).toList();
|
||||
}
|
||||
|
||||
Future<File?> findFile({required String filemd5, required String fileName}) async {
|
||||
@ -400,59 +367,4 @@ class LocalServiceRepository {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> storeImage({required List<int> image, required String msgmd5}) async {
|
||||
var md5Output = AccumulatorSink<Digest>();
|
||||
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<List<int>?> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 09:44:03
|
||||
* @LastEditTime : 2022-10-22 21:01:35
|
||||
* @LastEditTime : 2022-10-18 14:45:20
|
||||
* @Description : Abstract TCP request class
|
||||
*/
|
||||
|
||||
@ -24,7 +24,6 @@ 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
|
||||
@ -244,15 +243,4 @@ class FetchContactRequest extends TCPRequest {
|
||||
|
||||
@override
|
||||
Map<String, Object?> 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<String, Object?> get body => {
|
||||
'timestamp': _timeStamp
|
||||
};
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 09:42:05
|
||||
* @LastEditTime : 2022-10-22 17:46:28
|
||||
* @LastEditTime : 2022-10-19 10:41:43
|
||||
* @Description : TCP repository
|
||||
*/
|
||||
|
||||
@ -26,25 +26,13 @@ class TCPRepository {
|
||||
required int remotePort
|
||||
}): _socket = socket, _remoteAddress = remoteAddress, _remotePort = remotePort {
|
||||
Future(() async {
|
||||
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;
|
||||
}
|
||||
}
|
||||
try{
|
||||
await for(var response in _socket) {
|
||||
_pullResponse(response);
|
||||
await Future.delayed(const Duration(microseconds: 0));
|
||||
}
|
||||
} catch(e) {
|
||||
_socket.close();
|
||||
}
|
||||
// _responseRawStreamController.close();
|
||||
// _payloadPullStreamController.close();
|
||||
@ -54,40 +42,10 @@ class TCPRepository {
|
||||
});
|
||||
//This future never ends, would that be bothersome?
|
||||
Future(() async {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
@ -98,22 +56,14 @@ class TCPRepository {
|
||||
}
|
||||
responseQueue.cancel();
|
||||
payloadQueue.cancel();
|
||||
}).onError((error, stackTrace) {_socket?.close();});
|
||||
}).onError((error, stackTrace) {_socket.close();});
|
||||
}
|
||||
|
||||
static Future<TCPRepository> create({
|
||||
required String serverAddress,
|
||||
required int serverPort
|
||||
}) async {
|
||||
Socket socket;
|
||||
while(true) {
|
||||
try{
|
||||
socket = await Socket.connect(serverAddress, serverPort);
|
||||
break;
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var socket = await Socket.connect(serverAddress, serverPort);
|
||||
return TCPRepository._internal(
|
||||
socket: socket,
|
||||
remoteAddress: serverAddress,
|
||||
@ -128,7 +78,7 @@ class TCPRepository {
|
||||
);
|
||||
}
|
||||
|
||||
Socket? _socket;
|
||||
final Socket _socket;
|
||||
final String _remoteAddress;
|
||||
final int _remotePort;
|
||||
|
||||
@ -409,7 +359,7 @@ class TCPRepository {
|
||||
}
|
||||
|
||||
void dispose() async {
|
||||
await _socket?.flush();
|
||||
await _socket?.close();
|
||||
await _socket.flush();
|
||||
await _socket.close();
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 17:04:12
|
||||
* @LastEditTime : 2022-10-23 10:30:41
|
||||
* @LastEditTime : 2022-10-14 10:38:44
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -61,7 +61,7 @@ class SearchPage extends StatelessWidget {
|
||||
const SizedBox(height: 16.0,),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 24.0,
|
||||
horizontal: 36.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: 24.0,
|
||||
horizontal: 36.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: 24.0,
|
||||
horizontal: 36.0,
|
||||
vertical: 8.0
|
||||
),
|
||||
child: Text(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 21:41:49
|
||||
* @LastEditTime : 2022-10-23 10:30:06
|
||||
* @LastEditTime : 2022-10-17 22:23:46
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -24,8 +24,8 @@ class HistoryTile extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 24.0
|
||||
vertical: 16.0,
|
||||
horizontal: 36.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 yda, return 'yda'
|
||||
//If date is yesterday, return 'yesterday'
|
||||
if(date.day == DateTime.now().day - 1) {
|
||||
return 'yda ${date.hour}:${date.minute.toString().padLeft(2, '0')}';
|
||||
return 'yesterday';
|
||||
}
|
||||
//If date is within this week, return the weekday in english
|
||||
if(date.weekday < DateTime.now().weekday) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-13 21:41:41
|
||||
* @LastEditTime : 2022-10-23 10:30:24
|
||||
* @LastEditTime : 2022-10-18 11:28:17
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -35,8 +35,8 @@ class UserTile extends StatelessWidget {
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 8,
|
||||
horizontal: 36,
|
||||
vertical: 16,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
14
pubspec.lock
14
pubspec.lock
@ -78,6 +78,13 @@ 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:
|
||||
@ -284,13 +291,6 @@ 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:
|
||||
|
||||
@ -57,8 +57,7 @@ dependencies:
|
||||
easy_debounce: ^2.0.2+1
|
||||
path: ^1.8.2
|
||||
window_manager: ^0.2.7
|
||||
# easy_image_viewer: ^1.1.0
|
||||
photo_view: ^0.14.0
|
||||
easy_image_viewer: ^1.1.0
|
||||
|
||||
# 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