mirror of
https://github.com/Linloir/Simple-TCP-Client.git
synced 2025-12-19 17:58:11 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 04846da2c3 | |||
| a2e683ac2c | |||
| 828fb1b5b9 | |||
| 7803294ef4 | |||
| a68ef5ba25 | |||
| c0ac46939e | |||
| 01e7685a4d | |||
| a3d4311479 | |||
| 3544e5ab3e | |||
| 354fcb3263 | |||
| 5594bd6ba7 | |||
| da53d22261 | |||
| a758929b46 | |||
| ba59d23484 | |||
| ba452c7fd8 |
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 14:03:16
|
* @Date : 2022-10-13 14:03:16
|
||||||
* @LastEditTime : 2022-10-20 10:52:30
|
* @LastEditTime : 2022-10-22 21:30:04
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ class ChatPage extends StatelessWidget {
|
|||||||
//Return history tile
|
//Return history tile
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 24,
|
horizontal: 16,
|
||||||
vertical: 8
|
vertical: 8
|
||||||
),
|
),
|
||||||
child: HistoryTile(
|
child: HistoryTile(
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 14:03:56
|
* @Date : 2022-10-13 14:03:56
|
||||||
* @LastEditTime : 2022-10-20 11:04:40
|
* @LastEditTime : 2022-10-22 22:46:18
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-14 17:07:13
|
* @Date : 2022-10-14 17:07:13
|
||||||
* @LastEditTime : 2022-10-18 15:44:34
|
* @LastEditTime : 2022-10-22 23:31:19
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -35,7 +35,10 @@ class FileBox extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 0.0,
|
||||||
|
vertical: 6.0
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@ -43,38 +46,38 @@ class FileBox extends StatelessWidget {
|
|||||||
child: history.status == ChatHistoryStatus.none || history.status == ChatHistoryStatus.done ?
|
child: history.status == ChatHistoryStatus.none || history.status == ChatHistoryStatus.done ?
|
||||||
Icon(
|
Icon(
|
||||||
Icons.file_present_rounded,
|
Icons.file_present_rounded,
|
||||||
size: 24,
|
size: 20,
|
||||||
color: history.type == ChatHistoryType.income ? Colors.blue[800] : Colors.white.withOpacity(0.8),
|
color: history.type == ChatHistoryType.income ? Colors.blue[800] : Colors.white.withOpacity(0.8),
|
||||||
) : history.status == ChatHistoryStatus.failed ?
|
) : history.status == ChatHistoryStatus.failed ?
|
||||||
Icon(
|
Icon(
|
||||||
Icons.refresh_rounded,
|
Icons.refresh_rounded,
|
||||||
size: 24,
|
size: 20,
|
||||||
color: history.type == ChatHistoryType.income ? Colors.red[800] : Colors.white.withOpacity(0.8),
|
color: history.type == ChatHistoryType.income ? Colors.red[800] : Colors.white.withOpacity(0.8),
|
||||||
) : history.status == ChatHistoryStatus.processing ?
|
) : history.status == ChatHistoryStatus.processing ?
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 18.0,
|
height: 16.0,
|
||||||
width: 18.0,
|
width: 16.0,
|
||||||
child: LoadingIndicator(
|
child: LoadingIndicator(
|
||||||
indicatorType: Indicator.ballPulseSync,
|
indicatorType: Indicator.ballPulseSync,
|
||||||
colors: [Colors.white.withOpacity(0.8)],
|
colors: [Colors.white.withOpacity(0.8)],
|
||||||
),
|
),
|
||||||
) :
|
) :
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 18.0,
|
height: 16.0,
|
||||||
width: 18.0,
|
width: 16.0,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
color: history.type == ChatHistoryType.income ? Colors.blue[800] : Colors.white.withOpacity(0.8),
|
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(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
history.message.contentDecoded,
|
history.message.contentDecoded,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20.0,
|
fontSize: 16.0,
|
||||||
color: history.type == ChatHistoryType.income ? Colors.grey[900] : Colors.white
|
color: history.type == ChatHistoryType.income ? Colors.grey[900] : Colors.white
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-14 17:04:20
|
* @Date : 2022-10-14 17:04:20
|
||||||
* @LastEditTime : 2022-10-20 13:47:29
|
* @LastEditTime : 2022-10-23 11:35:10
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:photo_view/photo_view.dart';
|
||||||
import 'package:tcp_client/chat/model/chat_history.dart';
|
import 'package:tcp_client/chat/model/chat_history.dart';
|
||||||
|
|
||||||
class ImageBox extends StatelessWidget {
|
class ImageBox extends StatelessWidget {
|
||||||
@ -25,14 +26,56 @@ class ImageBox extends StatelessWidget {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 500, maxHeight: 200),
|
constraints: const BoxConstraints(maxWidth: 200, maxHeight: 150),
|
||||||
|
child: Hero(
|
||||||
|
tag: history.message.contentmd5,
|
||||||
child: history.preCachedImage ?? Image.memory(base64Decode(history.message.contentDecoded)),
|
child: history.preCachedImage ?? Image.memory(base64Decode(history.message.contentDecoded)),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
Material(
|
Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
splashColor: Colors.white.withOpacity(0.1),
|
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();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
},
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-14 17:04:12
|
* @Date : 2022-10-14 17:04:12
|
||||||
* @LastEditTime : 2022-10-15 10:53:28
|
* @LastEditTime : 2022-10-23 10:49:14
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -25,7 +25,10 @@ class TextBox extends StatelessWidget {
|
|||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: (){},
|
onTap: (){},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 0.0,
|
||||||
|
vertical: 6.0
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -35,14 +38,14 @@ class TextBox extends StatelessWidget {
|
|||||||
if(history.status == ChatHistoryStatus.sending)
|
if(history.status == ChatHistoryStatus.sending)
|
||||||
...[
|
...[
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 15.0,
|
height: 12.0,
|
||||||
width: 15.0,
|
width: 12.0,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
color: Colors.white.withOpacity(0.5),
|
color: Colors.white.withOpacity(0.5),
|
||||||
strokeWidth: 2.0,
|
strokeWidth: 2.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16.0,),
|
const SizedBox(width: 12.0,),
|
||||||
],
|
],
|
||||||
if(history.status == ChatHistoryStatus.failed)
|
if(history.status == ChatHistoryStatus.failed)
|
||||||
...[
|
...[
|
||||||
@ -53,7 +56,7 @@ class TextBox extends StatelessWidget {
|
|||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.error_rounded,
|
Icons.error_rounded,
|
||||||
color: Colors.white.withOpacity(0.5),
|
color: Colors.white.withOpacity(0.5),
|
||||||
size: 20,
|
size: 18,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
context.read<ChatCubit>().tcpRepository.pushRequest(SendMessageRequest(
|
context.read<ChatCubit>().tcpRepository.pushRequest(SendMessageRequest(
|
||||||
@ -71,9 +74,9 @@ class TextBox extends StatelessWidget {
|
|||||||
Icon(
|
Icon(
|
||||||
Icons.check_rounded,
|
Icons.check_rounded,
|
||||||
color: Colors.white.withOpacity(0.5),
|
color: Colors.white.withOpacity(0.5),
|
||||||
size: 20,
|
size: 18,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12.0,),
|
const SizedBox(width: 8.0,),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
Flexible(
|
Flexible(
|
||||||
@ -81,7 +84,7 @@ class TextBox extends StatelessWidget {
|
|||||||
history.message.contentDecoded,
|
history.message.contentDecoded,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 16,
|
||||||
color: history.type == ChatHistoryType.income ? Colors.grey[900] : Colors.white
|
color: history.type == ChatHistoryType.income ? Colors.grey[900] : Colors.white
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,15 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 14:03:45
|
* @Date : 2022-10-13 14:03:45
|
||||||
* @LastEditTime : 2022-10-15 10:52:30
|
* @LastEditTime : 2022-10-23 10:55:42
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
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/model/chat_history.dart';
|
||||||
import 'package:tcp_client/chat/view/in_message_box.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/chat/view/out_message_box.dart';
|
||||||
import 'package:tcp_client/common/avatar/avatar.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 {
|
class HistoryTile extends StatelessWidget {
|
||||||
const HistoryTile({
|
const HistoryTile({
|
||||||
@ -34,7 +39,7 @@ class HistoryTile extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
UserAvatar(userid: history.message.senderID),
|
UserAvatar(userid: history.message.senderID, size: 42,),
|
||||||
const SizedBox(width: 16.0,),
|
const SizedBox(width: 16.0,),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: InMessageBox(history: history)
|
child: InMessageBox(history: history)
|
||||||
@ -55,10 +60,59 @@ class HistoryTile extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
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<ChatCubit>().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,),
|
const SizedBox(width: 16.0,),
|
||||||
UserAvatar(userid: history.message.senderID),
|
UserAvatar(userid: history.message.senderID, size: 42,),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-14 13:49:47
|
* @Date : 2022-10-14 13:49:47
|
||||||
* @LastEditTime : 2022-10-20 13:56:20
|
* @LastEditTime : 2022-10-23 10:07:48
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -42,13 +42,6 @@ class InMessageBox extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
boxShadow: [BoxShadow(blurRadius: 5.0, color: Colors.grey.withOpacity(0.3))]
|
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(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -56,7 +49,15 @@ class InMessageBox extends StatelessWidget {
|
|||||||
if(history.message.type == MessageType.file)
|
if(history.message.type == MessageType.file)
|
||||||
FileBox(history: history),
|
FileBox(history: history),
|
||||||
if(history.message.type == MessageType.image)
|
if(history.message.type == MessageType.image)
|
||||||
ImageBox(history: history),
|
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)
|
if(history.message.type == MessageType.plaintext)
|
||||||
TextBox(history: history),
|
TextBox(history: history),
|
||||||
if(history.message.type != MessageType.image)
|
if(history.message.type != MessageType.image)
|
||||||
@ -72,7 +73,6 @@ class InMessageBox extends StatelessWidget {
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
|
||||||
),
|
),
|
||||||
if(history.message.type == MessageType.image)
|
if(history.message.type == MessageType.image)
|
||||||
...[
|
...[
|
||||||
@ -96,9 +96,9 @@ class InMessageBox extends StatelessWidget {
|
|||||||
if(date.day == DateTime.now().day) {
|
if(date.day == DateTime.now().day) {
|
||||||
return '${date.hour}:${date.minute.toString().padLeft(2, '0')}';
|
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) {
|
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 is within this week, return the weekday in english
|
||||||
if(date.weekday < DateTime.now().weekday) {
|
if(date.weekday < DateTime.now().weekday) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-14 17:54:30
|
* @Date : 2022-10-14 17:54:30
|
||||||
* @LastEditTime : 2022-10-20 11:18:48
|
* @LastEditTime : 2022-10-23 10:13:22
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -34,7 +34,12 @@ class InputBox extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
// height: 64,
|
// 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(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@ -79,7 +84,6 @@ class InputBox extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8.0,),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
var chatCubit = context.read<ChatCubit>();
|
var chatCubit = context.read<ChatCubit>();
|
||||||
@ -98,7 +102,6 @@ class InputBox extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
icon: Icon(Icons.attach_file_rounded, color: Colors.grey[700],)
|
icon: Icon(Icons.attach_file_rounded, color: Colors.grey[700],)
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8.0,),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
var chatCubit = context.read<ChatCubit>();
|
var chatCubit = context.read<ChatCubit>();
|
||||||
@ -116,7 +119,6 @@ class InputBox extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
icon: Icon(Icons.photo_rounded, color: Colors.grey[700],)
|
icon: Icon(Icons.photo_rounded, color: Colors.grey[700],)
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8.0,),
|
|
||||||
BlocBuilder<MessageInputCubit, MessageInputState>(
|
BlocBuilder<MessageInputCubit, MessageInputState>(
|
||||||
builder:(context, state) {
|
builder:(context, state) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-14 13:49:28
|
* @Date : 2022-10-14 13:49:28
|
||||||
* @LastEditTime : 2022-10-19 23:47:20
|
* @LastEditTime : 2022-10-23 10:07:32
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -42,13 +42,6 @@ class OutMessageBox extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
boxShadow: [BoxShadow(blurRadius: 5.0, color: Colors.grey.withOpacity(0.2))]
|
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(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -56,7 +49,15 @@ class OutMessageBox extends StatelessWidget {
|
|||||||
if(history.message.type == MessageType.file)
|
if(history.message.type == MessageType.file)
|
||||||
FileBox(history: history),
|
FileBox(history: history),
|
||||||
if(history.message.type == MessageType.image)
|
if(history.message.type == MessageType.image)
|
||||||
ImageBox(history: history),
|
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)
|
if(history.message.type == MessageType.plaintext)
|
||||||
TextBox(history: history),
|
TextBox(history: history),
|
||||||
if(history.message.type != MessageType.image)
|
if(history.message.type != MessageType.image)
|
||||||
@ -72,7 +73,6 @@ class OutMessageBox extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
|
||||||
),
|
),
|
||||||
if(history.message.type == MessageType.image)
|
if(history.message.type == MessageType.image)
|
||||||
...[
|
...[
|
||||||
@ -96,9 +96,9 @@ class OutMessageBox extends StatelessWidget {
|
|||||||
if(date.day == DateTime.now().day) {
|
if(date.day == DateTime.now().day) {
|
||||||
return '${date.hour}:${date.minute.toString().padLeft(2, '0')}';
|
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) {
|
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 is within this week, return the weekday in english
|
||||||
if(date.weekday < DateTime.now().weekday) {
|
if(date.weekday < DateTime.now().weekday) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 14:02:28
|
* @Date : 2022-10-13 14:02:28
|
||||||
* @LastEditTime : 2022-10-17 19:26:13
|
* @LastEditTime : 2022-10-22 21:08:39
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -9,8 +9,10 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:flutter/cupertino.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/home/cubit/home_state.dart';
|
||||||
import 'package:tcp_client/repositories/local_service_repository/local_service_repository.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/models/tcp_response.dart';
|
||||||
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
|
import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart';
|
||||||
|
|
||||||
@ -19,11 +21,39 @@ class HomeCubit extends Cubit<HomeState> {
|
|||||||
required this.localServiceRepository,
|
required this.localServiceRepository,
|
||||||
required this.tcpRepository,
|
required this.tcpRepository,
|
||||||
required this.pageController
|
required this.pageController
|
||||||
}): super(const HomeState(page: HomePagePosition.message)) {
|
}): super(const HomeState(page: HomePagePosition.message, status: HomePageStatus.initializing)) {
|
||||||
pageController.addListener(() {
|
pageController.addListener(() {
|
||||||
emit(state.copyWith(page: HomePagePosition.fromValue((pageController.page ?? 0).round())));
|
emit(state.copyWith(page: HomePagePosition.fromValue((pageController.page ?? 0).round())));
|
||||||
});
|
});
|
||||||
subscription = tcpRepository.responseStreamBroadcast.listen(_onTCPResponse);
|
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;
|
final LocalServiceRepository localServiceRepository;
|
||||||
@ -39,16 +69,26 @@ class HomeCubit extends Cubit<HomeState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onTCPResponse(TCPResponse response) {
|
void _onTCPResponse(TCPResponse response) async {
|
||||||
|
if(response.status == TCPResponseStatus.err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch(response.type) {
|
switch(response.type) {
|
||||||
case TCPResponseType.forwardMessage: {
|
case TCPResponseType.forwardMessage: {
|
||||||
response as ForwardMessageResponse;
|
response as ForwardMessageResponse;
|
||||||
localServiceRepository.storeMessages([response.message]);
|
await localServiceRepository.storeMessages([response.message]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TCPResponseType.fetchMessage: {
|
case TCPResponseType.fetchMessage: {
|
||||||
response as FetchMessageResponse;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 14:02:24
|
* @Date : 2022-10-13 14:02:24
|
||||||
* @LastEditTime : 2022-10-13 16:55:05
|
* @LastEditTime : 2022-10-21 23:28:33
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
enum HomePageStatus { initializing, done }
|
||||||
|
|
||||||
enum HomePagePosition {
|
enum HomePagePosition {
|
||||||
message(0),
|
message(0),
|
||||||
contact(1),
|
contact(1),
|
||||||
@ -26,13 +28,14 @@ enum HomePagePosition {
|
|||||||
|
|
||||||
class HomeState extends Equatable {
|
class HomeState extends Equatable {
|
||||||
final HomePagePosition page;
|
final HomePagePosition page;
|
||||||
|
final HomePageStatus status;
|
||||||
|
|
||||||
const HomeState({required this.page});
|
const HomeState({required this.page, required this.status});
|
||||||
|
|
||||||
HomeState copyWith({HomePagePosition? page}) {
|
HomeState copyWith({HomePagePosition? page, HomePageStatus? status}) {
|
||||||
return HomeState(page: page ?? this.page);
|
return HomeState(page: page ?? this.page, status: status ?? this.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [page];
|
List<Object> get props => [page, status];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-11 11:05:08
|
* @Date : 2022-10-11 11:05:08
|
||||||
* @LastEditTime : 2022-10-19 11:08:50
|
* @LastEditTime : 2022-10-21 23:56:24
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -119,9 +119,12 @@ class HomePageView extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Center(
|
body: BlocBuilder<HomeCubit, HomeState>(
|
||||||
child: BlocBuilder<HomeCubit, HomeState>(
|
builder:(context, state) => Stack(
|
||||||
builder:(context, state) => PageView(
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: Center(
|
||||||
|
child: PageView(
|
||||||
controller: context.read<HomeCubit>().pageController,
|
controller: context.read<HomeCubit>().pageController,
|
||||||
children: [
|
children: [
|
||||||
MessagePage(),
|
MessagePage(),
|
||||||
@ -131,6 +134,43 @@ class HomePageView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
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<HomeCubit, HomeState>(
|
bottomNavigationBar: BlocBuilder<HomeCubit, HomeState>(
|
||||||
builder: (context, state) => BottomNavigationBar(
|
builder: (context, state) => BottomNavigationBar(
|
||||||
items: const [
|
items: const [
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-12 23:38:31
|
* @Date : 2022-10-12 23:38:31
|
||||||
* @LastEditTime : 2022-10-19 18:02:22
|
* @LastEditTime : 2022-10-21 23:14:02
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -40,10 +40,7 @@ class MessageListCubit extends Cubit<MessageListState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return msgList;
|
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;
|
final LocalServiceRepository localServiceRepository;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 13:17:52
|
* @Date : 2022-10-13 13:17:52
|
||||||
* @LastEditTime : 2022-10-20 00:52:14
|
* @LastEditTime : 2022-10-23 10:09:09
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -150,9 +150,9 @@ class MessageTile extends StatelessWidget {
|
|||||||
if(date.day == DateTime.now().day) {
|
if(date.day == DateTime.now().day) {
|
||||||
return '${date.hour}:${date.minute.toString().padLeft(2, '0')}';
|
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) {
|
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 is within this week, return the weekday in english
|
||||||
if(date.weekday < DateTime.now().weekday) {
|
if(date.weekday < DateTime.now().weekday) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-12 15:06:30
|
* @Date : 2022-10-12 15:06:30
|
||||||
* @LastEditTime : 2022-10-20 20:55:07
|
* @LastEditTime : 2022-10-23 10:15:11
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ class LoginPage extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 6,
|
flex: 6,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: MediaQuery.of(context).size.width * 0.5,
|
width: MediaQuery.of(context).size.width * 0.7,
|
||||||
child: const LoginPanel()
|
child: const LoginPanel()
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-12 16:29:25
|
* @Date : 2022-10-12 16:29:25
|
||||||
* @LastEditTime : 2022-10-20 20:43:51
|
* @LastEditTime : 2022-10-23 10:28:05
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -44,6 +44,11 @@ class UsernameInput extends StatelessWidget {
|
|||||||
onChanged: (username) {
|
onChanged: (username) {
|
||||||
context.read<LoginCubit>().onUsernameChange(Username.dirty(username));
|
context.read<LoginCubit>().onUsernameChange(Username.dirty(username));
|
||||||
},
|
},
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
textCapitalization: TextCapitalization.none,
|
||||||
|
autocorrect: false,
|
||||||
|
enableIMEPersonalizedLearning: false,
|
||||||
|
enableSuggestions: false,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Username',
|
labelText: 'Username',
|
||||||
errorText: state.username.invalid ? 'Invalid username' : null
|
errorText: state.username.invalid ? 'Invalid username' : null
|
||||||
@ -60,12 +65,21 @@ class PasswordInput extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<LoginCubit, LoginState>(
|
return BlocBuilder<LoginCubit, LoginState>(
|
||||||
buildWhen: (previous, current) => previous.password != current.password,
|
buildWhen: (previous, current) => previous.password != current.password || previous.status != current.status,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (password) {
|
onChanged: (password) {
|
||||||
context.read<LoginCubit>().onPasswordChange(Password.dirty(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,
|
obscureText: true,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Password',
|
labelText: 'Password',
|
||||||
|
|||||||
@ -1,53 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-10 08:04:53
|
* @Date : 2022-10-10 08:04:53
|
||||||
* @LastEditTime : 2022-10-20 23:11:45
|
* @LastEditTime : 2022-10-23 11:36:04
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
import 'package:easy_debounce/easy_debounce.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.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/home/home_page.dart';
|
||||||
import 'package:tcp_client/initialization/cubit/initialization_cubit.dart';
|
import 'package:tcp_client/initialization/cubit/initialization_cubit.dart';
|
||||||
import 'package:tcp_client/initialization/cubit/initialization_state.dart';
|
import 'package:tcp_client/initialization/cubit/initialization_state.dart';
|
||||||
import 'package:tcp_client/initialization/initialization_page.dart';
|
import 'package:tcp_client/initialization/initialization_page.dart';
|
||||||
import 'package:tcp_client/login/login_page.dart';
|
import 'package:tcp_client/login/login_page.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
|
|
||||||
void main() async {
|
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());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,44 +25,7 @@ class MyApp extends StatefulWidget {
|
|||||||
State<MyApp> createState() => MyAppState();
|
State<MyApp> createState() => MyAppState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyAppState extends State<MyApp> with WindowListener {
|
class MyAppState extends State<MyApp> {
|
||||||
// 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-12 17:36:38
|
* @Date : 2022-10-12 17:36:38
|
||||||
* @LastEditTime : 2022-10-20 20:55:17
|
* @LastEditTime : 2022-10-23 10:15:31
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
@ -82,7 +82,7 @@ class RegisterPage extends StatelessWidget {
|
|||||||
listenWhen: (previous, current) => previous.status != current.status,
|
listenWhen: (previous, current) => previous.status != current.status,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: MediaQuery.of(context).size.width * 0.5,
|
width: MediaQuery.of(context).size.width * 0.7,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-12 16:29:25
|
* @Date : 2022-10-12 16:29:25
|
||||||
* @LastEditTime : 2022-10-20 20:54:22
|
* @LastEditTime : 2022-10-23 10:28:12
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -44,6 +44,11 @@ class UsernameInput extends StatelessWidget {
|
|||||||
onChanged: (username) {
|
onChanged: (username) {
|
||||||
context.read<RegisterCubit>().onUsernameChange(Username.dirty(username));
|
context.read<RegisterCubit>().onUsernameChange(Username.dirty(username));
|
||||||
},
|
},
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
|
textCapitalization: TextCapitalization.none,
|
||||||
|
autocorrect: false,
|
||||||
|
enableIMEPersonalizedLearning: false,
|
||||||
|
enableSuggestions: false,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Username',
|
labelText: 'Username',
|
||||||
errorText: state.username.invalid ? 'Invalid username' : null
|
errorText: state.username.invalid ? 'Invalid username' : null
|
||||||
@ -60,12 +65,21 @@ class PasswordInput extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<RegisterCubit, RegisterState>(
|
return BlocBuilder<RegisterCubit, RegisterState>(
|
||||||
buildWhen: (previous, current) => previous.password != current.password,
|
buildWhen: (previous, current) => previous.password != current.password || previous.status != current.status,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (password) {
|
onChanged: (password) {
|
||||||
context.read<RegisterCubit>().onPasswordChange(Password.dirty(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,
|
obscureText: true,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Password',
|
labelText: 'Password',
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-11 10:56:02
|
* @Date : 2022-10-11 10:56:02
|
||||||
* @LastEditTime : 2022-10-20 17:24:26
|
* @LastEditTime : 2022-10-22 01:22:58
|
||||||
* @Description : Local Service Repository
|
* @Description : Local Service Repository
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:convert/convert.dart';
|
import 'package:convert/convert.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
@ -17,11 +16,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
import 'package:tcp_client/repositories/common_models/message.dart';
|
import 'package:tcp_client/repositories/common_models/message.dart';
|
||||||
import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
||||||
import 'package:tcp_client/repositories/local_service_repository/models/local_file.dart';
|
import 'package:tcp_client/repositories/local_service_repository/models/local_file.dart';
|
||||||
//Windows platform
|
import 'package:sqflite/sqflite.dart';
|
||||||
import 'package:sqflite_common/sqlite_api.dart';
|
|
||||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
|
||||||
//Android platform
|
|
||||||
// import 'package:sqflite/sqflite.dart';
|
|
||||||
|
|
||||||
class LocalServiceRepository {
|
class LocalServiceRepository {
|
||||||
late final Database _database;
|
late final Database _database;
|
||||||
@ -62,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(
|
// await db.execute(
|
||||||
// '''
|
// '''
|
||||||
@ -91,20 +102,11 @@ class LocalServiceRepository {
|
|||||||
UserInfo? currentUser,
|
UserInfo? currentUser,
|
||||||
required String databaseFilePath
|
required String databaseFilePath
|
||||||
}) async {
|
}) async {
|
||||||
//Windows platform
|
var database = await openDatabase(
|
||||||
var database = await databaseFactoryFfi.openDatabase(
|
|
||||||
databaseFilePath,
|
databaseFilePath,
|
||||||
options: OpenDatabaseOptions(
|
|
||||||
version: 1,
|
version: 1,
|
||||||
onCreate: _onDatabaseCreate
|
onCreate: _onDatabaseCreate
|
||||||
)
|
|
||||||
);
|
);
|
||||||
//Android platform
|
|
||||||
// var database = await openDatabase(
|
|
||||||
// databaseFilePath,
|
|
||||||
// version: 1,
|
|
||||||
// onCreate: _onDatabaseCreate
|
|
||||||
// );
|
|
||||||
return LocalServiceRepository._internal(database: database);
|
return LocalServiceRepository._internal(database: database);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,28 +118,32 @@ class LocalServiceRepository {
|
|||||||
);
|
);
|
||||||
if (filePickResult == null) return null;
|
if (filePickResult == null) return null;
|
||||||
var file = File(filePickResult.files.single.path!);
|
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;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeMessages(List<Message> messages) async {
|
Future<void> storeMessages(List<Message> messages) async {
|
||||||
await _database.transaction((txn) async {
|
await _database.transaction((txn) async {
|
||||||
for(var message in messages) {
|
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(
|
await txn.insert(
|
||||||
'msgs',
|
'msgs',
|
||||||
message.jsonObject,
|
message.jsonObject,
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace
|
conflictAlgorithm: ConflictAlgorithm.replace
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +168,20 @@ class LocalServiceRepository {
|
|||||||
for(var rawMessage in rawMessages) {
|
for(var rawMessage in rawMessages) {
|
||||||
var message = Message.fromJSONObject(jsonObject: rawMessage);
|
var message = Message.fromJSONObject(jsonObject: rawMessage);
|
||||||
if(message.contentDecoded.toLowerCase().contains(pattern.toLowerCase())) {
|
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);
|
alikeMessages.add(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,6 +207,8 @@ class LocalServiceRepository {
|
|||||||
messages.add([]);
|
messages.add([]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
//Since message page does not show message
|
||||||
|
//There is no need to fetch message here
|
||||||
messages.add([Message.fromJSONObject(jsonObject: queryResult[0])]);
|
messages.add([Message.fromJSONObject(jsonObject: queryResult[0])]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,7 +234,18 @@ class LocalServiceRepository {
|
|||||||
limit: num,
|
limit: num,
|
||||||
offset: position
|
offset: position
|
||||||
);
|
);
|
||||||
return queryResult.map((e) => Message.fromJSONObject(jsonObject: e)).toList();
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> findFile({required String filemd5, required String fileName}) async {
|
Future<File?> findFile({required String filemd5, required String fileName}) async {
|
||||||
@ -367,4 +400,59 @@ class LocalServiceRepository {
|
|||||||
return null;
|
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
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-11 09:44:03
|
* @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
|
* @Description : Abstract TCP request class
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -24,6 +24,7 @@ enum TCPRequestType {
|
|||||||
modifyProfile ('MODIFYPROFILE'), //Modify user profile
|
modifyProfile ('MODIFYPROFILE'), //Modify user profile
|
||||||
sendMessage ('SENDMSG'), //Send message
|
sendMessage ('SENDMSG'), //Send message
|
||||||
fetchMessage ('FETCHMSG'), //Fetch message
|
fetchMessage ('FETCHMSG'), //Fetch message
|
||||||
|
ackFetch ('ACKFETCH'), //Acknowledge message fetch
|
||||||
findFile ('FINDFILE'), //Find file by md5 before transmitting the file
|
findFile ('FINDFILE'), //Find file by md5 before transmitting the file
|
||||||
fetchFile ('FETCHFILE'), //Fetch file and file md5 by message md5
|
fetchFile ('FETCHFILE'), //Fetch file and file md5 by message md5
|
||||||
searchUser ('SEARCHUSR'), //Search username and userid by username
|
searchUser ('SEARCHUSR'), //Search username and userid by username
|
||||||
@ -244,3 +245,14 @@ class FetchContactRequest extends TCPRequest {
|
|||||||
@override
|
@override
|
||||||
Map<String, Object?> get body => {};
|
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
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-11 09:42:05
|
* @Date : 2022-10-11 09:42:05
|
||||||
* @LastEditTime : 2022-10-19 10:41:43
|
* @LastEditTime : 2022-10-22 17:46:28
|
||||||
* @Description : TCP repository
|
* @Description : TCP repository
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -26,13 +26,25 @@ class TCPRepository {
|
|||||||
required int remotePort
|
required int remotePort
|
||||||
}): _socket = socket, _remoteAddress = remoteAddress, _remotePort = remotePort {
|
}): _socket = socket, _remoteAddress = remoteAddress, _remotePort = remotePort {
|
||||||
Future(() async {
|
Future(() async {
|
||||||
|
while(true) {
|
||||||
try{
|
try{
|
||||||
await for(var response in _socket) {
|
await for(var response in _socket!) {
|
||||||
_pullResponse(response);
|
_pullResponse(response);
|
||||||
await Future.delayed(const Duration(microseconds: 0));
|
await Future.delayed(const Duration(microseconds: 0));
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
_socket.close();
|
_socket?.close();
|
||||||
|
_socket = null;
|
||||||
|
while(true) {
|
||||||
|
try{
|
||||||
|
_socket = await Socket.connect(remoteAddress, remotePort);
|
||||||
|
break;
|
||||||
|
} catch (e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// _responseRawStreamController.close();
|
// _responseRawStreamController.close();
|
||||||
// _payloadPullStreamController.close();
|
// _payloadPullStreamController.close();
|
||||||
@ -42,10 +54,40 @@ class TCPRepository {
|
|||||||
});
|
});
|
||||||
//This future never ends, would that be bothersome?
|
//This future never ends, would that be bothersome?
|
||||||
Future(() async {
|
Future(() async {
|
||||||
await for(var request in _requestStreamController.stream) {
|
TCPRequest? failedRequest;
|
||||||
await _socket.addStream(request.stream);
|
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);
|
||||||
}
|
}
|
||||||
}).onError((error, stackTrace) {_socket.close();});
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
Future(() async {
|
Future(() async {
|
||||||
var responseQueue = StreamQueue(_responseRawStreamController.stream);
|
var responseQueue = StreamQueue(_responseRawStreamController.stream);
|
||||||
var payloadQueue = StreamQueue(_payloadRawStreamController.stream);
|
var payloadQueue = StreamQueue(_payloadRawStreamController.stream);
|
||||||
@ -56,14 +98,22 @@ class TCPRepository {
|
|||||||
}
|
}
|
||||||
responseQueue.cancel();
|
responseQueue.cancel();
|
||||||
payloadQueue.cancel();
|
payloadQueue.cancel();
|
||||||
}).onError((error, stackTrace) {_socket.close();});
|
}).onError((error, stackTrace) {_socket?.close();});
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<TCPRepository> create({
|
static Future<TCPRepository> create({
|
||||||
required String serverAddress,
|
required String serverAddress,
|
||||||
required int serverPort
|
required int serverPort
|
||||||
}) async {
|
}) 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(
|
return TCPRepository._internal(
|
||||||
socket: socket,
|
socket: socket,
|
||||||
remoteAddress: serverAddress,
|
remoteAddress: serverAddress,
|
||||||
@ -78,7 +128,7 @@ class TCPRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Socket _socket;
|
Socket? _socket;
|
||||||
final String _remoteAddress;
|
final String _remoteAddress;
|
||||||
final int _remotePort;
|
final int _remotePort;
|
||||||
|
|
||||||
@ -359,7 +409,7 @@ class TCPRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void dispose() async {
|
void dispose() async {
|
||||||
await _socket.flush();
|
await _socket?.flush();
|
||||||
await _socket.close();
|
await _socket?.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 17:04:12
|
* @Date : 2022-10-13 17:04:12
|
||||||
* @LastEditTime : 2022-10-14 10:38:44
|
* @LastEditTime : 2022-10-23 10:30:41
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ class SearchPage extends StatelessWidget {
|
|||||||
const SizedBox(height: 16.0,),
|
const SizedBox(height: 16.0,),
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: 36.0,
|
horizontal: 24.0,
|
||||||
vertical: 8.0
|
vertical: 8.0
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -80,7 +80,7 @@ class SearchPage extends StatelessWidget {
|
|||||||
const SizedBox(height: 16.0,),
|
const SizedBox(height: 16.0,),
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: 36.0,
|
horizontal: 24.0,
|
||||||
vertical: 8.0
|
vertical: 8.0
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -102,7 +102,7 @@ class SearchPage extends StatelessWidget {
|
|||||||
const SizedBox(height: 16.0,),
|
const SizedBox(height: 16.0,),
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: 36.0,
|
horizontal: 24.0,
|
||||||
vertical: 8.0
|
vertical: 8.0
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 21:41:49
|
* @Date : 2022-10-13 21:41:49
|
||||||
* @LastEditTime : 2022-10-17 22:23:46
|
* @LastEditTime : 2022-10-23 10:30:06
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -24,8 +24,8 @@ class HistoryTile extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 16.0,
|
vertical: 8.0,
|
||||||
horizontal: 36.0
|
horizontal: 24.0
|
||||||
),
|
),
|
||||||
child: IntrinsicHeight(
|
child: IntrinsicHeight(
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -87,9 +87,9 @@ class HistoryTile extends StatelessWidget {
|
|||||||
if(date.day == DateTime.now().day) {
|
if(date.day == DateTime.now().day) {
|
||||||
return '${date.hour}:${date.minute}';
|
return '${date.hour}:${date.minute}';
|
||||||
}
|
}
|
||||||
//If date is yesterday, return 'yesterday'
|
//If date is yda, return 'yda'
|
||||||
if(date.day == DateTime.now().day - 1) {
|
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 is within this week, return the weekday in english
|
||||||
if(date.weekday < DateTime.now().weekday) {
|
if(date.weekday < DateTime.now().weekday) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author : Linloir
|
* @Author : Linloir
|
||||||
* @Date : 2022-10-13 21:41:41
|
* @Date : 2022-10-13 21:41:41
|
||||||
* @LastEditTime : 2022-10-18 11:28:17
|
* @LastEditTime : 2022-10-23 10:30:24
|
||||||
* @Description :
|
* @Description :
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -35,8 +35,8 @@ class UserTile extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 36,
|
horizontal: 24,
|
||||||
vertical: 16,
|
vertical: 8,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
14
pubspec.lock
14
pubspec.lock
@ -78,13 +78,6 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2+1"
|
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:
|
equatable:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -291,6 +284,13 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
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:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -57,7 +57,8 @@ dependencies:
|
|||||||
easy_debounce: ^2.0.2+1
|
easy_debounce: ^2.0.2+1
|
||||||
path: ^1.8.2
|
path: ^1.8.2
|
||||||
window_manager: ^0.2.7
|
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.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user