forked from cwtch.im/cwtch-ui
preview
This commit is contained in:
parent
9cb6eb857d
commit
6f3d5b65cd
|
@ -15,7 +15,9 @@ abstract class Cwtch {
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void LoadProfiles(String pass);
|
void LoadProfiles(String pass);
|
||||||
// ignore: non_constant_identifier_names
|
// 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
|
// ignore: non_constant_identifier_names
|
||||||
void ResetTor();
|
void ResetTor();
|
||||||
|
|
|
@ -224,7 +224,10 @@ class CwtchNotifier {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "SendMessageToPeerError":
|
case "SendMessageToPeerError":
|
||||||
// Ignore
|
// Ignore dealt with by IndexedFailure
|
||||||
|
break;
|
||||||
|
case "SendMessageToGroupError":
|
||||||
|
// Ignore dealt with by IndexedFailure
|
||||||
break;
|
break;
|
||||||
case "IndexedFailure":
|
case "IndexedFailure":
|
||||||
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]);
|
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]);
|
||||||
|
|
|
@ -115,13 +115,13 @@ class CwtchFfi implements Cwtch {
|
||||||
}
|
}
|
||||||
|
|
||||||
CwtchFfi(CwtchNotifier _cwtchNotifier) {
|
CwtchFfi(CwtchNotifier _cwtchNotifier) {
|
||||||
String library_path = getLibraryPath();
|
String libraryPath = getLibraryPath();
|
||||||
if (library_path == UNSUPPORTED_OS) {
|
if (libraryPath == UNSUPPORTED_OS) {
|
||||||
print("OS ${Platform.operatingSystem} not supported by cwtch/ffi");
|
print("OS ${Platform.operatingSystem} not supported by cwtch/ffi");
|
||||||
// emergency, ideally the app stays on splash and just posts the error till user closes
|
// emergency, ideally the app stays on splash and just posts the error till user closes
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
library = DynamicLibrary.open(library_path);
|
library = DynamicLibrary.open(libraryPath);
|
||||||
cwtchNotifier = _cwtchNotifier;
|
cwtchNotifier = _cwtchNotifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -716,4 +716,21 @@ class CwtchFfi implements Cwtch {
|
||||||
malloc.free(utf8profile);
|
malloc.free(utf8profile);
|
||||||
return jsonMessage;
|
return jsonMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void ChangePassword(String profile, String pass, String newpass, String newpassAgain) {
|
||||||
|
var changePasswordC = library.lookup<NativeFunction<void_from_string_string_string_string_function>>("c_ChangePassword");
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
final ChangePasswordFn = changePasswordC.asFunction<VoidFromStringStringStringStringFn>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,4 +284,9 @@ class CwtchGomobile implements Cwtch {
|
||||||
String defaultDownloadPath() {
|
String defaultDownloadPath() {
|
||||||
return this.androidHomeDirectoryStr;
|
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});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,17 @@ class ErrorHandler extends ChangeNotifier {
|
||||||
|
|
||||||
// Add Contact Specific Errors...
|
// Add Contact Specific Errors...
|
||||||
static const String addContactErrorPrefix = "addcontact";
|
static const String addContactErrorPrefix = "addcontact";
|
||||||
|
static const String changePasswordErrorPrefix = "changepassword";
|
||||||
static const String invalidImportStringErrorType = "invalid_import_string";
|
static const String invalidImportStringErrorType = "invalid_import_string";
|
||||||
static const String contactAlreadyExistsErrorType = "contact_already_exists";
|
static const String contactAlreadyExistsErrorType = "contact_already_exists";
|
||||||
bool invalidImportStringError = false;
|
bool invalidImportStringError = false;
|
||||||
bool contactAlreadyExistsError = false;
|
bool contactAlreadyExistsError = false;
|
||||||
bool explicitAddContactSuccess = false;
|
bool explicitAddContactSuccess = false;
|
||||||
|
|
||||||
|
// ChangePassword
|
||||||
|
bool changePasswordError = false;
|
||||||
|
bool explicitChangePasswordSuccess = false;
|
||||||
|
|
||||||
// Import Bundle Specific Errors
|
// Import Bundle Specific Errors
|
||||||
static const String importBundleErrorPrefix = "importBundle";
|
static const String importBundleErrorPrefix = "importBundle";
|
||||||
bool importBundleError = false;
|
bool importBundleError = false;
|
||||||
|
@ -39,6 +44,9 @@ class ErrorHandler extends ChangeNotifier {
|
||||||
deletedServerError = false;
|
deletedServerError = false;
|
||||||
deletedServerSuccess = false;
|
deletedServerSuccess = false;
|
||||||
|
|
||||||
|
changePasswordError = false;
|
||||||
|
explicitChangePasswordSuccess = false;
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +66,9 @@ class ErrorHandler extends ChangeNotifier {
|
||||||
case deleteProfileErrorPrefix:
|
case deleteProfileErrorPrefix:
|
||||||
handleDeleteProfileError(errorType);
|
handleDeleteProfileError(errorType);
|
||||||
break;
|
break;
|
||||||
|
case changePasswordErrorPrefix:
|
||||||
|
handleChangePasswordError(errorType);
|
||||||
|
break;
|
||||||
case deletedServerErrorPrefix:
|
case deletedServerErrorPrefix:
|
||||||
handleDeletedServerError(errorType);
|
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) {
|
handleDeletedServerError(String errorType) {
|
||||||
// reset
|
// reset
|
||||||
deletedServerError = false;
|
deletedServerError = false;
|
||||||
|
|
|
@ -150,5 +150,6 @@ class MessageMetadata extends ChangeNotifier {
|
||||||
notifyListeners();
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,14 +54,16 @@ class FileMessage extends Message {
|
||||||
if (!validHash(rootHash, nonce)) {
|
if (!validHash(rootHash, nonce)) {
|
||||||
return MessageRow(MalformedBubble());
|
return MessageRow(MalformedBubble());
|
||||||
}
|
}
|
||||||
return FileBubble(
|
return Container(
|
||||||
nameSuggestion,
|
alignment: Alignment.center,
|
||||||
rootHash,
|
child: FileBubble(
|
||||||
nonce,
|
nameSuggestion,
|
||||||
fileSize,
|
rootHash,
|
||||||
isAuto: metadata.isAuto,
|
nonce,
|
||||||
interactive: false,
|
fileSize,
|
||||||
);
|
isAuto: metadata.isAuto,
|
||||||
|
interactive: false,
|
||||||
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:cwtch/config.dart';
|
||||||
import 'package:cwtch/cwtch/cwtch.dart';
|
import 'package:cwtch/cwtch/cwtch.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -278,7 +280,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
||||||
// TODO Toast
|
// TODO Toast
|
||||||
}
|
}
|
||||||
|
|
||||||
void _createPressed() {
|
void _createPressed() async {
|
||||||
// This will run all the validations in the form including
|
// This will run all the validations in the form including
|
||||||
// checking that display name is not empty, and an actual check that the passwords
|
// 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).
|
// match (and are provided if the user has requested an encrypted profile).
|
||||||
|
@ -301,17 +303,32 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
||||||
} else {
|
} else {
|
||||||
// At this points passwords have been validated to be the same and not empty
|
// 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...
|
// Update both password and name, even if name hasn't been changed...
|
||||||
|
var profile = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||||
Provider.of<ProfileInfoState>(context, listen: false).nickname = ctrlrNick.value.text;
|
Provider.of<ProfileInfoState>(context, listen: false).nickname = ctrlrNick.value.text;
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(Provider.of<ProfileInfoState>(context, listen: false).onion, "profile.name", ctrlrNick.value.text);
|
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(profile, "profile.name", ctrlrNick.value.text);
|
||||||
final updatePasswordEvent = {
|
Provider.of<FlwtchState>(context, listen: false).cwtch.ChangePassword(profile, ctrlrOldPass.text, ctrlrPass.text, ctrlrPass2.text);
|
||||||
"EventType": "ChangePassword",
|
|
||||||
"Data": {"Password": ctrlrOldPass.text, "NewPassword": ctrlrPass.text}
|
|
||||||
};
|
|
||||||
final updatePasswordEventJson = jsonEncode(updatePasswordEvent);
|
|
||||||
|
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(Provider.of<ProfileInfoState>(context, listen: false).onion, updatePasswordEventJson);
|
EnvironmentConfig.debugLog("waiting for change password response");
|
||||||
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
Navigator.of(context).pop();
|
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...
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,8 +83,8 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return LayoutBuilder(builder: (bcontext, constraints) {
|
return LayoutBuilder(builder: (bcontext, constraints) {
|
||||||
var wdgSender = Center(
|
var wdgSender = Visibility(
|
||||||
widthFactor: 1,
|
visible: widget.interactive,
|
||||||
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())));
|
||||||
|
|
||||||
|
@ -99,40 +99,47 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
if (!showFileSharing) {
|
if (!showFileSharing) {
|
||||||
wdgDecorations = Text('\u202F');
|
wdgDecorations = Text('\u202F');
|
||||||
} else if (fromMe) {
|
} else if (fromMe) {
|
||||||
wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
wdgDecorations = Visibility(
|
||||||
|
visible: widget.interactive,
|
||||||
|
child: MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate));
|
||||||
} else if (downloadComplete) {
|
} else if (downloadComplete) {
|
||||||
// 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 lpath = path!.toLowerCase();
|
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")) {
|
||||||
isPreview = true;
|
isPreview = true;
|
||||||
wdgDecorations = GestureDetector(
|
wdgDecorations = Center(
|
||||||
child: Image.file(
|
child: GestureDetector(
|
||||||
myFile!,
|
child: Padding(
|
||||||
cacheWidth: 2048, // limit the amount of space the image can decode too, we keep this high-ish to allow quality previews...
|
padding: EdgeInsets.all(1.0),
|
||||||
filterQuality: FilterQuality.medium,
|
child: Image.file(
|
||||||
fit: BoxFit.fill,
|
myFile!,
|
||||||
alignment: Alignment.center,
|
cacheWidth: 2048, // limit the amount of space the image can decode too, we keep this high-ish to allow quality previews...
|
||||||
width: constraints.maxWidth,
|
filterQuality: FilterQuality.medium,
|
||||||
isAntiAlias: false,
|
fit: BoxFit.cover,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
alignment: Alignment.center,
|
||||||
return MalformedBubble();
|
height: MediaQuery.of(bcontext).size.height * 0.30,
|
||||||
},
|
isAntiAlias: false,
|
||||||
),
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return MalformedBubble();
|
||||||
|
},
|
||||||
|
)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
pop(bcontext, myFile!, wdgMessage);
|
pop(bcontext, myFile!, wdgMessage);
|
||||||
},
|
},
|
||||||
);
|
));
|
||||||
} else {
|
} 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) {
|
} else if (downloadActive) {
|
||||||
if (!downloadGotManifest) {
|
if (!downloadGotManifest) {
|
||||||
wdgDecorations = Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F');
|
wdgDecorations = Visibility(visible: widget.interactive, child: Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F'));
|
||||||
} else {
|
} else {
|
||||||
wdgDecorations = LinearProgressIndicator(
|
wdgDecorations = Visibility(
|
||||||
value: Provider.of<ProfileInfoState>(context).downloadProgress(widget.fileKey()),
|
visible: widget.interactive,
|
||||||
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
child: LinearProgressIndicator(
|
||||||
);
|
value: Provider.of<ProfileInfoState>(context).downloadProgress(widget.fileKey()),
|
||||||
|
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
} else if (flagStarted) {
|
} else if (flagStarted) {
|
||||||
// in this case, the download was done in a previous application launch,
|
// in this case, the download was done in a previous application launch,
|
||||||
|
@ -141,17 +148,21 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F');
|
wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F');
|
||||||
} else {
|
} else {
|
||||||
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey()) ?? "";
|
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey()) ?? "";
|
||||||
wdgDecorations = Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
wdgDecorations = Visibility(
|
||||||
Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'),
|
visible: widget.interactive,
|
||||||
ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton))
|
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) {
|
} else if (!widget.isAuto) {
|
||||||
wdgDecorations = Center(
|
wdgDecorations = Visibility(
|
||||||
widthFactor: 1,
|
visible: widget.interactive,
|
||||||
child: Wrap(children: [
|
child: Center(
|
||||||
Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F'), onPressed: _btnAccept)),
|
widthFactor: 1,
|
||||||
]));
|
child: Wrap(children: [
|
||||||
|
Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F'), onPressed: _btnAccept)),
|
||||||
|
])));
|
||||||
} else {
|
} else {
|
||||||
wdgDecorations = Container();
|
wdgDecorations = Container();
|
||||||
}
|
}
|
||||||
|
@ -174,9 +185,7 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
|
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: fromMe
|
children: fromMe ? [wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)] : [wdgSender, isPreview ? Container() : wdgMessage, wdgDecorations]),
|
||||||
? [wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)]
|
|
||||||
: [wdgSender, isPreview ? Container() : wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)]),
|
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue