UI Updates to new Cwtch API
continuous-integration/drone/pr Build is pending Details

This commit is contained in:
Sarah Jamie Lewis 2021-11-25 15:59:54 -08:00
parent 1d6b533df3
commit 880c1c107b
17 changed files with 369 additions and 415 deletions

View File

@ -23,7 +23,8 @@ class CwtchNotifier {
late AppState appState; late AppState appState;
late ServerListState serverListState; late ServerListState serverListState;
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN, ServerListState serverListStateCN) { CwtchNotifier(
ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN, ServerListState serverListStateCN) {
profileCN = pcn; profileCN = pcn;
settings = settingsCN; settings = settingsCN;
error = errorCN; error = errorCN;
@ -51,7 +52,7 @@ class CwtchNotifier {
EnvironmentConfig.debugLog("NewServer $data"); EnvironmentConfig.debugLog("NewServer $data");
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState( profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(
data["ProfileOnion"], data["ProfileOnion"],
data["ConversationID"], int.parse(data["ConversationID"]),
data["RemotePeer"], data["RemotePeer"],
nickname: data["nick"], nickname: data["nick"],
status: data["status"], status: data["status"],
@ -68,13 +69,7 @@ class CwtchNotifier {
break; break;
case "NewServer": case "NewServer":
EnvironmentConfig.debugLog("NewServer $data"); EnvironmentConfig.debugLog("NewServer $data");
serverListState.add( serverListState.add(data["Onion"], data["ServerBundle"], data["Running"] == "true", data["Description"], data["Autostart"] == "true", data["StorageType"] == "storage-password");
data["Onion"],
data["ServerBundle"],
data["Running"] == "true",
data["Description"],
data["Autostart"] == "true",
data["StorageType"] == "storage-password");
break; break;
case "ServerIntentUpdate": case "ServerIntentUpdate":
EnvironmentConfig.debugLog("ServerIntentUpdate $data"); EnvironmentConfig.debugLog("ServerIntentUpdate $data");
@ -158,7 +153,7 @@ class CwtchNotifier {
case "IndexedAcknowledgement": case "IndexedAcknowledgement":
var messageID = data["Index"]; var messageID = data["Index"];
var identifier = int.parse(data["ConversationID"]); var identifier = int.parse(data["ConversationID"]);
var idx = identifier.toString() + messageID; var idx = identifier.toString() + messageID;
// We return -1 for protocol message acks if there is no message // We return -1 for protocol message acks if there is no message
if (idx == "-1") break; if (idx == "-1") break;
@ -207,7 +202,7 @@ class CwtchNotifier {
// For now we perform some minimal checks on the sent timestamp to use to provide a useful ordering for honest contacts // For now we perform some minimal checks on the sent timestamp to use to provide a useful ordering for honest contacts
// and ensure that malicious contacts in groups can only set this timestamp to a value within the range of `last seen message time` // and ensure that malicious contacts in groups can only set this timestamp to a value within the range of `last seen message time`
// and `local now`. // and `local now`.
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], timestampSent.toLocal()); profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, timestampSent.toLocal());
notificationManager.notify("New Message From Group!"); notificationManager.notify("New Message From Group!");
} }
} else { } else {
@ -271,8 +266,8 @@ class CwtchNotifier {
case "UpdateGlobalSettings": case "UpdateGlobalSettings":
settings.handleUpdate(jsonDecode(data["Data"])); settings.handleUpdate(jsonDecode(data["Data"]));
break; break;
case "SetAttribute": case "UpdatedProfileAttribute":
if (data["Key"] == "public.name") { if (data["Key"] == "public.profile.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']}");
@ -308,7 +303,7 @@ class CwtchNotifier {
} }
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(groupInvite["GroupID"]) == null) { if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(groupInvite["GroupID"]) == null) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["ConversationID"], groupInvite["GroupID"], profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["ConversationID"], groupInvite["GroupID"],
authorization: ContactAuthorization.approved, authorization: ContactAuthorization.approved,
imagePath: data["PicturePath"], imagePath: data["PicturePath"],
nickname: groupInvite["GroupName"], nickname: groupInvite["GroupName"],

View File

@ -63,10 +63,16 @@ typedef get_json_blob_from_str_str_int_function = Pointer<Utf8> Function(Pointer
typedef GetJsonBlobFromStrStrIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int); typedef GetJsonBlobFromStrStrIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int);
typedef get_json_blob_from_str_int_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Int32, Int32); typedef get_json_blob_from_str_int_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Int32, Int32);
typedef GetJsonBlobFromStrIntIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, int, int); typedef GetJsonBlobFromStrIntIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, int, int);
typedef get_json_blob_from_str_int_string_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Int32, Pointer<Utf8>, Int32); typedef get_json_blob_from_str_int_string_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Int32, Pointer<Utf8>, Int32);
typedef GetJsonBlobFromStrIntStringFn = Pointer<Utf8> Function(Pointer<Utf8>, int, int, Pointer<Utf8>, int,); typedef GetJsonBlobFromStrIntStringFn = Pointer<Utf8> Function(
Pointer<Utf8>,
int,
int,
Pointer<Utf8>,
int,
);
// func c_GetMessagesByContentHash(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, contenthash_ptr *C.char, contenthash_len C.int) *C.char // func c_GetMessagesByContentHash(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, contenthash_ptr *C.char, contenthash_len C.int) *C.char
typedef get_json_blob_from_str_str_str_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32); typedef get_json_blob_from_str_str_str_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
@ -78,10 +84,9 @@ typedef VoidFromStringIntStringFn = void Function(Pointer<Utf8>, int, int, Point
typedef void_from_string_int_string_string_function = Void Function(Pointer<Utf8>, Int32, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32); typedef void_from_string_int_string_string_function = Void Function(Pointer<Utf8>, Int32, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef VoidFromStringIntStringStringFn = void Function(Pointer<Utf8>, int, int, Pointer<Utf8>, int, Pointer<Utf8>, int); typedef VoidFromStringIntStringStringFn = void Function(Pointer<Utf8>, int, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
typedef void_from_string_int_int_function =Void Function(Pointer<Utf8>, Int32, Int32, Int32); typedef void_from_string_int_int_function = Void Function(Pointer<Utf8>, Int32, Int32, Int32);
typedef VoidFromStringIntIntFn = void Function(Pointer<Utf8>, int, int, int); typedef VoidFromStringIntIntFn = void Function(Pointer<Utf8>, int, int, int);
typedef appbus_events_function = Pointer<Utf8> Function(); typedef appbus_events_function = Pointer<Utf8> Function();
typedef AppbusEventsFn = Pointer<Utf8> Function(); typedef AppbusEventsFn = Pointer<Utf8> Function();
@ -325,7 +330,7 @@ class CwtchFfi implements Cwtch {
@override @override
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void AcceptContact(String profileOnion, int contactHandle) { void AcceptContact(String profileOnion, int contactHandle) {
var acceptContact = library.lookup<NativeFunction<string_int_to_void_function>>("c_AcceptContact"); var acceptContact = library.lookup<NativeFunction<string_int_to_void_function>>("c_AcceptConversation");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final AcceptContact = acceptContact.asFunction<VoidFromStringIntFn>(); final AcceptContact = acceptContact.asFunction<VoidFromStringIntFn>();
final u1 = profileOnion.toNativeUtf8(); final u1 = profileOnion.toNativeUtf8();
@ -430,7 +435,6 @@ class CwtchFfi implements Cwtch {
malloc.free(u3); malloc.free(u3);
} }
@override @override
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void ResetTor() { void ResetTor() {
@ -453,7 +457,6 @@ class CwtchFfi implements Cwtch {
malloc.free(u2); malloc.free(u2);
} }
@override @override
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void RejectInvite(String profileOnion, int groupHandle) { void RejectInvite(String profileOnion, int groupHandle) {
@ -490,7 +493,6 @@ class CwtchFfi implements Cwtch {
final u1 = profileOnion.toNativeUtf8(); final u1 = profileOnion.toNativeUtf8();
ArchiveConversation(u1, u1.length, handle); ArchiveConversation(u1, u1.length, handle);
malloc.free(u1); malloc.free(u1);
} }
@override @override
@ -504,7 +506,6 @@ class CwtchFfi implements Cwtch {
malloc.free(u1); malloc.free(u1);
} }
@override @override
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void DeleteProfile(String onion, String currentPassword) { void DeleteProfile(String onion, String currentPassword) {
@ -526,7 +527,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);

View File

@ -201,7 +201,6 @@ class CwtchGomobile implements Cwtch {
cwtchPlatform.invokeMethod("ArchiveConversation", {"ProfileOnion": profileOnion, "handle": contactHandle}); cwtchPlatform.invokeMethod("ArchiveConversation", {"ProfileOnion": profileOnion, "handle": contactHandle});
} }
@override @override
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void SetProfileAttribute(String profile, String key, String val) { void SetProfileAttribute(String profile, String key, String val) {
@ -268,7 +267,7 @@ class CwtchGomobile implements Cwtch {
cwtchPlatform.invokeMethod("SetServerAttribute", {"ServerOnion": serverOnion, "Key": key, "Val": val}); cwtchPlatform.invokeMethod("SetServerAttribute", {"ServerOnion": serverOnion, "Key": key, "Val": val});
} }
@override @override
Future<void> Shutdown() async { Future<void> Shutdown() async {
print("gomobile.dart Shutdown"); print("gomobile.dart Shutdown");
cwtchPlatform.invokeMethod("Shutdown", {}); cwtchPlatform.invokeMethod("Shutdown", {});

View File

@ -208,7 +208,6 @@ class ContactListState extends ChangeNotifier {
int idx = _contacts.indexWhere((element) => element.onion == byHandle); int idx = _contacts.indexWhere((element) => element.onion == byHandle);
return idx >= 0 ? _contacts[idx] : null; return idx >= 0 ? _contacts[idx] : null;
} }
} }
class ProfileInfoState extends ChangeNotifier { class ProfileInfoState extends ChangeNotifier {
@ -601,7 +600,7 @@ class ContactInfoState extends ChangeNotifier {
if (newVal > 0) { if (newVal > 0) {
this._newMarker = newVal; this._newMarker = newVal;
} else { } else {
this._newMarkerClearAt = DateTime.now().add(const Duration(minutes:2)); this._newMarkerClearAt = DateTime.now().add(const Duration(minutes: 2));
} }
this._unreadMessages = newVal; this._unreadMessages = newVal;
notifyListeners(); notifyListeners();
@ -616,6 +615,7 @@ class ContactInfoState extends ChangeNotifier {
} }
return this._newMarker; return this._newMarker;
} }
// what's a getter that sometimes sets without a setter // what's a getter that sometimes sets without a setter
// that sometimes doesn't set // that sometimes doesn't set
set newMarker(int newVal) { set newMarker(int newVal) {

View File

@ -127,5 +127,6 @@ class MessageMetadata extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
MessageMetadata(this.profileOnion, this.conversationIdentifier, this.messageIndex, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._flags, this._ackd, this._error); MessageMetadata(
this.profileOnion, this.conversationIdentifier, this.messageIndex, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._flags, this._ackd, this._error);
} }

View File

@ -24,12 +24,7 @@ class ServerListState extends ChangeNotifier {
if (idx >= 0) { if (idx >= 0) {
_servers[idx] = sis; _servers[idx] = sis;
} else { } else {
_servers.add(ServerInfoState(onion: onion, _servers.add(ServerInfoState(onion: onion, serverBundle: serverBundle, running: running, description: description, autoStart: autoStart, isEncrypted: isEncrypted));
serverBundle: serverBundle,
running: running,
description: description,
autoStart: autoStart,
isEncrypted: isEncrypted));
} }
notifyListeners(); notifyListeners();
} }
@ -37,7 +32,7 @@ class ServerListState extends ChangeNotifier {
void updateServer(String onion, String serverBundle, bool running, String description, bool autoStart, bool isEncrypted) { void updateServer(String onion, String serverBundle, bool running, String description, bool autoStart, bool isEncrypted) {
int idx = _servers.indexWhere((element) => element.onion == onion); int idx = _servers.indexWhere((element) => element.onion == onion);
if (idx >= 0) { if (idx >= 0) {
_servers[idx] = ServerInfoState(onion: onion, serverBundle: serverBundle, running: running, description: description, autoStart: autoStart, isEncrypted: isEncrypted); _servers[idx] = ServerInfoState(onion: onion, serverBundle: serverBundle, running: running, description: description, autoStart: autoStart, isEncrypted: isEncrypted);
} else { } else {
print("Tried to update server list without a starting state...this is probably an error"); print("Tried to update server list without a starting state...this is probably an error");
} }

View File

@ -296,11 +296,13 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
// Profile Editing // Profile Editing
if (ctrlrPass.value.text.isEmpty) { if (ctrlrPass.value.text.isEmpty) {
// Don't update password, only update name // Don't update password, only update name
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(Provider.of<ProfileInfoState>(context, listen: false).onion, "profile.name", ctrlrNick.value.text);
Navigator.of(context).pop(); Navigator.of(context).pop();
} 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...
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(Provider.of<ProfileInfoState>(context, listen: false).onion, "profile.name", ctrlrNick.value.text);
final updatePasswordEvent = { final updatePasswordEvent = {
"EventType": "ChangePassword", "EventType": "ChangePassword",

View File

@ -53,7 +53,6 @@ class _AddEditServerViewState extends State<AddEditServerView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: ctrlrOnion.text.isEmpty ? Text(AppLocalizations.of(context)!.addServerTitle) : Text(AppLocalizations.of(context)!.editServerTitle), title: ctrlrOnion.text.isEmpty ? Text(AppLocalizations.of(context)!.addServerTitle) : Text(AppLocalizations.of(context)!.editServerTitle),
@ -82,232 +81,222 @@ class _AddEditServerViewState extends State<AddEditServerView> {
child: Form( child: Form(
key: _formKey, key: _formKey,
child: Container( child: Container(
margin: EdgeInsets.fromLTRB(30, 0, 30, 10), margin: EdgeInsets.fromLTRB(30, 0, 30, 10),
padding: EdgeInsets.fromLTRB(20, 0 , 20, 10), padding: EdgeInsets.fromLTRB(20, 0, 20, 10),
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [
children: [ // Onion
Visibility(
visible: serverInfoState.onion.isNotEmpty,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox(
height: 20,
),
CwtchLabel(label: AppLocalizations.of(context)!.serverAddress),
SizedBox(
height: 20,
),
SelectableText(serverInfoState.onion)
])),
// Onion // Description
Visibility( Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
visible: serverInfoState.onion.isNotEmpty, SizedBox(
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ height: 20,
SizedBox( ),
height: 20, CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel),
), Text(AppLocalizations.of(context)!.serverDescriptionDescription),
CwtchLabel(label: AppLocalizations.of(context)!.serverAddress), SizedBox(
SizedBox( height: 20,
height: 20, ),
), CwtchTextField(
SelectableText( controller: ctrlrDesc,
serverInfoState.onion labelText: "Description",
) autofocus: false,
])), )
]),
// Description SizedBox(
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ height: 20,
SizedBox( ),
height: 20,
),
CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel),
Text(AppLocalizations.of(context)!.serverDescriptionDescription),
SizedBox(
height: 20,
),
CwtchTextField(
controller: ctrlrDesc,
labelText: "Description",
autofocus: false,
)
]),
SizedBox( // Enabled
height: 20, Visibility(
), visible: serverInfoState.onion.isNotEmpty,
child: SwitchListTile(
title: Text(AppLocalizations.of(context)!.serverEnabled, style: TextStyle(color: settings.current().mainTextColor())),
subtitle: Text(AppLocalizations.of(context)!.serverEnabledDescription),
value: serverInfoState.running,
onChanged: (bool value) {
serverInfoState.setRunning(value);
if (value) {
Provider.of<FlwtchState>(context, listen: false).cwtch.LaunchServer(serverInfoState.onion);
} else {
Provider.of<FlwtchState>(context, listen: false).cwtch.StopServer(serverInfoState.onion);
}
},
activeTrackColor: settings.theme.defaultButtonActiveColor(),
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
secondary: Icon(CwtchIcons.negative_heart_24px, color: settings.current().mainTextColor()),
)),
// Enabled // Auto start
Visibility( SwitchListTile(
visible: serverInfoState.onion.isNotEmpty, title: Text(AppLocalizations.of(context)!.serverAutostartLabel, style: TextStyle(color: settings.current().mainTextColor())),
child: SwitchListTile( subtitle: Text(AppLocalizations.of(context)!.serverAutostartDescription),
title: Text(AppLocalizations.of(context)!.serverEnabled, style: TextStyle(color: settings.current().mainTextColor())), value: serverInfoState.autoStart,
subtitle: Text(AppLocalizations.of(context)!.serverEnabledDescription), onChanged: (bool value) {
value: serverInfoState.running, serverInfoState.setAutostart(value);
onChanged: (bool value) {
serverInfoState.setRunning(value);
if (value) {
Provider.of<FlwtchState>(context, listen: false).cwtch.LaunchServer(serverInfoState.onion);
} else {
Provider.of<FlwtchState>(context, listen: false).cwtch.StopServer(serverInfoState.onion);
}
},
activeTrackColor: settings.theme.defaultButtonActiveColor(),
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
secondary: Icon(CwtchIcons.negative_heart_24px, color: settings.current().mainTextColor()),
)),
// Auto start if (!serverInfoState.onion.isEmpty) {
SwitchListTile( Provider.of<FlwtchState>(context, listen: false).cwtch.SetServerAttribute(serverInfoState.onion, "autostart", value ? "true" : "false");
title: Text(AppLocalizations.of(context)!.serverAutostartLabel, style: TextStyle(color: settings.current().mainTextColor())), }
subtitle: Text(AppLocalizations.of(context)!.serverAutostartDescription), },
value: serverInfoState.autoStart, activeTrackColor: settings.theme.defaultButtonActiveColor(),
onChanged: (bool value) { inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
serverInfoState.setAutostart(value); secondary: Icon(CwtchIcons.favorite_24dp, color: settings.current().mainTextColor()),
),
if (! serverInfoState.onion.isEmpty) { // ***** Password *****
Provider.of<FlwtchState>(context, listen: false).cwtch.SetServerAttribute(serverInfoState.onion, "autostart", value ? "true" : "false");
}
},
activeTrackColor: settings.theme.defaultButtonActiveColor(),
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
secondary: Icon(CwtchIcons.favorite_24dp, color: settings.current().mainTextColor()),
),
// use password toggle
Visibility(
visible: serverInfoState.onion.isEmpty,
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
SizedBox(
height: 20,
),
Checkbox(
value: usePassword,
fillColor: MaterialStateProperty.all(settings.current().defaultButtonColor()),
activeColor: settings.current().defaultButtonActiveColor(),
onChanged: _handleSwitchPassword,
),
Text(
AppLocalizations.of(context)!.radioUsePassword,
style: TextStyle(color: settings.current().mainTextColor()),
),
SizedBox(
height: 20,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 24),
child: Text(
usePassword ? AppLocalizations.of(context)!.encryptedServerDescription : AppLocalizations.of(context)!.plainServerDescription,
textAlign: TextAlign.center,
)),
SizedBox(
height: 20,
),
])),
// ***** Password ***** // current password
Visibility(
visible: serverInfoState.onion.isNotEmpty && serverInfoState.isEncrypted,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel),
SizedBox(
height: 20,
),
CwtchPasswordField(
controller: ctrlrOldPass,
autoFillHints: [AutofillHints.newPassword],
validator: (value) {
// Password field can be empty when just updating the profile, not on creation
if (serverInfoState.isEncrypted && serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
return AppLocalizations.of(context)!.passwordErrorEmpty;
}
if (Provider.of<ErrorHandler>(context).deletedServerError == true) {
return AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer;
}
return null;
},
),
SizedBox(
height: 20,
),
])),
// use password toggle // new passwords 1 & 2
Visibility( Visibility(
visible: serverInfoState.onion.isEmpty, // Currently we don't support password change for servers so also gate this on Add server, when ready to support changing password remove the onion.isEmpty check
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ visible: serverInfoState.onion.isEmpty && usePassword,
SizedBox( child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
height: 20, CwtchLabel(label: AppLocalizations.of(context)!.newPassword),
), SizedBox(
Checkbox( height: 20,
value: usePassword, ),
fillColor: MaterialStateProperty.all(settings.current().defaultButtonColor()), CwtchPasswordField(
activeColor: settings.current().defaultButtonActiveColor(), controller: ctrlrPass,
onChanged: _handleSwitchPassword, validator: (value) {
), // Password field can be empty when just updating the profile, not on creation
Text( if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
AppLocalizations.of(context)!.radioUsePassword, return AppLocalizations.of(context)!.passwordErrorEmpty;
style: TextStyle(color: settings.current().mainTextColor()), }
), if (value != ctrlrPass2.value.text) {
SizedBox( return AppLocalizations.of(context)!.passwordErrorMatch;
height: 20, }
), return null;
Padding( },
padding: EdgeInsets.symmetric(horizontal: 24), ),
child: Text( SizedBox(
usePassword ? AppLocalizations.of(context)!.encryptedServerDescription : AppLocalizations.of(context)!.plainServerDescription, height: 20,
textAlign: TextAlign.center, ),
)), CwtchLabel(label: AppLocalizations.of(context)!.password2Label),
SizedBox( SizedBox(
height: 20, height: 20,
), ),
])), CwtchPasswordField(
controller: ctrlrPass2,
validator: (value) {
// Password field can be empty when just updating the profile, not on creation
if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
return AppLocalizations.of(context)!.passwordErrorEmpty;
}
if (value != ctrlrPass.value.text) {
return AppLocalizations.of(context)!.passwordErrorMatch;
}
return null;
}),
]),
),
SizedBox(
height: 20,
),
// current password Row(
Visibility( mainAxisAlignment: MainAxisAlignment.center,
visible: serverInfoState.onion.isNotEmpty && serverInfoState.isEncrypted, children: [
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Expanded(
CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel), child: ElevatedButton(
SizedBox( onPressed: serverInfoState.onion.isEmpty ? _createPressed : _savePressed,
height: 20, child: Text(
), serverInfoState.onion.isEmpty ? AppLocalizations.of(context)!.addServerTitle : AppLocalizations.of(context)!.saveServerButton,
CwtchPasswordField( textAlign: TextAlign.center,
controller: ctrlrOldPass, ),
autoFillHints: [AutofillHints.newPassword], ),
validator: (value) { ),
// Password field can be empty when just updating the profile, not on creation ],
if (serverInfoState.isEncrypted && ),
serverInfoState.onion.isEmpty && Visibility(
value.isEmpty && visible: serverInfoState.onion.isNotEmpty,
usePassword) { child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
return AppLocalizations.of(context)!.passwordErrorEmpty; SizedBox(
} height: 20,
if (Provider.of<ErrorHandler>(context).deletedServerError == true) { ),
return AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer; Tooltip(
} message: AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer,
return null; child: ElevatedButton.icon(
}, onPressed: () {
), showAlertDialog(context);
SizedBox( },
height: 20, icon: Icon(Icons.delete_forever),
), label: Text(AppLocalizations.of(context)!.deleteBtn),
])), ))
]))
// new passwords 1 & 2 // ***** END Password *****
Visibility( ]))))));
// Currently we don't support password change for servers so also gate this on Add server, when ready to support changing password remove the onion.isEmpty check
visible: serverInfoState.onion.isEmpty && usePassword,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
CwtchLabel(label: AppLocalizations.of(context)!.newPassword),
SizedBox(
height: 20,
),
CwtchPasswordField(
controller: ctrlrPass,
validator: (value) {
// Password field can be empty when just updating the profile, not on creation
if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
return AppLocalizations.of(context)!.passwordErrorEmpty;
}
if (value != ctrlrPass2.value.text) {
return AppLocalizations.of(context)!.passwordErrorMatch;
}
return null;
},
),
SizedBox(
height: 20,
),
CwtchLabel(label: AppLocalizations.of(context)!.password2Label),
SizedBox(
height: 20,
),
CwtchPasswordField(
controller: ctrlrPass2,
validator: (value) {
// Password field can be empty when just updating the profile, not on creation
if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
return AppLocalizations.of(context)!.passwordErrorEmpty;
}
if (value != ctrlrPass.value.text) {
return AppLocalizations.of(context)!.passwordErrorMatch;
}
return null;
}),
]),
),
SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: ElevatedButton(
onPressed: serverInfoState.onion.isEmpty ? _createPressed : _savePressed,
child: Text(
serverInfoState.onion.isEmpty ? AppLocalizations.of(context)!.addServerTitle : AppLocalizations.of(context)!.saveServerButton,
textAlign: TextAlign.center,
),
),
),
],
),
Visibility(
visible: serverInfoState.onion.isNotEmpty,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
SizedBox(
height: 20,
),
Tooltip(
message: AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer,
child: ElevatedButton.icon(
onPressed: () {
showAlertDialog(context);
},
icon: Icon(Icons.delete_forever),
label: Text(AppLocalizations.of(context)!.deleteBtn),
))
]))
// ***** END Password *****
]))))));
}); });
}); });
} }
@ -318,29 +307,20 @@ class _AddEditServerViewState extends State<AddEditServerView> {
// match (and are provided if the user has requested an encrypted profile). // match (and are provided if the user has requested an encrypted profile).
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
if (usePassword) { if (usePassword) {
Provider Provider.of<FlwtchState>(context, listen: false).cwtch.CreateServer(ctrlrPass.value.text, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
.of<FlwtchState>(context, listen: false)
.cwtch
.CreateServer(ctrlrPass.value.text, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
} else { } else {
Provider Provider.of<FlwtchState>(context, listen: false).cwtch.CreateServer(DefaultPassword, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
.of<FlwtchState>(context, listen: false)
.cwtch
.CreateServer(DefaultPassword, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
} }
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
} }
void _savePressed() { void _savePressed() {
var server = Provider.of<ServerInfoState>(context, listen: false); var server = Provider.of<ServerInfoState>(context, listen: false);
Provider.of<FlwtchState>(context, listen: false) Provider.of<FlwtchState>(context, listen: false).cwtch.SetServerAttribute(server.onion, "description", ctrlrDesc.text);
.cwtch.SetServerAttribute(server.onion, "description", ctrlrDesc.text);
server.setDescription(ctrlrDesc.text); server.setDescription(ctrlrDesc.text);
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
// TODO support change password // TODO support change password
} }
@ -358,16 +338,11 @@ class _AddEditServerViewState extends State<AddEditServerView> {
Widget continueButton = ElevatedButton( Widget continueButton = ElevatedButton(
child: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn), child: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn),
onPressed: () { onPressed: () {
var onion = Provider var onion = Provider.of<ServerInfoState>(context, listen: false).onion;
.of<ServerInfoState>(context, listen: false) Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteServer(onion, Provider.of<ServerInfoState>(context, listen: false).isEncrypted ? ctrlrOldPass.value.text : DefaultPassword);
.onion;
Provider
.of<FlwtchState>(context, listen: false)
.cwtch
.DeleteServer(onion, Provider.of<ServerInfoState>(context, listen: false).isEncrypted ? ctrlrOldPass.value.text : DefaultPassword);
Future.delayed( Future.delayed(
const Duration(milliseconds: 500), const Duration(milliseconds: 500),
() { () {
if (globalErrorHandler.deletedServerSuccess) { if (globalErrorHandler.deletedServerSuccess) {
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.deleteServerSuccess + ":" + onion)); final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.deleteServerSuccess + ":" + onion));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
@ -395,4 +370,4 @@ class _AddEditServerViewState extends State<AddEditServerView> {
}, },
); );
} }
} }

View File

@ -112,7 +112,6 @@ class _ContactsViewState extends State<ContactsView> {
Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion)); Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
})); }));
// TODO servers // TODO servers
// Search contacts // Search contacts

