From 33a8a301704e729318698ed5506d579ddbbdaad3 Mon Sep 17 00:00:00 2001 From: erinn Date: Tue, 14 Dec 2021 13:33:30 -0800 Subject: [PATCH] wip image previews --- lib/cwtch/cwtch.dart | 3 ++ lib/cwtch/cwtchNotifier.dart | 7 +++- lib/cwtch/ffi.dart | 8 +++- lib/cwtch/gomobile.dart | 9 ++++- lib/model.dart | 13 +++--- lib/settings.dart | 16 ++++++++ lib/views/globalsettingsview.dart | 34 ++++++++++++++++ lib/widgets/filebubble.dart | 35 +++++++++++++++- lib/widgets/folderpicker.dart | 67 +++++++++++++++++++++++++++++++ lib/widgets/profilerow.dart | 2 +- 10 files changed, 180 insertions(+), 14 deletions(-) create mode 100644 lib/widgets/folderpicker.dart diff --git a/lib/cwtch/cwtch.dart b/lib/cwtch/cwtch.dart index c7efdbf3..95a95d75 100644 --- a/lib/cwtch/cwtch.dart +++ b/lib/cwtch/cwtch.dart @@ -96,5 +96,8 @@ abstract class Cwtch { // ignore: non_constant_identifier_names void Shutdown(); + // non-ffi + String defaultDownloadPath(); + void dispose(); } diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index 7f16ab49..08f0d363 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -263,7 +263,7 @@ class CwtchNotifier { settings.handleUpdate(jsonDecode(data["Data"])); break; case "SetAttribute": - if (data["Key"] == "public.name") { + if (data["Key"] == "public.profile.name") {//"public.name") { profileCN.getProfile(data["ProfileOnion"])?.nickname = data["Data"]; } else { EnvironmentConfig.debugLog("unhandled set attribute event: ${data['Key']}"); @@ -366,6 +366,11 @@ class CwtchNotifier { EnvironmentConfig.debugLog("unhandled peer attribute event: ${data['Path']}"); } break; + case "ManifestSizeReceived": + if (!profileCN.getProfile(data["ProfileOnion"])!.downloadActive(data["FileKey"])) { + profileCN.getProfile(data["ProfileOnion"])?.downloadUpdate(data["FileKey"], 0, 1); + } + break; case "ManifestSaved": profileCN.getProfile(data["ProfileOnion"])?.downloadMarkManifest(data["FileKey"]); break; diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index 87eb66d1..8783e180 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -557,7 +557,7 @@ class CwtchFfi implements Cwtch { final SetProfileAttribute = setProfileAttribute.asFunction(); final u1 = profile.toNativeUtf8(); final u2 = key.toNativeUtf8(); - final u3 = key.toNativeUtf8(); + final u3 = val.toNativeUtf8(); SetProfileAttribute(u1, u1.length, u2, u2.length, u3, u3.length); malloc.free(u1); malloc.free(u2); @@ -728,4 +728,10 @@ class CwtchFfi implements Cwtch { final Free = free.asFunction(); Free(ptr); } + + @override + String defaultDownloadPath() { + Map envVars = Platform.environment; + return path.join(envVars['HOME']!, "Downloads"); + } } diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index ae30a80c..0046b681 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -28,6 +28,7 @@ class CwtchGomobile implements Cwtch { late Future androidLibraryDir; late Future androidHomeDirectory; + String androidHomeDirectoryStr = ""; late CwtchNotifier cwtchNotifier; CwtchGomobile(CwtchNotifier _cwtchNotifier) { @@ -44,7 +45,8 @@ class CwtchGomobile implements Cwtch { // ignore: non_constant_identifier_names Future Start() async { print("gomobile.dart: Start()..."); - var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch"); + androidHomeDirectoryStr = (await androidHomeDirectory).path; + var cwtchDir = path.join(androidHomeDirectoryStr, ".cwtch"); if (EnvironmentConfig.BUILD_VER == dev_version) { cwtchDir = path.join(cwtchDir, "dev"); } @@ -283,4 +285,9 @@ class CwtchGomobile implements Cwtch { Future GetMessageByContentHash(String profile, String handle, String contentHash) { return cwtchPlatform.invokeMethod("GetMessageByContentHash", {"profile": profile, "contact": handle, "contentHash": contentHash}); } + + @override + String defaultDownloadPath() { + return this.androidHomeDirectoryStr; + } } diff --git a/lib/model.dart b/lib/model.dart index 23780557..0309c824 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -364,13 +364,11 @@ class ProfileInfoState extends ChangeNotifier { void downloadUpdate(String fileKey, int progress, int numChunks) { if (!downloadActive(fileKey)) { + this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now()); if (progress < 0) { - this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now()); this._downloads[fileKey]!.interrupted = true; - notifyListeners(); - } else { - print("error: received progress for unknown download " + fileKey); } + notifyListeners(); } else { if (this._downloads[fileKey]!.interrupted) { this._downloads[fileKey]!.interrupted = false; @@ -383,11 +381,10 @@ class ProfileInfoState extends ChangeNotifier { void downloadMarkManifest(String fileKey) { if (!downloadActive(fileKey)) { - print("error: received download completion notice for unknown download " + fileKey); - } else { - this._downloads[fileKey]!.gotManifest = true; - notifyListeners(); + this._downloads[fileKey] = FileDownloadProgress(1, DateTime.now()); } + this._downloads[fileKey]!.gotManifest = true; + notifyListeners(); } void downloadMarkFinished(String fileKey, String finalPath) { diff --git a/lib/settings.dart b/lib/settings.dart index 436e7d88..0c83f235 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -11,6 +11,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; const TapirGroupsExperiment = "tapir-groups-experiment"; const ServerManagementExperiment = "servers-experiment"; const FileSharingExperiment = "filesharing"; +const ImagePreviewsExperiment = "filesharing-images"; enum DualpaneMode { Single, @@ -34,6 +35,7 @@ class Settings extends ChangeNotifier { bool blockUnknownConnections = false; bool streamerMode = false; + String _downloadPath = ""; /// Set the dark theme. void setDark() { @@ -90,6 +92,13 @@ class Settings extends ChangeNotifier { _uiColumnModePortrait = uiColumnModeFromString(settings["UIColumnModePortrait"]); _uiColumnModeLandscape = uiColumnModeFromString(settings["UIColumnModeLandscape"]); + // image previews/profile pic storage path + for (var i = 0; i < 30; i++) { + print("|"); + } + print("setting DownloadPath to " + settings["DownloadPath"]); + _downloadPath = settings["DownloadPath"] ?? ""; + // Push the experimental settings to Consumers of Settings notifyListeners(); } @@ -222,6 +231,12 @@ class Settings extends ChangeNotifier { } } + String get downloadPath => _downloadPath; + set downloadPath(String newval) { + _downloadPath = newval; + notifyListeners(); + } + /// Construct a default settings object. Settings(this.locale, this.theme); @@ -242,6 +257,7 @@ class Settings extends ChangeNotifier { "FirstTime": false, "UIColumnModePortrait": uiColumnModePortrait.toString(), "UIColumnModeLandscape": uiColumnModeLandscape.toString(), + "DownloadPath": _downloadPath, }; } } diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index 144ad05f..c3fb8722 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/models/servers.dart'; +import 'package:cwtch/widgets/folderpicker.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:flutter/material.dart'; import 'package:cwtch/settings.dart'; @@ -227,6 +228,39 @@ class _GlobalSettingsViewState extends State { inactiveTrackColor: settings.theme.defaultButtonDisabledColor(), secondary: Icon(Icons.attach_file, color: settings.current().mainTextColor()), ), + Visibility( + visible: settings.isExperimentEnabled(FileSharingExperiment), + child: Column(children:[ + SwitchListTile( + title: Text("Image Previews and Profile Pics", style: TextStyle(color: settings.current().mainTextColor())), + subtitle: Text("TODO: Write a description with lots of warnings about how image previews can be an insecurity vector and you should only enable them with great caution..."), + value: settings.isExperimentEnabled(ImagePreviewsExperiment), + onChanged: (bool value) { + if (value) { + settings.enableExperiment(ImagePreviewsExperiment); + settings.downloadPath = Provider.of(context, listen: false).cwtch.defaultDownloadPath(); + } else { + settings.disableExperiment(ImagePreviewsExperiment); + } + saveSettings(context); + }, + activeTrackColor: settings.theme.defaultButtonActiveColor(), + inactiveTrackColor: settings.theme.defaultButtonDisabledColor(), + secondary: Icon(Icons.attach_file, color: settings.current().mainTextColor()), + ), + Visibility( + visible: settings.isExperimentEnabled(ImagePreviewsExperiment), + child: CwtchFolderPicker( + label:"Download folder",//todo:l18n + initialValue: settings.downloadPath, + onSave: (newVal) { + settings.downloadPath = newVal; + saveSettings(context); + }, + ), + ), + ]), + ), ], )), AboutListTile( diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index 33fa503e..e9b6baf3 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -33,6 +33,8 @@ class FileBubble extends StatefulWidget { } class FileBubbleState extends State { + File? myFile; + @override void initState() { super.initState(); @@ -62,6 +64,7 @@ class FileBubbleState extends State { child: SelectableText(senderDisplayStr + '\u202F', style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of(context).theme.messageFromMeTextColor() : Provider.of(context).theme.messageFromOtherTextColor()))); + var isPreview = false; var wdgMessage = !showFileSharing ? Text(AppLocalizations.of(context)!.messageEnableFileSharing) : fromMe @@ -76,7 +79,17 @@ class FileBubbleState extends State { } else if (Provider.of(context).downloadComplete(widget.fileKey())) { // in this case, whatever marked download.complete would have also set the path var path = Provider.of(context).downloadFinalPath(widget.fileKey())!; - wdgDecorations = Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F'); + var lpath = path.toLowerCase(); + 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); }); + } + + isPreview = true; + wdgDecorations = GestureDetector(child: Image.file(myFile!, width: 200), onTap:(){pop(myFile!, wdgMessage);},); + } else { + wdgDecorations = Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F'); + } } else if (Provider.of(context).downloadActive(widget.fileKey())) { if (!Provider.of(context).downloadGotManifest(widget.fileKey())) { wdgDecorations = Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F'); @@ -136,7 +149,7 @@ class FileBubbleState extends State { mainAxisSize: MainAxisSize.min, children: fromMe ? [wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)] - : [wdgSender, wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)]), + : [wdgSender, isPreview ? Container() : wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)]), ) ]))))); }); @@ -289,4 +302,22 @@ class FileBubbleState extends State { )), ); } + + void pop(File myFile, Widget meta) async { + await showDialog( + context: context, + builder: (_) => Dialog( + child: Container(padding: EdgeInsets.all(10), width: 500, height: 550, child:Column(children:[meta,Container( + width: 300, + height: 300, + decoration: BoxDecoration( + image: DecorationImage( + image: Image.file(myFile).image, + fit: BoxFit.scaleDown + ) + ), + ),Icon(Icons.arrow_downward)]), + )) + ); + } } diff --git a/lib/widgets/folderpicker.dart b/lib/widgets/folderpicker.dart new file mode 100644 index 00000000..4e6d146f --- /dev/null +++ b/lib/widgets/folderpicker.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'dart:io'; + +import 'package:file_picker_desktop/file_picker_desktop.dart'; +import 'buttontextfield.dart'; +import 'cwtchlabel.dart'; + +class CwtchFolderPicker extends StatefulWidget { + final String label; + final String initialValue; + final Function(String)? onSave; + const CwtchFolderPicker({Key? key, this.label = "", this.initialValue = "", this.onSave}) : super(key: key); + + @override + _CwtchFolderPickerState createState() => _CwtchFolderPickerState(); +} + +class _CwtchFolderPickerState extends State { + final TextEditingController ctrlrVal = TextEditingController(); + + @override + void initState() { + super.initState(); + ctrlrVal.text = widget.initialValue; + } + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.all(10), + padding: EdgeInsets.all(2), + child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ + CwtchLabel(label: widget.label), + SizedBox( + height: 20, + ), + CwtchButtonTextField( + controller: ctrlrVal, + readonly: Platform.isAndroid, + onPressed: () async { + if (Platform.isAndroid) { + return; + } + + try { + var selectedDirectory = await getDirectoryPath(); + if (selectedDirectory != null) { + //File directory = File(selectedDirectory); + selectedDirectory += "/"; + ctrlrVal.text = selectedDirectory; + if (widget.onSave != null) { + widget.onSave!(selectedDirectory); + } + } else { + // User canceled the picker + } + } catch (e) { + print(e); + } + }, + icon: Icon(Icons.folder), + tooltip: "Browse",//todo: l18n + ) + ])); + } +} diff --git a/lib/widgets/profilerow.dart b/lib/widgets/profilerow.dart index 39bce219..4dd4b3c2 100644 --- a/lib/widgets/profilerow.dart +++ b/lib/widgets/profilerow.dart @@ -102,7 +102,7 @@ class _ProfileRowState extends State { } void _pushEditProfile({onion: "", displayName: "", profileImage: "", encrypted: true}) { - Provider.of(context).reset(); + Provider.of(context, listen: false).reset(); Navigator.of(context).push(MaterialPageRoute( builder: (BuildContext context) { return MultiProvider(