cwtch-ui/lib/widgets/filebubble.dart

387 lines
17 KiB
Dart
Raw Normal View History

2021-09-21 21:57:40 +00:00
import 'dart:io';
import 'package:cwtch/config.dart';
2021-09-21 21:57:40 +00:00
import 'package:cwtch/models/message.dart';
2021-12-14 23:37:27 +00:00
import 'package:cwtch/widgets/malformedbubble.dart';
2021-09-21 21:57:40 +00:00
import 'package:file_picker_desktop/file_picker_desktop.dart';
2021-09-29 20:19:56 +00:00
import 'package:flutter/cupertino.dart';
2021-09-21 21:57:40 +00:00
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../main.dart';
import '../model.dart';
import 'package:intl/intl.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../settings.dart';
import 'messagebubbledecorations.dart';
// Like MessageBubble but for displaying chat overlay 100/101 invitations
// Offers the user an accept/reject button if they don't have a matching contact already
class FileBubble extends StatefulWidget {
final String nameSuggestion;
final String rootHash;
final String nonce;
final int fileSize;
2021-09-29 20:19:56 +00:00
final bool interactive;
2021-12-17 01:04:29 +00:00
final bool isAuto;
2021-09-21 21:57:40 +00:00
2021-12-17 01:04:29 +00:00
FileBubble(this.nameSuggestion, this.rootHash, this.nonce, this.fileSize, {this.isAuto = false, this.interactive = true});
2021-09-21 21:57:40 +00:00
@override
FileBubbleState createState() => FileBubbleState();
String fileKey() {
return this.rootHash + "." + this.nonce;
}
}
class FileBubbleState extends State<FileBubble> {
2021-12-14 21:33:30 +00:00
File? myFile;
2021-11-04 22:31:50 +00:00
@override
void initState() {
super.initState();
}
2021-09-21 21:57:40 +00:00
@override
Widget build(BuildContext context) {
var fromMe = Provider.of<MessageMetadata>(context, listen: false).senderHandle == Provider.of<ProfileInfoState>(context).onion;
var flagStarted = Provider.of<MessageMetadata>(context).attributes["file-downloaded"] == "true";
2021-09-21 21:57:40 +00:00
var borderRadiousEh = 15.0;
var showFileSharing = Provider.of<Settings>(context, listen: false).isExperimentEnabled(FileSharingExperiment);
2021-09-21 21:57:40 +00:00
var prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(Provider.of<MessageMetadata>(context).timestamp);
var downloadComplete = Provider.of<ProfileInfoState>(context).downloadComplete(widget.fileKey());
var downloadInterrupted = Provider.of<ProfileInfoState>(context).downloadInterrupted(widget.fileKey());
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey());
if (downloadComplete) {
var lpath = path!.toLowerCase();
2021-12-19 02:09:18 +00:00
if (lpath.endsWith(".jpg") || lpath.endsWith(".jpeg") || lpath.endsWith(".png") || lpath.endsWith(".gif") || lpath.endsWith(".webp") || lpath.endsWith(".bmp")) {
if (myFile == null) {
setState(() {
myFile = new File(path);
});
}
}
}
var downloadActive = Provider.of<ProfileInfoState>(context).downloadActive(widget.fileKey());
var downloadGotManifest = Provider.of<ProfileInfoState>(context).downloadGotManifest(widget.fileKey());
2021-09-21 21:57:40 +00:00
// If the sender is not us, then we want to give them a nickname...
var senderDisplayStr = "";
var senderIsContact = false;
2021-09-21 21:57:40 +00:00
if (!fromMe) {
2021-11-18 23:44:54 +00:00
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
2021-09-21 21:57:40 +00:00
if (contact != null) {
senderDisplayStr = contact.nickname;
senderIsContact = true;
2021-09-21 21:57:40 +00:00
} else {
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
}
}
2021-12-17 00:54:53 +00:00
return LayoutBuilder(builder: (bcontext, constraints) {
2021-12-18 00:54:30 +00:00
var wdgSender = Visibility(
visible: widget.interactive,
child: SelectableText(senderDisplayStr + '\u202F',
2021-12-19 02:39:07 +00:00
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor)));
var isPreview = false;
var wdgMessage = !showFileSharing
? Text(AppLocalizations.of(context)!.messageEnableFileSharing)
: fromMe
? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash, widget.fileSize)
: (fileChrome(AppLocalizations.of(context)!.messageFileOffered + ":", widget.nameSuggestion, widget.rootHash, widget.fileSize,
Provider.of<ProfileInfoState>(context).downloadSpeed(widget.fileKey())));
Widget wdgDecorations;
2021-09-21 21:57:40 +00:00
if (!showFileSharing) {
wdgDecorations = Text('\u202F');
} else if (fromMe) {
2021-12-18 00:54:30 +00:00
wdgDecorations = Visibility(
visible: widget.interactive,
child: MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate));
} else if (downloadComplete) {
// in this case, whatever marked download.complete would have also set the path
2021-12-19 02:09:18 +00:00
if (Provider.of<Settings>(context).shouldPreview(path!)) {
isPreview = true;
2021-12-18 00:54:30 +00:00
wdgDecorations = Center(
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: Padding(
padding: EdgeInsets.all(1.0),
child: Image.file(
myFile!,
cacheWidth: 2048,
// limit the amount of space the image can decode too, we keep this high-ish to allow quality previews...
filterQuality: FilterQuality.medium,
fit: BoxFit.scaleDown,
alignment: Alignment.center,
height: MediaQuery.of(bcontext).size.height * 0.30,
isAntiAlias: false,
errorBuilder: (context, error, stackTrace) {
return MalformedBubble();
},
)),
onTap: () {
pop(bcontext, myFile!, wdgMessage);
},
)));
} else {
2021-12-18 00:54:30 +00:00
wdgDecorations = Visibility(visible: widget.interactive, child: Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F'));
}
} else if (downloadActive) {
if (!downloadGotManifest) {
2021-12-18 00:54:30 +00:00
wdgDecorations = Visibility(visible: widget.interactive, child: Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F'));
} else {
2021-12-18 00:54:30 +00:00
wdgDecorations = Visibility(
visible: widget.interactive,
child: LinearProgressIndicator(
value: Provider.of<ProfileInfoState>(context).downloadProgress(widget.fileKey()),
2021-12-19 02:39:07 +00:00
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
2021-12-18 00:54:30 +00:00
));
}
} else if (flagStarted) {
// in this case, the download was done in a previous application launch,
// so we probably have to request an info lookup
if (!downloadInterrupted) {
wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F');
} else {
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey()) ?? "";
2021-12-18 00:54:30 +00:00
wdgDecorations = Visibility(
visible: widget.interactive,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'),
ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton))
]));
}
} else if (!senderIsContact) {
2021-12-19 02:09:18 +00:00
wdgDecorations = Text(AppLocalizations.of(context)!.msgAddToAccept);
2021-12-17 01:04:29 +00:00
} else if (!widget.isAuto) {
2021-12-18 00:54:30 +00:00
wdgDecorations = Visibility(
visible: widget.interactive,
child: Center(
widthFactor: 1,
child: Wrap(children: [
Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F'), onPressed: _btnAccept)),
])));
2021-12-17 01:04:29 +00:00
} else {
wdgDecorations = Container();
2021-09-29 20:31:01 +00:00
}
2021-12-14 23:37:27 +00:00
return Container(
constraints: constraints,
decoration: BoxDecoration(
2021-12-19 02:39:07 +00:00
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor,
border: Border.all(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor, width: 1),
2021-12-14 23:37:27 +00:00
borderRadius: BorderRadius.only(
topLeft: Radius.circular(borderRadiousEh),
topRight: Radius.circular(borderRadiousEh),
bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero,
bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh),
),
),
child: Padding(
padding: EdgeInsets.all(9.0),
child: Column(
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
2021-12-18 00:54:30 +00:00
children: fromMe ? [wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)] : [wdgSender, isPreview ? Container() : wdgMessage, wdgDecorations]),
2021-12-14 23:37:27 +00:00
));
2021-09-21 21:57:40 +00:00
});
}
void _btnAccept() async {
String? selectedFileName;
File? file;
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
var conversation = Provider.of<ContactInfoState>(context, listen: false).identifier;
var idx = Provider.of<MessageMetadata>(context, listen: false).messageID;
2021-09-21 21:57:40 +00:00
if (Platform.isAndroid) {
Provider.of<ProfileInfoState>(context, listen: false).downloadInit(widget.fileKey(), (widget.fileSize / 4096).ceil());
Provider.of<FlwtchState>(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "file-downloaded", "true");
//Provider.of<MessageMetadata>(context, listen: false).attributes |= 0x02;
2021-11-18 23:44:54 +00:00
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
if (contact != null) {
2021-11-25 23:59:54 +00:00
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateDownloadableFile(profileOnion, contact.identifier, widget.nameSuggestion, widget.fileKey());
2021-11-18 23:44:54 +00:00
}
2021-09-21 21:57:40 +00:00
} else {
try {
2021-09-30 00:20:35 +00:00
selectedFileName = await saveFile(
defaultFileName: widget.nameSuggestion,
);
if (selectedFileName != null) {
file = File(selectedFileName);
EnvironmentConfig.debugLog("saving to " + file.path);
2021-09-30 00:20:35 +00:00
var manifestPath = file.path + ".manifest";
Provider.of<ProfileInfoState>(context, listen: false).downloadInit(widget.fileKey(), (widget.fileSize / 4096).ceil());
Provider.of<FlwtchState>(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "file-downloaded", "true");
//Provider.of<MessageMetadata>(context, listen: false).flags |= 0x02;
ContactInfoState? contact = Provider.of<ProfileInfoState>(context, listen: false).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
2021-11-18 23:44:54 +00:00
if (contact != null) {
2021-11-25 23:59:54 +00:00
Provider.of<FlwtchState>(context, listen: false).cwtch.DownloadFile(profileOnion, contact.identifier, file.path, manifestPath, widget.fileKey());
2021-11-18 23:44:54 +00:00
}
2021-09-30 00:20:35 +00:00
}
2021-09-21 21:57:40 +00:00
} catch (e) {
print(e);
}
}
}
2021-11-04 22:31:50 +00:00
void _btnResume() async {
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
2021-11-18 23:44:54 +00:00
var handle = Provider.of<MessageMetadata>(context, listen: false).conversationIdentifier;
2021-11-04 22:31:50 +00:00
Provider.of<ProfileInfoState>(context, listen: false).downloadMarkResumed(widget.fileKey());
Provider.of<FlwtchState>(context, listen: false).cwtch.VerifyOrResumeDownload(profileOnion, handle, widget.fileKey());
}
2021-09-30 17:53:32 +00:00
// Construct an file chrome for the sender
2021-09-27 19:53:21 +00:00
Widget senderFileChrome(String chrome, String fileName, String rootHash, int fileSize) {
2021-09-29 20:19:56 +00:00
return ListTile(
visualDensity: VisualDensity.compact,
title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [
SelectableText(
chrome + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
2021-09-29 20:19:56 +00:00
),
textAlign: TextAlign.left,
maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine,
),
SelectableText(
fileName + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
2021-09-29 20:19:56 +00:00
fontWeight: FontWeight.bold,
overflow: TextOverflow.ellipsis,
),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.parent,
maxLines: 2,
),
SelectableText(
prettyBytes(fileSize) + '\u202F' + '\n',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
2021-09-29 20:19:56 +00:00
),
textAlign: TextAlign.left,
maxLines: 2,
)
]),
subtitle: SelectableText(
'sha512: ' + rootHash + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
2021-09-29 20:19:56 +00:00
fontSize: 10,
fontFamily: "monospace",
),
textAlign: TextAlign.left,
maxLines: 4,
textWidthBasis: TextWidthBasis.parent,
2021-09-21 21:57:40 +00:00
),
leading: Icon(Icons.attach_file, size: 32, color: Provider.of<Settings>(context).theme.messageFromMeTextColor));
2021-09-21 21:57:40 +00:00
}
2021-09-30 17:53:32 +00:00
// Construct an file chrome
2021-09-27 19:53:21 +00:00
Widget fileChrome(String chrome, String fileName, String rootHash, int fileSize, String speed) {
2021-09-29 20:19:56 +00:00
return ListTile(
visualDensity: VisualDensity.compact,
title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [
SelectableText(
chrome + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor,
2021-09-29 20:19:56 +00:00
),
textAlign: TextAlign.left,
maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine,
2021-09-21 21:57:40 +00:00
),
2021-09-29 20:19:56 +00:00
SelectableText(
fileName + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor,
2021-09-29 20:19:56 +00:00
fontWeight: FontWeight.bold,
overflow: TextOverflow.ellipsis,
),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.parent,
maxLines: 2,
2021-09-21 21:57:40 +00:00
),
2021-09-29 20:19:56 +00:00
SelectableText(
AppLocalizations.of(context)!.labelFilesize + ': ' + prettyBytes(fileSize) + '\u202F' + '\n',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor,
2021-09-29 20:19:56 +00:00
),
textAlign: TextAlign.left,
maxLines: 2,
)
]),
subtitle: SelectableText(
'sha512: ' + rootHash + '\u202F',
2021-09-21 21:57:40 +00:00
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
2021-09-29 20:19:56 +00:00
fontSize: 10,
fontFamily: "monospace",
2021-09-21 21:57:40 +00:00
),
textAlign: TextAlign.left,
maxLines: 4,
2021-09-29 20:19:56 +00:00
textWidthBasis: TextWidthBasis.parent,
2021-09-27 19:53:21 +00:00
),
leading: Icon(Icons.attach_file, size: 32, color: Provider.of<Settings>(context).theme.messageFromOtherTextColor),
2021-09-29 20:19:56 +00:00
trailing: Visibility(
visible: speed != "0 B/s",
child: SelectableText(
speed + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
2021-09-29 20:19:56 +00:00
),
textAlign: TextAlign.left,
maxLines: 1,
textWidthBasis: TextWidthBasis.longestLine,
)),
);
2021-09-21 21:57:40 +00:00
}
2021-12-14 21:33:30 +00:00
2021-12-17 00:54:53 +00:00
void pop(context, File myFile, Widget meta) async {
2021-12-14 21:33:30 +00:00
await showDialog(
context: context,
builder: (_) => Dialog(
2021-12-17 00:54:53 +00:00
alignment: Alignment.center,
child: Container(
2021-12-14 23:37:27 +00:00
padding: EdgeInsets.all(10),
child: Column(children: [
ListTile(
title: meta,
trailing: IconButton(
icon: Icon(Icons.close),
color: Provider.of<Settings>(context, listen: false).theme.toolbarIconColor,
iconSize: 32,
onPressed: () {
Navigator.pop(context, true);
})),
2021-12-17 00:54:53 +00:00
Image.file(
myFile,
cacheWidth: (MediaQuery.of(context).size.width * 0.6).floor(),
2021-12-18 01:02:24 +00:00
width: (MediaQuery.of(context).size.width * 0.6),
height: (MediaQuery.of(context).size.height * 0.6),
2021-12-17 00:54:53 +00:00
fit: BoxFit.scaleDown,
2021-12-15 01:13:04 +00:00
),
SizedBox(
height: 20,
),
2021-12-17 00:54:53 +00:00
Visibility(visible: !Platform.isAndroid, child: Text(myFile.path, textAlign: TextAlign.center)),
Visibility(visible: Platform.isAndroid, child: IconButton(icon: Icon(Icons.arrow_downward), onPressed: androidExport)),
2021-12-14 23:37:27 +00:00
]),
)));
2021-12-14 21:33:30 +00:00
}
2021-12-14 23:50:08 +00:00
void androidExport() async {
2021-12-15 01:13:04 +00:00
if (myFile != null) {
Provider.of<FlwtchState>(context, listen: false).cwtch.ExportPreviewedFile(myFile!.path, widget.nameSuggestion);
}
2021-12-14 23:50:08 +00:00
}
2021-09-21 21:57:40 +00:00
}