View File

@ -191,9 +191,8 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
secondary: Icon(CwtchIcons.enable_groups, color: settings.current().mainTextColor()), secondary: Icon(CwtchIcons.enable_groups, color: settings.current().mainTextColor()),
), ),
Visibility( Visibility(
visible: !Platform.isAndroid && !Platform.isIOS, visible: !Platform.isAndroid && !Platform.isIOS,
child: child: SwitchListTile(
SwitchListTile(
title: Text(AppLocalizations.of(context)!.settingServers, style: TextStyle(color: settings.current().mainTextColor())), title: Text(AppLocalizations.of(context)!.settingServers, style: TextStyle(color: settings.current().mainTextColor())),
subtitle: Text(AppLocalizations.of(context)!.settingServersDescription), subtitle: Text(AppLocalizations.of(context)!.settingServersDescription),
value: settings.isExperimentEnabled(ServerManagementExperiment), value: settings.isExperimentEnabled(ServerManagementExperiment),

View File

@ -41,10 +41,10 @@ class _MessageViewState extends State<MessageView> {
void initState() { void initState() {
scrollListener.itemPositions.addListener(() { scrollListener.itemPositions.addListener(() {
if (scrollListener.itemPositions.value.length != 0 && if (scrollListener.itemPositions.value.length != 0 &&
Provider.of<AppState>(context, listen: false).unreadMessagesBelow == true && Provider.of<AppState>(context, listen: false).unreadMessagesBelow == true &&
scrollListener.itemPositions.value.any((element) => element.index == 0)) { scrollListener.itemPositions.value.any((element) => element.index == 0)) {
Provider.of<AppState>(context, listen: false).initialScrollIndex = 0; Provider.of<AppState>(context, listen: false).initialScrollIndex = 0;
Provider.of<AppState>(context, listen: false).unreadMessagesBelow = false; Provider.of<AppState>(context, listen: false).unreadMessagesBelow = false;
} }
}); });
super.initState(); super.initState();

View File

@ -57,9 +57,9 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
SizedBox( SizedBox(
width: 10, width: 10,
), ),
Expanded(child: Text(MediaQuery.of(context).size.width > 600 ? Expanded(
AppLocalizations.of(context)!.titleManageProfiles : AppLocalizations.of(context)!.titleManageProfilesShort, child: Text(MediaQuery.of(context).size.width > 600 ? AppLocalizations.of(context)!.titleManageProfiles : AppLocalizations.of(context)!.titleManageProfilesShort,
style: TextStyle(color: settings.current().mainTextColor()))) style: TextStyle(color: settings.current().mainTextColor())))
]), ]),
actions: getActions(), actions: getActions(),
), ),
@ -238,7 +238,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
if (tiles.isEmpty) { if (tiles.isEmpty) {
return Center( return Center(
child: Text( child: Text(
AppLocalizations.of(context)!.unlockProfileTip, AppLocalizations.of(context)!.unlockProfileTip,
textAlign: TextAlign.center, textAlign: TextAlign.center,
)); ));
} }

View File

@ -30,44 +30,45 @@ class _ServersView extends State<ServersView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( MediaQuery.of(context).size.width > 600 ? AppLocalizations.of(context)!.serversManagerTitleLong : AppLocalizations.of(context)!.serversManagerTitleShort), title: Text(MediaQuery.of(context).size.width > 600 ? AppLocalizations.of(context)!.serversManagerTitleLong : AppLocalizations.of(context)!.serversManagerTitleShort),
actions: getActions(), actions: getActions(),
),
floatingActionButton: FloatingActionButton(
onPressed: _pushAddServer,
tooltip: AppLocalizations.of(context)!.addServerTooltip,
child: Icon(
Icons.add,
semanticLabel: AppLocalizations.of(context)!.addServerTooltip,
), ),
), floatingActionButton: FloatingActionButton(
body: Consumer<ServerListState>( onPressed: _pushAddServer,
builder: (context, svrs, child) { tooltip: AppLocalizations.of(context)!.addServerTooltip,
final tiles = svrs.servers.map((ServerInfoState server) { child: Icon(
return ChangeNotifierProvider<ServerInfoState>.value( Icons.add,
value: server, semanticLabel: AppLocalizations.of(context)!.addServerTooltip,
builder: (context, child) => RepaintBoundary(child: ServerRow()), ),
),
body: Consumer<ServerListState>(
builder: (context, svrs, child) {
final tiles = svrs.servers.map(
(ServerInfoState server) {
return ChangeNotifierProvider<ServerInfoState>.value(
value: server,
builder: (context, child) => RepaintBoundary(child: ServerRow()),
);
},
); );
final divided = ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
if (tiles.isEmpty) {
return Center(
child: Text(
AppLocalizations.of(context)!.unlockServerTip,
textAlign: TextAlign.center,
));
}
return ListView(children: divided);
}, },
); ));
final divided = ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
if (tiles.isEmpty) {
return Center(
child: Text(
AppLocalizations.of(context)!.unlockServerTip,
textAlign: TextAlign.center,
));
}
return ListView(children: divided);
},
));
} }
List<Widget> getActions() { List<Widget> getActions() {
@ -93,41 +94,41 @@ class _ServersView extends State<ServersView> {
padding: MediaQuery.of(context).viewInsets, padding: MediaQuery.of(context).viewInsets,
child: RepaintBoundary( child: RepaintBoundary(
child: Container( child: Container(
height: 200, // bespoke value courtesy of the [TextField] docs height: 200, // bespoke value courtesy of the [TextField] docs
child: Center( child: Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Text(AppLocalizations.of(context)!.enterServerPassword), Text(AppLocalizations.of(context)!.enterServerPassword),
SizedBox( SizedBox(
height: 20, height: 20,
), ),
CwtchPasswordField( CwtchPasswordField(
autofocus: true, autofocus: true,
controller: ctrlrPassword, controller: ctrlrPassword,
action: unlock, action: unlock,
validator: (value) {}, validator: (value) {},
), ),
SizedBox( SizedBox(
height: 20, height: 20,
), ),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
Spacer(), Spacer(),
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock), child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock),
onPressed: () { onPressed: () {
unlock(ctrlrPassword.value.text); unlock(ctrlrPassword.value.text);
}, },
)), )),
Spacer() Spacer()
]), ]),
], ],
))), ))),
))); )));
}); });
} }
@ -141,9 +142,11 @@ class _ServersView extends State<ServersView> {
Navigator.of(context).push(MaterialPageRoute<void>( Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) { builder: (BuildContext context) {
return MultiProvider( return MultiProvider(
providers: [ChangeNotifierProvider<ServerInfoState>( providers: [
create: (_) => ServerInfoState(onion: "", serverBundle: "", description: "", autoStart: true, running: false, isEncrypted: true), ChangeNotifierProvider<ServerInfoState>(
)], create: (_) => ServerInfoState(onion: "", serverBundle: "", description: "", autoStart: true, running: false, isEncrypted: true),
)
],
child: AddEditServerView(), child: AddEditServerView(),
); );
}, },

View File

@ -89,15 +89,15 @@ class FileBubbleState extends State<FileBubble> {
} 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,
// so we probably have to request an info lookup // so we probably have to request an info lookup
if (!Provider.of<ProfileInfoState>(context).downloadInterrupted(widget.fileKey()) ) { if (!Provider.of<ProfileInfoState>(context).downloadInterrupted(widget.fileKey())) {
wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F'); wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F');
Provider.of<FlwtchState>(context, listen: false).cwtch.CheckDownloadStatus(Provider.of<ProfileInfoState>(context, listen: false).onion, widget.fileKey()); Provider.of<FlwtchState>(context, listen: false).cwtch.CheckDownloadStatus(Provider.of<ProfileInfoState>(context, listen: false).onion, widget.fileKey());
} else { } else {
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey()) ?? ""; var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey()) ?? "";
wdgDecorations = Column( wdgDecorations = Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
crossAxisAlignment: CrossAxisAlignment.start, Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'),
children:[Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'),ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton))] ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton))
); ]);
} }
} else { } else {
wdgDecorations = Center( wdgDecorations = Center(
@ -156,11 +156,7 @@ class FileBubbleState extends State<FileBubble> {
Provider.of<MessageMetadata>(context, listen: false).flags |= 0x02; Provider.of<MessageMetadata>(context, listen: false).flags |= 0x02;
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle); ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
if (contact != null) { if (contact != null) {
Provider Provider.of<FlwtchState>(context, listen: false).cwtch.CreateDownloadableFile(profileOnion, contact.identifier, widget.nameSuggestion, widget.fileKey());
.of<FlwtchState>(context, listen: false)
.cwtch
.CreateDownloadableFile(
profileOnion, contact.identifier, widget.nameSuggestion, widget.fileKey());
} }
} else { } else {
try { try {
@ -176,11 +172,7 @@ class FileBubbleState extends State<FileBubble> {
Provider.of<MessageMetadata>(context, listen: false).flags |= 0x02; Provider.of<MessageMetadata>(context, listen: false).flags |= 0x02;
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle); ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
if (contact != null) { if (contact != null) {
Provider Provider.of<FlwtchState>(context, listen: false).cwtch.DownloadFile(profileOnion, contact.identifier, file.path, manifestPath, widget.fileKey());
.of<FlwtchState>(context, listen: false)
.cwtch
.DownloadFile(profileOnion, contact.identifier, file.path, manifestPath,
widget.fileKey());
} }
} }
} catch (e) { } catch (e) {

View File

@ -52,7 +52,7 @@ class MessageBubbleState extends State<MessageBubble> {
var wdgMessage; var wdgMessage;
if (!showClickableLinks) { if (!showClickableLinks) {
wdgMessage = SelectableText( wdgMessage = SelectableText(
widget.content + '\u202F', widget.content + '\u202F',
//key: Key(myKey), //key: Key(myKey),
@ -134,8 +134,7 @@ class MessageBubbleState extends State<MessageBubble> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Text( Text(
"Opening this link will launch an application outside of Cwtch and may reveal metadata or otherwise compromise the security of Cwtch. Only open links from people you trust. Are you sure you want to continue?" "Opening this link will launch an application outside of Cwtch and may reveal metadata or otherwise compromise the security of Cwtch. Only open links from people you trust. Are you sure you want to continue?"),
),
Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Container( Container(
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10), margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
@ -170,6 +169,6 @@ class MessageBubbleState extends State<MessageBubble> {
], ],
)), )),
)); ));
}); });
} }
} }

