Merge pull request 'Implement Quoting' (#96) from quote into trunk
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Reviewed-on: #96 Reviewed-by: Dan Ballard <dan@openprivacy.ca>
This commit is contained in:
commit
84a0ca5d19
|
@ -1 +1 @@
|
||||||
v1.0.0-22-g343c3bc-2021-07-06-15-30
|
v1.0.0-25-g801a805-2021-07-07-16-10
|
|
@ -122,6 +122,12 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||||
val indexI = a.getInt("index")
|
val indexI = a.getInt("index")
|
||||||
return Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, handle, indexI.toLong())).build())
|
return Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, handle, indexI.toLong())).build())
|
||||||
}
|
}
|
||||||
|
"GetMessageByContentHash" -> {
|
||||||
|
val profile = (a.get("profile") as? String) ?: ""
|
||||||
|
val handle = (a.get("contact") as? String) ?: ""
|
||||||
|
val contentHash = (a.get("contentHash") as? String) ?: ""
|
||||||
|
return Result.success(Data.Builder().putString("result", Cwtch.getMessagesByContentHash(profile, handle, contentHash)).build())
|
||||||
|
}
|
||||||
"UpdateMessageFlags" -> {
|
"UpdateMessageFlags" -> {
|
||||||
val profile = (a.get("profile") as? String) ?: ""
|
val profile = (a.get("profile") as? String) ?: ""
|
||||||
val handle = (a.get("contact") as? String) ?: ""
|
val handle = (a.get("contact") as? String) ?: ""
|
||||||
|
|
|
@ -32,6 +32,8 @@ abstract class Cwtch {
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
Future<dynamic> GetMessage(String profile, String handle, int index);
|
Future<dynamic> GetMessage(String profile, String handle, int index);
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
|
Future<dynamic> GetMessageByContentHash(String profile, String handle, String contentHash);
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
void UpdateMessageFlags(String profile, String handle, int index, int flags);
|
void UpdateMessageFlags(String profile, String handle, int index, int flags);
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void SendMessage(String profile, String handle, String message);
|
void SendMessage(String profile, String handle, String message);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:cwtch/models/servers.dart';
|
import 'package:cwtch/models/servers.dart';
|
||||||
import 'package:cwtch/notification_manager.dart';
|
import 'package:cwtch/notification_manager.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -115,7 +116,7 @@ class CwtchNotifier {
|
||||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
||||||
if (key == null) break;
|
if (key == null) break;
|
||||||
try {
|
try {
|
||||||
var message = Provider.of<MessageState>(key.currentContext!, listen: false);
|
var message = Provider.of<MessageMetadata>(key.currentContext!, listen: false);
|
||||||
if (message == null) break;
|
if (message == null) break;
|
||||||
message.ackd = true;
|
message.ackd = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -138,7 +139,7 @@ class CwtchNotifier {
|
||||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.getMessageKey(idx);
|
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.getMessageKey(idx);
|
||||||
if (key == null) break;
|
if (key == null) break;
|
||||||
try {
|
try {
|
||||||
var message = Provider.of<MessageState>(key.currentContext!, listen: false);
|
var message = Provider.of<MessageMetadata>(key.currentContext!, listen: false);
|
||||||
if (message == null) break;
|
if (message == null) break;
|
||||||
message.ackd = true;
|
message.ackd = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -156,7 +157,7 @@ class CwtchNotifier {
|
||||||
var idx = data["Index"];
|
var idx = data["Index"];
|
||||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
||||||
try {
|
try {
|
||||||
var message = Provider.of<MessageState>(key!.currentContext!, listen: false);
|
var message = Provider.of<MessageMetadata>(key!.currentContext!, listen: false);
|
||||||
message.error = true;
|
message.error = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore, we likely have an old key that has been replaced with an actual signature
|
// ignore, we likely have an old key that has been replaced with an actual signature
|
||||||
|
@ -169,7 +170,7 @@ class CwtchNotifier {
|
||||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.getMessageKey(idx);
|
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.getMessageKey(idx);
|
||||||
if (key == null) break;
|
if (key == null) break;
|
||||||
try {
|
try {
|
||||||
var message = Provider.of<MessageState>(key.currentContext!, listen: false);
|
var message = Provider.of<MessageMetadata>(key.currentContext!, listen: false);
|
||||||
if (message == null) break;
|
if (message == null) break;
|
||||||
message.error = true;
|
message.error = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -57,9 +57,9 @@ typedef GetIntFromStrStrFn = int Function(Pointer<Utf8>, int, Pointer<Utf8>, int
|
||||||
typedef get_json_blob_from_str_str_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32);
|
typedef get_json_blob_from_str_str_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32);
|
||||||
typedef GetJsonBlobFromStrStrIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int);
|
typedef GetJsonBlobFromStrStrIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int);
|
||||||
|
|
||||||
//func GetMessages(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, start C.int, end 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_int_int_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int32, Int32);
|
typedef get_json_blob_from_str_str_str_function = Pointer<Utf8> Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
||||||
typedef GetJsonBlobFromStrStrIntIntFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int, int);
|
typedef GetJsonBlobFromStrStrStrFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, 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();
|
||||||
|
@ -397,4 +397,17 @@ class CwtchFfi implements Cwtch {
|
||||||
_receivePort.close();
|
_receivePort.close();
|
||||||
print("Receive Port Closed");
|
print("Receive Port Closed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future GetMessageByContentHash(String profile, String handle, String contentHash) async {
|
||||||
|
var getMessagesByContentHashC = library.lookup<NativeFunction<get_json_blob_from_str_str_str_function>>("c_GetMessagesByContentHash");
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
final GetMessagesByContentHash = getMessagesByContentHashC.asFunction<GetJsonBlobFromStrStrStrFn>();
|
||||||
|
final utf8profile = profile.toNativeUtf8();
|
||||||
|
final utf8handle = handle.toNativeUtf8();
|
||||||
|
final utf8contentHash = contentHash.toNativeUtf8();
|
||||||
|
Pointer<Utf8> jsonMessageBytes = GetMessagesByContentHash(utf8profile, utf8profile.length, utf8handle, utf8handle.length, utf8contentHash, utf8contentHash.length);
|
||||||
|
String jsonMessage = jsonMessageBytes.toDartString();
|
||||||
|
return jsonMessage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,4 +183,9 @@ class CwtchGomobile implements Cwtch {
|
||||||
print("gomobile.dart Shutdown");
|
print("gomobile.dart Shutdown");
|
||||||
cwtchPlatform.invokeMethod("Shutdown", {});
|
cwtchPlatform.invokeMethod("Shutdown", {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future GetMessageByContentHash(String profile, String handle, String contentHash) {
|
||||||
|
return cwtchPlatform.invokeMethod("GetMessageByContentHash", {"profile": profile, "contact": handle, "contentHash": contentHash});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"@@locale": "de",
|
"@@locale": "de",
|
||||||
"@@last_modified": "2021-06-29T19:15:43+02:00",
|
"@@last_modified": "2021-07-07T18:42:50+02:00",
|
||||||
|
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
|
||||||
|
"tooltipReplyToThisMessage": "Reply to this message",
|
||||||
"tooltipRejectContactRequest": "Reject this contact request",
|
"tooltipRejectContactRequest": "Reject this contact request",
|
||||||
"tooltipAcceptContactRequest": "Accept this contact request.",
|
"tooltipAcceptContactRequest": "Accept this contact request.",
|
||||||
"notificationNewMessageFromGroup": "Neue Nachricht in einer Gruppe!",
|
"notificationNewMessageFromGroup": "Neue Nachricht in einer Gruppe!",
|
||||||
|
@ -42,16 +44,10 @@
|
||||||
"tooltipAddContact": "Neuen Kontakt oder Unterhaltung hinzufügen",
|
"tooltipAddContact": "Neuen Kontakt oder Unterhaltung hinzufügen",
|
||||||
"titleManageContacts": "Unterhaltungen",
|
"titleManageContacts": "Unterhaltungen",
|
||||||
"titleManageServers": "Server verwalten",
|
"titleManageServers": "Server verwalten",
|
||||||
"dateMonthsAgo": "Months Ago",
|
|
||||||
"dateNever": "Nie",
|
"dateNever": "Nie",
|
||||||
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
|
|
||||||
"dateLastYear": "Letzes Jahr",
|
"dateLastYear": "Letzes Jahr",
|
||||||
"dateYesterday": "Gestern",
|
"dateYesterday": "Gestern",
|
||||||
"dateLastMonth": "Letzter Monat",
|
"dateLastMonth": "Letzter Monat",
|
||||||
"dateWeeksAgo": "Weeks Ago",
|
|
||||||
"dateDaysAgo": "Days Ago",
|
|
||||||
"dateHoursAgo": "Hours Ago",
|
|
||||||
"dateMinutesAgo": "Minutes Ago",
|
|
||||||
"dateRightNow": "Jetzt",
|
"dateRightNow": "Jetzt",
|
||||||
"successfullAddedContact": "Erfolgreich hinzugefügt",
|
"successfullAddedContact": "Erfolgreich hinzugefügt",
|
||||||
"descriptionBlockUnknownConnections": "Falls aktiviert, wird diese Einstellung alle Verbindungen von Cwtch Usern autmoatisch schliessen, wenn sie nicht in deinen Kontakten sind.",
|
"descriptionBlockUnknownConnections": "Falls aktiviert, wird diese Einstellung alle Verbindungen von Cwtch Usern autmoatisch schliessen, wenn sie nicht in deinen Kontakten sind.",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"@@locale": "en",
|
"@@locale": "en",
|
||||||
"@@last_modified": "2021-06-29T19:15:43+02:00",
|
"@@last_modified": "2021-07-07T18:42:50+02:00",
|
||||||
|
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
|
||||||
|
"tooltipReplyToThisMessage": "Reply to this message",
|
||||||
"tooltipRejectContactRequest": "Reject this contact request",
|
"tooltipRejectContactRequest": "Reject this contact request",
|
||||||
"tooltipAcceptContactRequest": "Accept this contact request.",
|
"tooltipAcceptContactRequest": "Accept this contact request.",
|
||||||
"notificationNewMessageFromGroup": "New message in a group!",
|
"notificationNewMessageFromGroup": "New message in a group!",
|
||||||
|
@ -42,16 +44,10 @@
|
||||||
"tooltipAddContact": "Add a new contact or conversation",
|
"tooltipAddContact": "Add a new contact or conversation",
|
||||||
"titleManageContacts": "Conversations",
|
"titleManageContacts": "Conversations",
|
||||||
"titleManageServers": "Manage Servers",
|
"titleManageServers": "Manage Servers",
|
||||||
"dateMonthsAgo": "Months Ago",
|
|
||||||
"dateNever": "Never",
|
"dateNever": "Never",
|
||||||
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
|
|
||||||
"dateLastYear": "Last Year",
|
"dateLastYear": "Last Year",
|
||||||
"dateYesterday": "Yesterday",
|
"dateYesterday": "Yesterday",
|
||||||
"dateLastMonth": "Last Month",
|
"dateLastMonth": "Last Month",
|
||||||
"dateWeeksAgo": "Weeks Ago",
|
|
||||||
"dateDaysAgo": "Days Ago",
|
|
||||||
"dateHoursAgo": "Hours Ago",
|
|
||||||
"dateMinutesAgo": "Minutes Ago",
|
|
||||||
"dateRightNow": "Right Now",
|
"dateRightNow": "Right Now",
|
||||||
"successfullAddedContact": "Successfully added ",
|
"successfullAddedContact": "Successfully added ",
|
||||||
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
|
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"@@locale": "es",
|
"@@locale": "es",
|
||||||
"@@last_modified": "2021-06-29T19:15:43+02:00",
|
"@@last_modified": "2021-07-07T18:42:50+02:00",
|
||||||
|
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
|
||||||
|
"tooltipReplyToThisMessage": "Reply to this message",
|
||||||
"tooltipRejectContactRequest": "Reject this contact request",
|
"tooltipRejectContactRequest": "Reject this contact request",
|
||||||
"tooltipAcceptContactRequest": "Accept this contact request.",
|
"tooltipAcceptContactRequest": "Accept this contact request.",
|
||||||
"notificationNewMessageFromGroup": "New message in a group!",
|
"notificationNewMessageFromGroup": "New message in a group!",
|
||||||
|
@ -42,16 +44,10 @@
|
||||||
"tooltipAddContact": "Add a new contact or conversation",
|
"tooltipAddContact": "Add a new contact or conversation",
|
||||||
"titleManageContacts": "Conversations",
|
"titleManageContacts": "Conversations",
|
||||||
"titleManageServers": "Manage Servers",
|
"titleManageServers": "Manage Servers",
|
||||||
"dateMonthsAgo": "Months Ago",
|
|
||||||
"dateNever": "Never",
|
"dateNever": "Never",
|
||||||
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
|
|
||||||
"dateLastYear": "Last Year",
|
"dateLastYear": "Last Year",
|
||||||
"dateYesterday": "Yesterday",
|
"dateYesterday": "Yesterday",
|
||||||
"dateLastMonth": "Last Month",
|
"dateLastMonth": "Last Month",
|
||||||
"dateWeeksAgo": "Weeks Ago",
|
|
||||||
"dateDaysAgo": "Days Ago",
|
|
||||||
"dateHoursAgo": "Hours Ago",
|
|
||||||
"dateMinutesAgo": "Minutes Ago",
|
|
||||||
"dateRightNow": "Right Now",
|
"dateRightNow": "Right Now",
|
||||||
"successfullAddedContact": "Successfully added ",
|
"successfullAddedContact": "Successfully added ",
|
||||||
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
|
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
{
|
{
|
||||||
"@@locale": "fr",
|
"@@locale": "fr",
|
||||||
"@@last_modified": "2021-06-29T19:15:43+02:00",
|
"@@last_modified": "2021-07-07T18:42:50+02:00",
|
||||||
"tooltipRejectContactRequest": "Reject this contact request",
|
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
|
||||||
"tooltipAcceptContactRequest": "Accept this contact request.",
|
"tooltipReplyToThisMessage": "Reply to this message",
|
||||||
|
"tooltipRejectContactRequest": "Refuser cette demande de contact",
|
||||||
|
"tooltipAcceptContactRequest": "Acceptez cette demande de contact.",
|
||||||
"notificationNewMessageFromGroup": "Nouveau message dans un groupe !",
|
"notificationNewMessageFromGroup": "Nouveau message dans un groupe !",
|
||||||
"notificationNewMessageFromPeer": "Nouveau message d'un contact !",
|
"notificationNewMessageFromPeer": "Nouveau message d'un contact !",
|
||||||
"tooltipHidePassword": "Masquer le mot de passe",
|
"tooltipHidePassword": "Masquer le mot de passe",
|
||||||
"tooltipShowPassword": "Afficher le mot de passe",
|
"tooltipShowPassword": "Afficher le mot de passe",
|
||||||
"serverNotSynced": "Synchronisation des nouveaux messages (Cela peut prendre un certain temps)...",
|
"serverNotSynced": "Synchronisation des nouveaux messages (Cela peut prendre un certain temps)...",
|
||||||
"groupInviteSettingsWarning": "Vous avez été invité à rejoindre un groupe ! Veuillez activer l'expérience de discussion de groupe dans les paramètres pour afficher cette invitation.",
|
"groupInviteSettingsWarning": "Vous avez été invité à rejoindre un groupe ! Veuillez activer l'expérience de discussion de groupe dans les paramètres pour afficher cette invitation.",
|
||||||
"shutdownCwtchAction": "Arrêt Cwtch",
|
"shutdownCwtchAction": "Arrêt de Cwtch",
|
||||||
"shutdownCwtchDialog": "Êtes-vous sûr de vouloir arrêter Cwtch ? Ceci fermera toutes les connexions, et quittera l'application.",
|
"shutdownCwtchDialog": "Êtes-vous sûr de vouloir arrêter Cwtch ? Ceci fermera toutes les connexions, et quittera l'application.",
|
||||||
"shutdownCwtchDialogTitle": "Arrêter Cwtch ?",
|
"shutdownCwtchDialogTitle": "Arrêter Cwtch ?",
|
||||||
"shutdownCwtchTooltip": "Arrêt Cwtch",
|
"shutdownCwtchTooltip": "Arrêt de Cwtch",
|
||||||
"malformedMessage": "Message mal formé",
|
"malformedMessage": "Message mal formé",
|
||||||
"profileDeleteSuccess": "Le profil a été supprimé avec succès",
|
"profileDeleteSuccess": "Le profil a été supprimé avec succès",
|
||||||
"debugLog": "Activer le journal de la console de débogage",
|
"debugLog": "Activer le journal de la console de débogage",
|
||||||
|
@ -42,16 +44,10 @@
|
||||||
"tooltipAddContact": "Ajouter un nouveau contact ou une nouvelle conversation",
|
"tooltipAddContact": "Ajouter un nouveau contact ou une nouvelle conversation",
|
||||||
"titleManageContacts": "Conversations",
|
"titleManageContacts": "Conversations",
|
||||||
"titleManageServers": "Gérer les serveurs",
|
"titleManageServers": "Gérer les serveurs",
|
||||||
"dateMonthsAgo": "Il y a plusieurs mois",
|
|
||||||
"dateNever": "Jamais",
|
"dateNever": "Jamais",
|
||||||
"dateYearsAgo": "Il y a X ans (affiché à côté d'une ligne de contact pour indiquer l'heure de la dernière action)",
|
|
||||||
"dateLastYear": "L'année dernière",
|
"dateLastYear": "L'année dernière",
|
||||||
"dateYesterday": "Hier",
|
"dateYesterday": "Hier",
|
||||||
"dateLastMonth": "Le mois dernier",
|
"dateLastMonth": "Le mois dernier",
|
||||||
"dateWeeksAgo": "Il y a quelques semaines",
|
|
||||||
"dateDaysAgo": "Il y a quelques jours",
|
|
||||||
"dateHoursAgo": "Il y a quelques heures",
|
|
||||||
"dateMinutesAgo": "Il y a quelques minutes",
|
|
||||||
"dateRightNow": "Maintenant",
|
"dateRightNow": "Maintenant",
|
||||||
"successfullAddedContact": "Ajouté avec succès ",
|
"successfullAddedContact": "Ajouté avec succès ",
|
||||||
"descriptionBlockUnknownConnections": "Si elle est activée, cette option fermera automatiquement les connexions des utilisateurs de Cwtch qui n'ont pas été ajoutés à votre liste de contacts.",
|
"descriptionBlockUnknownConnections": "Si elle est activée, cette option fermera automatiquement les connexions des utilisateurs de Cwtch qui n'ont pas été ajoutés à votre liste de contacts.",
|
||||||
|
@ -66,7 +62,7 @@
|
||||||
"enterCurrentPasswordForDelete": "Veuillez entrer le mot de passe actuel pour supprimer ce profil.",
|
"enterCurrentPasswordForDelete": "Veuillez entrer le mot de passe actuel pour supprimer ce profil.",
|
||||||
"enableGroups": "Activer la discussion de groupe",
|
"enableGroups": "Activer la discussion de groupe",
|
||||||
"experimentsEnabled": "Activer les expériences",
|
"experimentsEnabled": "Activer les expériences",
|
||||||
"localeIt": "Italienne",
|
"localeIt": "Italien",
|
||||||
"localeEs": "Espagnol",
|
"localeEs": "Espagnol",
|
||||||
"addListItem": "Ajouter un nouvel élément de liste",
|
"addListItem": "Ajouter un nouvel élément de liste",
|
||||||
"addNewItem": "Ajouter un nouvel élément à la liste",
|
"addNewItem": "Ajouter un nouvel élément à la liste",
|
||||||
|
@ -80,9 +76,9 @@
|
||||||
"loadingTor": "Chargement de tor...",
|
"loadingTor": "Chargement de tor...",
|
||||||
"smallTextLabel": "Petit",
|
"smallTextLabel": "Petit",
|
||||||
"defaultScalingText": "Taille par défaut du texte (échelle:",
|
"defaultScalingText": "Taille par défaut du texte (échelle:",
|
||||||
"builddate": "Construit sur : 2%",
|
"builddate": "Construit le : %2",
|
||||||
"version": "Version 1%",
|
"version": "Version %1",
|
||||||
"versionTor": "Version 1% avec tor 2%",
|
"versionTor": "Version %1 avec tor %2",
|
||||||
"themeDark": "Sombre",
|
"themeDark": "Sombre",
|
||||||
"themeLight": "Clair",
|
"themeLight": "Clair",
|
||||||
"settingTheme": "Thème",
|
"settingTheme": "Thème",
|
||||||
|
@ -94,8 +90,8 @@
|
||||||
"localeEn": "Anglais",
|
"localeEn": "Anglais",
|
||||||
"settingLanguage": "Langue",
|
"settingLanguage": "Langue",
|
||||||
"blockUnknownLabel": "Bloquer les pairs inconnus",
|
"blockUnknownLabel": "Bloquer les pairs inconnus",
|
||||||
"zoomLabel": "Interface zoom (essentiellement la taille du texte et des composants de l'interface)",
|
"zoomLabel": "Zoom de l'interface (affecte principalement la taille du texte et des boutons)",
|
||||||
"versionBuilddate": "Version 1% avec tor 2%",
|
"versionBuilddate": "Version : %1 Construite le : %2",
|
||||||
"cwtchSettingsTitle": "Préférences Cwtch",
|
"cwtchSettingsTitle": "Préférences Cwtch",
|
||||||
"unlock": "Déverrouiller",
|
"unlock": "Déverrouiller",
|
||||||
"yourServers": "Vos serveurs",
|
"yourServers": "Vos serveurs",
|
||||||
|
@ -105,7 +101,7 @@
|
||||||
"enterProfilePassword": "Entrez un mot de passe pour consulter vos profils",
|
"enterProfilePassword": "Entrez un mot de passe pour consulter vos profils",
|
||||||
"addNewProfileBtn": "Ajouter un nouveau profil",
|
"addNewProfileBtn": "Ajouter un nouveau profil",
|
||||||
"deleteConfirmText": "SUPPRIMER",
|
"deleteConfirmText": "SUPPRIMER",
|
||||||
"deleteProfileConfirmBtn": "Vraiment supprimer le profil",
|
"deleteProfileConfirmBtn": "Supprimer vraiment le profil ?",
|
||||||
"deleteConfirmLabel": "Tapez SUPPRIMER pour confirmer",
|
"deleteConfirmLabel": "Tapez SUPPRIMER pour confirmer",
|
||||||
"deleteProfileBtn": "Supprimer le profil",
|
"deleteProfileBtn": "Supprimer le profil",
|
||||||
"passwordChangeError": "Erreur lors de la modification du mot de passe : le mot de passe fourni est rejeté",
|
"passwordChangeError": "Erreur lors de la modification du mot de passe : le mot de passe fourni est rejeté",
|
||||||
|
@ -129,7 +125,7 @@
|
||||||
"profileName": "Pseudo",
|
"profileName": "Pseudo",
|
||||||
"editProfileTitle": "Modifier le profil",
|
"editProfileTitle": "Modifier le profil",
|
||||||
"addProfileTitle": "Ajouter un nouveau profil",
|
"addProfileTitle": "Ajouter un nouveau profil",
|
||||||
"deleteBtn": "Effacer",
|
"deleteBtn": "Supprimer",
|
||||||
"unblockBtn": "Débloquer le pair",
|
"unblockBtn": "Débloquer le pair",
|
||||||
"dontSavePeerHistory": "Supprimer l'historique des pairs",
|
"dontSavePeerHistory": "Supprimer l'historique des pairs",
|
||||||
"savePeerHistoryDescription": "Détermine s'il faut ou non supprimer tout historique associé au pair.",
|
"savePeerHistoryDescription": "Détermine s'il faut ou non supprimer tout historique associé au pair.",
|
||||||
|
@ -150,16 +146,16 @@
|
||||||
"peerOfflineMessage": "Le pair est hors ligne, les messages ne peuvent pas être remis pour le moment",
|
"peerOfflineMessage": "Le pair est hors ligne, les messages ne peuvent pas être remis pour le moment",
|
||||||
"peerBlockedMessage": "Le pair est bloqué",
|
"peerBlockedMessage": "Le pair est bloqué",
|
||||||
"pendingLabel": "En attente",
|
"pendingLabel": "En attente",
|
||||||
"acknowledgedLabel": "Confirmé",
|
"acknowledgedLabel": "Accusé de réception",
|
||||||
"couldNotSendMsgError": "Impossible d'envoyer ce message",
|
"couldNotSendMsgError": "Impossible d'envoyer ce message",
|
||||||
"dmTooltip": "Envoyer un message privé",
|
"dmTooltip": "Envoyer un message privé",
|
||||||
"membershipDescription": "Liste des utilisateurs ayant envoyés un ou plusieurs messages au groupe. Cette liste peut ne pas être representatives de l'ensemble des membres du groupe.",
|
"membershipDescription": "Liste des utilisateurs ayant envoyés un ou plusieurs messages au groupe. Cette liste peut ne pas être représentatives de l'ensemble des membres du groupe.",
|
||||||
"addListItemBtn": "Ajouter un élément",
|
"addListItemBtn": "Ajouter un élément",
|
||||||
"peerNotOnline": "Le pair est hors ligne, les messages ne peuvent pas être remis pour le moment",
|
"peerNotOnline": "Le pair est hors ligne, les messages ne peuvent pas être remis pour le moment",
|
||||||
"searchList": "Liste de recherche",
|
"searchList": "Liste de recherche",
|
||||||
"update": "Mise à jour",
|
"update": "Mise à jour",
|
||||||
"inviteBtn": "Invitation",
|
"inviteBtn": "Invitation",
|
||||||
"inviteToGroupLabel": "Inviter quelqu'un",
|
"inviteToGroupLabel": "Inviter au groupe",
|
||||||
"groupNameLabel": "Nom du groupe",
|
"groupNameLabel": "Nom du groupe",
|
||||||
"viewServerInfo": "Informations sur le serveur",
|
"viewServerInfo": "Informations sur le serveur",
|
||||||
"serverSynced": "Synchronisé",
|
"serverSynced": "Synchronisé",
|
||||||
|
@ -192,6 +188,6 @@
|
||||||
"createGroupTab": "Créer un groupe",
|
"createGroupTab": "Créer un groupe",
|
||||||
"addPeerTab": "Ajouter un pair",
|
"addPeerTab": "Ajouter un pair",
|
||||||
"createGroupBtn": "Créer",
|
"createGroupBtn": "Créer",
|
||||||
"defaultGroupName": "Un super groupe",
|
"defaultGroupName": "Un groupe génial",
|
||||||
"createGroupTitle": "Créer un groupe"
|
"createGroupTitle": "Créer un groupe"
|
||||||
}
|
}
|
|
@ -1,71 +1,67 @@
|
||||||
{
|
{
|
||||||
"@@locale": "it",
|
"@@locale": "it",
|
||||||
"@@last_modified": "2021-06-29T19:15:43+02:00",
|
"@@last_modified": "2021-07-07T18:42:50+02:00",
|
||||||
"tooltipRejectContactRequest": "Reject this contact request",
|
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
|
||||||
"tooltipAcceptContactRequest": "Accept this contact request.",
|
"tooltipReplyToThisMessage": "Reply to this message",
|
||||||
"notificationNewMessageFromGroup": "New message in a group!",
|
"tooltipRejectContactRequest": "Rifiuta questa richiesta di contatto",
|
||||||
"notificationNewMessageFromPeer": "New message from a contact!",
|
"tooltipAcceptContactRequest": "Accetta questa richiesta di contatto.",
|
||||||
"tooltipHidePassword": "Hide Password",
|
"notificationNewMessageFromGroup": "Nuovo messaggio in un gruppo!",
|
||||||
"tooltipShowPassword": "Show Password",
|
"notificationNewMessageFromPeer": "Nuovo messaggio da un contatto!",
|
||||||
"serverNotSynced": "Non sincronizzato",
|
"tooltipHidePassword": "Nascondi la password",
|
||||||
"groupInviteSettingsWarning": "You have been invited to join a group! Please enable the Group Chat Experiment in Settings to view this Invitation.",
|
"tooltipShowPassword": "Mostra la password",
|
||||||
"shutdownCwtchAction": "Shutdown Cwtch",
|
"serverNotSynced": "Sincronizzazione nuovi messaggi (l'operazione può richiedere del tempo)...",
|
||||||
"shutdownCwtchDialog": "Are you sure you want to shutdown Cwtch? This will close all connections, and exit the application.",
|
"groupInviteSettingsWarning": "Sei stato invitato ad unirti ad un gruppo! Abilita l'Esperimento di chat di gruppo in Impostazioni per visualizzare questo Invito.",
|
||||||
"shutdownCwtchDialogTitle": "Shutdown Cwtch?",
|
"shutdownCwtchAction": "Chiudi Cwtch",
|
||||||
"shutdownCwtchTooltip": "Shutdown Cwtch",
|
"shutdownCwtchDialog": "Sei sicuro di voler chiudere Cwtch? Questo chiuderà tutte le connessioni e uscirà dall'applicazione.",
|
||||||
"malformedMessage": "Malformed message",
|
"shutdownCwtchDialogTitle": "Chiudi Cwtch?",
|
||||||
"profileDeleteSuccess": "Successfully deleted profile",
|
"shutdownCwtchTooltip": "Chiudi Cwtch",
|
||||||
"debugLog": "Turn on console debug logging",
|
"malformedMessage": "Messaggio non valido",
|
||||||
"torNetworkStatus": "Tor network status",
|
"profileDeleteSuccess": "Profilo eliminato con successo",
|
||||||
"addContactFirst": "Add or pick a contact to begin chatting.",
|
"debugLog": "Attiva la registrazione del debug della console",
|
||||||
"createProfileToBegin": "Please create or unlock a profile to begin",
|
"torNetworkStatus": "Stato della rete Tor",
|
||||||
"nickChangeSuccess": "Profile nickname changed successfully",
|
"addContactFirst": "Aggiungi o scegli un contatto per iniziare a chattare.",
|
||||||
"addServerFirst": "You need to add a server before you can create a group",
|
"createProfileToBegin": "Crea o sblocca un profilo per iniziare",
|
||||||
"deleteProfileSuccess": "Successfully deleted profile",
|
"nickChangeSuccess": "Nickname del profilo modificato con successo",
|
||||||
"sendInvite": "Send a contact or group invite",
|
"addServerFirst": "È necessario aggiungere un server prima di poter creare un gruppo",
|
||||||
"sendMessage": "Send Message",
|
"deleteProfileSuccess": "Profilo eliminato con successo",
|
||||||
"cancel": "Cancel",
|
"sendInvite": "Invia un invito a un contatto o a un gruppo",
|
||||||
"resetTor": "Reset",
|
"sendMessage": "Invia messaggio",
|
||||||
"torStatus": "Tor Status",
|
"cancel": "Annulla",
|
||||||
"torVersion": "Tor Version",
|
"resetTor": "Resettare",
|
||||||
"sendAnInvitation": "You sent an invitation for: ",
|
"torStatus": "Stato di Tor",
|
||||||
"contactSuggestion": "This is a contact suggestion for: ",
|
"torVersion": "Versione di Tor",
|
||||||
"rejected": "Rejected!",
|
"sendAnInvitation": "Hai inviato un invito per:",
|
||||||
"accepted": "Accepted!",
|
"contactSuggestion": "Questo è un suggerimento di contatto per:",
|
||||||
"chatHistoryDefault": "This conversation will be deleted when Cwtch is closed! Message history can be enabled per-conversation via the Settings menu in the upper right.",
|
"rejected": "Rifiutato!",
|
||||||
"newPassword": "New Password",
|
"accepted": "Accettato!",
|
||||||
"yesLeave": "Yes, Leave This Conversation",
|
"chatHistoryDefault": "Questa conversazione sarà cancellata quando Cwtch sarà chiuso! La cronologia dei messaggi può essere abilitata per ogni conversazione tramite il menu Impostazioni in alto a destra.",
|
||||||
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
|
"newPassword": "Nuova password",
|
||||||
"leaveGroup": "Leave This Conversation",
|
"yesLeave": "Sì, lascia questa conversazione",
|
||||||
"inviteToGroup": "You have been invited to join a group:",
|
"reallyLeaveThisGroupPrompt": "Uscire da questa conversazione? Tutti i messaggi e gli attributi verranno eliminati.",
|
||||||
"pasteAddressToAddContact": "... incolla qui un indirizzo per aggiungere un contatto...",
|
"leaveGroup": "Lascia questa conversazione",
|
||||||
"tooltipAddContact": "Add a new contact or conversation",
|
"inviteToGroup": "Hai ricevuto un invito a unirti a un gruppo:",
|
||||||
"titleManageContacts": "Conversations",
|
"pasteAddressToAddContact": "Incolla qui un indirizzo cwtch, un invito o un mazzo di chiavi per aggiungere una nuova conversazione",
|
||||||
"titleManageServers": "Manage Servers",
|
"tooltipAddContact": "Aggiungi un nuovo contatto o conversazione",
|
||||||
"dateMonthsAgo": "Months Ago",
|
"titleManageContacts": "Conversazioni",
|
||||||
"dateNever": "Never",
|
"titleManageServers": "Gestisci i server",
|
||||||
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
|
"dateNever": "Mai",
|
||||||
"dateLastYear": "Last Year",
|
"dateLastYear": "L'anno scorso",
|
||||||
"dateYesterday": "Yesterday",
|
"dateYesterday": "Ieri",
|
||||||
"dateLastMonth": "Last Month",
|
"dateLastMonth": "Mese scorso",
|
||||||
"dateWeeksAgo": "Weeks Ago",
|
"dateRightNow": "Ora",
|
||||||
"dateDaysAgo": "Days Ago",
|
"successfullAddedContact": "Aggiunto con successo ",
|
||||||
"dateHoursAgo": "Hours Ago",
|
"descriptionBlockUnknownConnections": "Se attivata, questa opzione chiuderà automaticamente le connessioni degli utenti Cwtch che non sono stati aggiunti alla tua lista di contatti.",
|
||||||
"dateMinutesAgo": "Minutes Ago",
|
"descriptionExperimentsGroups": "L'esperimento di gruppo permette a Cwtch di connettersi con un'infrastruttura server non fidata per facilitare la comunicazione con più di un contatto.",
|
||||||
"dateRightNow": "Right Now",
|
"descriptionExperiments": "Gli esperimenti di Cwtch sono opzioni a scelta che aggiungono a Cwtch funzionalità che possono avere diverse considerazioni sulla privacy rispetto alla tradizionale chat 1:1 resistente ai metadati, ad esempio chat di gruppo, integrazione di bot ecc.",
|
||||||
"successfullAddedContact": "Successfully added ",
|
"titleManageProfiles": "Gestisci i profili Cwtch",
|
||||||
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
|
"tooltipUnlockProfiles": "Sblocca i profili crittografati inserendo la loro password.",
|
||||||
"descriptionExperimentsGroups": "The group experiment allows Cwtch to connect with untrusted server infrastructure to facilitate communication with more than one contact.",
|
"tooltipOpenSettings": "Aprire il pannello delle impostazioni",
|
||||||
"descriptionExperiments": "Cwtch experiments are optional, opt-in features that add additional functionality to Cwtch that may have different privacy considerations than traditional 1:1 metadata resistant chat e.g. group chat, bot integration etc.",
|
"invalidImportString": "Importazione stringa non valida",
|
||||||
"titleManageProfiles": "Manage Cwtch Profiles",
|
"contactAlreadyExists": "Il contatto esiste già",
|
||||||
"tooltipUnlockProfiles": "Unlock encrypted profiles by entering their password.",
|
"conversationSettings": "Impostazioni di conversazione",
|
||||||
"tooltipOpenSettings": "Open the settings pane",
|
"enterCurrentPasswordForDelete": "Inserisci la password attuale per eliminare questo profilo.",
|
||||||
"invalidImportString": "Invalid import string",
|
"enableGroups": "Abilita la chat di gruppo",
|
||||||
"contactAlreadyExists": "Contact Already Exists",
|
"experimentsEnabled": "Abilita esperimenti",
|
||||||
"conversationSettings": "Conversation Settings",
|
|
||||||
"enterCurrentPasswordForDelete": "Please enter current password to delete this profile.",
|
|
||||||
"enableGroups": "Enable Group Chat",
|
|
||||||
"experimentsEnabled": "Esperimenti abilitati",
|
|
||||||
"localeIt": "Italiano",
|
"localeIt": "Italiano",
|
||||||
"localeEs": "Spagnolo",
|
"localeEs": "Spagnolo",
|
||||||
"addListItem": "Aggiungi un nuovo elemento alla lista",
|
"addListItem": "Aggiungi un nuovo elemento alla lista",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"@@locale": "pl",
|
"@@locale": "pl",
|
||||||
"@@last_modified": "2021-07-05T21:26:10+02:00",
|
"@@last_modified": "2021-07-07T18:42:50+02:00",
|
||||||
|
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
|
||||||
|
"tooltipReplyToThisMessage": "Reply to this message",
|
||||||
"tooltipRejectContactRequest": "Reject this contact request",
|
"tooltipRejectContactRequest": "Reject this contact request",
|
||||||
"tooltipAcceptContactRequest": "Accept this contact request.",
|
"tooltipAcceptContactRequest": "Accept this contact request.",
|
||||||
"notificationNewMessageFromGroup": "New message in a group!",
|
"notificationNewMessageFromGroup": "New message in a group!",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"@@locale": "pt",
|
"@@locale": "pt",
|
||||||
"@@last_modified": "2021-06-29T19:15:43+02:00",
|
"@@last_modified": "2021-07-07T18:42:50+02:00",
|
||||||
|
"tooltipRemoveThisQuotedMessage": "Remove quoted message.",
|
||||||
|
"tooltipReplyToThisMessage": "Reply to this message",
|
||||||
"tooltipRejectContactRequest": "Reject this contact request",
|
"tooltipRejectContactRequest": "Reject this contact request",
|
||||||
"tooltipAcceptContactRequest": "Accept this contact request.",
|
"tooltipAcceptContactRequest": "Accept this contact request.",
|
||||||
"notificationNewMessageFromGroup": "New message in a group!",
|
"notificationNewMessageFromGroup": "New message in a group!",
|
||||||
|
@ -42,16 +44,10 @@
|
||||||
"tooltipAddContact": "Add a new contact or conversation",
|
"tooltipAddContact": "Add a new contact or conversation",
|
||||||
"titleManageContacts": "Conversations",
|
"titleManageContacts": "Conversations",
|
||||||
"titleManageServers": "Manage Servers",
|
"titleManageServers": "Manage Servers",
|
||||||
"dateMonthsAgo": "Months Ago",
|
|
||||||
"dateNever": "Never",
|
"dateNever": "Never",
|
||||||
"dateYearsAgo": "X Years Ago (displayed next to a contact row to indicate time of last action)",
|
|
||||||
"dateLastYear": "Last Year",
|
"dateLastYear": "Last Year",
|
||||||
"dateYesterday": "Yesterday",
|
"dateYesterday": "Yesterday",
|
||||||
"dateLastMonth": "Last Month",
|
"dateLastMonth": "Last Month",
|
||||||
"dateWeeksAgo": "Weeks Ago",
|
|
||||||
"dateDaysAgo": "Days Ago",
|
|
||||||
"dateHoursAgo": "Hours Ago",
|
|
||||||
"dateMinutesAgo": "Minutes Ago",
|
|
||||||
"dateRightNow": "Right Now",
|
"dateRightNow": "Right Now",
|
||||||
"successfullAddedContact": "Successfully added ",
|
"successfullAddedContact": "Successfully added ",
|
||||||
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
|
"descriptionBlockUnknownConnections": "If turned on, this option will automatically close connections from Cwtch users that have not been added to your contact list.",
|
||||||
|
|
144
lib/model.dart
144
lib/model.dart
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cwtch/widgets/messagerow.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:cwtch/models/servers.dart';
|
import 'package:cwtch/models/servers.dart';
|
||||||
import 'package:cwtch/widgets/messagebubble.dart';
|
import 'package:cwtch/widgets/messagebubble.dart';
|
||||||
|
@ -67,6 +68,7 @@ class AppState extends ChangeNotifier {
|
||||||
String appError = "";
|
String appError = "";
|
||||||
String? _selectedProfile;
|
String? _selectedProfile;
|
||||||
String? _selectedConversation;
|
String? _selectedConversation;
|
||||||
|
int? _selectedIndex;
|
||||||
|
|
||||||
void SetCwtchInit() {
|
void SetCwtchInit() {
|
||||||
cwtchInit = true;
|
cwtchInit = true;
|
||||||
|
@ -90,6 +92,12 @@ class AppState extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int? get selectedIndex => _selectedIndex;
|
||||||
|
set selectedIndex(int? newVal) {
|
||||||
|
this._selectedIndex = newVal;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
bool isLandscape(BuildContext c) => MediaQuery.of(c).size.width > MediaQuery.of(c).size.height;
|
bool isLandscape(BuildContext c) => MediaQuery.of(c).size.width > MediaQuery.of(c).size.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,7 +344,7 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
late int _unreadMessages = 0;
|
late int _unreadMessages = 0;
|
||||||
late int _totalMessages = 0;
|
late int _totalMessages = 0;
|
||||||
late DateTime _lastMessageTime;
|
late DateTime _lastMessageTime;
|
||||||
late Map<String, GlobalKey<MessageBubbleState>> keys;
|
late Map<String, GlobalKey<MessageRowState>> keys;
|
||||||
|
|
||||||
// todo: a nicer way to model contacts, groups and other "entities"
|
// todo: a nicer way to model contacts, groups and other "entities"
|
||||||
late bool _isGroup;
|
late bool _isGroup;
|
||||||
|
@ -368,7 +376,7 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
this._savePeerHistory = savePeerHistory;
|
this._savePeerHistory = savePeerHistory;
|
||||||
this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime;
|
this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime;
|
||||||
this._server = server;
|
this._server = server;
|
||||||
keys = Map<String, GlobalKey<MessageBubbleState>>();
|
keys = Map<String, GlobalKey<MessageRowState>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
String get nickname => this._nickname;
|
String get nickname => this._nickname;
|
||||||
|
@ -444,137 +452,11 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalKey<MessageBubbleState> getMessageKey(String index) {
|
GlobalKey<MessageRowState> getMessageKey(String index) {
|
||||||
if (keys[index] == null) {
|
if (keys[index] == null) {
|
||||||
keys[index] = GlobalKey<MessageBubbleState>();
|
keys[index] = GlobalKey<MessageRowState>();
|
||||||
}
|
}
|
||||||
GlobalKey<MessageBubbleState> ret = keys[index]!;
|
GlobalKey<MessageRowState> ret = keys[index]!;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageState extends ChangeNotifier {
|
|
||||||
final String profileOnion;
|
|
||||||
final String contactHandle;
|
|
||||||
final int messageIndex;
|
|
||||||
late String _message;
|
|
||||||
late int _overlay;
|
|
||||||
late String _inviteTarget;
|
|
||||||
late String _inviteNick;
|
|
||||||
late DateTime _timestamp;
|
|
||||||
late String _senderOnion;
|
|
||||||
late int _flags;
|
|
||||||
String? _senderImage;
|
|
||||||
late String _signature = "";
|
|
||||||
late bool _ackd = false;
|
|
||||||
late bool _error = false;
|
|
||||||
late bool _loaded = false;
|
|
||||||
late bool _malformed = false;
|
|
||||||
|
|
||||||
MessageState({
|
|
||||||
required BuildContext context,
|
|
||||||
required this.profileOnion,
|
|
||||||
required this.contactHandle,
|
|
||||||
required this.messageIndex,
|
|
||||||
}) {
|
|
||||||
this._senderOnion = profileOnion;
|
|
||||||
tryLoad(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
get message => this._message;
|
|
||||||
get overlay => this._overlay;
|
|
||||||
get timestamp => this._timestamp;
|
|
||||||
int get flags => this._flags;
|
|
||||||
set flags(int newVal) {
|
|
||||||
this._flags = newVal;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get ackd => this._ackd;
|
|
||||||
bool get error => this._error;
|
|
||||||
bool get malformed => this._malformed;
|
|
||||||
bool get loaded => this._loaded;
|
|
||||||
get senderOnion => this._senderOnion;
|
|
||||||
get senderImage => this._senderImage;
|
|
||||||
get signature => this._signature;
|
|
||||||
get isInvite => this.overlay == 100 || this.overlay == 101;
|
|
||||||
get inviteTarget => this._inviteTarget;
|
|
||||||
get inviteNick => this._inviteNick;
|
|
||||||
|
|
||||||
set ackd(bool newVal) {
|
|
||||||
this._ackd = newVal;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
set error(bool newVal) {
|
|
||||||
this._error = newVal;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
set malformed(bool newVal) {
|
|
||||||
this._malformed = newVal;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
set loaded(bool newVal) {
|
|
||||||
// quickly-arriving messages get discarded before loading sometimes
|
|
||||||
if (!hasListeners) return;
|
|
||||||
this._loaded = newVal;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void tryLoad(BuildContext context) {
|
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, messageIndex).then((jsonMessage) {
|
|
||||||
try {
|
|
||||||
dynamic messageWrapper = jsonDecode(jsonMessage);
|
|
||||||
if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
|
|
||||||
this._senderOnion = profileOnion;
|
|
||||||
Future.delayed(const Duration(milliseconds: 2), () {
|
|
||||||
tryLoad(context);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dynamic message = jsonDecode(messageWrapper['Message']);
|
|
||||||
this._message = message['d'];
|
|
||||||
this._overlay = int.parse(message['o'].toString());
|
|
||||||
this._timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
|
|
||||||
this._senderOnion = messageWrapper['PeerID'];
|
|
||||||
this._senderImage = messageWrapper['ContactImage'];
|
|
||||||
this._flags = int.parse(messageWrapper['Flags'].toString(), radix: 2);
|
|
||||||
|
|
||||||
// If this is a group, store the signature
|
|
||||||
if (contactHandle.length == 32) {
|
|
||||||
this._signature = messageWrapper['Signature'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this is an invite, get the contact handle
|
|
||||||
if (this.isInvite) {
|
|
||||||
if (message['d'].toString().length == 56) {
|
|
||||||
this._inviteTarget = message['d'];
|
|
||||||
var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(this._inviteTarget);
|
|
||||||
this._inviteNick = targetContact == null ? message['d'] : targetContact.nickname;
|
|
||||||
} else {
|
|
||||||
var parts = message['d'].toString().split("||");
|
|
||||||
if (parts.length == 2) {
|
|
||||||
var jsonObj = jsonDecode(utf8.fuse(base64).decode(parts[1].substring(5)));
|
|
||||||
this._inviteTarget = jsonObj['GroupID'];
|
|
||||||
this._inviteNick = jsonObj['GroupName'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loaded = true;
|
|
||||||
|
|
||||||
//update ackd and error last as they are changenotified
|
|
||||||
this.ackd = messageWrapper['Acknowledged'];
|
|
||||||
if (messageWrapper['Error'] != null) {
|
|
||||||
this.error = true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this._overlay = -1;
|
|
||||||
this.loaded = true;
|
|
||||||
this.malformed = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../main.dart';
|
||||||
|
import 'messages/invitemessage.dart';
|
||||||
|
import 'messages/malformedmessage.dart';
|
||||||
|
import 'messages/quotedmessage.dart';
|
||||||
|
import 'messages/textmessage.dart';
|
||||||
|
|
||||||
|
// Define the overlays
|
||||||
|
const TextMessageOverlay = 1;
|
||||||
|
const QuotedMessageOverlay = 10;
|
||||||
|
const SuggestContactOverlay = 100;
|
||||||
|
const InviteGroupOverlay = 101;
|
||||||
|
|
||||||
|
// Defines the length of the tor v3 onion address. Code using this constant will
|
||||||
|
// need to updated when we allow multiple different identifiers. At which time
|
||||||
|
// it will likely be prudent to define a proper Contact wrapper.
|
||||||
|
const TorV3ContactHandleLength = 56;
|
||||||
|
|
||||||
|
// Defines the length of a Cwtch v2 Group.
|
||||||
|
const GroupConversationHandleLength = 32;
|
||||||
|
|
||||||
|
abstract class Message {
|
||||||
|
MessageMetadata getMetadata();
|
||||||
|
Widget getWidget(BuildContext context);
|
||||||
|
Widget getPreviewWidget(BuildContext context);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Message> messageHandler(BuildContext context, String profileOnion, String contactHandle, int index) {
|
||||||
|
try {
|
||||||
|
var rawMessageEnvelopeFuture = Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, index);
|
||||||
|
return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) {
|
||||||
|
dynamic messageWrapper = jsonDecode(rawMessageEnvelope);
|
||||||
|
// There are 2 conditions in which this error condition can be met:
|
||||||
|
// 1. The application == nil, in which case this instance of the UI is already
|
||||||
|
// broken beyond repair, and will either be replaced by a new version, or requires a complete
|
||||||
|
// restart.
|
||||||
|
// 2. This index was incremented and we happened to fetch the timeline prior to the messages inclusion.
|
||||||
|
// This should be rare as Timeline addition/fetching is mutex protected and Dart itself will pipeline the
|
||||||
|
// calls to libCwtch-go - however because we use goroutines on the backend there is always a chance that one
|
||||||
|
// will find itself delayed.
|
||||||
|
// The second case is recoverable by tail-recursing this future.
|
||||||
|
if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
|
||||||
|
return Future.delayed(Duration(seconds: 2), () {
|
||||||
|
print("Tail recursive call to messageHandler called. This should be a rare event. If you see multiples of this log over a short period of time please log it as a bug.");
|
||||||
|
return messageHandler(context, profileOnion, contactHandle, index).then((value) => value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the initial metadata
|
||||||
|
var timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
|
||||||
|
var senderHandle = messageWrapper['PeerID'];
|
||||||
|
var senderImage = messageWrapper['ContactImage'];
|
||||||
|
var flags = int.parse(messageWrapper['Flags'].toString(), radix: 2);
|
||||||
|
var ackd = messageWrapper['Acknowledged'];
|
||||||
|
var error = messageWrapper['Error'] != null;
|
||||||
|
String? signature;
|
||||||
|
// If this is a group, store the signature
|
||||||
|
if (contactHandle.length == GroupConversationHandleLength) {
|
||||||
|
signature = messageWrapper['Signature'];
|
||||||
|
}
|
||||||
|
var metadata = MessageMetadata(profileOnion, contactHandle, index, timestamp, senderHandle, senderImage, signature, flags, ackd, error);
|
||||||
|
|
||||||
|
try {
|
||||||
|
dynamic message = jsonDecode(messageWrapper['Message']);
|
||||||
|
var content = message['d'] as dynamic;
|
||||||
|
var overlay = int.parse(message['o'].toString());
|
||||||
|
|
||||||
|
switch (overlay) {
|
||||||
|
case TextMessageOverlay:
|
||||||
|
return TextMessage(metadata, content);
|
||||||
|
case SuggestContactOverlay:
|
||||||
|
case InviteGroupOverlay:
|
||||||
|
return InviteMessage(overlay, metadata, content);
|
||||||
|
case QuotedMessageOverlay:
|
||||||
|
return QuotedMessage(metadata, content);
|
||||||
|
default:
|
||||||
|
// Metadata is valid, content is not..
|
||||||
|
return MalformedMessage(metadata);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return MalformedMessage(metadata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return Future.value(MalformedMessage(MessageMetadata(profileOnion, contactHandle, index, DateTime.now(), "", "", null, 0, false, true)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageMetadata extends ChangeNotifier {
|
||||||
|
// meta-metadata
|
||||||
|
final String profileOnion;
|
||||||
|
final String contactHandle;
|
||||||
|
final int messageIndex;
|
||||||
|
|
||||||
|
final DateTime timestamp;
|
||||||
|
final String senderHandle;
|
||||||
|
final String? senderImage;
|
||||||
|
int _flags;
|
||||||
|
bool _ackd;
|
||||||
|
bool _error;
|
||||||
|
|
||||||
|
final String? signature;
|
||||||
|
|
||||||
|
int get flags => this._flags;
|
||||||
|
set flags(int newVal) {
|
||||||
|
this._flags = newVal;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get ackd => this._ackd;
|
||||||
|
set ackd(bool newVal) {
|
||||||
|
this._ackd = newVal;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get error => this._error;
|
||||||
|
set error(bool newVal) {
|
||||||
|
this._error = newVal;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageMetadata(this.profileOnion, this.contactHandle, this.messageIndex, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._flags, this._ackd, this._error);
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
|
import 'package:cwtch/widgets/invitationbubble.dart';
|
||||||
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
|
import 'package:cwtch/widgets/messagerow.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../model.dart';
|
||||||
|
|
||||||
|
class InviteMessage extends Message {
|
||||||
|
final MessageMetadata metadata;
|
||||||
|
final String content;
|
||||||
|
final int overlay;
|
||||||
|
|
||||||
|
InviteMessage(this.overlay, this.metadata, this.content);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getWidget(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (bcontext, child) {
|
||||||
|
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
|
||||||
|
|
||||||
|
String inviteTarget;
|
||||||
|
String inviteNick;
|
||||||
|
|
||||||
|
if (this.content.length == TorV3ContactHandleLength) {
|
||||||
|
inviteTarget = this.content;
|
||||||
|
var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(inviteTarget);
|
||||||
|
inviteNick = targetContact == null ? this.content : targetContact.nickname;
|
||||||
|
} else {
|
||||||
|
var parts = this.content.toString().split("||");
|
||||||
|
if (parts.length == 2) {
|
||||||
|
var jsonObj = jsonDecode(utf8.fuse(base64).decode(parts[1].substring(5)));
|
||||||
|
inviteTarget = jsonObj['GroupID'];
|
||||||
|
inviteNick = jsonObj['GroupName'];
|
||||||
|
} else {
|
||||||
|
return MessageRow(MalformedBubble());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getPreviewWidget(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (bcontext, child) {
|
||||||
|
String inviteTarget;
|
||||||
|
String inviteNick;
|
||||||
|
|
||||||
|
if (this.content.length == TorV3ContactHandleLength) {
|
||||||
|
inviteTarget = this.content;
|
||||||
|
var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(inviteTarget);
|
||||||
|
inviteNick = targetContact == null ? this.content : targetContact.nickname;
|
||||||
|
} else {
|
||||||
|
var parts = this.content.toString().split("||");
|
||||||
|
if (parts.length == 2) {
|
||||||
|
var jsonObj = jsonDecode(utf8.fuse(base64).decode(parts[1].substring(5)));
|
||||||
|
inviteTarget = jsonObj['GroupID'];
|
||||||
|
inviteNick = jsonObj['GroupName'];
|
||||||
|
} else {
|
||||||
|
return MalformedBubble();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return InvitationBubble(overlay, inviteTarget, inviteNick);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
MessageMetadata getMetadata() {
|
||||||
|
return this.metadata;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
|
import 'package:cwtch/widgets/messagerow.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class MalformedMessage extends Message {
|
||||||
|
final MessageMetadata metadata;
|
||||||
|
MalformedMessage(this.metadata);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getWidget(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (context, child) {
|
||||||
|
return MessageRow(MalformedBubble());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getPreviewWidget(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (bcontext, child) {
|
||||||
|
return MalformedBubble();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
MessageMetadata getMetadata() {
|
||||||
|
return this.metadata;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
|
import 'package:cwtch/models/messages/malformedmessage.dart';
|
||||||
|
import 'package:cwtch/widgets/messagerow.dart';
|
||||||
|
import 'package:cwtch/widgets/quotedmessage.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../main.dart';
|
||||||
|
import '../../model.dart';
|
||||||
|
|
||||||
|
class LocallyIndexedMessage {
|
||||||
|
final dynamic message;
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
LocallyIndexedMessage(this.message, this.index);
|
||||||
|
|
||||||
|
LocallyIndexedMessage.fromJson(Map<String, dynamic> json)
|
||||||
|
: message = json['Message'],
|
||||||
|
index = json['LocalIndex'];
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'Message': message,
|
||||||
|
'LocalIndex': index,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class QuotedMessage extends Message {
|
||||||
|
final MessageMetadata metadata;
|
||||||
|
final String content;
|
||||||
|
QuotedMessage(this.metadata, this.content);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getPreviewWidget(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (bcontext, child) {
|
||||||
|
try {
|
||||||
|
dynamic message = jsonDecode(this.content);
|
||||||
|
return Text(message["body"]);
|
||||||
|
} catch (e) {
|
||||||
|
return MalformedMessage(this.metadata).getWidget(context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
MessageMetadata getMetadata() {
|
||||||
|
return this.metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getWidget(BuildContext context) {
|
||||||
|
try {
|
||||||
|
dynamic message = jsonDecode(this.content);
|
||||||
|
|
||||||
|
if (message["body"] == null || message["quotedHash"] == null) {
|
||||||
|
return MalformedMessage(this.metadata).getWidget(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
var quotedMessagePotentials = Provider.of<FlwtchState>(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.contactHandle, message["quotedHash"]);
|
||||||
|
int messageIndex = metadata.messageIndex;
|
||||||
|
Future<LocallyIndexedMessage?> quotedMessage = quotedMessagePotentials.then((matchingMessages) {
|
||||||
|
if (matchingMessages == "[]") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// reverse order the messages from newest to oldest and return the
|
||||||
|
// first matching message where it's index is less than the index of this
|
||||||
|
// message
|
||||||
|
try {
|
||||||
|
var list = (jsonDecode(matchingMessages) as List<dynamic>).map((data) => LocallyIndexedMessage.fromJson(data)).toList();
|
||||||
|
LocallyIndexedMessage candidate = list.reversed.firstWhere((element) => messageIndex < element.index, orElse: () {
|
||||||
|
return list.firstWhere((element) => messageIndex > element.index);
|
||||||
|
});
|
||||||
|
return candidate;
|
||||||
|
} catch (e) {
|
||||||
|
// Malformed Message will be returned...
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (bcontext, child) {
|
||||||
|
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
|
||||||
|
return MessageRow(
|
||||||
|
QuotedMessageBubble(message["body"], quotedMessage.then((LocallyIndexedMessage? localIndex) {
|
||||||
|
if (localIndex != null) {
|
||||||
|
return messageHandler(context, metadata.profileOnion, metadata.contactHandle, localIndex.index);
|
||||||
|
}
|
||||||
|
return MalformedMessage(this.metadata);
|
||||||
|
})),
|
||||||
|
key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return MalformedMessage(this.metadata).getWidget(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
|
import 'package:cwtch/widgets/messagebubble.dart';
|
||||||
|
import 'package:cwtch/widgets/messagerow.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../model.dart';
|
||||||
|
|
||||||
|
class TextMessage extends Message {
|
||||||
|
final MessageMetadata metadata;
|
||||||
|
final String content;
|
||||||
|
|
||||||
|
TextMessage(this.metadata, this.content);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getPreviewWidget(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (bcontext, child) {
|
||||||
|
return Text(this.content);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
MessageMetadata getMetadata() {
|
||||||
|
return this.metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getWidget(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (bcontext, child) {
|
||||||
|
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
|
||||||
|
return MessageRow(MessageBubble(this.content), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
|
import 'package:cwtch/widgets/messageloadingbubble.dart';
|
||||||
import 'package:cwtch/widgets/profileimage.dart';
|
import 'package:cwtch/widgets/profileimage.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -104,11 +107,32 @@ class _MessageViewState extends State<MessageView> {
|
||||||
|
|
||||||
void _sendMessage([String? ignoredParam]) {
|
void _sendMessage([String? ignoredParam]) {
|
||||||
if (ctrlrCompose.value.text.isNotEmpty) {
|
if (ctrlrCompose.value.text.isNotEmpty) {
|
||||||
ChatMessage cm = new ChatMessage(o: 1, d: ctrlrCompose.value.text);
|
if (Provider.of<AppState>(context).selectedConversation != null && Provider.of<AppState>(context).selectedIndex != null) {
|
||||||
Provider.of<FlwtchState>(context, listen: false)
|
Provider.of<FlwtchState>(context)
|
||||||
.cwtch
|
.cwtch
|
||||||
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm));
|
.GetMessage(Provider.of<AppState>(context).selectedProfile!, Provider.of<AppState>(context).selectedConversation!, Provider.of<AppState>(context).selectedIndex!)
|
||||||
_sendMessageHelper();
|
.then((data) {
|
||||||
|
try {
|
||||||
|
var messageWrapper = jsonDecode(data! as String);
|
||||||
|
var bytes1 = utf8.encode(messageWrapper["PeerID"] + messageWrapper['Message']);
|
||||||
|
var digest1 = sha256.convert(bytes1);
|
||||||
|
var contentHash = base64Encode(digest1.bytes);
|
||||||
|
var quotedMessage = "{\"quotedHash\":\"" + contentHash + "\",\"body\":\"" + ctrlrCompose.value.text + "\"}";
|
||||||
|
ChatMessage cm = new ChatMessage(o: QuotedMessageOverlay, d: quotedMessage);
|
||||||
|
Provider.of<FlwtchState>(context, listen: false)
|
||||||
|
.cwtch
|
||||||
|
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm));
|
||||||
|
} catch (e) {}
|
||||||
|
Provider.of<AppState>(context, listen: false).selectedIndex = null;
|
||||||
|
_sendMessageHelper();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ChatMessage cm = new ChatMessage(o: TextMessageOverlay, d: ctrlrCompose.value.text);
|
||||||
|
Provider.of<FlwtchState>(context, listen: false)
|
||||||
|
.cwtch
|
||||||
|
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm));
|
||||||
|
_sendMessageHelper();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +154,7 @@ class _MessageViewState extends State<MessageView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildComposeBox() {
|
Widget _buildComposeBox() {
|
||||||
return Container(
|
var composeBox = Container(
|
||||||
color: Provider.of<Settings>(context).theme.backgroundMainColor(),
|
color: Provider.of<Settings>(context).theme.backgroundMainColor(),
|
||||||
padding: EdgeInsets.all(2),
|
padding: EdgeInsets.all(2),
|
||||||
margin: EdgeInsets.all(2),
|
margin: EdgeInsets.all(2),
|
||||||
|
@ -141,52 +165,88 @@ class _MessageViewState extends State<MessageView> {
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(border: Border(top: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor()))),
|
decoration: BoxDecoration(border: Border(top: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor()))),
|
||||||
child: RawKeyboardListener(
|
child: RawKeyboardListener(
|
||||||
focusNode: FocusNode(),
|
focusNode: FocusNode(),
|
||||||
onKey: handleKeyPress,
|
onKey: handleKeyPress,
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
key: Key('txtCompose'),
|
key: Key('txtCompose'),
|
||||||
controller: ctrlrCompose,
|
controller: ctrlrCompose,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
autofocus: !Platform.isAndroid,
|
autofocus: !Platform.isAndroid,
|
||||||
textInputAction: TextInputAction.newline,
|
textInputAction: TextInputAction.newline,
|
||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
onFieldSubmitted: _sendMessage,
|
onFieldSubmitted: _sendMessage,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
enabledBorder: InputBorder.none,
|
enabledBorder: InputBorder.none,
|
||||||
focusedBorder: InputBorder.none,
|
focusedBorder: InputBorder.none,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
prefixIcon: IconButton(
|
prefixIcon: IconButton(
|
||||||
icon: Icon(CwtchIcons.send_invite, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
icon: Icon(CwtchIcons.send_invite, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||||
tooltip: AppLocalizations.of(context)!.sendInvite,
|
tooltip: AppLocalizations.of(context)!.sendInvite,
|
||||||
enableFeedback: true,
|
enableFeedback: true,
|
||||||
splashColor: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
splashColor: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
||||||
hoverColor: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
hoverColor: Provider.of<Settings>(context).theme.defaultButtonActiveColor(),
|
||||||
onPressed: () => _modalSendInvitation(context)),
|
onPressed: () => _modalSendInvitation(context)),
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
icon: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
icon: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||||
tooltip: AppLocalizations.of(context)!.sendMessage,
|
tooltip: AppLocalizations.of(context)!.sendMessage,
|
||||||
onPressed: _sendMessage,
|
onPressed: _sendMessage,
|
||||||
),
|
),
|
||||||
)))),
|
)))),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var children;
|
||||||
|
if (Provider.of<AppState>(context).selectedConversation != null && Provider.of<AppState>(context).selectedIndex != null) {
|
||||||
|
var quoted = FutureBuilder(
|
||||||
|
future: messageHandler(context, Provider.of<AppState>(context).selectedProfile!, Provider.of<AppState>(context).selectedConversation!, Provider.of<AppState>(context).selectedIndex!),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
var message = snapshot.data! as Message;
|
||||||
|
return Container(
|
||||||
|
margin: EdgeInsets.all(5),
|
||||||
|
padding: EdgeInsets.all(5),
|
||||||
|
color: message.getMetadata().senderHandle != Provider.of<AppState>(context).selectedProfile
|
||||||
|
? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()
|
||||||
|
: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
|
||||||
|
child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
||||||
|
Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(Icons.reply, size: 32))),
|
||||||
|
Center(widthFactor: 1.0, child: message.getPreviewWidget(context)),
|
||||||
|
Center(
|
||||||
|
widthFactor: 1.0,
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(Icons.highlight_remove),
|
||||||
|
tooltip: AppLocalizations.of(context)!.tooltipRemoveThisQuotedMessage,
|
||||||
|
onPressed: () {
|
||||||
|
Provider.of<AppState>(context, listen: false).selectedIndex = null;
|
||||||
|
},
|
||||||
|
))
|
||||||
|
]));
|
||||||
|
} else {
|
||||||
|
return MessageLoadingBubble();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
children = [quoted, composeBox];
|
||||||
|
} else {
|
||||||
|
children = [composeBox];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(mainAxisSize: MainAxisSize.min, children: children);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the message if enter is pressed without the shift key...
|
// Send the message if enter is pressed without the shift key...
|
||||||
void handleKeyPress(event) {
|
void handleKeyPress(event) {
|
||||||
var data = event.data as RawKeyEventData;
|
var data = event.data as RawKeyEventData;
|
||||||
if (data.logicalKey == LogicalKeyboardKey.enter && !event.isShiftPressed) {
|
if (data.logicalKey == LogicalKeyboardKey.enter && !event.isShiftPressed) {
|
||||||
final messageWithoutNewLine = ctrlrCompose.value.text.trimRight();
|
final messageWithoutNewLine = ctrlrCompose.value.text.trimRight();
|
||||||
ctrlrCompose.value = TextEditingValue(
|
ctrlrCompose.value = TextEditingValue(text: messageWithoutNewLine);
|
||||||
text: messageWithoutNewLine
|
_sendMessage();
|
||||||
);
|
}
|
||||||
_sendMessage();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void placeHolder() => {};
|
void placeHolder() => {};
|
||||||
|
|
|
@ -98,6 +98,7 @@ class _ContactRowState extends State<ContactRow> {
|
||||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(contact.onion)!.unreadMessages = 0;
|
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(contact.onion)!.unreadMessages = 0;
|
||||||
// triggers update in Double/TripleColumnView
|
// triggers update in Double/TripleColumnView
|
||||||
Provider.of<AppState>(context, listen: false).selectedConversation = contact.onion;
|
Provider.of<AppState>(context, listen: false).selectedConversation = contact.onion;
|
||||||
|
Provider.of<AppState>(context, listen: false).selectedIndex = null;
|
||||||
// if in singlepane mode, push to the stack
|
// if in singlepane mode, push to the stack
|
||||||
var isLandscape = Provider.of<AppState>(context, listen: false).isLandscape(context);
|
var isLandscape = Provider.of<AppState>(context, listen: false).isLandscape(context);
|
||||||
if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(contact.onion);
|
if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(contact.onion);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -15,6 +16,12 @@ import 'messagebubbledecorations.dart';
|
||||||
// Like MessageBubble but for displaying chat overlay 100/101 invitations
|
// Like MessageBubble but for displaying chat overlay 100/101 invitations
|
||||||
// Offers the user an accept/reject button if they don't have a matching contact already
|
// Offers the user an accept/reject button if they don't have a matching contact already
|
||||||
class InvitationBubble extends StatefulWidget {
|
class InvitationBubble extends StatefulWidget {
|
||||||
|
final int overlay;
|
||||||
|
final String inviteTarget;
|
||||||
|
final String inviteNick;
|
||||||
|
|
||||||
|
InvitationBubble(this.overlay, this.inviteTarget, this.inviteNick);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
InvitationBubbleState createState() => InvitationBubbleState();
|
InvitationBubbleState createState() => InvitationBubbleState();
|
||||||
}
|
}
|
||||||
|
@ -25,32 +32,22 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (Provider.of<MessageState>(context).malformed) {
|
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||||
return MalformedBubble();
|
var isGroup = widget.overlay == InviteGroupOverlay;
|
||||||
}
|
isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(widget.inviteTarget) != null;
|
||||||
|
|
||||||
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
|
|
||||||
var isGroup = Provider.of<MessageState>(context).overlay == 101;
|
|
||||||
isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget) != null;
|
|
||||||
var prettyDate = "";
|
|
||||||
var borderRadiousEh = 15.0;
|
var borderRadiousEh = 15.0;
|
||||||
var showGroupInvite = Provider.of<Settings>(context).isExperimentEnabled(TapirGroupsExperiment);
|
var showGroupInvite = Provider.of<Settings>(context).isExperimentEnabled(TapirGroupsExperiment);
|
||||||
rejected = Provider.of<MessageState>(context).flags & 0x01 == 0x01;
|
rejected = Provider.of<MessageMetadata>(context).flags & 0x01 == 0x01;
|
||||||
var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
var prettyDate = DateFormat.yMd().add_jm().format(Provider.of<MessageMetadata>(context).timestamp);
|
||||||
|
|
||||||
if (Provider.of<MessageState>(context).timestamp != null) {
|
|
||||||
// user-configurable timestamps prolly ideal? #todo
|
|
||||||
prettyDate = DateFormat.yMd().add_jm().format(Provider.of<MessageState>(context).timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the sender is not us, then we want to give them a nickname...
|
// If the sender is not us, then we want to give them a nickname...
|
||||||
var senderDisplayStr = "";
|
var senderDisplayStr = "";
|
||||||
if (!fromMe && Provider.of<MessageState>(context).senderOnion != null) {
|
if (!fromMe) {
|
||||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).senderOnion);
|
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
senderDisplayStr = contact.nickname;
|
senderDisplayStr = contact.nickname;
|
||||||
} else {
|
} else {
|
||||||
senderDisplayStr = Provider.of<MessageState>(context).senderOnion;
|
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +58,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
|
|
||||||
// If we receive an invite for ourselves, treat it as a bug. The UI no longer allows this so it could have only come from
|
// If we receive an invite for ourselves, treat it as a bug. The UI no longer allows this so it could have only come from
|
||||||
// some kind of malfeasance.
|
// some kind of malfeasance.
|
||||||
var selfInvite = Provider.of<MessageState>(context).inviteNick == Provider.of<ProfileInfoState>(context).onion;
|
var selfInvite = widget.inviteNick == Provider.of<ProfileInfoState>(context).onion;
|
||||||
if (selfInvite) {
|
if (selfInvite) {
|
||||||
return MalformedBubble();
|
return MalformedBubble();
|
||||||
}
|
}
|
||||||
|
@ -69,16 +66,15 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
var wdgMessage = isGroup && !showGroupInvite
|
var wdgMessage = isGroup && !showGroupInvite
|
||||||
? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning)
|
? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning)
|
||||||
: fromMe
|
: fromMe
|
||||||
? senderInviteChrome(AppLocalizations.of(context)!.sendAnInvitation,
|
? senderInviteChrome(
|
||||||
isGroup ? Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget)!.nickname : Provider.of<MessageState>(context).message, myKey)
|
AppLocalizations.of(context)!.sendAnInvitation, isGroup ? Provider.of<ProfileInfoState>(context).contactList.getContact(widget.inviteTarget)!.nickname : widget.inviteTarget)
|
||||||
: (inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : AppLocalizations.of(context)!.contactSuggestion, Provider.of<MessageState>(context).inviteNick,
|
: (inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : AppLocalizations.of(context)!.contactSuggestion, widget.inviteNick, widget.inviteTarget));
|
||||||
Provider.of<MessageState>(context).inviteTarget, myKey));
|
|
||||||
|
|
||||||
Widget wdgDecorations;
|
Widget wdgDecorations;
|
||||||
if (isGroup && !showGroupInvite) {
|
if (isGroup && !showGroupInvite) {
|
||||||
wdgDecorations = Text('\u202F');
|
wdgDecorations = Text('\u202F');
|
||||||
} else if (fromMe) {
|
} else if (fromMe) {
|
||||||
wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageState>(context).ackd, errored: Provider.of<MessageState>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
||||||
} else if (isAccepted) {
|
} else if (isAccepted) {
|
||||||
wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F');
|
wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F');
|
||||||
} else if (this.rejected) {
|
} else if (this.rejected) {
|
||||||
|
@ -131,22 +127,22 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
setState(() {
|
setState(() {
|
||||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||||
var contact = Provider.of<ContactInfoState>(context, listen: false).onion;
|
var contact = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||||
var idx = Provider.of<MessageState>(context, listen: false).messageIndex;
|
var idx = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of<MessageState>(context, listen: false).flags | 0x01);
|
Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of<MessageMetadata>(context, listen: false).flags | 0x01);
|
||||||
Provider.of<MessageState>(context).flags |= 0x01;
|
Provider.of<MessageMetadata>(context).flags |= 0x01;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _btnAccept() {
|
void _btnAccept() {
|
||||||
setState(() {
|
setState(() {
|
||||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, Provider.of<MessageState>(context, listen: false).message);
|
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, widget.inviteTarget);
|
||||||
isAccepted = true;
|
isAccepted = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct an invite chrome for the sender
|
// Construct an invite chrome for the sender
|
||||||
Widget senderInviteChrome(String chrome, String targetName, String myKey) {
|
Widget senderInviteChrome(String chrome, String targetName) {
|
||||||
return Wrap(children: [
|
return Wrap(children: [
|
||||||
SelectableText(
|
SelectableText(
|
||||||
chrome + '\u202F',
|
chrome + '\u202F',
|
||||||
|
@ -159,7 +155,6 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
),
|
),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
targetName + '\u202F',
|
targetName + '\u202F',
|
||||||
key: Key(myKey),
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
||||||
),
|
),
|
||||||
|
@ -171,7 +166,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct an invite chrome
|
// Construct an invite chrome
|
||||||
Widget inviteChrome(String chrome, String targetName, String targetId, String myKey) {
|
Widget inviteChrome(String chrome, String targetName, String targetId) {
|
||||||
return Wrap(children: [
|
return Wrap(children: [
|
||||||
SelectableText(
|
SelectableText(
|
||||||
chrome + '\u202F',
|
chrome + '\u202F',
|
||||||
|
@ -184,7 +179,6 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
),
|
),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
targetName + '\u202F',
|
targetName + '\u202F',
|
||||||
key: Key(myKey),
|
|
||||||
style: TextStyle(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor()),
|
style: TextStyle(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor()),
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -8,6 +9,10 @@ import '../settings.dart';
|
||||||
import 'messagebubbledecorations.dart';
|
import 'messagebubbledecorations.dart';
|
||||||
|
|
||||||
class MessageBubble extends StatefulWidget {
|
class MessageBubble extends StatefulWidget {
|
||||||
|
final String content;
|
||||||
|
|
||||||
|
MessageBubble(this.content);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MessageBubbleState createState() => MessageBubbleState();
|
MessageBubbleState createState() => MessageBubbleState();
|
||||||
}
|
}
|
||||||
|
@ -17,33 +22,30 @@ class MessageBubbleState extends State<MessageBubble> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
|
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||||
var prettyDate = "";
|
var prettyDate = "";
|
||||||
var borderRadiousEh = 15.0;
|
var borderRadiousEh = 15.0;
|
||||||
var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
// var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
||||||
|
|
||||||
if (Provider.of<MessageState>(context).timestamp != null) {
|
DateTime messageDate = Provider.of<MessageMetadata>(context).timestamp;
|
||||||
// user-configurable timestamps prolly ideal? #todo
|
prettyDate = DateFormat.yMd().add_jm().format(messageDate.toLocal());
|
||||||
DateTime messageDate = Provider.of<MessageState>(context).timestamp;
|
|
||||||
prettyDate = DateFormat.yMd().add_jm().format(messageDate.toLocal());
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the sender is not us, then we want to give them a nickname...
|
// If the sender is not us, then we want to give them a nickname...
|
||||||
var senderDisplayStr = "";
|
var senderDisplayStr = "";
|
||||||
if (!fromMe && Provider.of<MessageState>(context).senderOnion != null) {
|
if (!fromMe) {
|
||||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).senderOnion);
|
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
senderDisplayStr = contact.nickname;
|
senderDisplayStr = contact.nickname;
|
||||||
} else {
|
} else {
|
||||||
senderDisplayStr = Provider.of<MessageState>(context).senderOnion;
|
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var wdgSender = SelectableText(senderDisplayStr,
|
var wdgSender = SelectableText(senderDisplayStr,
|
||||||
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 wdgMessage = SelectableText(
|
var wdgMessage = SelectableText(
|
||||||
(Provider.of<MessageState>(context).message ?? "") + '\u202F',
|
widget.content + '\u202F',
|
||||||
key: Key(myKey),
|
//key: Key(myKey),
|
||||||
focusNode: _focus,
|
focusNode: _focus,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||||
|
@ -52,9 +54,9 @@ class MessageBubbleState extends State<MessageBubble> {
|
||||||
textWidthBasis: TextWidthBasis.longestLine,
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
);
|
);
|
||||||
|
|
||||||
var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageState>(context).ackd, errored: Provider.of<MessageState>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
||||||
|
|
||||||
var error = Provider.of<MessageState>(context).error;
|
var error = Provider.of<MessageMetadata>(context).error;
|
||||||
|
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
//print(constraints.toString()+", "+constraints.maxWidth.toString());
|
//print(constraints.toString()+", "+constraints.maxWidth.toString());
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
|
import 'package:cwtch/models/messages/malformedmessage.dart';
|
||||||
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
|
import 'package:cwtch/widgets/messageloadingbubble.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import '../main.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
import 'messagerow.dart';
|
import 'messagerow.dart';
|
||||||
|
@ -68,22 +73,22 @@ class _MessageListState extends State<MessageList> {
|
||||||
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
|
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
|
||||||
reverse: true, // NOTE: There seems to be a bug in flutter that corrects the mouse wheel scroll, but not the drag direction...
|
reverse: true, // NOTE: There seems to be a bug in flutter that corrects the mouse wheel scroll, but not the drag direction...
|
||||||
itemBuilder: (itemBuilderContext, index) {
|
itemBuilder: (itemBuilderContext, index) {
|
||||||
var trueIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
|
var profileOnion = Provider.of<ProfileInfoState>(outerContext, listen: false).onion;
|
||||||
return ChangeNotifierProvider(
|
var contactHandle = Provider.of<ContactInfoState>(outerContext, listen: false).onion;
|
||||||
key: ValueKey(trueIndex),
|
var messageIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
|
||||||
create: (x) => MessageState(
|
|
||||||
context: itemBuilderContext,
|
return FutureBuilder(
|
||||||
profileOnion: Provider.of<ProfileInfoState>(outerContext, listen: false).onion,
|
future: messageHandler(outerContext, profileOnion, contactHandle, messageIndex),
|
||||||
// We don't want to listen for updates to the contact handle...
|
builder: (context, snapshot) {
|
||||||
contactHandle: Provider.of<ContactInfoState>(x, listen: false).onion,
|
if (snapshot.hasData) {
|
||||||
messageIndex: trueIndex,
|
var message = snapshot.data as Message;
|
||||||
),
|
// Already includes MessageRow,,
|
||||||
builder: (bcontext, child) {
|
return message.getWidget(context);
|
||||||
String idx = Provider.of<ContactInfoState>(outerContext).isGroup == true && Provider.of<MessageState>(bcontext).signature.isEmpty == false
|
} else {
|
||||||
? Provider.of<MessageState>(bcontext).signature
|
return MessageLoadingBubble();
|
||||||
: trueIndex.toString();
|
}
|
||||||
return RepaintBoundary(child: MessageRow(key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx)));
|
},
|
||||||
});
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: null)))
|
: null)))
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cwtch/widgets/profileimage.dart';
|
import 'package:cwtch/widgets/profileimage.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -8,40 +9,38 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import '../main.dart';
|
import '../main.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
import 'invitationbubble.dart';
|
|
||||||
import 'malformedbubble.dart';
|
|
||||||
import 'messagebubble.dart';
|
|
||||||
import 'messageloadingbubble.dart';
|
|
||||||
|
|
||||||
class MessageRow extends StatefulWidget {
|
class MessageRow extends StatefulWidget {
|
||||||
MessageRow({Key? key}) : super(key: key);
|
final Widget child;
|
||||||
|
MessageRow(this.child, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_MessageRowState createState() => _MessageRowState();
|
MessageRowState createState() => MessageRowState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MessageRowState extends State<MessageRow> {
|
class MessageRowState extends State<MessageRow> {
|
||||||
|
bool showMenu = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
|
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||||
var malformed = Provider.of<MessageState>(context).malformed;
|
|
||||||
|
|
||||||
// If the message is malformed then override fromme as we can't trust it
|
Widget wdgIcons = Visibility(
|
||||||
if (malformed) {
|
visible: this.showMenu,
|
||||||
fromMe = false;
|
child: IconButton(
|
||||||
}
|
tooltip: AppLocalizations.of(context)!.tooltipReplyToThisMessage,
|
||||||
|
onPressed: () {
|
||||||
Widget wdgBubble =
|
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context).messageIndex;
|
||||||
Flexible(flex: 3, fit: FlexFit.loose, child: Provider.of<MessageState>(context).loaded == true ? widgetForOverlay(Provider.of<MessageState>(context).overlay) : MessageLoadingBubble());
|
},
|
||||||
Widget wdgIcons = Icon(Icons.delete_forever_outlined, color: Provider.of<Settings>(context).theme.dropShadowColor());
|
icon: Icon(Icons.reply, color: Provider.of<Settings>(context).theme.dropShadowColor())));
|
||||||
Widget wdgSpacer = Expanded(child: SizedBox(width: 60, height: 10));
|
Widget wdgSpacer = Expanded(child: SizedBox(width: 60, height: 10));
|
||||||
var widgetRow = <Widget>[];
|
var widgetRow = <Widget>[];
|
||||||
|
|
||||||
if (fromMe) {
|
if (fromMe) {
|
||||||
widgetRow = <Widget>[
|
widgetRow = <Widget>[
|
||||||
wdgSpacer,
|
wdgSpacer,
|
||||||
//wdgIcons,
|
wdgIcons,
|
||||||
wdgBubble,
|
Flexible(flex: 3, fit: FlexFit.loose, child: widget.child),
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
var contact = Provider.of<ContactInfoState>(context);
|
var contact = Provider.of<ContactInfoState>(context);
|
||||||
|
@ -51,7 +50,7 @@ class _MessageRowState extends State<MessageRow> {
|
||||||
padding: EdgeInsets.all(4.0),
|
padding: EdgeInsets.all(4.0),
|
||||||
child: ProfileImage(
|
child: ProfileImage(
|
||||||
diameter: 48.0,
|
diameter: 48.0,
|
||||||
imagePath: Provider.of<MessageState>(context).senderImage ?? contact.imagePath,
|
imagePath: Provider.of<MessageMetadata>(context).senderImage ?? contact.imagePath,
|
||||||
//maskOut: contact.status != "Authenticated",
|
//maskOut: contact.status != "Authenticated",
|
||||||
border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor(),
|
border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor(),
|
||||||
badgeTextColor: Colors.red, badgeColor: Colors.red,
|
badgeTextColor: Colors.red, badgeColor: Colors.red,
|
||||||
|
@ -59,28 +58,36 @@ class _MessageRowState extends State<MessageRow> {
|
||||||
|
|
||||||
widgetRow = <Widget>[
|
widgetRow = <Widget>[
|
||||||
wdgPortrait,
|
wdgPortrait,
|
||||||
wdgBubble,
|
Flexible(flex: 3, fit: FlexFit.loose, child: widget.child),
|
||||||
//wdgIcons,
|
wdgIcons,
|
||||||
wdgSpacer,
|
wdgSpacer,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Padding(padding: EdgeInsets.all(2), child: Row(mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, children: widgetRow));
|
return MouseRegion(
|
||||||
}
|
// For desktop...
|
||||||
|
|
||||||
Widget widgetForOverlay(int o) {
|
onHover: (event) {
|
||||||
switch (o) {
|
setState(() {
|
||||||
case 1:
|
this.showMenu = true;
|
||||||
return MessageBubble();
|
});
|
||||||
case 100:
|
},
|
||||||
case 101:
|
onExit: (event) {
|
||||||
return InvitationBubble();
|
setState(() {
|
||||||
}
|
this.showMenu = false;
|
||||||
return MalformedBubble();
|
});
|
||||||
|
},
|
||||||
|
child: GestureDetector(
|
||||||
|
|
||||||
|
// Swipe to quote
|
||||||
|
onHorizontalDragEnd: (details) {
|
||||||
|
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||||
|
},
|
||||||
|
child: Padding(padding: EdgeInsets.all(2), child: Row(mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, children: widgetRow))));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _btnAdd() {
|
void _btnAdd() {
|
||||||
var sender = Provider.of<MessageState>(context, listen: false).senderOnion;
|
var sender = Provider.of<MessageMetadata>(context, listen: false).senderHandle;
|
||||||
if (sender == null || sender == "") {
|
if (sender == null || sender == "") {
|
||||||
print("sender not yet loaded");
|
print("sender not yet loaded");
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
|
import 'package:cwtch/widgets/messageloadingbubble.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../model.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import '../settings.dart';
|
||||||
|
import 'messagebubbledecorations.dart';
|
||||||
|
|
||||||
|
class QuotedMessageBubble extends StatefulWidget {
|
||||||
|
final Future<Message> quotedMessage;
|
||||||
|
final String body;
|
||||||
|
|
||||||
|
QuotedMessageBubble(this.body, this.quotedMessage);
|
||||||
|
|
||||||
|
@override
|
||||||
|
QuotedMessageBubbleState createState() => QuotedMessageBubbleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
|
||||||
|
FocusNode _focus = FocusNode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||||
|
var prettyDate = "";
|
||||||
|
var borderRadiousEh = 15.0;
|
||||||
|
|
||||||
|
DateTime messageDate = Provider.of<MessageMetadata>(context).timestamp;
|
||||||
|
prettyDate = DateFormat.yMd().add_jm().format(messageDate.toLocal());
|
||||||
|
|
||||||
|
// If the sender is not us, then we want to give them a nickname...
|
||||||
|
var senderDisplayStr = "";
|
||||||
|
if (!fromMe) {
|
||||||
|
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||||
|
if (contact != null) {
|
||||||
|
senderDisplayStr = contact.nickname;
|
||||||
|
} else {
|
||||||
|
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var wdgSender = SelectableText(senderDisplayStr,
|
||||||
|
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()));
|
||||||
|
|
||||||
|
var wdgMessage = SelectableText(
|
||||||
|
widget.body + '\u202F',
|
||||||
|
focusNode: _focus,
|
||||||
|
style: TextStyle(
|
||||||
|
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
|
);
|
||||||
|
|
||||||
|
var wdgQuote = FutureBuilder(
|
||||||
|
future: widget.quotedMessage,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
try {
|
||||||
|
var qMessage = (snapshot.data! as Message);
|
||||||
|
// Swap the background color for quoted tweets..
|
||||||
|
return Container(
|
||||||
|
margin: EdgeInsets.all(5),
|
||||||
|
padding: EdgeInsets.all(5),
|
||||||
|
color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor() : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
|
||||||
|
child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [
|
||||||
|
Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(Icons.reply, size: 32))),
|
||||||
|
Center(widthFactor: 1.0, child: qMessage.getPreviewWidget(context))
|
||||||
|
]));
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
return MalformedBubble();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This should be almost instantly resolved, any failure likely means an issue in decoding...
|
||||||
|
return MessageLoadingBubble();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
||||||
|
|
||||||
|
var error = Provider.of<MessageMetadata>(context).error;
|
||||||
|
|
||||||
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
|
return RepaintBoundary(
|
||||||
|
child: Container(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: error
|
||||||
|
? malformedColor
|
||||||
|
: (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()),
|
||||||
|
border: Border.all(
|
||||||
|
color: error
|
||||||
|
? malformedColor
|
||||||
|
: (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(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 ? [wdgQuote, wdgMessage, wdgDecorations] : [wdgSender, wdgQuote, wdgMessage, wdgDecorations])))));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
11
pubspec.lock
11
pubspec.lock
|
@ -42,7 +42,7 @@ packages:
|
||||||
name: charcode
|
name: charcode
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.3.1"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -57,6 +57,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.15.0"
|
||||||
|
crypto:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -368,7 +375,7 @@ packages:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.0"
|
version: "0.4.1"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -35,6 +35,7 @@ dependencies:
|
||||||
ffi: ^1.0.0
|
ffi: ^1.0.0
|
||||||
path_provider: ^2.0.0
|
path_provider: ^2.0.0
|
||||||
desktop_notifications: 0.5.0
|
desktop_notifications: 0.5.0
|
||||||
|
crypto: 3.0.1
|
||||||
|
|
||||||
glob: any
|
glob: any
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in New Issue