wip image previews

This commit is contained in:
erinn 2021-12-14 13:33:30 -08:00
parent 6a5309427f
commit 33a8a30170
10 changed files with 180 additions and 14 deletions

View File

@ -96,5 +96,8 @@ abstract class Cwtch {
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void Shutdown(); void Shutdown();
// non-ffi
String defaultDownloadPath();
void dispose(); void dispose();
} }

View File

@ -263,7 +263,7 @@ class CwtchNotifier {
settings.handleUpdate(jsonDecode(data["Data"])); settings.handleUpdate(jsonDecode(data["Data"]));
break; break;
case "SetAttribute": case "SetAttribute":
if (data["Key"] == "public.name") { if (data["Key"] == "public.profile.name") {//"public.name") {
profileCN.getProfile(data["ProfileOnion"])?.nickname = data["Data"]; profileCN.getProfile(data["ProfileOnion"])?.nickname = data["Data"];
} else { } else {
EnvironmentConfig.debugLog("unhandled set attribute event: ${data['Key']}"); EnvironmentConfig.debugLog("unhandled set attribute event: ${data['Key']}");
@ -366,6 +366,11 @@ class CwtchNotifier {
EnvironmentConfig.debugLog("unhandled peer attribute event: ${data['Path']}"); EnvironmentConfig.debugLog("unhandled peer attribute event: ${data['Path']}");
} }
break; break;
case "ManifestSizeReceived":
if (!profileCN.getProfile(data["ProfileOnion"])!.downloadActive(data["FileKey"])) {
profileCN.getProfile(data["ProfileOnion"])?.downloadUpdate(data["FileKey"], 0, 1);
}
break;
case "ManifestSaved": case "ManifestSaved":
profileCN.getProfile(data["ProfileOnion"])?.downloadMarkManifest(data["FileKey"]); profileCN.getProfile(data["ProfileOnion"])?.downloadMarkManifest(data["FileKey"]);
break; break;

View File

@ -557,7 +557,7 @@ class CwtchFfi implements Cwtch {
final SetProfileAttribute = setProfileAttribute.asFunction<VoidFromStringStringStringFn>(); final SetProfileAttribute = setProfileAttribute.asFunction<VoidFromStringStringStringFn>();
final u1 = profile.toNativeUtf8(); final u1 = profile.toNativeUtf8();
final u2 = key.toNativeUtf8(); final u2 = key.toNativeUtf8();
final u3 = key.toNativeUtf8(); final u3 = val.toNativeUtf8();
SetProfileAttribute(u1, u1.length, u2, u2.length, u3, u3.length); SetProfileAttribute(u1, u1.length, u2, u2.length, u3, u3.length);
malloc.free(u1); malloc.free(u1);
malloc.free(u2); malloc.free(u2);
@ -728,4 +728,10 @@ class CwtchFfi implements Cwtch {
final Free = free.asFunction<FreeFn>(); final Free = free.asFunction<FreeFn>();
Free(ptr); Free(ptr);
} }
@override
String defaultDownloadPath() {
Map<String, String> envVars = Platform.environment;
return path.join(envVars['HOME']!, "Downloads");
}
} }

View File