View File

@ -199,7 +199,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
))))); )))));
var mark = Provider.of<ContactInfoState>(context).newMarker; var mark = Provider.of<ContactInfoState>(context).newMarker;
if (mark > 0 && mark == Provider.of<ContactInfoState>(context).totalMessages - Provider.of<MessageMetadata>(context).messageIndex) { if (mark > 0 && mark == Provider.of<ContactInfoState>(context).totalMessages - Provider.of<MessageMetadata>(context).messageIndex) {
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Align(alignment:Alignment.center ,child:_bubbleNew()), mr]); return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Align(alignment: Alignment.center, child: _bubbleNew()), mr]);
} else { } else {
return mr; return mr;
} }
@ -209,9 +209,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(), color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
border: Border.all( border: Border.all(color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(), width: 1),
color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
width: 1),
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(8), topLeft: Radius.circular(8),
topRight: Radius.circular(8), topRight: Radius.circular(8),
@ -219,9 +217,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
bottomRight: Radius.circular(8), bottomRight: Radius.circular(8),
), ),
), ),
child: Padding( child: Padding(padding: EdgeInsets.all(9.0), child: Text(AppLocalizations.of(context)!.newMessagesLabel)));
padding: EdgeInsets.all(9.0),
child: Text(AppLocalizations.of(context)!.newMessagesLabel)));
} }
void _runAnimation(Offset pixelsPerSecond, Size size) { void _runAnimation(Offset pixelsPerSecond, Size size) {

View File

@ -21,40 +21,38 @@ class _ServerRowState extends State<ServerRow> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var server = Provider.of<ServerInfoState>(context); var server = Provider.of<ServerInfoState>(context);
return Card(clipBehavior: Clip.antiAlias, return Card(
clipBehavior: Clip.antiAlias,
margin: EdgeInsets.all(0.0), margin: EdgeInsets.all(0.0),
child: InkWell( child: InkWell(
child: Row( child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding( Padding(
padding: const EdgeInsets.all(6.0), //border size padding: const EdgeInsets.all(6.0), //border size
child: Icon(CwtchIcons.dns_24px, child: Icon(CwtchIcons.dns_24px,
color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor(), color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor(), size: 64)),
size: 64)
),
Expanded( Expanded(
child: Column( child: Column(
children: [ children: [
Text( Text(
server.description, server.description,
semanticsLabel: server.description, semanticsLabel: server.description,
style: Provider.of<FlwtchState>(context).biggerFont.apply(color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()), style: Provider.of<FlwtchState>(context)
.biggerFont
.apply(color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
softWrap: true,
overflow: TextOverflow.ellipsis,
),
Visibility(
visible: !Provider.of<Settings>(context).streamerMode,
child: ExcludeSemantics(
child: Text(
server.onion,
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), style: TextStyle(color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
Visibility( )))
visible: !Provider.of<Settings>(context).streamerMode, ],
child: ExcludeSemantics( )),
child: Text(
server.onion,
softWrap: true,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
)))
],
)),
// Copy server button // Copy server button
IconButton( IconButton(
@ -75,23 +73,23 @@ class _ServerRowState extends State<ServerRow> {
_pushEditServer(server); _pushEditServer(server);
}, },
) )
]))); ])));
} }
void _pushEditServer(ServerInfoState server ) { void _pushEditServer(ServerInfoState server) {
Provider.of<ErrorHandler>(context).reset(); Provider.of<ErrorHandler>(context).reset();
Navigator.of(context).push(MaterialPageRoute<void>( Navigator.of(context).push(MaterialPageRoute<void>(
settings: RouteSettings(name: "serveraddedit"), settings: RouteSettings(name: "serveraddedit"),
builder: (BuildContext context) { builder: (BuildContext context) {
return MultiProvider( return MultiProvider(
providers: [ChangeNotifierProvider<ServerInfoState>( providers: [
create: (_) => server, ChangeNotifierProvider<ServerInfoState>(
)], create: (_) => server,
)
],
child: AddEditServerView(), child: AddEditServerView(),
); );
}, },
)); ));
} }
} }