From 33a8a301704e729318698ed5506d579ddbbdaad3 Mon Sep 17 00:00:00 2001 From: erinn Date: Tue, 14 Dec 2021 13:33:30 -0800 Subject: [PATCH 01/19] 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( From 8be0fa39573e7e7c61f2a7f3951433f8296136e6 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Tue, 14 Dec 2021 15:37:27 -0800 Subject: [PATCH 02/19] File Bubble Design --- lib/widgets/filebubble.dart | 111 +++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index 5a254657..81d712fd 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:cwtch/config.dart'; import 'package:cwtch/models/message.dart'; +import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:file_picker_desktop/file_picker_desktop.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -59,7 +60,7 @@ class FileBubbleState extends State { senderDisplayStr = Provider.of(context).senderHandle; } } - + return LayoutBuilder(builder: (context, constraints) { var wdgSender = Center( widthFactor: 1, child: SelectableText(senderDisplayStr + '\u202F', @@ -83,11 +84,29 @@ class FileBubbleState extends State { 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); }); + setState(() { + myFile = new File(path); + }); } isPreview = true; - wdgDecorations = GestureDetector(child: Image.file(myFile!, width: 200), onTap:(){pop(myFile!, wdgMessage);},); + wdgDecorations = GestureDetector( + 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.fill, + alignment: Alignment.center, + width: constraints.maxWidth, + isAntiAlias: false, + errorBuilder: (context, error, stackTrace) { + return MalformedBubble(); + }, + ), + onTap: () { + pop(myFile!, wdgMessage); + }, + ); } else { wdgDecorations = Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F'); } @@ -121,38 +140,29 @@ class FileBubbleState extends State { ])); } - return LayoutBuilder(builder: (context, constraints) { - //print(constraints.toString()+", "+constraints.maxWidth.toString()); - return Center( - widthFactor: 1.0, - child: Container( - decoration: BoxDecoration( - color: fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor() : Provider.of(context).theme.messageFromOtherBackgroundColor(), - border: - Border.all(color: fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor() : Provider.of(context).theme.messageFromOtherBackgroundColor(), width: 1), - 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: Center( - widthFactor: 1.0, - child: Padding( - padding: EdgeInsets.all(9.0), - child: Wrap(alignment: WrapAlignment.start, children: [ - Center( - widthFactor: 1.0, - child: Column( - crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, - mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: fromMe - ? [wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)] - : [wdgSender, isPreview ? Container() : wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)]), - ) - ]))))); + + return Container( + constraints: constraints, + decoration: BoxDecoration( + color: fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor() : Provider.of(context).theme.messageFromOtherBackgroundColor(), + border: Border.all(color: fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor() : Provider.of(context).theme.messageFromOtherBackgroundColor(), width: 1), + 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, + children: fromMe + ? [wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)] + : [wdgSender, isPreview ? Container() : wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)]), + )); }); } @@ -313,17 +323,24 @@ class FileBubbleState extends State { 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)]), - )) - ); + 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, cacheHeight: 1024, cacheWidth: 1024).image, + fit: BoxFit.fitWidth, + filterQuality: FilterQuality.none, + )), + ), + Icon(Icons.arrow_downward) + ]), + ))); } } From 8c4a5aee90fed61d68945ca46050db1e5c03efa0 Mon Sep 17 00:00:00 2001 From: erinn Date: Tue, 14 Dec 2021 15:50:08 -0800 Subject: [PATCH 03/19] wip image previews --- .../kotlin/im/cwtch/flwtch/MainActivity.kt | 28 +++++++++++++++++++ lib/cwtch/cwtch.dart | 2 ++ lib/cwtch/ffi.dart | 5 ++++ lib/cwtch/gomobile.dart | 5 ++++ lib/views/globalsettingsview.dart | 1 - lib/widgets/filebubble.dart | 7 ++++- 6 files changed, 46 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt b/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt index 20940c89..d0e3d06e 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt @@ -22,6 +22,7 @@ import io.flutter.plugin.common.ErrorLogResult import org.json.JSONObject import java.util.concurrent.TimeUnit +import java.io.File import android.net.Uri import android.provider.DocumentsContract @@ -57,9 +58,11 @@ class MainActivity: FlutterActivity() { // "Download to..." prompt extra arguments private val FILEPICKER_REQUEST_CODE = 234 + private val PREVIEW_EXPORT_REQUEST_CODE = 235 private var dlToProfile = "" private var dlToHandle = "" private var dlToFileKey = "" + private var exportFromPath = "" // handles clicks received from outside the app (ie, notifications) override fun onNewIntent(intent: Intent) { @@ -102,6 +105,11 @@ class MainActivity: FlutterActivity() { "manifestpath" to manifestPath, "filekey" to this.dlToFileKey )), ErrorLogResult(""));//placeholder; this Result is never actually invoked + } else if (requestCode == PREVIEW_EXPORT_REQUEST_CODE) { + val targetPath = intent!!.getData().toString() + var srcFile = File(this.exportFromPath) + Log.i("MainActivity:PREVIEW_EXPORT", "exporting previewed file") + srcFile.copyTo(File(targetPath)); } } @@ -170,6 +178,26 @@ class MainActivity: FlutterActivity() { } startActivityForResult(intent, FILEPICKER_REQUEST_CODE) return + } else if (call.method == "ExportPreviewedFile") { + this.exportFromPath = argmap["Path"] ?: "" + val suggestion = argmap["FileName"] ?: "filename.ext" + var imgType = "jpeg" + if (suggestion.endsWith("png")) { + imgType = "png" + } else if (suggestion.endsWith("webp")) { + imgType = "webp" + } else if (suggestion.endsWith("bmp")) { + imgType = "bmp" + } else if (suggestion.endsWith("gif")) { + imgType = "gif" + } + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "image/" + imgType + putExtra(Intent.EXTRA_TITLE, suggestion) + } + startActivityForResult(intent, PREVIEW_EXPORT_REQUEST_CODE) + return } // ...otherwise fallthru to a normal ffi method call (and return the result using the result callback) diff --git a/lib/cwtch/cwtch.dart b/lib/cwtch/cwtch.dart index f7ce4262..8a2c740e 100644 --- a/lib/cwtch/cwtch.dart +++ b/lib/cwtch/cwtch.dart @@ -55,6 +55,8 @@ abstract class Cwtch { void CheckDownloadStatus(String profile, String fileKey); // ignore: non_constant_identifier_names void VerifyOrResumeDownload(String profile, int handle, String filekey); + // ignore: non_constant_identifier_names + void ExportPreviewedFile(String sourceFile, String suggestion); // ignore: non_constant_identifier_names void ArchiveConversation(String profile, int handle); diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index f585438f..3c829794 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -402,6 +402,11 @@ class CwtchFfi implements Cwtch { // android only - do nothing } + // ignore: non_constant_identifier_names + void ExportPreviewedFile(String sourceFile, String suggestion) { + // android only - do nothing + } + @override // ignore: non_constant_identifier_names void CheckDownloadStatus(String profileOnion, String fileKey) { diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index ccf7833d..c121ff28 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -149,6 +149,11 @@ class CwtchGomobile implements Cwtch { cwtchPlatform.invokeMethod("CreateDownloadableFile", {"ProfileOnion": profileOnion, "conversation": conversation, "filename": filenameSuggestion, "filekey": filekey}); } + // ignore: non_constant_identifier_names + void ExportPreviewedFile(String sourceFile, String suggestion) { + cwtchPlatform.invokeMethod("ExportPreviewedFile", {"Path": sourceFile, "FileName": suggestion,}); + } + @override // ignore: non_constant_identifier_names void CheckDownloadStatus(String profileOnion, String fileKey) { diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index 428eb553..2179e4a4 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -227,7 +227,6 @@ class _GlobalSettingsViewState extends State { inactiveTrackColor: settings.theme.defaultButtonDisabledColor(), secondary: Icon(Icons.attach_file, color: settings.current().mainTextColor()), ), -<<<<<<< HEAD Visibility( visible: settings.isExperimentEnabled(FileSharingExperiment), child: Column(children:[ diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index 5a254657..4a2b6e40 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -322,8 +322,13 @@ class FileBubbleState extends State { fit: BoxFit.scaleDown ) ), - ),Icon(Icons.arrow_downward)]), + ),IconButton(icon: Icon(Icons.arrow_downward), onPressed: androidExport), + ]), )) ); } + + void androidExport() async { + + } } From 56e9e3607493fe779d21d5dda3225a2d7e3ecd8d Mon Sep 17 00:00:00 2001 From: erinn Date: Tue, 14 Dec 2021 17:12:16 -0800 Subject: [PATCH 04/19] l10n image previews --- lib/l10n/intl_de.arb | 4 +++- lib/l10n/intl_en.arb | 4 +++- lib/l10n/intl_es.arb | 4 +++- lib/l10n/intl_fr.arb | 4 +++- lib/l10n/intl_it.arb | 4 +++- lib/l10n/intl_pl.arb | 4 +++- lib/l10n/intl_pt.arb | 4 +++- lib/l10n/intl_ru.arb | 4 +++- 8 files changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 71936b39..b63210c7 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,6 +1,8 @@ { "@@locale": "de", - "@@last_modified": "2021-12-13T23:43:26+01:00", + "@@last_modified": "2021-12-15T02:08:40+01:00", + "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", + "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", "enableExperimentClickableLinks": "Enable Clickable Links", "serverConnectionsLabel": "Connection", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index f1546402..58109efe 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1,6 +1,8 @@ { "@@locale": "en", - "@@last_modified": "2021-12-13T23:43:26+01:00", + "@@last_modified": "2021-12-15T02:08:40+01:00", + "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", + "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", "enableExperimentClickableLinks": "Enable Clickable Links", "serverConnectionsLabel": "Connection", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index ce52d391..87431675 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,6 +1,8 @@ { "@@locale": "es", - "@@last_modified": "2021-12-13T23:43:26+01:00", + "@@last_modified": "2021-12-15T02:08:40+01:00", + "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", + "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", "enableExperimentClickableLinks": "Enable Clickable Links", "serverConnectionsLabel": "Connection", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 66c5b046..e6eb508a 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,6 +1,8 @@ { "@@locale": "fr", - "@@last_modified": "2021-12-13T23:43:26+01:00", + "@@last_modified": "2021-12-15T02:08:40+01:00", + "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", + "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", "enableExperimentClickableLinks": "Enable Clickable Links", "serverMetricsLabel": "Métriques du serveur", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index ece23e55..a1b40d0a 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,6 +1,8 @@ { "@@locale": "it", - "@@last_modified": "2021-12-13T23:43:26+01:00", + "@@last_modified": "2021-12-15T02:08:40+01:00", + "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", + "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", "enableExperimentClickableLinks": "Enable Clickable Links", "serverConnectionsLabel": "Connection", diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index a5d18aa7..efc512b6 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,6 +1,8 @@ { "@@locale": "pl", - "@@last_modified": "2021-12-13T23:43:26+01:00", + "@@last_modified": "2021-12-15T02:08:40+01:00", + "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", + "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", "enableExperimentClickableLinks": "Enable Clickable Links", "serverConnectionsLabel": "Connection", diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index f41f5a06..08e4c07b 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,6 +1,8 @@ { "@@locale": "pt", - "@@last_modified": "2021-12-13T23:43:26+01:00", + "@@last_modified": "2021-12-15T02:08:40+01:00", + "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", + "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", "enableExperimentClickableLinks": "Enable Clickable Links", "serverConnectionsLabel": "Connection", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index d4137344..8ab31d17 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1,6 +1,8 @@ { "@@locale": "ru", - "@@last_modified": "2021-12-13T23:43:26+01:00", + "@@last_modified": "2021-12-15T02:08:40+01:00", + "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", + "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", "enableExperimentClickableLinks": "Enable Clickable Links", "serverConnectionsLabel": "Connection", From b994c42803315947bb8e1c692532e0ab14d80794 Mon Sep 17 00:00:00 2001 From: erinn Date: Tue, 14 Dec 2021 17:13:13 -0800 Subject: [PATCH 05/19] wip image previews --- .../main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt | 2 +- .../main/kotlin/im/cwtch/flwtch/MainActivity.kt | 14 +++++++++++++- lib/views/globalsettingsview.dart | 2 +- pubspec.lock | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt index e8c737ad..ed5283d8 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt @@ -140,7 +140,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : val data = JSONObject(evt.Data); val tempFile = data.getString("TempFile"); val fileKey = data.getString("FileKey"); - if (tempFile != "") { + if (tempFile != "" && tempFile != data.getString("FilePath")) { val filePath = data.getString("FilePath"); Log.i("FlwtchWorker", "moving "+tempFile+" to "+filePath); val sourcePath = Paths.get(tempFile); diff --git a/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt b/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt index d0e3d06e..2663f54c 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt @@ -23,6 +23,9 @@ import io.flutter.plugin.common.ErrorLogResult import org.json.JSONObject import java.util.concurrent.TimeUnit import java.io.File +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.StandardCopyOption import android.net.Uri import android.provider.DocumentsContract @@ -109,7 +112,16 @@ class MainActivity: FlutterActivity() { val targetPath = intent!!.getData().toString() var srcFile = File(this.exportFromPath) Log.i("MainActivity:PREVIEW_EXPORT", "exporting previewed file") - srcFile.copyTo(File(targetPath)); + val sourcePath = Paths.get(this.exportFromPath); + val targetUri = Uri.parse(targetPath); + val os = this.applicationContext.getContentResolver().openOutputStream(targetUri); + val bytesWritten = Files.copy(sourcePath, os); + Log.d("MainActivity:PREVIEW_EXPORT", "copied " + bytesWritten.toString() + " bytes"); + if (bytesWritten != 0L) { + os?.flush(); + os?.close(); + //Files.delete(sourcePath); + } } } diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index 2179e4a4..a88f121b 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -232,7 +232,7 @@ class _GlobalSettingsViewState extends State { 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..."), + subtitle: Text("Images"), value: settings.isExperimentEnabled(ImagePreviewsExperiment), onChanged: (bool value) { if (value) { diff --git a/pubspec.lock b/pubspec.lock index e990cfd9..3c1db18c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -417,7 +417,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.7" + version: "0.4.3" typed_data: dependency: transitive description: From e026b2d542e87ddea35ebe7ee88ae17c8234455a Mon Sep 17 00:00:00 2001 From: erinn Date: Tue, 14 Dec 2021 17:19:13 -0800 Subject: [PATCH 06/19] l10n image previews --- lib/l10n/intl_de.arb | 3 ++- lib/l10n/intl_en.arb | 3 ++- lib/l10n/intl_es.arb | 3 ++- lib/l10n/intl_fr.arb | 3 ++- lib/l10n/intl_it.arb | 3 ++- lib/l10n/intl_pl.arb | 3 ++- lib/l10n/intl_pt.arb | 3 ++- lib/l10n/intl_ru.arb | 3 ++- 8 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index b63210c7..28b7856f 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,6 +1,7 @@ { "@@locale": "de", - "@@last_modified": "2021-12-15T02:08:40+01:00", + "@@last_modified": "2021-12-15T02:16:43+01:00", + "settingDownloadFolder": "Download Folder", "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 58109efe..a6f7bb7b 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1,6 +1,7 @@ { "@@locale": "en", - "@@last_modified": "2021-12-15T02:08:40+01:00", + "@@last_modified": "2021-12-15T02:16:43+01:00", + "settingDownloadFolder": "Download Folder", "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 87431675..235ed3f8 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,6 +1,7 @@ { "@@locale": "es", - "@@last_modified": "2021-12-15T02:08:40+01:00", + "@@last_modified": "2021-12-15T02:16:43+01:00", + "settingDownloadFolder": "Download Folder", "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index e6eb508a..a3c382c6 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,6 +1,7 @@ { "@@locale": "fr", - "@@last_modified": "2021-12-15T02:08:40+01:00", + "@@last_modified": "2021-12-15T02:16:43+01:00", + "settingDownloadFolder": "Download Folder", "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index a1b40d0a..1182bb5d 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,6 +1,7 @@ { "@@locale": "it", - "@@last_modified": "2021-12-15T02:08:40+01:00", + "@@last_modified": "2021-12-15T02:16:43+01:00", + "settingDownloadFolder": "Download Folder", "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index efc512b6..1099025b 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,6 +1,7 @@ { "@@locale": "pl", - "@@last_modified": "2021-12-15T02:08:40+01:00", + "@@last_modified": "2021-12-15T02:16:43+01:00", + "settingDownloadFolder": "Download Folder", "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index 08e4c07b..5feb857e 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,6 +1,7 @@ { "@@locale": "pt", - "@@last_modified": "2021-12-15T02:08:40+01:00", + "@@last_modified": "2021-12-15T02:16:43+01:00", + "settingDownloadFolder": "Download Folder", "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 8ab31d17..aecde383 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1,6 +1,7 @@ { "@@locale": "ru", - "@@last_modified": "2021-12-15T02:08:40+01:00", + "@@last_modified": "2021-12-15T02:16:43+01:00", + "settingDownloadFolder": "Download Folder", "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", "settingImagePreviews": "Image Previews and Profile Pictures", "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", From 51fae2e7efa45590f259312e703cf4301cf8f156 Mon Sep 17 00:00:00 2001 From: erinn Date: Tue, 14 Dec 2021 17:25:05 -0800 Subject: [PATCH 07/19] wip image previews --- lib/views/globalsettingsview.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index a88f121b..89f6f865 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -231,8 +231,8 @@ class _GlobalSettingsViewState extends State { visible: settings.isExperimentEnabled(FileSharingExperiment), child: Column(children:[ SwitchListTile( - title: Text("Image Previews and Profile Pics", style: TextStyle(color: settings.current().mainTextColor())), - subtitle: Text("Images"), + title: Text(AppLocalizations.of(context)!.settingImagePreviews, style: TextStyle(color: settings.current().mainTextColor())), + subtitle: Text(AppLocalizations.of(context)!.settingImagePreviewsDescription), value: settings.isExperimentEnabled(ImagePreviewsExperiment), onChanged: (bool value) { if (value) { @@ -248,9 +248,9 @@ class _GlobalSettingsViewState extends State { secondary: Icon(Icons.attach_file, color: settings.current().mainTextColor()), ), Visibility( - visible: settings.isExperimentEnabled(ImagePreviewsExperiment), + visible: settings.isExperimentEnabled(ImagePreviewsExperiment) && !Platform.isAndroid, child: CwtchFolderPicker( - label:"Download folder",//todo:l18n + label: AppLocalizations.of(context)!.settingDownloadFolder, initialValue: settings.downloadPath, onSave: (newVal) { settings.downloadPath = newVal; From a4cb5051b5fc4764b1668a55daf0b4c33cb3a5d9 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 15 Dec 2021 13:43:14 -0800 Subject: [PATCH 08/19] Fixing Duplicate Key Issues + FileBubble SetState Builder Issues --- lib/model.dart | 2 - lib/models/message.dart | 12 +- lib/models/messages/filemessage.dart | 1 - lib/models/messages/invitemessage.dart | 3 +- lib/models/messages/quotedmessage.dart | 9 +- lib/models/profileservers.dart | 4 +- lib/views/addeditprofileview.dart | 2 +- lib/views/contactsview.dart | 2 +- lib/views/globalsettingsview.dart | 2 +- lib/views/profileserversview.dart | 103 +++++++-------- lib/views/remoteserverview.dart | 139 ++++++++++---------- lib/widgets/filebubble.dart | 167 +++++++++++++------------ lib/widgets/folderpicker.dart | 68 +++++----- lib/widgets/remoteserverrow.dart | 72 +++++------ lib/widgets/serverrow.dart | 93 +++++++------- 15 files changed, 331 insertions(+), 348 deletions(-) diff --git a/lib/model.dart b/lib/model.dart index ae0ba25a..c72a45d2 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -282,8 +282,6 @@ class ProfileInfoState extends ChangeNotifier { this._contacts.updateLastMessageTime(this._contacts._contacts.first.identifier, this._contacts._contacts.first.lastMessageTime); } } - - } // Parse out the server list json into our server info state struct... diff --git a/lib/models/message.dart b/lib/models/message.dart index 4df9eace..6fb1028b 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -58,11 +58,15 @@ Message compileOverlay(MessageMetadata metadata, String messageData) { } Future messageHandler(BuildContext context, String profileOnion, int conversationIdentifier, int index, {bool byID = false}) { - var cache = Provider.of(context).contactList.getContact(conversationIdentifier)?.messageCache; - if (cache != null && cache.length > index) { - if (cache[index] != null) { - return Future.value(compileOverlay(cache[index]!.metadata, cache[index]!.wrapper)); + try { + var cache = Provider.of(context, listen: false).contactList.getContact(conversationIdentifier)?.messageCache; + if (cache != null && cache.length > index) { + if (cache[index] != null) { + return Future.value(compileOverlay(cache[index]!.metadata, cache[index]!.wrapper)); + } } + } catch (e) { + // provider check failed...make an expensive call... } try { diff --git a/lib/models/messages/filemessage.dart b/lib/models/messages/filemessage.dart index 9254c6d2..a0eba982 100644 --- a/lib/models/messages/filemessage.dart +++ b/lib/models/messages/filemessage.dart @@ -19,7 +19,6 @@ class FileMessage extends Message { @override Widget getWidget(BuildContext context, Key key) { return ChangeNotifierProvider.value( - key: key, value: this.metadata, builder: (bcontext, child) { dynamic shareObj = jsonDecode(this.content); diff --git a/lib/models/messages/invitemessage.dart b/lib/models/messages/invitemessage.dart index 149ba5e0..2965abc9 100644 --- a/lib/models/messages/invitemessage.dart +++ b/lib/models/messages/invitemessage.dart @@ -19,7 +19,6 @@ class InviteMessage extends Message { @override Widget getWidget(BuildContext context, Key key) { return ChangeNotifierProvider.value( - key: key, value: this.metadata, builder: (bcontext, child) { String inviteTarget; @@ -37,7 +36,7 @@ class InviteMessage extends Message { inviteTarget = jsonObj['GroupID']; inviteNick = jsonObj['GroupName']; } else { - return MessageRow(MalformedBubble(), key: key); + return MessageRow(MalformedBubble()); } } return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick, invite), key: key); diff --git a/lib/models/messages/quotedmessage.dart b/lib/models/messages/quotedmessage.dart index 5a69ca91..2eb54b28 100644 --- a/lib/models/messages/quotedmessage.dart +++ b/lib/models/messages/quotedmessage.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/messages/malformedmessage.dart'; +import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/messagerow.dart'; import 'package:cwtch/widgets/quotedmessage.dart'; import 'package:flutter/widgets.dart'; @@ -51,7 +52,7 @@ class QuotedMessage extends Message { dynamic message = jsonDecode(this.content); return Text(message["body"]); } catch (e) { - return MalformedMessage(this.metadata).getWidget(context, Key("malformed")); + return MalformedBubble(); } }); } @@ -67,7 +68,7 @@ class QuotedMessage extends Message { dynamic message = jsonDecode(this.content); if (message["body"] == null || message["quotedHash"] == null) { - return MalformedMessage(this.metadata).getWidget(context, key); + return MalformedBubble(); } var quotedMessagePotentials = Provider.of(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.conversationIdentifier, message["quotedHash"]); @@ -94,14 +95,14 @@ class QuotedMessage extends Message { return MessageRow( QuotedMessageBubble(message["body"], quotedMessage.then((LocallyIndexedMessage? localIndex) { if (localIndex != null) { - return messageHandler(context, metadata.profileOnion, metadata.conversationIdentifier, localIndex.index); + return messageHandler(bcontext, metadata.profileOnion, metadata.conversationIdentifier, localIndex.index); } return MalformedMessage(this.metadata); })), key: key); }); } catch (e) { - return MalformedMessage(this.metadata).getWidget(context, key); + return MalformedBubble(); } } } diff --git a/lib/models/profileservers.dart b/lib/models/profileservers.dart index 5f422538..4b868b95 100644 --- a/lib/models/profileservers.dart +++ b/lib/models/profileservers.dart @@ -35,7 +35,7 @@ class ProfileServerListState extends ChangeNotifier { // online v offline if (a.status == "Synced" && b.status != "Synced") { return -1; - } else if (a.status != "Synced" && b.status == "Synced") { + } else if (a.status != "Synced" && b.status == "Synced") { return 1; } @@ -73,7 +73,7 @@ class RemoteServerInfoState extends ChangeNotifier { List _groups = []; RemoteServerInfoState({required this.onion, required this.identifier, required this.description, required this.status}); - + void updateDescription(String newDescription) { this.description = newDescription; notifyListeners(); diff --git a/lib/views/addeditprofileview.dart b/lib/views/addeditprofileview.dart index 03084ca2..8065c9b4 100644 --- a/lib/views/addeditprofileview.dart +++ b/lib/views/addeditprofileview.dart @@ -107,7 +107,7 @@ class _AddEditProfileViewState extends State { labelText: AppLocalizations.of(context)!.yourDisplayName, validator: (value) { if (value.isEmpty) { - return AppLocalizations.of(context)!.displayNameTooltip; + return AppLocalizations.of(context)!.displayNameTooltip; } return null; }, diff --git a/lib/views/contactsview.dart b/lib/views/contactsview.dart index a05ef4df..5c2f564d 100644 --- a/lib/views/contactsview.dart +++ b/lib/views/contactsview.dart @@ -117,7 +117,7 @@ class _ContactsViewState extends State { if (Provider.of(context, listen: false).isExperimentEnabled(ServerManagementExperiment)) { actions.add(IconButton( icon: Icon(CwtchIcons.dns_24px), - tooltip: AppLocalizations.of(context)!.manageKnownServersButton, + tooltip: AppLocalizations.of(context)!.manageKnownServersButton, onPressed: () { _pushServers(); })); diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index 89f6f865..5b4d5d64 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -229,7 +229,7 @@ class _GlobalSettingsViewState extends State { ), Visibility( visible: settings.isExperimentEnabled(FileSharingExperiment), - child: Column(children:[ + child: Column(children: [ SwitchListTile( title: Text(AppLocalizations.of(context)!.settingImagePreviews, style: TextStyle(color: settings.current().mainTextColor())), subtitle: Text(AppLocalizations.of(context)!.settingImagePreviewsDescription), diff --git a/lib/views/profileserversview.dart b/lib/views/profileserversview.dart index 7ac06520..bd4fd40e 100644 --- a/lib/views/profileserversview.dart +++ b/lib/views/profileserversview.dart @@ -10,14 +10,12 @@ import '../main.dart'; import '../model.dart'; import '../settings.dart'; - class ProfileServersView extends StatefulWidget { @override _ProfileServersView createState() => _ProfileServersView(); } class _ProfileServersView extends State { - @override void dispose() { super.dispose(); @@ -25,9 +23,10 @@ class _ProfileServersView extends State { @override Widget build(BuildContext context) { - - var knownServers = Provider.of(context).serverList.servers.map((RemoteServerInfoState remoteServer) { return remoteServer.onion + ".onion"; }).toSet(); - var importServerList = Provider.of(context).servers.where((server) => !knownServers.contains(server.onion) ).map>((ServerInfoState serverInfo) { + var knownServers = Provider.of(context).serverList.servers.map((RemoteServerInfoState remoteServer) { + return remoteServer.onion + ".onion"; + }).toSet(); + var importServerList = Provider.of(context).servers.where((server) => !knownServers.contains(server.onion)).map>((ServerInfoState serverInfo) { return DropdownMenuItem( value: serverInfo.onion, child: Text( @@ -37,25 +36,22 @@ class _ProfileServersView extends State { ); }).toList(); - importServerList.insert(0, DropdownMenuItem( - value: "", - child: Text(AppLocalizations.of(context)!.importLocalServerSelectText))); + importServerList.insert(0, DropdownMenuItem(value: "", child: Text(AppLocalizations.of(context)!.importLocalServerSelectText))); return Scaffold( appBar: AppBar( - title: Text(MediaQuery - .of(context) - .size - .width > 600 ? AppLocalizations.of(context)!.manageKnownServersLong : AppLocalizations.of(context)!.manageKnownServersShort), + title: Text(MediaQuery.of(context).size.width > 600 ? AppLocalizations.of(context)!.manageKnownServersLong : AppLocalizations.of(context)!.manageKnownServersShort), ), - body: Consumer(builder: (context, profile, child) { + body: Consumer( + builder: (context, profile, child) { ProfileServerListState servers = profile.serverList; - final tiles = servers.servers.map((RemoteServerInfoState server) { - return ChangeNotifierProvider.value( - value: server, - builder: (context, child) => RepaintBoundary(child: RemoteServerRow()), - ); - }, + final tiles = servers.servers.map( + (RemoteServerInfoState server) { + return ChangeNotifierProvider.value( + value: server, + builder: (context, child) => RepaintBoundary(child: RemoteServerRow()), + ); + }, ); final divided = ListTile.divideTiles( @@ -63,37 +59,31 @@ class _ProfileServersView extends State { tiles: tiles, ).toList(); - final importCard = Card( child: ListTile( - title: Text(AppLocalizations.of(context)!.importLocalServerLabel), - leading: Icon(CwtchIcons.add_circle_24px , color: Provider.of(context).current().mainTextColor()), - trailing: DropdownButton( - onChanged: (String? importServer) { - if (importServer!.isNotEmpty) { - var server = Provider.of(context).getServer(importServer)!; - showImportConfirm(context, profile.onion, server.onion, server.description, server.serverBundle); - } - - }, - value: "", - items: importServerList, - - ))); + final importCard = Card( + child: ListTile( + title: Text(AppLocalizations.of(context)!.importLocalServerLabel), + leading: Icon(CwtchIcons.add_circle_24px, color: Provider.of(context).current().mainTextColor()), + trailing: DropdownButton( + onChanged: (String? importServer) { + if (importServer!.isNotEmpty) { + var server = Provider.of(context).getServer(importServer)!; + showImportConfirm(context, profile.onion, server.onion, server.description, server.serverBundle); + } + }, + value: "", + items: importServerList, + ))); return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { - return Scrollbar( - isAlwaysShown: true, - child: SingleChildScrollView( - clipBehavior: Clip.antiAlias, - child: - Container( - margin: EdgeInsets.fromLTRB(5, 0, 5, 10), - padding: EdgeInsets.fromLTRB(5, 0, 5, 10), - child: Column(children: [ - - if (importServerList.length > 1) importCard, - - Column( children: divided ) - ]))));}); + return Scrollbar( + isAlwaysShown: true, + child: SingleChildScrollView( + clipBehavior: Clip.antiAlias, + child: Container( + margin: EdgeInsets.fromLTRB(5, 0, 5, 10), + padding: EdgeInsets.fromLTRB(5, 0, 5, 10), + child: Column(children: [if (importServerList.length > 1) importCard, Column(children: divided)])))); + }); return ListView(children: divided); }, @@ -102,7 +92,7 @@ class _ProfileServersView extends State { showImportConfirm(BuildContext context, String profileHandle, String serverHandle, String serverDesc, String bundle) { var serverLabel = serverDesc.isNotEmpty ? serverDesc : serverHandle; - serverHandle = serverHandle.substring(0, serverHandle.length-6 ); // remove '.onion' + serverHandle = serverHandle.substring(0, serverHandle.length - 6); // remove '.onion' // set up the buttons Widget cancelButton = ElevatedButton( child: Text(AppLocalizations.of(context)!.cancel), @@ -118,15 +108,9 @@ class _ProfileServersView extends State { Future.delayed(const Duration(milliseconds: 500), () { var profile = Provider.of(context); if (profile.serverList.getServer(serverHandle) != null) { - profile.serverList.getServer(serverHandle)?.updateDescription( - serverDesc); + profile.serverList.getServer(serverHandle)?.updateDescription(serverDesc); - Provider - .of(context, listen: false) - .cwtch - .SetConversationAttribute(profile.onion, profile.serverList - .getServer(serverHandle) - !.identifier, "server.description", serverDesc); + Provider.of(context, listen: false).cwtch.SetConversationAttribute(profile.onion, profile.serverList.getServer(serverHandle)!.identifier, "server.description", serverDesc); } }); Navigator.of(context).pop(); @@ -149,7 +133,4 @@ class _ProfileServersView extends State { }, ); } - - - -} \ No newline at end of file +} diff --git a/lib/views/remoteserverview.dart b/lib/views/remoteserverview.dart index bc940807..e43a79e5 100644 --- a/lib/views/remoteserverview.dart +++ b/lib/views/remoteserverview.dart @@ -50,63 +50,62 @@ class _RemoteServerViewState extends State { Widget build(BuildContext context) { return Consumer3(builder: (context, profile, serverInfoState, settings, child) { return Scaffold( - appBar: AppBar( - title: Text(ctrlrDesc.text.isNotEmpty ? ctrlrDesc.text : serverInfoState.onion) - ), - body: Container( - margin: EdgeInsets.fromLTRB(30, 0, 30, 10), - padding: EdgeInsets.fromLTRB(20, 0, 20, 10), - child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - height: 20, - ), - CwtchLabel(label: AppLocalizations.of(context)!.serverAddress), - SizedBox( - height: 20, - ), - SelectableText( - serverInfoState.onion - ), + appBar: AppBar(title: Text(ctrlrDesc.text.isNotEmpty ? ctrlrDesc.text : serverInfoState.onion)), + body: Container( + margin: EdgeInsets.fromLTRB(30, 0, 30, 10), + padding: EdgeInsets.fromLTRB(20, 0, 20, 10), + child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ + SizedBox( + height: 20, + ), + CwtchLabel(label: AppLocalizations.of(context)!.serverAddress), + SizedBox( + height: 20, + ), + SelectableText(serverInfoState.onion), - // Description - SizedBox( - height: 20, - ), - CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel), - Text(AppLocalizations.of(context)!.serverDescriptionDescription), - SizedBox( - height: 20, - ), - CwtchButtonTextField( - controller: ctrlrDesc, - readonly: false, - tooltip: AppLocalizations.of(context)!.saveBtn, - labelText: AppLocalizations.of(context)!.fieldDescriptionLabel, - icon: Icon(Icons.save), - onPressed: () { - Provider.of(context, listen: false).cwtch.SetConversationAttribute(profile.onion, serverInfoState.identifier, "server.description", ctrlrDesc.text); - serverInfoState.updateDescription(ctrlrDesc.text); - }, - ), + // Description + SizedBox( + height: 20, + ), + CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel), + Text(AppLocalizations.of(context)!.serverDescriptionDescription), + SizedBox( + height: 20, + ), + CwtchButtonTextField( + controller: ctrlrDesc, + readonly: false, + tooltip: AppLocalizations.of(context)!.saveBtn, + labelText: AppLocalizations.of(context)!.fieldDescriptionLabel, + icon: Icon(Icons.save), + onPressed: () { + Provider.of(context, listen: false).cwtch.SetConversationAttribute(profile.onion, serverInfoState.identifier, "server.description", ctrlrDesc.text); + serverInfoState.updateDescription(ctrlrDesc.text); + }, + ), - SizedBox( - height: 20, - ), + SizedBox( + height: 20, + ), - Padding(padding: EdgeInsets.all(8), child: Text( AppLocalizations.of(context)!.groupsOnThisServerLabel),), - Expanded(child: _buildGroupsList(serverInfoState)) - ]))); - - }); + Padding( + padding: EdgeInsets.all(8), + child: Text(AppLocalizations.of(context)!.groupsOnThisServerLabel), + ), + Expanded(child: _buildGroupsList(serverInfoState)) + ]))); + }); } Widget _buildGroupsList(RemoteServerInfoState serverInfoState) { - final tiles = serverInfoState.groups.map((ContactInfoState group) { - return ChangeNotifierProvider.value( - value: group, - builder: (context, child) => RepaintBoundary(child: _buildGroupRow(group)), // ServerRow()), - ); - }, + final tiles = serverInfoState.groups.map( + (ContactInfoState group) { + return ChangeNotifierProvider.value( + value: group, + builder: (context, child) => RepaintBoundary(child: _buildGroupRow(group)), // ServerRow()), + ); + }, ); final divided = ListTile.divideTiles( @@ -126,26 +125,22 @@ class _RemoteServerViewState extends State { Widget _buildGroupRow(ContactInfoState group) { return Padding( padding: const EdgeInsets.all(6.0), //border size - child: Column( - children: [ - Text( - group.nickname, - style: Provider.of(context).biggerFont.apply(color: Provider.of(context).theme.portraitOnlineBorderColor()), - softWrap: true, - overflow: TextOverflow.ellipsis, - ), - Visibility( - visible: !Provider.of(context).streamerMode, - child: ExcludeSemantics( - child: Text( - group.onion, - softWrap: true, - overflow: TextOverflow.ellipsis, - style: TextStyle(color: Provider.of(context).theme.portraitOnlineBorderColor()), - ))) - ]) - ); + child: Column(children: [ + Text( + group.nickname, + style: Provider.of(context).biggerFont.apply(color: Provider.of(context).theme.portraitOnlineBorderColor()), + softWrap: true, + overflow: TextOverflow.ellipsis, + ), + Visibility( + visible: !Provider.of(context).streamerMode, + child: ExcludeSemantics( + child: Text( + group.onion, + softWrap: true, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: Provider.of(context).theme.portraitOnlineBorderColor()), + ))) + ])); } - } - diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index 8054957d..a6e7c999 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -49,6 +49,26 @@ class FileBubbleState extends State { var borderRadiousEh = 15.0; var showFileSharing = Provider.of(context).isExperimentEnabled(FileSharingExperiment); var prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(Provider.of(context).timestamp); + var downloadComplete = Provider.of(context).downloadComplete(widget.fileKey()); + var downloadInterrupted = Provider.of(context).downloadInterrupted(widget.fileKey()); + + if (downloadInterrupted) { + Provider.of(context, listen: false).cwtch.CheckDownloadStatus(Provider.of(context, listen: false).onion, widget.fileKey()); + } + var path = Provider.of(context).downloadFinalPath(widget.fileKey()); + if (downloadComplete) { + 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); + }); + } + } + } + + var downloadActive = Provider.of(context).downloadActive(widget.fileKey()); + var downloadGotManifest = Provider.of(context).downloadGotManifest(widget.fileKey()); // If the sender is not us, then we want to give them a nickname... var senderDisplayStr = ""; @@ -61,85 +81,76 @@ class FileBubbleState extends State { } } return LayoutBuilder(builder: (context, constraints) { - var wdgSender = Center( - widthFactor: 1, - 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 - ? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash, widget.fileSize) - : (fileChrome(AppLocalizations.of(context)!.messageFileOffered + ":", widget.nameSuggestion, widget.rootHash, widget.fileSize, - Provider.of(context).downloadSpeed(widget.fileKey()))); - Widget wdgDecorations; - if (!showFileSharing) { - wdgDecorations = Text('\u202F'); - } else if (fromMe) { - wdgDecorations = MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, prettyDate: prettyDate); - } 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())!; - 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!, - 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.fill, - alignment: Alignment.center, - width: constraints.maxWidth, - isAntiAlias: false, - errorBuilder: (context, error, stackTrace) { - return MalformedBubble(); - }, - ), - 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'); - } else { - wdgDecorations = LinearProgressIndicator( - value: Provider.of(context).downloadProgress(widget.fileKey()), - color: Provider.of(context).theme.defaultButtonActiveColor(), - ); - } - } 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 (!Provider.of(context).downloadInterrupted(widget.fileKey())) { - wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F'); - Provider.of(context, listen: false).cwtch.CheckDownloadStatus(Provider.of(context, listen: false).onion, widget.fileKey()); - } else { - var path = Provider.of(context).downloadFinalPath(widget.fileKey()) ?? ""; - wdgDecorations = Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'), - ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton)) - ]); - } - } else { - wdgDecorations = Center( + var wdgSender = Center( widthFactor: 1, - child: Wrap(children: [ - Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F'), onPressed: _btnAccept)), - ])); - } + 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 + ? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash, widget.fileSize) + : (fileChrome(AppLocalizations.of(context)!.messageFileOffered + ":", widget.nameSuggestion, widget.rootHash, widget.fileSize, + Provider.of(context).downloadSpeed(widget.fileKey()))); + Widget wdgDecorations; + if (!showFileSharing) { + wdgDecorations = Text('\u202F'); + } else if (fromMe) { + wdgDecorations = MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, prettyDate: prettyDate); + } else if (downloadComplete) { + // in this case, whatever marked download.complete would have also set the path + var lpath = path!.toLowerCase(); + if (lpath.endsWith("jpg") || lpath.endsWith("jpeg") || lpath.endsWith("png") || lpath.endsWith("gif") || lpath.endsWith("webp") || lpath.endsWith("bmp")) { + isPreview = true; + wdgDecorations = GestureDetector( + 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.fill, + alignment: Alignment.center, + width: constraints.maxWidth, + isAntiAlias: false, + errorBuilder: (context, error, stackTrace) { + return MalformedBubble(); + }, + ), + onTap: () { + pop(myFile!, wdgMessage); + }, + ); + } else { + wdgDecorations = Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F'); + } + } else if (downloadActive) { + if (!downloadGotManifest) { + wdgDecorations = Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F'); + } else { + wdgDecorations = LinearProgressIndicator( + value: Provider.of(context).downloadProgress(widget.fileKey()), + color: Provider.of(context).theme.defaultButtonActiveColor(), + ); + } + } 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(context).downloadFinalPath(widget.fileKey()) ?? ""; + wdgDecorations = Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'), + ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton)) + ]); + } + } else { + wdgDecorations = Center( + widthFactor: 1, + child: Wrap(children: [ + Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F'), onPressed: _btnAccept)), + ])); + } return Container( constraints: constraints, @@ -193,7 +204,7 @@ class FileBubbleState extends State { Provider.of(context, listen: false).downloadInit(widget.fileKey(), (widget.fileSize / 4096).ceil()); Provider.of(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "file-downloaded", "true"); //Provider.of(context, listen: false).flags |= 0x02; - ContactInfoState? contact = Provider.of(context).contactList.findContact(Provider.of(context).senderHandle); + ContactInfoState? contact = Provider.of(context, listen: false).contactList.findContact(Provider.of(context).senderHandle); if (contact != null) { Provider.of(context, listen: false).cwtch.DownloadFile(profileOnion, contact.identifier, file.path, manifestPath, widget.fileKey()); } diff --git a/lib/widgets/folderpicker.dart b/lib/widgets/folderpicker.dart index 4e6d146f..7e9f9bfd 100644 --- a/lib/widgets/folderpicker.dart +++ b/lib/widgets/folderpicker.dart @@ -28,40 +28,40 @@ class _CwtchFolderPickerState extends State { @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); + 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; } - } else { - // User canceled the picker - } - } catch (e) { - print(e); - } - }, - icon: Icon(Icons.folder), - tooltip: "Browse",//todo: l18n - ) - ])); + + 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/remoteserverrow.dart b/lib/widgets/remoteserverrow.dart index 339a170d..b3c2f356 100644 --- a/lib/widgets/remoteserverrow.dart +++ b/lib/widgets/remoteserverrow.dart @@ -23,46 +23,42 @@ class _RemoteServerRowState extends State { @override Widget build(BuildContext context) { var server = Provider.of(context); - var description = server.description.isNotEmpty ? server.description : server.onion; + var description = server.description.isNotEmpty ? server.description : server.onion; var running = server.status == "Synced"; - return Consumer( - builder: (context, profile, child) { - return Card(clipBehavior: Clip.antiAlias, + return Consumer(builder: (context, profile, child) { + return Card( + clipBehavior: Clip.antiAlias, margin: EdgeInsets.all(0.0), child: InkWell( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Padding( + padding: const EdgeInsets.all(6.0), //border size + child: Icon(CwtchIcons.dns_24px, + color: running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor(), size: 64)), + Expanded( + child: Column( children: [ - Padding( - padding: const EdgeInsets.all(6.0), //border size - child: Icon(CwtchIcons.dns_24px, - color: running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor(), - size: 64) - + Text( + description, + semanticsLabel: description, + style: Provider.of(context) + .biggerFont + .apply(color: running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor()), + softWrap: true, + overflow: TextOverflow.ellipsis, ), - Expanded( - child: Column( - children: [ - Text( - description, - semanticsLabel: description, - style: Provider.of(context).biggerFont.apply(color: running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor()), - softWrap: true, - overflow: TextOverflow.ellipsis, - ), - Visibility( - visible: !Provider.of(context).streamerMode, - child: ExcludeSemantics( - child: Text( - server.onion, - softWrap: true, - overflow: TextOverflow.ellipsis, - style: TextStyle(color: running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor()), - ))) - ], - )), - - ]), + Visibility( + visible: !Provider.of(context).streamerMode, + child: ExcludeSemantics( + child: Text( + server.onion, + softWrap: true, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor()), + ))) + ], + )), + ]), onTap: () { Navigator.of(context).push(MaterialPageRoute( settings: RouteSettings(name: "remoteserverview"), @@ -72,7 +68,7 @@ class _RemoteServerRowState extends State { child: RemoteServerView(), ); })); - } - ));}); - } + })); + }); + } } diff --git a/lib/widgets/serverrow.dart b/lib/widgets/serverrow.dart index a4e8bb05..65edb974 100644 --- a/lib/widgets/serverrow.dart +++ b/lib/widgets/serverrow.dart @@ -26,58 +26,57 @@ class _ServerRowState extends State { margin: EdgeInsets.all(0.0), child: InkWell( child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Padding( - padding: const EdgeInsets.all(6.0), //border size - child: Icon(CwtchIcons.dns_24px, - color: server.running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor(), size: 64)), - Expanded( - child: Column( - children: [ - Text( - server.description, - semanticsLabel: server.description, - style: Provider.of(context) - .biggerFont - .apply(color: server.running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor()), - softWrap: true, - overflow: TextOverflow.ellipsis, - ), - Visibility( - visible: !Provider.of(context).streamerMode, - child: ExcludeSemantics( - child: Text( - server.onion, + Padding( + padding: const EdgeInsets.all(6.0), //border size + child: Icon(CwtchIcons.dns_24px, + color: server.running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor(), size: 64)), + Expanded( + child: Column( + children: [ + Text( + server.description, + semanticsLabel: server.description, + style: Provider.of(context) + .biggerFont + .apply(color: server.running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor()), softWrap: true, overflow: TextOverflow.ellipsis, - style: TextStyle(color: server.running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor()), - ))) - ], - )), + ), + Visibility( + visible: !Provider.of(context).streamerMode, + child: ExcludeSemantics( + child: Text( + server.onion, + softWrap: true, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: server.running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor()), + ))) + ], + )), - // Copy server button - IconButton( - enableFeedback: true, - tooltip: AppLocalizations.of(context)!.copyServerKeys, - icon: Icon(CwtchIcons.address_copy_2, color: Provider.of(context).current().mainTextColor()), - onPressed: () { - Clipboard.setData(new ClipboardData(text: server.serverBundle)); - }, - ), + // Copy server button + IconButton( + enableFeedback: true, + tooltip: AppLocalizations.of(context)!.copyServerKeys, + icon: Icon(CwtchIcons.address_copy_2, color: Provider.of(context).current().mainTextColor()), + onPressed: () { + Clipboard.setData(new ClipboardData(text: server.serverBundle)); + }, + ), - // Edit button - IconButton( - enableFeedback: true, - tooltip: AppLocalizations.of(context)!.editServerTitle, - icon: Icon(Icons.create, color: Provider.of(context).current().mainTextColor()), - onPressed: () { + // Edit button + IconButton( + enableFeedback: true, + tooltip: AppLocalizations.of(context)!.editServerTitle, + icon: Icon(Icons.create, color: Provider.of(context).current().mainTextColor()), + onPressed: () { + _pushEditServer(server); + }, + ) + ]), + onTap: () { _pushEditServer(server); - }, - ) - ]), - onTap: () { - _pushEditServer(server); - } - )); + })); } void _pushEditServer(ServerInfoState server) { From 8a8c5fc3e2a3b0555c0dbc571fe821985400140e Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 16 Dec 2021 12:52:25 -0800 Subject: [PATCH 09/19] Update Langs --- lib/l10n/intl_de.arb | 12 +++++++++- lib/l10n/intl_en.arb | 12 +++++++++- lib/l10n/intl_es.arb | 12 +++++++++- lib/l10n/intl_fr.arb | 22 +++++++++++++----- lib/l10n/intl_it.arb | 12 +++++++++- lib/l10n/intl_pl.arb | 12 +++++++++- lib/l10n/intl_pt.arb | 12 +++++++++- lib/l10n/intl_ru.arb | 54 ++++++++++++++++++++++++++------------------ 8 files changed, 114 insertions(+), 34 deletions(-) diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 28b7856f..7f6185a9 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,6 +1,16 @@ { "@@locale": "de", - "@@last_modified": "2021-12-15T02:16:43+01:00", + "@@last_modified": "2021-12-16T21:50:40+01:00", + "themeColorLabel": "Color Theme", + "themeNameNeon2": "Neon2", + "themeNameNeon1": "Neon1", + "themeNameMidnight": "Midnight", + "themeNameMermaid": "Mermaid", + "themeNamePumpkin": "Pumpkin", + "themeNameGhost": "Ghost", + "themeNameVampire": "Vampire", + "themeNameWitch": "Witch", + "themeNameCwtch": "Cwtch", "settingDownloadFolder": "Download Folder", "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", "settingImagePreviews": "Image Previews and Profile Pictures", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index a6f7bb7b..1c306294 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1,6 +1,16 @@ { "@@locale": "en", - "@@last_modified": "2021-12-15T02:16:43+01:00", + "@@last_modified": "2021-12-16T21:50:40+01:00", + "themeColorLabel": "Color Theme", + "themeNameNeon2": "Neon2", + "themeNameNeon1": "Neon1", + "themeNameMidnight": "Midnight", + "themeNameMermaid": "Mermaid", + "themeNamePumpkin": "Pumpkin", + "themeNameGhost": "Ghost", + "themeNameVampire": "Vampire", + "themeNameWitch": "Witch", + "themeNameCwtch": "Cwtch", "settingDownloadFolder": "Download Folder", "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", "settingImagePreviews": "Image Previews and Profile Pictures", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 235ed3f8..f2272aa0 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,6 +1,16 @@ { "@@locale": "es", - "@@last_modified": "2021-12-15T02:16:43+01:00", + "@@last_modified": "2021-12-16T21:50:40+01:00", + "themeColorLabel": "Color Theme", + "themeNameNeon2": "Neon2", + "themeNameNeon1": "Neon1", + "themeNameMidnight": "Midnight", + "themeNameMermaid": "Mermaid", + "themeNamePumpkin": "Pumpkin", + "themeNameGhost": "Ghost", + "themeNameVampire": "Vampire", + "themeNameWitch": "Witch", + "themeNameCwtch": "Cwtch", "settingDownloadFolder": "Download Folder", "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", "settingImagePreviews": "Image Previews and Profile Pictures", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index a3c382c6..be4c5d78 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,11 +1,21 @@ { "@@locale": "fr", - "@@last_modified": "2021-12-15T02:16:43+01:00", - "settingDownloadFolder": "Download Folder", - "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", - "settingImagePreviews": "Image Previews and Profile Pictures", - "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", - "enableExperimentClickableLinks": "Enable Clickable Links", + "@@last_modified": "2021-12-16T21:50:40+01:00", + "experimentClickableLinksDescription": "L'expérience des liens cliquables vous permet de cliquer sur les URLs partagés dans les messages", + "themeNameWitch": "Sorcière", + "themeNameVampire": "Vampire", + "themeNamePumpkin": "Citrouille", + "themeNameNeon2": "Néon2", + "themeNameNeon1": "Néon1", + "themeNameMidnight": "Minuit", + "themeNameMermaid": "Sirène", + "themeNameGhost": "Fantôme", + "themeNameCwtch": "Cwtch", + "themeColorLabel": "Thème de couleur", + "settingImagePreviewsDescription": "Les images seront téléchargées et prévisualisées automatiquement. Veuillez noter que la prévisualisation des images peut souvent conduire à des failles de sécurité, et vous ne devriez pas activer cette expérience si vous utilisez Cwtch avec des contacts non fiables. Les images de profil sont prévues pour Cwtch 1.6.", + "settingImagePreviews": "Aperçu des images et photos de profil", + "settingDownloadFolder": "Dossier de téléchargement", + "enableExperimentClickableLinks": "Activer les liens cliquables", "serverMetricsLabel": "Métriques du serveur", "serverTotalMessagesLabel": "Nombre total de messages", "serverConnectionsLabel": "Connexion", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 1182bb5d..e74df5bf 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,6 +1,16 @@ { "@@locale": "it", - "@@last_modified": "2021-12-15T02:16:43+01:00", + "@@last_modified": "2021-12-16T21:50:40+01:00", + "themeColorLabel": "Color Theme", + "themeNameNeon2": "Neon2", + "themeNameNeon1": "Neon1", + "themeNameMidnight": "Midnight", + "themeNameMermaid": "Mermaid", + "themeNamePumpkin": "Pumpkin", + "themeNameGhost": "Ghost", + "themeNameVampire": "Vampire", + "themeNameWitch": "Witch", + "themeNameCwtch": "Cwtch", "settingDownloadFolder": "Download Folder", "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", "settingImagePreviews": "Image Previews and Profile Pictures", diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index 1099025b..ae17cc1c 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,6 +1,16 @@ { "@@locale": "pl", - "@@last_modified": "2021-12-15T02:16:43+01:00", + "@@last_modified": "2021-12-16T21:50:40+01:00", + "themeColorLabel": "Color Theme", + "themeNameNeon2": "Neon2", + "themeNameNeon1": "Neon1", + "themeNameMidnight": "Midnight", + "themeNameMermaid": "Mermaid", + "themeNamePumpkin": "Pumpkin", + "themeNameGhost": "Ghost", + "themeNameVampire": "Vampire", + "themeNameWitch": "Witch", + "themeNameCwtch": "Cwtch", "settingDownloadFolder": "Download Folder", "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", "settingImagePreviews": "Image Previews and Profile Pictures", diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index 5feb857e..da920cfe 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,6 +1,16 @@ { "@@locale": "pt", - "@@last_modified": "2021-12-15T02:16:43+01:00", + "@@last_modified": "2021-12-16T21:50:40+01:00", + "themeColorLabel": "Color Theme", + "themeNameNeon2": "Neon2", + "themeNameNeon1": "Neon1", + "themeNameMidnight": "Midnight", + "themeNameMermaid": "Mermaid", + "themeNamePumpkin": "Pumpkin", + "themeNameGhost": "Ghost", + "themeNameVampire": "Vampire", + "themeNameWitch": "Witch", + "themeNameCwtch": "Cwtch", "settingDownloadFolder": "Download Folder", "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", "settingImagePreviews": "Image Previews and Profile Pictures", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index aecde383..93b66ab4 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1,26 +1,38 @@ { "@@locale": "ru", - "@@last_modified": "2021-12-15T02:16:43+01:00", - "settingDownloadFolder": "Download Folder", - "settingImagePreviewsDescription": "Images will be downloaded and previewed automatically. Please note that image previews can often lead to security vulnerabilities, and you should not enable this Experiment if you use Cwtch with untrusted contacts. Profile pictures are planned for Cwtch 1.6.", - "settingImagePreviews": "Image Previews and Profile Pictures", - "experimentClickableLinksDescription": "The clickable links experiment allows you to click on URLs shared in messages", - "enableExperimentClickableLinks": "Enable Clickable Links", - "serverConnectionsLabel": "Connection", - "serverTotalMessagesLabel": "Total Messages", - "serverMetricsLabel": "Server Metrics", - "saveBtn": "Сохранить", + "@@last_modified": "2021-12-16T21:50:40+01:00", + "themeColorLabel": "Тема", + "themeNameNeon2": "Неон2", + "themeNameNeon1": "Неон1", + "themeNameMidnight": "Полночь", + "themeNameMermaid": "Русалка", + "themeNamePumpkin": "Тыква", + "themeNameGhost": "Призрак", + "themeNameVampire": "Вампир", + "themeNameWitch": "Ведьма", + "themeNameCwtch": "Cwtch", + "settingDownloadFolder": "Скачать папку", + "settingImagePreviewsDescription": "Автоматическая загрузка изображений. Обратите внимание, что предварительный просмотр изображений часто может использоваться для взлома или деаномизации. Не используйте данную функцию если Вы контактируете с ненадежными контактами. Аватары профиля запланированы для версии Cwtch 1.6.", + "settingImagePreviews": "Предпросмотр изображений и фотографий профиля", + "experimentClickableLinksDescription": "Экспериментальная функция которая позволяет нажимать на URL адреса в сообщениях", + "enableExperimentClickableLinks": "Включить кликабельные ссылки", + "serverConnectionsLabel": "Соединение", + "serverTotalMessagesLabel": "Всего сообщений", + "serverMetricsLabel": "Показатели сервера", + "manageKnownServersShort": "Серверы", + "manageKnownServersLong": "Управление серверами", + "displayNameTooltip": "Введите отображаемое имя", + "manageKnownServersButton": "Управление серверами", + "fieldDescriptionLabel": "Описание", + "groupsOnThisServerLabel": "Группы, в которых я нахожусь, размещены на этом сервере", + "importLocalServerButton": "Импорт %1", + "importLocalServerSelectText": "Выбрать локальный сервер", + "importLocalServerLabel": "Импортировать локальный сервер", + "newMessagesLabel": "Новое сообщение", + "localeRU": "Русский", "profileOnionLabel": "Send this address to contacts you want to connect with", - "manageKnownServersShort": "Servers", - "manageKnownServersLong": "Manage Known Servers", - "displayNameTooltip": "Please enter a display name", - "manageKnownServersButton": "Manage Known Servers", - "fieldDescriptionLabel": "Description", - "groupsOnThisServerLabel": "Groups I am in hosted on this server", - "importLocalServerButton": "Import %1", - "importLocalServerSelectText": "Select Local Server", - "importLocalServerLabel": "Import a locally hosted server", - "newMessagesLabel": "New Messages", + "savePeerHistory": "Хранить историю", + "saveBtn": "Сохранить", "networkStatusOnline": "В сети", "copiedToClipboardNotification": "Скопировано в буфер обмена", "defaultProfileName": "Алиса", @@ -29,7 +41,6 @@ "groupNameLabel": "Имя группы", "serverLabel": "Сервер", "copyBtn": "Копировать", - "localeRU": "Russian", "copyServerKeys": "Копировать ключи", "verfiyResumeButton": "Проверить\/продолжить", "fileCheckingStatus": "Проверка статуса загрузки", @@ -210,7 +221,6 @@ "unblockBtn": "Разблокировать контакт", "dontSavePeerHistory": "Удалить историю", "savePeerHistoryDescription": "Определяет политуку хранения или удаления переписки с данным контактом.", - "savePeerHistory": "Хранить исторую", "blockBtn": "Заблокировать контакт", "displayNameLabel": "Отображаемое имя", "addressLabel": "Адрес", From df3617c5a158a16aae084e802a9916635b97fafe Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 16 Dec 2021 16:54:53 -0800 Subject: [PATCH 10/19] Fixup Preview Dialog --- lib/cwtch/gomobile.dart | 5 ++++- lib/views/globalsettingsview.dart | 12 ++++++------ lib/widgets/filebubble.dart | 31 ++++++++++++------------------- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index c121ff28..36f4c24a 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -151,7 +151,10 @@ class CwtchGomobile implements Cwtch { // ignore: non_constant_identifier_names void ExportPreviewedFile(String sourceFile, String suggestion) { - cwtchPlatform.invokeMethod("ExportPreviewedFile", {"Path": sourceFile, "FileName": suggestion,}); + cwtchPlatform.invokeMethod("ExportPreviewedFile", { + "Path": sourceFile, + "FileName": suggestion, + }); } @override diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index 5b4d5d64..f473e223 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -250,12 +250,12 @@ class _GlobalSettingsViewState extends State { Visibility( visible: settings.isExperimentEnabled(ImagePreviewsExperiment) && !Platform.isAndroid, child: CwtchFolderPicker( - label: AppLocalizations.of(context)!.settingDownloadFolder, - initialValue: settings.downloadPath, - onSave: (newVal) { - settings.downloadPath = newVal; - saveSettings(context); - }, + label: AppLocalizations.of(context)!.settingDownloadFolder, + initialValue: settings.downloadPath, + onSave: (newVal) { + settings.downloadPath = newVal; + saveSettings(context); + }, ), ), SwitchListTile( diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index a6e7c999..b29ef870 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -80,7 +80,7 @@ class FileBubbleState extends State { senderDisplayStr = Provider.of(context).senderHandle; } } - return LayoutBuilder(builder: (context, constraints) { + return LayoutBuilder(builder: (bcontext, constraints) { var wdgSender = Center( widthFactor: 1, child: SelectableText(senderDisplayStr + '\u202F', @@ -117,7 +117,7 @@ class FileBubbleState extends State { }, ), onTap: () { - pop(myFile!, wdgMessage); + pop(bcontext, myFile!, wdgMessage); }, ); } else { @@ -330,30 +330,23 @@ class FileBubbleState extends State { ); } - void pop(File myFile, Widget meta) async { + void pop(context, File myFile, Widget meta) async { await showDialog( context: context, builder: (_) => Dialog( - child: Container( + alignment: Alignment.center, + 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, cacheHeight: 1024, cacheWidth: 1024).image, - fit: BoxFit.fitWidth, - filterQuality: FilterQuality.none, - )), - ), - Visibility( - visible: Platform.isAndroid, - child: IconButton(icon: Icon(Icons.arrow_downward), onPressed: androidExport) + Image.file( + myFile, + cacheHeight: (MediaQuery.of(context).size.height * 0.6).floor(), + cacheWidth: (MediaQuery.of(context).size.width * 0.6).floor(), + fit: BoxFit.scaleDown, ), + Visibility(visible: !Platform.isAndroid, child: Text(myFile.path, textAlign: TextAlign.center)), + Visibility(visible: Platform.isAndroid, child: IconButton(icon: Icon(Icons.arrow_downward), onPressed: androidExport)), ]), ))); } From 6e9fb6810e57eab1040ded5227a18ec8b3d6a78d Mon Sep 17 00:00:00 2001 From: erinn Date: Thu, 16 Dec 2021 17:04:29 -0800 Subject: [PATCH 11/19] message previews --- lib/cwtch/cwtchNotifier.dart | 6 ++++-- lib/model.dart | 4 ++-- lib/models/message.dart | 9 +++++---- lib/models/messages/filemessage.dart | 3 ++- lib/settings.dart | 6 +----- lib/widgets/filebubble.dart | 10 +++++++--- 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index be5429bf..67add61d 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -135,6 +135,7 @@ class CwtchNotifier { var timestamp = DateTime.tryParse(data['TimestampReceived'])!; var senderHandle = data['RemotePeer']; var senderImage = data['Picture']; + var isAuto = data['Auto'] == "true"; // We might not have received a contact created for this contact yet... // In that case the **next** event we receive will actually update these values... @@ -145,7 +146,7 @@ class CwtchNotifier { profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.newMarker++; } profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, DateTime.now()); - profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.updateMessageCache(identifier, messageID, timestamp, senderHandle, senderImage, data["Data"]); + profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.updateMessageCache(identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data["Data"]); // We only ever see messages from authenticated peers. // If the contact is marked as offline then override this - can happen when the contact is removed from the front @@ -191,10 +192,11 @@ class CwtchNotifier { var senderImage = data['Picture']; var timestampSent = DateTime.tryParse(data['TimestampSent'])!; var currentTotal = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages; + var isAuto = data['Auto'] == "true"; // Only bother to do anything if we know about the group and the provided index is greater than our current total... if (currentTotal != null && idx >= currentTotal) { - profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.updateMessageCache(identifier, idx, timestampSent, senderHandle, senderImage, data["Data"]); + profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.updateMessageCache(identifier, idx, timestampSent, senderHandle, senderImage, isAuto, data["Data"]); //if not currently open if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != identifier) { diff --git a/lib/model.dart b/lib/model.dart index c72a45d2..fe95d93f 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -706,8 +706,8 @@ class ContactInfoState extends ChangeNotifier { return ret; } - void updateMessageCache(int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, String data) { - this.messageCache.insert(0, MessageCache(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false), data)); + void updateMessageCache(int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data) { + this.messageCache.insert(0, MessageCache(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false, isAuto), data)); this.totalMessages += 1; } diff --git a/lib/models/message.dart b/lib/models/message.dart index 6fb1028b..5c3b322c 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -79,7 +79,7 @@ Future messageHandler(BuildContext context, String profileOnion, int co } return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) { - var metadata = MessageMetadata(profileOnion, conversationIdentifier, index, DateTime.now(), "", "", "", {}, false, true); + var metadata = MessageMetadata(profileOnion, conversationIdentifier, index, DateTime.now(), "", "", "", {}, false, true, false); try { dynamic messageWrapper = jsonDecode(rawMessageEnvelope); // There are 2 conditions in which this error condition can be met: @@ -107,7 +107,7 @@ Future messageHandler(BuildContext context, String profileOnion, int co var ackd = messageWrapper['Acknowledged']; var error = messageWrapper['Error'] != null; var signature = messageWrapper['Signature']; - metadata = MessageMetadata(profileOnion, conversationIdentifier, messageID, timestamp, senderHandle, senderImage, signature, attributes, ackd, error); + metadata = MessageMetadata(profileOnion, conversationIdentifier, messageID, timestamp, senderHandle, senderImage, signature, attributes, ackd, error, false); return compileOverlay(metadata, messageWrapper['Message']); } catch (e) { @@ -116,7 +116,7 @@ Future messageHandler(BuildContext context, String profileOnion, int co } }); } catch (e) { - return Future.value(MalformedMessage(MessageMetadata(profileOnion, conversationIdentifier, -1, DateTime.now(), "", "", "", {}, false, true))); + return Future.value(MalformedMessage(MessageMetadata(profileOnion, conversationIdentifier, -1, DateTime.now(), "", "", "", {}, false, true, false))); } } @@ -132,6 +132,7 @@ class MessageMetadata extends ChangeNotifier { final dynamic _attributes; bool _ackd; bool _error; + final bool isAuto; final String? signature; @@ -149,5 +150,5 @@ class MessageMetadata extends ChangeNotifier { notifyListeners(); } - MessageMetadata(this.profileOnion, this.conversationIdentifier, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._attributes, this._ackd, this._error); + MessageMetadata(this.profileOnion, this.conversationIdentifier, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._attributes, this._ackd, this._error, this.isAuto); } diff --git a/lib/models/messages/filemessage.dart b/lib/models/messages/filemessage.dart index a0eba982..52e455fc 100644 --- a/lib/models/messages/filemessage.dart +++ b/lib/models/messages/filemessage.dart @@ -34,7 +34,7 @@ class FileMessage extends Message { return MessageRow(MalformedBubble()); } - return MessageRow(FileBubble(nameSuggestion, rootHash, nonce, fileSize), key: key); + return MessageRow(FileBubble(nameSuggestion, rootHash, nonce, fileSize, isAuto: metadata.isAuto), key: key); }); } @@ -59,6 +59,7 @@ class FileMessage extends Message { rootHash, nonce, fileSize, + isAuto: metadata.isAuto, interactive: false, ); }); diff --git a/lib/settings.dart b/lib/settings.dart index 7d46ab3f..329871d2 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -93,11 +93,7 @@ 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"]); + // auto-download folder _downloadPath = settings["DownloadPath"] ?? ""; // Push the experimental settings to Consumers of Settings diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index a6e7c999..2f093f08 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -23,8 +23,9 @@ class FileBubble extends StatefulWidget { final String nonce; final int fileSize; final bool interactive; + final bool isAuto; - FileBubble(this.nameSuggestion, this.rootHash, this.nonce, this.fileSize, {this.interactive = true}); + FileBubble(this.nameSuggestion, this.rootHash, this.nonce, this.fileSize, {this.isAuto = false, this.interactive = true}); @override FileBubbleState createState() => FileBubbleState(); @@ -52,9 +53,10 @@ class FileBubbleState extends State { var downloadComplete = Provider.of(context).downloadComplete(widget.fileKey()); var downloadInterrupted = Provider.of(context).downloadInterrupted(widget.fileKey()); - if (downloadInterrupted) { + if (flagStarted && !downloadInterrupted) { Provider.of(context, listen: false).cwtch.CheckDownloadStatus(Provider.of(context, listen: false).onion, widget.fileKey()); } + var path = Provider.of(context).downloadFinalPath(widget.fileKey()); if (downloadComplete) { var lpath = path!.toLowerCase(); @@ -144,12 +146,14 @@ class FileBubbleState extends State { ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton)) ]); } - } else { + } else if (!widget.isAuto) { wdgDecorations = Center( widthFactor: 1, child: Wrap(children: [ Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F'), onPressed: _btnAccept)), ])); + } else { + wdgDecorations = Container(); } return Container( From c78dbf0ac367ae972ce4b59964c522798564d49e Mon Sep 17 00:00:00 2001 From: erinn Date: Fri, 17 Dec 2021 16:32:22 -0800 Subject: [PATCH 12/19] fileshare confirmations and contact-add-gating --- lib/views/messageview.dart | 78 ++++++++++++++++++++++++++++++++++--- lib/widgets/filebubble.dart | 26 +++++++++---- 2 files changed, 91 insertions(+), 13 deletions(-) diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index 539bebfb..270ca4a4 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -5,6 +5,7 @@ import 'package:cwtch/config.dart'; import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/messages/quotedmessage.dart'; +import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/messageloadingbubble.dart'; import 'package:cwtch/widgets/profileimage.dart'; @@ -37,6 +38,7 @@ class _MessageViewState extends State { int selectedContact = -1; ItemPositionsListener scrollListener = ItemPositionsListener.create(); ItemScrollController scrollController = ItemScrollController(); + File? imagePreview; @override void initState() { @@ -85,7 +87,7 @@ class _MessageViewState extends State { appBarButtons.add(IconButton( icon: Icon(Icons.attach_file, size: 24), tooltip: AppLocalizations.of(context)!.tooltipSendFile, - onPressed: _showFilePicker, + onPressed: (){_showFilePicker(context);}, )); } appBarButtons.add(IconButton( @@ -354,7 +356,7 @@ class _MessageViewState extends State { return contact.onion != Provider.of(context).onion; }, onChanged: (newVal) { setState(() { - this.selectedContact = Provider.of(context).contactList.findContact(newVal)!.identifier; + this.selectedContact = Provider.of(context, listen: false).contactList.findContact(newVal)!.identifier; }); })), SizedBox( @@ -375,7 +377,8 @@ class _MessageViewState extends State { }); } - void _showFilePicker() async { + void _showFilePicker(BuildContext ctx) async { + imagePreview = null; FilePickerResult? result = await FilePicker.platform.pickFiles(); if (result != null) { File file = File(result.files.first.path); @@ -383,11 +386,74 @@ class _MessageViewState extends State { // a manifest (see : https://git.openprivacy.ca/cwtch.im/cwtch/src/branch/master/protocol/files/manifest.go#L25) if (file.lengthSync() <= 10737418240) { print("Sending " + file.path); - _sendFile(file.path); + _confirmFileSend(ctx, file.path); } else { - print("file size cannot exceed 10 gigabytes"); - //todo: toast error + final snackBar = SnackBar( + content: Text("File size cannot exceed 10 GB"), + duration: Duration(seconds: 4), + ); + ScaffoldMessenger.of(ctx).showSnackBar(snackBar); } } } + + void _confirmFileSend(BuildContext ctx, String path) async { + showModalBottomSheet( + context: ctx, + builder: (BuildContext bcontext) { + var lpath = path.toLowerCase(); + var showPreview = false; + if (Provider.of(context, listen: false).isExperimentEnabled(ImagePreviewsExperiment) && (lpath.endsWith("jpg") || lpath.endsWith("jpeg") || lpath.endsWith("png") || lpath.endsWith("gif") || lpath.endsWith("webp") || lpath.endsWith("bmp"))) { + showPreview = true; + if (imagePreview == null) { + imagePreview = new File(path); + } + } + return Container( + height: 300, // bespoke value courtesy of the [TextField] docs + child: Center( + child: Padding( + padding: EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text("Are you sure you want to send $path?"), + SizedBox( + height: 20, + ), + Visibility(visible: showPreview, child: showPreview ? Image.file( + imagePreview!, + cacheHeight: 150, // limit the amount of space the image can decode too, we keep this high-ish to allow quality previews... + filterQuality: FilterQuality.medium, + fit: BoxFit.fill, + alignment: Alignment.center, + height: 150, + isAntiAlias: false, + errorBuilder: (context, error, stackTrace) { + return MalformedBubble(); + }, + ) : Container()), + Visibility(visible: showPreview, child: SizedBox(height: 10,)), + Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + ElevatedButton( + child: Text("Cancel", semanticsLabel: "Cancel"), + onPressed: () { + Navigator.pop(bcontext); + }, + ), + SizedBox(width: 20,), + ElevatedButton( + child: Text("Send File", semanticsLabel: "Send File"), + onPressed: () { + _sendFile(path); + Navigator.pop(bcontext); + }, + ), + ]), + ], + )), + )); + }); + } } diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index a3840f72..90f04423 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -49,6 +49,7 @@ class FileBubbleState extends State { var flagStarted = Provider.of(context).attributes["file-downloaded"] == "true"; var borderRadiousEh = 15.0; var showFileSharing = Provider.of(context).isExperimentEnabled(FileSharingExperiment); + var showImagePreviews = Provider.of(context).isExperimentEnabled(ImagePreviewsExperiment); var prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(Provider.of(context).timestamp); var downloadComplete = Provider.of(context).downloadComplete(widget.fileKey()); var downloadInterrupted = Provider.of(context).downloadInterrupted(widget.fileKey()); @@ -74,10 +75,12 @@ class FileBubbleState extends State { // If the sender is not us, then we want to give them a nickname... var senderDisplayStr = ""; + var senderIsContact = false; if (!fromMe) { ContactInfoState? contact = Provider.of(context).contactList.findContact(Provider.of(context).senderHandle); if (contact != null) { senderDisplayStr = contact.nickname; + senderIsContact = true; } else { senderDisplayStr = Provider.of(context).senderHandle; } @@ -103,7 +106,7 @@ class FileBubbleState extends State { } else if (downloadComplete) { // in this case, whatever marked download.complete would have also set the path var lpath = path!.toLowerCase(); - if (lpath.endsWith("jpg") || lpath.endsWith("jpeg") || lpath.endsWith("png") || lpath.endsWith("gif") || lpath.endsWith("webp") || lpath.endsWith("bmp")) { + if (showImagePreviews && (lpath.endsWith("jpg") || lpath.endsWith("jpeg") || lpath.endsWith("png") || lpath.endsWith("gif") || lpath.endsWith("webp") || lpath.endsWith("bmp"))) { isPreview = true; wdgDecorations = GestureDetector( child: Image.file( @@ -138,14 +141,23 @@ class FileBubbleState extends State { // 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'); + wdgDecorations = Text( + AppLocalizations.of(context)!.fileCheckingStatus + '...' + + '\u202F'); } else { - var path = Provider.of(context).downloadFinalPath(widget.fileKey()) ?? ""; - wdgDecorations = Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'), - ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton)) - ]); + var path = Provider.of(context).downloadFinalPath( + widget.fileKey()) ?? ""; + wdgDecorations = + Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + + path + '\u202F'), + ElevatedButton(onPressed: _btnResume, + child: Text( + AppLocalizations.of(context)!.verfiyResumeButton)) + ]); } + } else if (!senderIsContact) { + wdgDecorations = Text("Add this account to your contacts in order to accept this file."); } else if (!widget.isAuto) { wdgDecorations = Center( widthFactor: 1, From 6f3d5b65cd9f158616f86adff007a1cae1f0d1aa Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 17 Dec 2021 16:54:30 -0800 Subject: [PATCH 13/19] preview --- lib/cwtch/cwtch.dart | 4 +- lib/cwtch/cwtchNotifier.dart | 5 +- lib/cwtch/ffi.dart | 23 ++++++-- lib/cwtch/gomobile.dart | 5 ++ lib/errorHandler.dart | 26 +++++++++ lib/models/message.dart | 3 +- lib/models/messages/filemessage.dart | 18 ++++--- lib/views/addeditprofileview.dart | 37 +++++++++---- lib/widgets/filebubble.dart | 79 ++++++++++++++++------------ 9 files changed, 141 insertions(+), 59 deletions(-) diff --git a/lib/cwtch/cwtch.dart b/lib/cwtch/cwtch.dart index 8a2c740e..47e14a2a 100644 --- a/lib/cwtch/cwtch.dart +++ b/lib/cwtch/cwtch.dart @@ -15,7 +15,9 @@ abstract class Cwtch { // ignore: non_constant_identifier_names void LoadProfiles(String pass); // ignore: non_constant_identifier_names - void DeleteProfile(String onion, String pass); + void DeleteProfile(String profile, String pass); + // ignore: non_constant_identifier_names + void ChangePassword(String profile, String pass, String newpass, String newpassAgain); // ignore: non_constant_identifier_names void ResetTor(); diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index 67add61d..0734ed0a 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -224,7 +224,10 @@ class CwtchNotifier { } break; case "SendMessageToPeerError": - // Ignore + // Ignore dealt with by IndexedFailure + break; + case "SendMessageToGroupError": + // Ignore dealt with by IndexedFailure break; case "IndexedFailure": var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]); diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index 3c829794..75f9b3c3 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -115,13 +115,13 @@ class CwtchFfi implements Cwtch { } CwtchFfi(CwtchNotifier _cwtchNotifier) { - String library_path = getLibraryPath(); - if (library_path == UNSUPPORTED_OS) { + String libraryPath = getLibraryPath(); + if (libraryPath == UNSUPPORTED_OS) { print("OS ${Platform.operatingSystem} not supported by cwtch/ffi"); // emergency, ideally the app stays on splash and just posts the error till user closes exit(0); } - library = DynamicLibrary.open(library_path); + library = DynamicLibrary.open(libraryPath); cwtchNotifier = _cwtchNotifier; } @@ -716,4 +716,21 @@ class CwtchFfi implements Cwtch { malloc.free(utf8profile); return jsonMessage; } + + @override + // ignore: non_constant_identifier_names + void ChangePassword(String profile, String pass, String newpass, String newpassAgain) { + var changePasswordC = library.lookup>("c_ChangePassword"); + // ignore: non_constant_identifier_names + final ChangePasswordFn = changePasswordC.asFunction(); + final utf8profile = profile.toNativeUtf8(); + final utf8pass = pass.toNativeUtf8(); + final utf8newpass = newpass.toNativeUtf8(); + final utf8newpasssagain = newpassAgain.toNativeUtf8(); + ChangePasswordFn(utf8profile, utf8profile.length, utf8pass, utf8pass.length, utf8newpass, utf8newpass.length, utf8newpasssagain, utf8newpasssagain.length); + malloc.free(utf8profile); + malloc.free(utf8pass); + malloc.free(utf8newpass); + malloc.free(utf8newpasssagain); + } } diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index 36f4c24a..29bf082f 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -284,4 +284,9 @@ class CwtchGomobile implements Cwtch { String defaultDownloadPath() { return this.androidHomeDirectoryStr; } + + @override + void ChangePassword(String profile, String pass, String newpass, String newpassAgain) { + cwtchPlatform.invokeMethod("ChangePassword", {"ProfileOnion": profile, "OldPass": pass, "NewPass": newpass, "NewPassAgain": newpassAgain}); + } } diff --git a/lib/errorHandler.dart b/lib/errorHandler.dart index a54094c7..156b708a 100644 --- a/lib/errorHandler.dart +++ b/lib/errorHandler.dart @@ -6,12 +6,17 @@ class ErrorHandler extends ChangeNotifier { // Add Contact Specific Errors... static const String addContactErrorPrefix = "addcontact"; + static const String changePasswordErrorPrefix = "changepassword"; static const String invalidImportStringErrorType = "invalid_import_string"; static const String contactAlreadyExistsErrorType = "contact_already_exists"; bool invalidImportStringError = false; bool contactAlreadyExistsError = false; bool explicitAddContactSuccess = false; + // ChangePassword + bool changePasswordError = false; + bool explicitChangePasswordSuccess = false; + // Import Bundle Specific Errors static const String importBundleErrorPrefix = "importBundle"; bool importBundleError = false; @@ -39,6 +44,9 @@ class ErrorHandler extends ChangeNotifier { deletedServerError = false; deletedServerSuccess = false; + changePasswordError = false; + explicitChangePasswordSuccess = false; + notifyListeners(); } @@ -58,6 +66,9 @@ class ErrorHandler extends ChangeNotifier { case deleteProfileErrorPrefix: handleDeleteProfileError(errorType); break; + case changePasswordErrorPrefix: + handleChangePasswordError(errorType); + break; case deletedServerErrorPrefix: handleDeletedServerError(errorType); } @@ -115,6 +126,21 @@ class ErrorHandler extends ChangeNotifier { } } + handleChangePasswordError(String errorType) { + // Reset add contact errors + changePasswordError = false; + explicitChangePasswordSuccess = false; + + switch (errorType) { + case successErrorType: + explicitChangePasswordSuccess = true; + break; + default: + changePasswordError = true; + break; + } + } + handleDeletedServerError(String errorType) { // reset deletedServerError = false; diff --git a/lib/models/message.dart b/lib/models/message.dart index 5c3b322c..dc4c5b97 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -150,5 +150,6 @@ class MessageMetadata extends ChangeNotifier { notifyListeners(); } - MessageMetadata(this.profileOnion, this.conversationIdentifier, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._attributes, this._ackd, this._error, this.isAuto); + MessageMetadata( + this.profileOnion, this.conversationIdentifier, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._attributes, this._ackd, this._error, this.isAuto); } diff --git a/lib/models/messages/filemessage.dart b/lib/models/messages/filemessage.dart index 52e455fc..83387ac9 100644 --- a/lib/models/messages/filemessage.dart +++ b/lib/models/messages/filemessage.dart @@ -54,14 +54,16 @@ class FileMessage extends Message { if (!validHash(rootHash, nonce)) { return MessageRow(MalformedBubble()); } - return FileBubble( - nameSuggestion, - rootHash, - nonce, - fileSize, - isAuto: metadata.isAuto, - interactive: false, - ); + return Container( + alignment: Alignment.center, + child: FileBubble( + nameSuggestion, + rootHash, + nonce, + fileSize, + isAuto: metadata.isAuto, + interactive: false, + )); }); } diff --git a/lib/views/addeditprofileview.dart b/lib/views/addeditprofileview.dart index 8065c9b4..0f8a3dd5 100644 --- a/lib/views/addeditprofileview.dart +++ b/lib/views/addeditprofileview.dart @@ -1,6 +1,8 @@ import 'dart:convert'; +import 'dart:io'; import 'dart:math'; +import 'package:cwtch/config.dart'; import 'package:cwtch/cwtch/cwtch.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -278,7 +280,7 @@ class _AddEditProfileViewState extends State { // TODO Toast } - void _createPressed() { + void _createPressed() async { // This will run all the validations in the form including // checking that display name is not empty, and an actual check that the passwords // match (and are provided if the user has requested an encrypted profile). @@ -301,17 +303,32 @@ class _AddEditProfileViewState extends State { } else { // At this points passwords have been validated to be the same and not empty // Update both password and name, even if name hasn't been changed... + var profile = Provider.of(context, listen: false).onion; Provider.of(context, listen: false).nickname = ctrlrNick.value.text; - Provider.of(context, listen: false).cwtch.SetProfileAttribute(Provider.of(context, listen: false).onion, "profile.name", ctrlrNick.value.text); - final updatePasswordEvent = { - "EventType": "ChangePassword", - "Data": {"Password": ctrlrOldPass.text, "NewPassword": ctrlrPass.text} - }; - final updatePasswordEventJson = jsonEncode(updatePasswordEvent); + Provider.of(context, listen: false).cwtch.SetProfileAttribute(profile, "profile.name", ctrlrNick.value.text); + Provider.of(context, listen: false).cwtch.ChangePassword(profile, ctrlrOldPass.text, ctrlrPass.text, ctrlrPass2.text); - Provider.of(context, listen: false).cwtch.SendProfileEvent(Provider.of(context, listen: false).onion, updatePasswordEventJson); - - Navigator.of(context).pop(); + EnvironmentConfig.debugLog("waiting for change password response"); + Future.delayed(const Duration(milliseconds: 500), () { + if (globalErrorHandler.changePasswordError) { + // TODO: This isn't ideal, but because onChange can be fired during this future check + // and because the context can change after being popped we have this kind of double assertion... + // There is probably a better pattern to handle this... + if (AppLocalizations.of(context) != null) { + final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.passwordChangeError)); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + Navigator.pop(context); + return; + } + } + }).whenComplete(() { + if (globalErrorHandler.explicitChangePasswordSuccess) { + final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.newPassword)); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + Navigator.pop(context); + return; // otherwise round and round we go... + } + }); } } } diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index a3840f72..e824baf9 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -83,8 +83,8 @@ class FileBubbleState extends State { } } return LayoutBuilder(builder: (bcontext, constraints) { - var wdgSender = Center( - widthFactor: 1, + var wdgSender = Visibility( + visible: widget.interactive, child: SelectableText(senderDisplayStr + '\u202F', style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of(context).theme.messageFromMeTextColor() : Provider.of(context).theme.messageFromOtherTextColor()))); @@ -99,40 +99,47 @@ class FileBubbleState extends State { if (!showFileSharing) { wdgDecorations = Text('\u202F'); } else if (fromMe) { - wdgDecorations = MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, prettyDate: prettyDate); + wdgDecorations = Visibility( + visible: widget.interactive, + child: MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, prettyDate: prettyDate)); } else if (downloadComplete) { // in this case, whatever marked download.complete would have also set the path var lpath = path!.toLowerCase(); if (lpath.endsWith("jpg") || lpath.endsWith("jpeg") || lpath.endsWith("png") || lpath.endsWith("gif") || lpath.endsWith("webp") || lpath.endsWith("bmp")) { isPreview = true; - wdgDecorations = GestureDetector( - 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.fill, - alignment: Alignment.center, - width: constraints.maxWidth, - isAntiAlias: false, - errorBuilder: (context, error, stackTrace) { - return MalformedBubble(); - }, - ), + wdgDecorations = Center( + 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.cover, + 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 { - wdgDecorations = Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F'); + wdgDecorations = Visibility(visible: widget.interactive, child: Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F')); } } else if (downloadActive) { if (!downloadGotManifest) { - wdgDecorations = Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F'); + wdgDecorations = Visibility(visible: widget.interactive, child: Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F')); } else { - wdgDecorations = LinearProgressIndicator( - value: Provider.of(context).downloadProgress(widget.fileKey()), - color: Provider.of(context).theme.defaultButtonActiveColor(), - ); + wdgDecorations = Visibility( + visible: widget.interactive, + child: LinearProgressIndicator( + value: Provider.of(context).downloadProgress(widget.fileKey()), + color: Provider.of(context).theme.defaultButtonActiveColor(), + )); } } else if (flagStarted) { // in this case, the download was done in a previous application launch, @@ -141,17 +148,21 @@ class FileBubbleState extends State { wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F'); } else { var path = Provider.of(context).downloadFinalPath(widget.fileKey()) ?? ""; - wdgDecorations = Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'), - ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton)) - ]); + 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 (!widget.isAuto) { - wdgDecorations = Center( - widthFactor: 1, - child: Wrap(children: [ - Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F'), onPressed: _btnAccept)), - ])); + 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)), + ]))); } else { wdgDecorations = Container(); } @@ -174,9 +185,7 @@ class FileBubbleState extends State { crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, - children: fromMe - ? [wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)] - : [wdgSender, isPreview ? Container() : wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)]), + children: fromMe ? [wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)] : [wdgSender, isPreview ? Container() : wdgMessage, wdgDecorations]), )); }); } From 845ce3b5464d02a55de408cdc1fddf9f5afa3a4f Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 17 Dec 2021 17:02:24 -0800 Subject: [PATCH 14/19] Scaling + Android ChangePassword --- lib/widgets/filebubble.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index e824baf9..eeb412d1 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -115,7 +115,7 @@ class FileBubbleState extends State { 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.cover, + fit: BoxFit.scaleDown, alignment: Alignment.center, height: MediaQuery.of(bcontext).size.height * 0.30, isAntiAlias: false, @@ -354,8 +354,9 @@ class FileBubbleState extends State { meta, Image.file( myFile, - cacheHeight: (MediaQuery.of(context).size.height * 0.6).floor(), cacheWidth: (MediaQuery.of(context).size.width * 0.6).floor(), + width: (MediaQuery.of(context).size.width * 0.6), + height: (MediaQuery.of(context).size.height * 0.6), fit: BoxFit.scaleDown, ), Visibility(visible: !Platform.isAndroid, child: Text(myFile.path, textAlign: TextAlign.center)), From d329e7cc06bcf6474fcd32dc7ee3196fcd58adea Mon Sep 17 00:00:00 2001 From: erinn Date: Sat, 18 Dec 2021 17:43:32 -0800 Subject: [PATCH 15/19] image previews - dan comments - wip --- lib/cwtch/cwtch.dart | 2 ++ lib/cwtch/ffi.dart | 2 +- lib/model.dart | 3 +-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/cwtch/cwtch.dart b/lib/cwtch/cwtch.dart index 8a2c740e..67d77c08 100644 --- a/lib/cwtch/cwtch.dart +++ b/lib/cwtch/cwtch.dart @@ -49,12 +49,14 @@ abstract class Cwtch { void ShareFile(String profile, int handle, String filepath); // ignore: non_constant_identifier_names void DownloadFile(String profile, int handle, String filepath, String manifestpath, String filekey); + // android-only // ignore: non_constant_identifier_names void CreateDownloadableFile(String profile, int handle, String filenameSuggestion, String filekey); // ignore: non_constant_identifier_names void CheckDownloadStatus(String profile, String fileKey); // ignore: non_constant_identifier_names void VerifyOrResumeDownload(String profile, int handle, String filekey); + // android-only // ignore: non_constant_identifier_names void ExportPreviewedFile(String sourceFile, String suggestion); diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index 3c829794..84d1a559 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -700,7 +700,7 @@ class CwtchFfi implements Cwtch { @override String defaultDownloadPath() { Map envVars = Platform.environment; - return path.join(envVars['HOME']!, "Downloads"); + return path.join(envVars[Platform.isWindows ? 'UserProfile' : 'HOME']!, "Downloads"); } @override diff --git a/lib/model.dart b/lib/model.dart index fe95d93f..6c1ddd7b 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -399,15 +399,14 @@ class ProfileInfoState extends ChangeNotifier { if (progress < 0) { this._downloads[fileKey]!.interrupted = true; } - notifyListeners(); } else { if (this._downloads[fileKey]!.interrupted) { this._downloads[fileKey]!.interrupted = false; } this._downloads[fileKey]!.chunksDownloaded = progress; this._downloads[fileKey]!.chunksTotal = numChunks; - notifyListeners(); } + notifyListeners(); } void downloadMarkManifest(String fileKey) { From 26c84472d8726c9a32061a46bfdceb91b84666e2 Mon Sep 17 00:00:00 2001 From: erinn Date: Sat, 18 Dec 2021 18:02:44 -0800 Subject: [PATCH 16/19] l10n --- lib/l10n/intl_de.arb | 8 +++++++- lib/l10n/intl_en.arb | 8 +++++++- lib/l10n/intl_es.arb | 8 +++++++- lib/l10n/intl_fr.arb | 8 +++++++- lib/l10n/intl_it.arb | 8 +++++++- lib/l10n/intl_pl.arb | 8 +++++++- lib/l10n/intl_pt.arb | 8 +++++++- lib/l10n/intl_ru.arb | 8 +++++++- 8 files changed, 56 insertions(+), 8 deletions(-) diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 7f6185a9..feac77a3 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,6 +1,12 @@ { "@@locale": "de", - "@@last_modified": "2021-12-16T21:50:40+01:00", + "@@last_modified": "2021-12-19T02:59:05+01:00", + "msgAddToAccept": "Add this account to your contacts in order to accept this file.", + "btnSendFile": "Send File", + "msgConfirmSend": "Are you sure you want to send", + "msgFileTooBig": "File size cannot exceed 10 GB", + "storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...", + "loadingCwtch": "Loading Cwtch...", "themeColorLabel": "Color Theme", "themeNameNeon2": "Neon2", "themeNameNeon1": "Neon1", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 1c306294..71c05d12 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1,6 +1,12 @@ { "@@locale": "en", - "@@last_modified": "2021-12-16T21:50:40+01:00", + "@@last_modified": "2021-12-19T02:59:05+01:00", + "msgAddToAccept": "Add this account to your contacts in order to accept this file.", + "btnSendFile": "Send File", + "msgConfirmSend": "Are you sure you want to send", + "msgFileTooBig": "File size cannot exceed 10 GB", + "storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...", + "loadingCwtch": "Loading Cwtch...", "themeColorLabel": "Color Theme", "themeNameNeon2": "Neon2", "themeNameNeon1": "Neon1", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index f2272aa0..c865430d 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,6 +1,12 @@ { "@@locale": "es", - "@@last_modified": "2021-12-16T21:50:40+01:00", + "@@last_modified": "2021-12-19T02:59:05+01:00", + "msgAddToAccept": "Add this account to your contacts in order to accept this file.", + "btnSendFile": "Send File", + "msgConfirmSend": "Are you sure you want to send", + "msgFileTooBig": "File size cannot exceed 10 GB", + "storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...", + "loadingCwtch": "Loading Cwtch...", "themeColorLabel": "Color Theme", "themeNameNeon2": "Neon2", "themeNameNeon1": "Neon1", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index be4c5d78..6891a05a 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,6 +1,12 @@ { "@@locale": "fr", - "@@last_modified": "2021-12-16T21:50:40+01:00", + "@@last_modified": "2021-12-19T02:59:05+01:00", + "msgAddToAccept": "Add this account to your contacts in order to accept this file.", + "btnSendFile": "Send File", + "msgConfirmSend": "Are you sure you want to send", + "msgFileTooBig": "File size cannot exceed 10 GB", + "storageMigrationModalMessage": "Migration des profils vers un nouveau format de stockage. Cela peut prendre quelques minutes...", + "loadingCwtch": "Chargement de Cwtch...", "experimentClickableLinksDescription": "L'expérience des liens cliquables vous permet de cliquer sur les URLs partagés dans les messages", "themeNameWitch": "Sorcière", "themeNameVampire": "Vampire", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index e74df5bf..60eece90 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,6 +1,12 @@ { "@@locale": "it", - "@@last_modified": "2021-12-16T21:50:40+01:00", + "@@last_modified": "2021-12-19T02:59:05+01:00", + "msgAddToAccept": "Add this account to your contacts in order to accept this file.", + "btnSendFile": "Send File", + "msgConfirmSend": "Are you sure you want to send", + "msgFileTooBig": "File size cannot exceed 10 GB", + "storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...", + "loadingCwtch": "Loading Cwtch...", "themeColorLabel": "Color Theme", "themeNameNeon2": "Neon2", "themeNameNeon1": "Neon1", diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index ae17cc1c..3e6146d7 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,6 +1,12 @@ { "@@locale": "pl", - "@@last_modified": "2021-12-16T21:50:40+01:00", + "@@last_modified": "2021-12-19T02:59:05+01:00", + "msgAddToAccept": "Add this account to your contacts in order to accept this file.", + "btnSendFile": "Send File", + "msgConfirmSend": "Are you sure you want to send", + "msgFileTooBig": "File size cannot exceed 10 GB", + "storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...", + "loadingCwtch": "Loading Cwtch...", "themeColorLabel": "Color Theme", "themeNameNeon2": "Neon2", "themeNameNeon1": "Neon1", diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index da920cfe..ac4af6f9 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,6 +1,12 @@ { "@@locale": "pt", - "@@last_modified": "2021-12-16T21:50:40+01:00", + "@@last_modified": "2021-12-19T02:59:05+01:00", + "msgAddToAccept": "Add this account to your contacts in order to accept this file.", + "btnSendFile": "Send File", + "msgConfirmSend": "Are you sure you want to send", + "msgFileTooBig": "File size cannot exceed 10 GB", + "storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...", + "loadingCwtch": "Loading Cwtch...", "themeColorLabel": "Color Theme", "themeNameNeon2": "Neon2", "themeNameNeon1": "Neon1", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 93b66ab4..b6e71cdf 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1,6 +1,12 @@ { "@@locale": "ru", - "@@last_modified": "2021-12-16T21:50:40+01:00", + "@@last_modified": "2021-12-19T02:59:05+01:00", + "msgAddToAccept": "Add this account to your contacts in order to accept this file.", + "btnSendFile": "Send File", + "msgConfirmSend": "Are you sure you want to send", + "msgFileTooBig": "File size cannot exceed 10 GB", + "storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...", + "loadingCwtch": "Loading Cwtch...", "themeColorLabel": "Тема", "themeNameNeon2": "Неон2", "themeNameNeon1": "Неон1", From 6390cb55a497ba7c0ed718ef9dca6a63d6c91aa1 Mon Sep 17 00:00:00 2001 From: erinn Date: Sat, 18 Dec 2021 18:09:18 -0800 Subject: [PATCH 17/19] l10n --- lib/settings.dart | 14 ++++++++++++++ lib/views/messageview.dart | 11 +++++------ lib/widgets/filebubble.dart | 8 +++----- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/lib/settings.dart b/lib/settings.dart index 329871d2..7d9392c8 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -228,6 +228,20 @@ class Settings extends ChangeNotifier { } } + // checks experiment settings and file extension for image previews + // (ignores file size; if the user manually accepts the file, assume it's okay to preview) + bool shouldPreview(String path) { + var lpath = path.toLowerCase(); + return isExperimentEnabled(ImagePreviewsExperiment) && ( + lpath.endsWith(".jpg") || + lpath.endsWith(".jpeg") || + lpath.endsWith(".png") || + lpath.endsWith(".gif") || + lpath.endsWith(".webp") || + lpath.endsWith(".bmp") + ); + } + String get downloadPath => _downloadPath; set downloadPath(String newval) { _downloadPath = newval; diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index 270ca4a4..c4315647 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -389,7 +389,7 @@ class _MessageViewState extends State { _confirmFileSend(ctx, file.path); } else { final snackBar = SnackBar( - content: Text("File size cannot exceed 10 GB"), + content: Text(AppLocalizations.of(context)!.msgFileTooBig), duration: Duration(seconds: 4), ); ScaffoldMessenger.of(ctx).showSnackBar(snackBar); @@ -401,9 +401,8 @@ class _MessageViewState extends State { showModalBottomSheet( context: ctx, builder: (BuildContext bcontext) { - var lpath = path.toLowerCase(); var showPreview = false; - if (Provider.of(context, listen: false).isExperimentEnabled(ImagePreviewsExperiment) && (lpath.endsWith("jpg") || lpath.endsWith("jpeg") || lpath.endsWith("png") || lpath.endsWith("gif") || lpath.endsWith("webp") || lpath.endsWith("bmp"))) { + if (Provider.of(context, listen: false).shouldPreview(path)) { showPreview = true; if (imagePreview == null) { imagePreview = new File(path); @@ -418,7 +417,7 @@ class _MessageViewState extends State { mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - Text("Are you sure you want to send $path?"), + Text(AppLocalizations.of(context)!.msgConfirmSend + " $path?"), SizedBox( height: 20, ), @@ -437,14 +436,14 @@ class _MessageViewState extends State { Visibility(visible: showPreview, child: SizedBox(height: 10,)), Row(mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( - child: Text("Cancel", semanticsLabel: "Cancel"), + child: Text(AppLocalizations.of(context)!.cancel, semanticsLabel: AppLocalizations.of(context)!.cancel), onPressed: () { Navigator.pop(bcontext); }, ), SizedBox(width: 20,), ElevatedButton( - child: Text("Send File", semanticsLabel: "Send File"), + child: Text(AppLocalizations.of(context)!.btnSendFile, semanticsLabel: AppLocalizations.of(context)!.btnSendFile), onPressed: () { _sendFile(path); Navigator.pop(bcontext); diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index 460f8e3e..e40c6097 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -49,7 +49,6 @@ class FileBubbleState extends State { var flagStarted = Provider.of(context).attributes["file-downloaded"] == "true"; var borderRadiousEh = 15.0; var showFileSharing = Provider.of(context).isExperimentEnabled(FileSharingExperiment); - var showImagePreviews = Provider.of(context).isExperimentEnabled(ImagePreviewsExperiment); var prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(Provider.of(context).timestamp); var downloadComplete = Provider.of(context).downloadComplete(widget.fileKey()); var downloadInterrupted = Provider.of(context).downloadInterrupted(widget.fileKey()); @@ -61,7 +60,7 @@ class FileBubbleState extends State { var path = Provider.of(context).downloadFinalPath(widget.fileKey()); if (downloadComplete) { var lpath = path!.toLowerCase(); - if (lpath.endsWith("jpg") || lpath.endsWith("jpeg") || lpath.endsWith("png") || lpath.endsWith("gif") || lpath.endsWith("webp") || lpath.endsWith("bmp")) { + 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); @@ -107,8 +106,7 @@ class FileBubbleState extends State { child: MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, prettyDate: prettyDate)); } else if (downloadComplete) { // in this case, whatever marked download.complete would have also set the path - var lpath = path!.toLowerCase(); - if (showImagePreviews && (lpath.endsWith("jpg") || lpath.endsWith("jpeg") || lpath.endsWith("png") || lpath.endsWith("gif") || lpath.endsWith("webp") || lpath.endsWith("bmp"))) { + if (Provider.of(context).shouldPreview(path!)) { isPreview = true; wdgDecorations = Center( child: GestureDetector( @@ -161,7 +159,7 @@ class FileBubbleState extends State { ])); } } else if (!senderIsContact) { - wdgDecorations = Text("Add this account to your contacts in order to accept this file."); + wdgDecorations = Text(AppLocalizations.of(context)!.msgAddToAccept); } else if (!widget.isAuto) { wdgDecorations = Visibility( visible: widget.interactive, From d2d07c81e312c1399d4a5da80b10977ea5fe3f45 Mon Sep 17 00:00:00 2001 From: erinn Date: Sat, 18 Dec 2021 18:44:22 -0800 Subject: [PATCH 18/19] bump lcg --- LIBCWTCH-GO-MACOS.version | 2 +- LIBCWTCH-GO.version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LIBCWTCH-GO-MACOS.version b/LIBCWTCH-GO-MACOS.version index dcda997e..76fba53c 100644 --- a/LIBCWTCH-GO-MACOS.version +++ b/LIBCWTCH-GO-MACOS.version @@ -1 +1 @@ -2021-12-11-02-00-v1.5.0-9-gaa102bd \ No newline at end of file +2021-12-18-20-32-v1.5.1 \ No newline at end of file diff --git a/LIBCWTCH-GO.version b/LIBCWTCH-GO.version index 8f72ef13..d7e07e7c 100644 --- a/LIBCWTCH-GO.version +++ b/LIBCWTCH-GO.version @@ -1 +1 @@ -2021-12-11-07-00-v1.5.0-9-gaa102bd \ No newline at end of file +2021-12-19-01-32-v1.5.1 \ No newline at end of file From 92dea31a86447e210fe08971bd1515281c70df64 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Sat, 18 Dec 2021 23:32:52 -0500 Subject: [PATCH 19/19] drone update to use new flutter 2.8 containers --- .drone.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.drone.yml b/.drone.yml index 7079ca5e..72ba2bc3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,7 +8,7 @@ clone: steps: - name: clone - image: cirrusci/flutter:2.5.3 + image: cirrusci/flutter:2.8.0 environment: buildbot_key_b64: from_secret: buildbot_key_b64 @@ -24,7 +24,7 @@ steps: - git checkout $DRONE_COMMIT - name: fetch - image: cirrusci/flutter:2.5.3 + image: cirrusci/flutter:2.8.0 volumes: - name: deps path: /root/.pub-cache @@ -47,7 +47,7 @@ steps: # #Todo: fix all the lint errors and add `-set_exit_status` above to enforce linting - name: build-linux - image: openpriv/flutter-desktop:linux-fstable-2.5.3 + image: openpriv/flutter-desktop:linux-fstable-2.8.0 volumes: - name: deps path: /root/.pub-cache @@ -61,7 +61,7 @@ steps: - rm -r cwtch - name: test-build-android - image: cirrusci/flutter:2.5.3 + image: cirrusci/flutter:2.8.0 when: event: pull_request volumes: @@ -71,7 +71,7 @@ steps: - flutter build apk --debug - name: build-android - image: cirrusci/flutter:2.5.3 + image: cirrusci/flutter:2.8.0 when: event: push environment: @@ -95,7 +95,7 @@ steps: #- cp build/app/outputs/flutter-apk/app-debug.apk deploy/android - name: widget-tests - image: cirrusci/flutter:2.5.3 + image: cirrusci/flutter:2.8.0 volumes: - name: deps path: /root/.pub-cache @@ -174,7 +174,7 @@ clone: steps: - name: clone - image: openpriv/flutter-desktop:windows-sdk30-fstable2.5.3 + image: openpriv/flutter-desktop:windows-sdk30-fstable-2.8.1 environment: buildbot_key_b64: from_secret: buildbot_key_b64 @@ -192,7 +192,7 @@ steps: - git checkout $Env:DRONE_COMMIT - name: fetch - image: openpriv/flutter-desktop:windows-sdk30-fstable2.5.3 + image: openpriv/flutter-desktop:windows-sdk30-fstable-2.8.1 commands: - powershell -command "Invoke-WebRequest -Uri https://git.openprivacy.ca/openprivacy/buildfiles/raw/branch/master/tor/tor-win64-0.4.6.5.zip -OutFile tor.zip" - powershell -command "if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '7917561a7a063440a1ddfa9cb544ab9ffd09de84cea3dd66e3cc9cd349dd9f85b74a522ec390d7a974bc19b424c4d53af60e57bbc47e763d13cab6a203c4592f' ) { Write-Error 'tor.zip sha512sum mismatch' }" @@ -201,7 +201,7 @@ steps: - .\fetch-libcwtch-go.ps1 - name: build-windows - image: openpriv/flutter-desktop:windows-sdk30-fstable2.5.3 + image: openpriv/flutter-desktop:windows-sdk30-fstable-2.8.1 commands: - flutter pub get - $Env:version += type .\VERSION @@ -257,7 +257,7 @@ steps: - move *.sha512 deploy\$Env:builddir - name: deploy-windows - image: openpriv/flutter-desktop:windows-sdk30-fstable2.5.3 + image: openpriv/flutter-desktop:windows-sdk30-fstable-2.8.1 when: event: push status: [ success ]