@ -28,6 +28,7 @@ class CwtchGomobile implements Cwtch {
late Future<dynamic> androidLibraryDir; late Future<dynamic> androidLibraryDir;
late Future<dynamic> androidHomeDirectory; late Future<dynamic> androidHomeDirectory;
String androidHomeDirectoryStr = "";
late CwtchNotifier cwtchNotifier; late CwtchNotifier cwtchNotifier;
CwtchGomobile(CwtchNotifier _cwtchNotifier) { CwtchGomobile(CwtchNotifier _cwtchNotifier) {
@ -44,7 +45,8 @@ class CwtchGomobile implements Cwtch {
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
Future<void> Start() async { Future<void> Start() async {
print("gomobile.dart: Start()..."); 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) { if (EnvironmentConfig.BUILD_VER == dev_version) {
cwtchDir = path.join(cwtchDir, "dev"); cwtchDir = path.join(cwtchDir, "dev");
} }
@ -283,4 +285,9 @@ class CwtchGomobile implements Cwtch {
Future GetMessageByContentHash(String profile, String handle, String contentHash) { Future GetMessageByContentHash(String profile, String handle, String contentHash) {
return cwtchPlatform.invokeMethod("GetMessageByContentHash", {"profile": profile, "contact": handle, "contentHash": contentHash}); return cwtchPlatform.invokeMethod("GetMessageByContentHash", {"profile": profile, "contact": handle, "contentHash": contentHash});
} }
@override
String defaultDownloadPath() {
return this.androidHomeDirectoryStr;
}
} }

View File

@ -364,13 +364,11 @@ class ProfileInfoState extends ChangeNotifier {
void downloadUpdate(String fileKey, int progress, int numChunks) { void downloadUpdate(String fileKey, int progress, int numChunks) {
if (!downloadActive(fileKey)) { if (!downloadActive(fileKey)) {
this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now());
if (progress < 0) { if (progress < 0) {
this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now());
this._downloads[fileKey]!.interrupted = true; this._downloads[fileKey]!.interrupted = true;
notifyListeners();
} else {
print("error: received progress for unknown download " + fileKey);
} }
notifyListeners();
} else { } else {
if (this._downloads[fileKey]!.interrupted) { if (this._downloads[fileKey]!.interrupted) {
this._downloads[fileKey]!.interrupted = false; this._downloads[fileKey]!.interrupted = false;
@ -383,11 +381,10 @@ class ProfileInfoState extends ChangeNotifier {
void downloadMarkManifest(String fileKey) { void downloadMarkManifest(String fileKey) {
if (!downloadActive(fileKey)) { if (!downloadActive(fileKey)) {
print("error: received download completion notice for unknown download " + fileKey); this._downloads[fileKey] = FileDownloadProgress(1, DateTime.now());
} else {
this._downloads[fileKey]!.gotManifest = true;
notifyListeners();
} }
this._downloads[fileKey]!.gotManifest = true;
notifyListeners();
} }
void downloadMarkFinished(String fileKey, String finalPath) { void downloadMarkFinished(String fileKey, String finalPath) {

View File

@ -11,6 +11,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
const TapirGroupsExperiment = "tapir-groups-experiment"; const TapirGroupsExperiment = "tapir-groups-experiment";
const ServerManagementExperiment = "servers-experiment"; const ServerManagementExperiment = "servers-experiment";
const FileSharingExperiment = "filesharing"; const FileSharingExperiment = "filesharing";
const ImagePreviewsExperiment = "filesharing-images";
enum DualpaneMode { enum DualpaneMode {
Single, Single,
@ -34,6 +35,7 @@ class Settings extends ChangeNotifier {
bool blockUnknownConnections = false; bool blockUnknownConnections = false;
bool streamerMode = false; bool streamerMode = false;
String _downloadPath = "";
/// Set the dark theme. /// Set the dark theme.
void setDark() { void setDark() {
@ -90,6 +92,13 @@ class Settings extends ChangeNotifier {
_uiColumnModePortrait = uiColumnModeFromString(settings["UIColumnModePortrait"]); _uiColumnModePortrait = uiColumnModeFromString(settings["UIColumnModePortrait"]);
_uiColumnModeLandscape = uiColumnModeFromString(settings["UIColumnModeLandscape"]); _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 // Push the experimental settings to Consumers of Settings
notifyListeners(); 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. /// Construct a default settings object.
Settings(this.locale, this.theme); Settings(this.locale, this.theme);
@ -242,6 +257,7 @@ class Settings extends ChangeNotifier {
"FirstTime": false, "FirstTime": false,
"UIColumnModePortrait": uiColumnModePortrait.toString(), "UIColumnModePortrait": uiColumnModePortrait.toString(),
"UIColumnModeLandscape": uiColumnModeLandscape.toString(), "UIColumnModeLandscape": uiColumnModeLandscape.toString(),
"DownloadPath": _downloadPath,
}; };
} }
} }

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/models/servers.dart'; import 'package:cwtch/models/servers.dart';
import 'package:cwtch/widgets/folderpicker.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cwtch/settings.dart'; import 'package:cwtch/settings.dart';
@ -227,6 +228,39 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(), inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
secondary: Icon(Icons.attach_file, color: settings.current().mainTextColor()), 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<FlwtchState>(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( AboutListTile(

View File

@ -33,6 +33,8 @@ class FileBubble extends StatefulWidget {
} }
class FileBubbleState extends State<FileBubble> { class FileBubbleState extends State<FileBubble> {
File? myFile;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -62,6 +64,7 @@ class FileBubbleState extends State<FileBubble> {
child: SelectableText(senderDisplayStr + '\u202F', child: SelectableText(senderDisplayStr + '\u202F',
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()))); style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor())));
var isPreview = false;
var wdgMessage = !showFileSharing var wdgMessage = !showFileSharing
? Text(AppLocalizations.of(context)!.messageEnableFileSharing) ? Text(AppLocalizations.of(context)!.messageEnableFileSharing)
: fromMe : fromMe
@ -76,7 +79,17 @@ class FileBubbleState extends State<FileBubble> {
} else if (Provider.of<ProfileInfoState>(context).downloadComplete(widget.fileKey())) { } else if (Provider.of<ProfileInfoState>(context).downloadComplete(widget.fileKey())) {
// in this case, whatever marked download.complete would have also set the path // in this case, whatever marked download.complete would have also set the path
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey())!; var path = Provider.of<ProfileInfoState>(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<ProfileInfoState>(context).downloadActive(widget.fileKey())) { } else if (Provider.of<ProfileInfoState>(context).downloadActive(widget.fileKey())) {
if (!Provider.of<ProfileInfoState>(context).downloadGotManifest(widget.fileKey())) { if (!Provider.of<ProfileInfoState>(context).downloadGotManifest(widget.fileKey())) {
wdgDecorations = Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F'); wdgDecorations = Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F');
@ -136,7 +149,7 @@ class FileBubbleState extends State<FileBubble> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: fromMe children: fromMe
? [wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)] ? [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<FileBubble> {
)), )),
); );
} }
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)]),
))
);
}
} }

View File

@ -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<CwtchFolderPicker> {
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
)
]));
}
}

View File

@ -102,7 +102,7 @@ class _ProfileRowState extends State<ProfileRow> {
} }
void _pushEditProfile({onion: "", displayName: "", profileImage: "", encrypted: true}) { void _pushEditProfile({onion: "", displayName: "", profileImage: "", encrypted: true}) {
Provider.of<ErrorHandler>(context).reset(); Provider.of<ErrorHandler>(context, listen: false).reset();
Navigator.of(context).push(MaterialPageRoute<void>( Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) { builder: (BuildContext context) {
return MultiProvider( return MultiProvider(