mirror of https://github.com/CympleTech/ESSE.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
429 lines
16 KiB
429 lines
16 KiB
import 'dart:io'; |
|
|
|
import 'package:flutter/material.dart'; |
|
import 'package:image_save/image_save.dart'; |
|
import 'package:permission_handler/permission_handler.dart'; |
|
import 'package:open_filex/open_filex.dart'; |
|
|
|
import 'package:esse/l10n/localizations.dart'; |
|
import 'package:esse/utils/adaptive.dart'; |
|
import 'package:esse/utils/better_print.dart'; |
|
import 'package:esse/widgets/avatar.dart'; |
|
import 'package:esse/widgets/audio_player.dart'; |
|
import 'package:esse/widgets/shadow_dialog.dart'; |
|
import 'package:esse/widgets/user_info.dart'; |
|
import 'package:esse/global.dart'; |
|
import 'package:esse/rpc.dart'; |
|
|
|
import 'package:esse/apps/primitives.dart'; |
|
import 'package:esse/apps/chat/models.dart' show Request; |
|
import 'package:esse/apps/file/models.dart' show FileType, FileTypeExtension, parseFileType; |
|
import 'package:esse/apps/wallet/models.dart' show NetworkExtension, Network, Token, unitBalance; |
|
|
|
class ChatMessage extends StatelessWidget { |
|
final Widget? avatar; |
|
final String fpid; |
|
final String name; |
|
final BaseMessage message; |
|
|
|
const ChatMessage({Key? key, required this.fpid, required this.name, required this.message, this.avatar}): super(key: key); |
|
|
|
Widget _showContactCard(Widget avatar, String pid, String name, String title, ColorScheme color) { |
|
return Container( |
|
padding: const EdgeInsets.only(top: 10, bottom: 6.0, left: 10.0, right: 10.0), |
|
width: 200.0, |
|
decoration: BoxDecoration(color: const Color(0x40ADB0BB), borderRadius: BorderRadius.circular(10.0)), |
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, |
|
children: [ |
|
Row(children: [ |
|
avatar, |
|
Container(width: 135.0, padding: const EdgeInsets.only(left: 10.0), |
|
child: Column(children: [ |
|
Text(name, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(color: color.onPrimary, fontSize: 16.0)), |
|
const SizedBox(height: 4.0), |
|
Text(pidPrint(pid), style: TextStyle(color: Colors.grey, fontSize: 12.0)), |
|
]))]), |
|
const SizedBox(height: 5.0), |
|
const Divider(height: 1.0, color: Color(0x40ADB0BB)), |
|
const SizedBox(height: 3.0), |
|
Text(title, style: TextStyle(color: Colors.grey, fontSize: 10.0)), |
|
]) |
|
); |
|
} |
|
|
|
Widget _showText(context, color, maxWidth) { |
|
// text |
|
return Container( |
|
constraints: BoxConstraints(minWidth: 50, maxWidth: maxWidth), |
|
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 10.0), |
|
decoration: BoxDecoration( |
|
color: message.isMe ? Color(0xFF6174FF) : color.primaryVariant, |
|
borderRadius: message.isMe |
|
? BorderRadius.only( |
|
topLeft: Radius.circular(10), |
|
topRight: Radius.circular(10), |
|
bottomLeft: Radius.circular(10)) |
|
: BorderRadius.only( |
|
topLeft: Radius.circular(10), |
|
topRight: Radius.circular(10), |
|
bottomRight: Radius.circular(10)), |
|
), |
|
child: Text(message.content, |
|
style: TextStyle( |
|
color: message.isMe ? Colors.white : Color(0xFF1C1939), |
|
fontSize: 14.0))); |
|
} |
|
|
|
Widget _showImage(context, lang, color) { |
|
// image |
|
bool imageExsit = true; |
|
var thumImage; |
|
final imagePath = Global.imagePath + message.content; |
|
final thumPath = Global.thumbPath + message.content; |
|
if (FileSystemEntity.typeSync(thumPath) == |
|
FileSystemEntityType.notFound) { |
|
imageExsit = false; |
|
thumImage = AssetImage('assets/images/image_missing.png'); |
|
} else { |
|
thumImage = FileImage(File(thumPath)); |
|
} |
|
return GestureDetector( |
|
onTap: imageExsit |
|
? () => showShadowDialog( |
|
context, |
|
Icons.image_rounded, |
|
lang.album, |
|
Column(children: [ |
|
Image(image: FileImage(File(imagePath)), fit: BoxFit.cover), |
|
SizedBox(height: 15.0), |
|
if (Platform.isAndroid || Platform.isIOS) |
|
InkWell( |
|
onTap: () async { |
|
Map<Permission, PermissionStatus> statuses = await [ |
|
Permission.storage, |
|
].request(); |
|
|
|
if (statuses[Permission.storage] == PermissionStatus.granted) { |
|
|
|
// Save to album. |
|
final data = await File(imagePath).readAsBytes(); |
|
final bool? success = await ImageSave.saveImage(data, message.content, albumName: "ESSE"); |
|
print(success); |
|
|
|
Navigator.pop(context); |
|
} |
|
}, |
|
hoverColor: Colors.transparent, |
|
child: Container( |
|
width: 200.0, |
|
padding: const EdgeInsets.symmetric(vertical: 10.0), |
|
decoration: BoxDecoration( |
|
border: Border.all(color: color.primary), |
|
borderRadius: BorderRadius.circular(10.0)), |
|
child: Center(child: Text(lang.download, |
|
style: TextStyle(fontSize: 14.0, color: color.primary))), |
|
) |
|
), |
|
])) |
|
: () => {}, |
|
child: Container( |
|
width: imageExsit ? 120.0 : 60.0, |
|
child: Image(image: thumImage, fit: BoxFit.cover), |
|
)); |
|
} |
|
|
|
Widget _showFile(context, lang, color) { |
|
// file |
|
bool fileExsit = true; |
|
Widget fileImage; |
|
final filePath = Global.filePath + message.content; |
|
if (FileSystemEntity.typeSync(filePath) == |
|
FileSystemEntityType.notFound) { |
|
fileExsit = false; |
|
fileImage = Image(image: AssetImage('assets/images/image_missing.png'), fit: BoxFit.cover); |
|
} else { |
|
final params = parseFileType(message.content).params(); |
|
fileImage = Icon(params[0], color: params[1], size: 36.0); |
|
} |
|
return GestureDetector( |
|
onTap: fileExsit |
|
? () => showShadowDialog( |
|
context, |
|
Icons.folder_rounded, |
|
lang.files, |
|
Column(children: [ |
|
Text(message.content), |
|
SizedBox(height: 15.0), |
|
Container( |
|
height: 100.0, |
|
child: fileImage, |
|
), |
|
SizedBox(height: 15.0), |
|
InkWell( |
|
onTap: () => OpenFilex.open(filePath), |
|
hoverColor: Colors.transparent, |
|
child: Container( |
|
width: 200.0, |
|
padding: const EdgeInsets.symmetric(vertical: 10.0), |
|
decoration: BoxDecoration( |
|
border: Border.all(color: color.primary), |
|
borderRadius: BorderRadius.circular(10.0)), |
|
child: Center(child: Text(lang.open, |
|
style: TextStyle(fontSize: 14.0, color: color.primary))), |
|
) |
|
), |
|
])) |
|
: () => {}, |
|
child: Container( |
|
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), |
|
decoration: BoxDecoration( |
|
color: const Color(0x40ADB0BB), |
|
borderRadius: BorderRadius.circular(15.0), |
|
), |
|
child: Row(mainAxisSize: MainAxisSize.min, children: [ |
|
Container( |
|
height: 36.0, |
|
child: fileImage, |
|
), |
|
Container( |
|
padding: const EdgeInsets.only(left: 5.0), |
|
width: 120.0, |
|
child: Text(message.content, |
|
maxLines: 1, |
|
overflow: TextOverflow.ellipsis, |
|
style: fileExsit |
|
? TextStyle( |
|
color: color.onPrimary, |
|
fontSize: 14.0, |
|
) |
|
: TextStyle( |
|
color: color.onPrimary.withOpacity(0.5), |
|
decoration: TextDecoration.lineThrough, |
|
fontSize: 14.0, |
|
)), |
|
), |
|
]))); |
|
} |
|
|
|
Widget _showRecord() { |
|
final raws = message.showRecordTime(); |
|
// text |
|
return Container( |
|
width: 120.0, |
|
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), |
|
decoration: BoxDecoration( |
|
color: const Color(0x40ADB0BB), |
|
borderRadius: BorderRadius.circular(15.0), |
|
), |
|
child: RecordPlayer(path: Global.recordPath + raws[1], time: raws[0]), |
|
); |
|
} |
|
|
|
Widget _showContact(context, lang, color, maxWidth) { |
|
// contact [name, pid, avatar] |
|
final infos = message.showContact(); |
|
if (infos[1].length > 0) { |
|
return GestureDetector( |
|
onTap: () => showShadowDialog( |
|
context, |
|
Icons.person_rounded, |
|
lang.contactCard, |
|
UserInfo( |
|
showQr: false, id: infos[1], name: infos[0], |
|
remark: lang.fromContactCard(name), |
|
avatar: Avatar(width: 100.0, name: infos[0], avatarPath: infos[3]), |
|
callback: () { |
|
Navigator.pop(context); |
|
rpc.send('chat-request-create', [ |
|
infos[1], infos[2], infos[0], lang.fromContactCard(name) |
|
]); |
|
}, |
|
), |
|
), |
|
child: _showContactCard( |
|
Avatar(width: 40.0, name: infos[0], avatarPath: infos[3]), |
|
infos[1], infos[0], lang.contactCard, color |
|
) |
|
); |
|
} else { |
|
return _showText(context, color, maxWidth); |
|
} |
|
} |
|
|
|
Widget _showInvite(context, lang, color, maxWidth) { |
|
// contact [type, pid, addr, name, proof, key] |
|
final infos = message.showInvite(); |
|
if (infos[1].length > 0) { |
|
final GroupType gtype = infos[0]; |
|
return GestureDetector( |
|
onTap: () => showShadowDialog( |
|
context, |
|
Icons.groups_rounded, |
|
lang.groupChat, |
|
UserInfo(showQr: false, id: infos[1], name: infos[3], |
|
title: gtype.lang(lang), |
|
avatar: Container(width: 100.0, height: 100.0, |
|
padding: const EdgeInsets.all(8.0), |
|
decoration: BoxDecoration(color: color.surface, borderRadius: BorderRadius.circular(15.0)), |
|
child: Icon(Icons.groups_rounded, color: color.primary, size: 36.0), |
|
), |
|
callback: () { |
|
Navigator.pop(context); |
|
// TOOD join invite. |
|
// Provider.of<GroupChatProvider>(context, listen: false).join( |
|
// gtype, infos[1], infos[2], infos[3], fpid, infos[4], infos[5] |
|
// ); |
|
}, |
|
), |
|
), |
|
child: _showContactCard( |
|
Container(width: 40.0, height: 40.0, |
|
decoration: BoxDecoration(color: color.surface, borderRadius: BorderRadius.circular(10.0)), |
|
child: Icon(Icons.groups_rounded, color: color.primary, size: 20.0), |
|
), |
|
infos[1], infos[3], lang.groupChat, color) |
|
); |
|
} else { |
|
return _showText(context, color, maxWidth); |
|
} |
|
} |
|
|
|
Widget _showTransfer(context, lang, color, maxWidth) { |
|
// transfer [hash, to, amount, name, network, decimal] |
|
final infos = message.showTransfer(); |
|
if (infos.length > 3) { |
|
final logo = Token.getLogo(infos[3]); |
|
final network = infos.length > 4 ? NetworkExtension.fromInt(int.parse(infos[4])) : Network.EthMain; |
|
final decimal = infos.length > 5 ? int.parse(infos[5]) : 18; |
|
final amount = unitBalance(infos[2], decimal, 4); |
|
return Container( |
|
padding: const EdgeInsets.only(top: 10, bottom: 6.0, left: 10.0, right: 10.0), |
|
width: 200.0, |
|
decoration: BoxDecoration( |
|
color: network.params()[1].withOpacity(0.3), |
|
borderRadius: BorderRadius.circular(10.0) |
|
), |
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, |
|
children: [ |
|
Row(children: [ |
|
Container( |
|
width: 36.0, |
|
height: 36.0, |
|
decoration: BoxDecoration( |
|
image: DecorationImage( |
|
image: AssetImage(logo), |
|
fit: BoxFit.cover, |
|
), |
|
), |
|
), |
|
Container(width: 135.0, padding: const EdgeInsets.only(left: 10.0), |
|
child: Column(children: [ |
|
Text("${amount} ${infos[3]}", maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(color: color.onPrimary, fontSize: 16.0)), |
|
const SizedBox(height: 4.0), |
|
Text(infos[1], maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(color: Colors.grey, fontSize: 12.0)), |
|
]))]), |
|
const SizedBox(height: 5.0), |
|
const Divider(height: 1.0, color: Color(0x40ADB0BB)), |
|
const SizedBox(height: 3.0), |
|
Text(infos[0], maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(color: Colors.grey, fontSize: 10.0)), |
|
]) |
|
); |
|
} else { |
|
return _showText(context, color, maxWidth); |
|
} |
|
} |
|
|
|
|
|
Widget _show(context, color, lang, isDesktop, maxWidth) { |
|
//final width = MediaQuery.of(context).size.width * 0.6; |
|
|
|
if (message.type == MessageType.String) { |
|
return _showText(context, color, maxWidth); |
|
} else if (message.type == MessageType.Image) { |
|
return _showImage(context, lang, color); |
|
} else if (message.type == MessageType.File) { |
|
return _showFile(context, lang, color); |
|
} else if (message.type == MessageType.Contact) { |
|
return _showContact(context, lang, color, maxWidth); |
|
} else if (message.type == MessageType.Record) { |
|
return _showRecord(); |
|
} else if (message.type == MessageType.Invite) { |
|
return _showInvite(context, lang, color, maxWidth); |
|
} else if (message.type == MessageType.Transfer) { |
|
return _showTransfer(context, lang, color, maxWidth); |
|
} |
|
return _showText(context, color, maxWidth); |
|
} |
|
|
|
@override |
|
Widget build(BuildContext context) { |
|
final color = Theme.of(context).colorScheme; |
|
final lang = AppLocalizations.of(context); |
|
final isDesktop = isDisplayDesktop(context); |
|
final width = MediaQuery.of(context).size.width * 0.6; |
|
final maxWidth = isDesktop ? width - 300.0 : width; |
|
final messageShow = _show(context, color, lang, isDesktop, maxWidth); |
|
final isAvatar = avatar != null && !message.isMe; |
|
|
|
final timeWidget = Container( |
|
padding: EdgeInsets.only(top: 4.0), |
|
child: Row(children: [ |
|
if (message.isMe) Spacer(), |
|
if (isAvatar) |
|
Container( |
|
width: 60.0, |
|
child: Text(name, maxLines: 1, overflow: TextOverflow.ellipsis, |
|
style: TextStyle(color: color.onPrimary.withOpacity(0.5), fontSize: 10.0) |
|
)), |
|
const SizedBox(width: 4.0), |
|
Text(message.time.toString(), style: TextStyle( |
|
color: color.onPrimary.withOpacity(0.5), |
|
fontSize: 10.0)), |
|
const SizedBox(width: 4.0), |
|
if (message.isMe) |
|
Icon( |
|
message.isDelivery == null ? Icons.hourglass_top |
|
: (message.isDelivery! ? Icons.done : Icons.error), |
|
size: 10.0, |
|
color: message.isDelivery == null ? color.primaryVariant |
|
: (message.isDelivery! ? color.primary : Colors.red) |
|
), |
|
])); |
|
|
|
final mainWidget = Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ |
|
Expanded( |
|
child: Align( |
|
alignment: message.isMe ? Alignment.topRight : Alignment.topLeft, |
|
child: messageShow, |
|
), |
|
), |
|
]); |
|
|
|
return Padding( |
|
padding: const EdgeInsets.symmetric(vertical: 4.0), |
|
child: |
|
isAvatar |
|
? Row( |
|
crossAxisAlignment: CrossAxisAlignment.start, |
|
children: [ |
|
avatar!, |
|
const SizedBox(width: 4.0), |
|
Expanded( |
|
child: Column( |
|
children: [ |
|
mainWidget, |
|
timeWidget, |
|
] |
|
) |
|
) |
|
] |
|
) |
|
: Column( |
|
children: [ |
|
mainWidget, |
|
timeWidget, |
|
] |
|
) |
|
); |
|
} |
|
}
|
|
|