From 3f020472fa1c366292e7a6bb1ae21ce40a6cfbc5 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Fri, 9 Jul 2021 15:16:06 -0700 Subject: [PATCH] model contact uses authorization now; add tooltips to contact pics in chat; dual action: add or goto --- lib/cwtch/cwtchNotifier.dart | 12 +++--- lib/l10n/intl_de.arb | 11 +++-- lib/l10n/intl_en.arb | 7 +++- lib/l10n/intl_es.arb | 5 ++- lib/l10n/intl_fr.arb | 7 +++- lib/l10n/intl_it.arb | 7 +++- lib/l10n/intl_pl.arb | 7 +++- lib/l10n/intl_pt.arb | 7 +++- lib/model.dart | 47 +++++++++++++-------- lib/models/message.dart | 3 ++ lib/views/contactsview.dart | 34 +++++++++++++++ lib/views/peersettingsview.dart | 4 +- lib/widgets/contactrow.dart | 32 ++------------ lib/widgets/messagerow.dart | 74 +++++++++++++++++++++++++++------ lib/widgets/profileimage.dart | 35 +++++++++------- 15 files changed, 194 insertions(+), 98 deletions(-) diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index 4ecc0ec5..cdfbb15a 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -49,8 +49,7 @@ class CwtchNotifier { nickname: data["nick"], status: data["status"], imagePath: data["picture"], - isBlocked: data["authorization"] == "blocked", - isInvitation: data["authorization"] == "unknown", + authorization: stringToContactAuthorization(data["authorization"]), savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"], numMessages: int.parse(data["numMessages"]), numUnread: int.parse(data["unread"]), @@ -69,7 +68,7 @@ class CwtchNotifier { } if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) == null) { profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["GroupID"], - isInvitation: false, imagePath: data["PicturePath"], nickname: data["GroupName"], status: status, server: data["GroupServer"], isGroup: true, lastMessageTime: DateTime.now())); + authorization: ContactAuthorization.approved, imagePath: data["PicturePath"], nickname: data["GroupName"], status: status, server: data["GroupServer"], isGroup: true, lastMessageTime: DateTime.now())); profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now()); } break; @@ -91,8 +90,7 @@ class CwtchNotifier { contact.status = data["ConnectionState"]; } if (data["authorization"] != null) { - contact.isInvitation = data["authorization"] == "unknown"; - contact.isBlocked = data["authorization"] == "blocked"; + contact.authorization = stringToContactAuthorization(data["authorization"]); } // contact.[status/isBlocked] might change the list's sort order profileCN.getProfile(data["ProfileOnion"])?.contactList.resort(); @@ -227,7 +225,7 @@ class CwtchNotifier { if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(groupInvite["GroupID"]) == null) { profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], groupInvite["GroupID"], - isInvitation: false, + authorization: ContactAuthorization.approved, imagePath: data["PicturePath"], nickname: groupInvite["GroupName"], server: groupInvite["ServerHost"], @@ -241,7 +239,7 @@ class CwtchNotifier { case "AcceptGroupInvite": EnvironmentConfig.debugLog("accept group invite"); - profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.isInvitation = false; + profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.authorization = ContactAuthorization.approved; profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now()); break; case "ServerStateChange": diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index b8f913d3..0e5cb515 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,6 +1,9 @@ { "@@locale": "de", - "@@last_modified": "2021-07-07T23:42:20+02:00", + "@@last_modified": "2021-07-10T17:32:01+02:00", + "addContactConfirm": "Add contact %1", + "addContact": "Add contact", + "contactGoto": "Go to conversation with %1", "settingUIColumnOptionSame": "Same as portrait mode setting", "settingUIColumnDouble14Ratio": "Double (1:4)", "settingUIColumnDouble12Ratio": "Double (1:2)", @@ -120,7 +123,7 @@ "password1Label": "Passwort", "currentPasswordLabel": "aktuelles Passwort", "yourDisplayName": "Dein Anzeigename", - "profileOnionLabel": "Sende diese Adresse an andere Nutzer, mit denen du dich verbinden möchtest", + "profileOnionLabel": "Senden Sie diese Adresse an Peers, mit denen Sie sich verbinden möchten", "noPasswordWarning": "Wenn für dieses Konto kein Passwort verwendet wird, bedeutet dies, dass alle lokal gespeicherten Daten nicht verschlüsselt werden.", "radioNoPassword": "Unverschlüsselt (kein Passwort)", "radioUsePassword": "Passwort", @@ -132,13 +135,13 @@ "profileName": "Anzeigename", "editProfileTitle": "Profil bearbeiten", "addProfileTitle": "Neues Profil hinzufügen", - "deleteBtn": "löschen", + "deleteBtn": "Löschen", "unblockBtn": "Anderen Nutzer entsperren", "dontSavePeerHistory": "Verlauf mit anderem Nutzer löschen", "savePeerHistoryDescription": "Legt fest, ob ein mit dem anderen Nutzer verknüpfter Verlauf gelöscht werden soll oder nicht.", "savePeerHistory": "Peer-Verlauf speichern", "blockBtn": "Anderen Nutzer blockieren", - "saveBtn": "speichern", + "saveBtn": "Speichern", "displayNameLabel": "Angezeigename", "addressLabel": "Adresse", "puzzleGameBtn": "Puzzlespiel", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index e8ecdbdf..5cd879bc 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1,6 +1,9 @@ { "@@locale": "en", - "@@last_modified": "2021-07-07T23:42:20+02:00", + "@@last_modified": "2021-07-10T17:32:01+02:00", + "addContactConfirm": "Add contact %1", + "addContact": "Add contact", + "contactGoto": "Go to conversation with %1", "settingUIColumnOptionSame": "Same as portrait mode setting", "settingUIColumnDouble14Ratio": "Double (1:4)", "settingUIColumnDouble12Ratio": "Double (1:2)", @@ -163,7 +166,7 @@ "update": "Update", "inviteBtn": "Invite", "inviteToGroupLabel": "Invite to group", - "groupNameLabel": "Group Name", + "groupNameLabel": "Group name", "viewServerInfo": "Server Info", "serverSynced": "Synced", "serverConnectivityDisconnected": "Server Disconnected", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 3e41d232..c3e2d8c2 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,6 +1,9 @@ { "@@locale": "es", - "@@last_modified": "2021-07-07T23:42:20+02:00", + "@@last_modified": "2021-07-10T17:32:01+02:00", + "addContactConfirm": "Add contact %1", + "addContact": "Add contact", + "contactGoto": "Go to conversation with %1", "settingUIColumnOptionSame": "Same as portrait mode setting", "settingUIColumnDouble14Ratio": "Double (1:4)", "settingUIColumnDouble12Ratio": "Double (1:2)", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 5b938195..7bc91585 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,6 +1,9 @@ { "@@locale": "fr", - "@@last_modified": "2021-07-07T23:42:20+02:00", + "@@last_modified": "2021-07-10T17:32:01+02:00", + "addContactConfirm": "Add contact %1", + "addContact": "Ajouter le contact", + "contactGoto": "Aller à la conversation avec %1", "settingUIColumnOptionSame": "Même réglage que pour le mode portrait", "settingUIColumnDouble14Ratio": "Double (1:4)", "settingUIColumnDouble12Ratio": "Double (1:2)", @@ -132,7 +135,7 @@ "profileName": "Pseudo", "editProfileTitle": "Modifier le profil", "addProfileTitle": "Ajouter un nouveau profil", - "deleteBtn": "Supprimer", + "deleteBtn": "Effacer", "unblockBtn": "Débloquer le pair", "dontSavePeerHistory": "Supprimer l'historique des pairs", "savePeerHistoryDescription": "Détermine s'il faut ou non supprimer tout historique associé au pair.", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 3b792a33..9d2f9485 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,6 +1,9 @@ { "@@locale": "it", - "@@last_modified": "2021-07-07T23:42:20+02:00", + "@@last_modified": "2021-07-10T17:32:01+02:00", + "addContactConfirm": "Add contact %1", + "addContact": "Add contact", + "contactGoto": "Go to conversation with %1", "settingUIColumnOptionSame": "Same as portrait mode setting", "settingUIColumnDouble14Ratio": "Double (1:4)", "settingUIColumnDouble12Ratio": "Double (1:2)", @@ -124,7 +127,7 @@ "noPasswordWarning": "Non utilizzare una password su questo account significa che tutti i dati archiviati localmente non verranno criptati", "radioNoPassword": "Non criptato (senza password)", "radioUsePassword": "Password", - "copiedToClipboardNotification": "Copiato negli appunti", + "copiedToClipboardNotification": "Copiato negli Appunti", "copyBtn": "Copia", "editProfile": "Modifica profilo", "newProfile": "Nuovo profilo", diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index 4467f8af..fe721ecf 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,6 +1,9 @@ { "@@locale": "pl", - "@@last_modified": "2021-07-07T23:42:20+02:00", + "@@last_modified": "2021-07-10T17:32:01+02:00", + "addContactConfirm": "Add contact %1", + "addContact": "Add contact", + "contactGoto": "Go to conversation with %1", "settingUIColumnOptionSame": "Same as portrait mode setting", "settingUIColumnDouble14Ratio": "Double (1:4)", "settingUIColumnDouble12Ratio": "Double (1:2)", @@ -163,7 +166,7 @@ "update": "Update", "inviteBtn": "Invite", "inviteToGroupLabel": "Invite to group", - "groupNameLabel": "Group Name", + "groupNameLabel": "Group name", "viewServerInfo": "Server Info", "serverSynced": "Synced", "serverConnectivityDisconnected": "Server Disconnected", diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index e2241442..53180cc3 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,6 +1,9 @@ { "@@locale": "pt", - "@@last_modified": "2021-07-07T23:42:20+02:00", + "@@last_modified": "2021-07-10T17:32:01+02:00", + "addContactConfirm": "Add contact %1", + "addContact": "Add contact", + "contactGoto": "Go to conversation with %1", "settingUIColumnOptionSame": "Same as portrait mode setting", "settingUIColumnDouble14Ratio": "Double (1:4)", "settingUIColumnDouble12Ratio": "Double (1:2)", @@ -163,7 +166,7 @@ "update": "Update", "inviteBtn": "Convidar", "inviteToGroupLabel": "Convidar ao grupo", - "groupNameLabel": "Nome do Grupo", + "groupNameLabel": "Nome do grupo", "viewServerInfo": "Server Info", "serverSynced": "Synced", "serverConnectivityDisconnected": "Server Disconnected", diff --git a/lib/model.dart b/lib/model.dart index 8830c92b..086b2916 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -213,8 +213,7 @@ class ProfileInfoState extends ChangeNotifier { nickname: contact["name"], status: contact["status"], imagePath: contact["picture"], - isBlocked: contact["authorization"] == "blocked", - isInvitation: contact["authorization"] == "unknown", + authorization: stringToContactAuthorization(contact["authorization"]), savePeerHistory: contact["saveConversationHistory"], numMessages: contact["numMessages"], numUnread: contact["numUnread"], @@ -316,8 +315,7 @@ class ProfileInfoState extends ChangeNotifier { nickname: contact["name"], status: contact["status"], imagePath: contact["picture"], - isBlocked: contact["authorization"] == "blocked", - isInvitation: contact["authorization"] == "unknown", + authorization: stringToContactAuthorization(contact["authorization"]), savePeerHistory: contact["saveConversationHistory"], numMessages: contact["numMessages"], numUnread: contact["numUnread"], @@ -331,13 +329,30 @@ class ProfileInfoState extends ChangeNotifier { } } +enum ContactAuthorization { + unknown, + approved, + blocked +} + +ContactAuthorization stringToContactAuthorization(String authStr) { + switch(authStr) { + case "approved": + return ContactAuthorization.approved; + case "blocked": + return ContactAuthorization.blocked; + default: + return ContactAuthorization.unknown; + } +} + + class ContactInfoState extends ChangeNotifier { final String profileOnion; final String onion; late String _nickname; - late bool _isInvitation; - late bool _isBlocked; + late ContactAuthorization _authorization; late String _status; late String _imagePath; late String _savePeerHistory; @@ -355,8 +370,7 @@ class ContactInfoState extends ChangeNotifier { this.onion, { nickname = "", isGroup = false, - isInvitation = false, - isBlocked = false, + authorization = ContactAuthorization.unknown, status = "", imagePath = "", savePeerHistory = "DeleteHistoryConfirmed", @@ -367,8 +381,7 @@ class ContactInfoState extends ChangeNotifier { }) { this._nickname = nickname; this._isGroup = isGroup; - this._isInvitation = isInvitation; - this._isBlocked = isBlocked; + this._authorization = authorization; this._status = status; this._imagePath = imagePath; this._totalMessages = numMessages; @@ -398,15 +411,13 @@ class ContactInfoState extends ChangeNotifier { notifyListeners(); } - bool get isBlocked => this._isBlocked; - set isBlocked(bool newVal) { - this._isBlocked = newVal; - notifyListeners(); - } + bool get isBlocked => this._authorization == ContactAuthorization.blocked; - bool get isInvitation => this._isInvitation; - set isInvitation(bool newVal) { - this._isInvitation = newVal; + bool get isInvitation => this._authorization == ContactAuthorization.unknown; + + ContactAuthorization get authorization => this._authorization; + set authorization(ContactAuthorization newAuth) { + this._authorization = newAuth; notifyListeners(); } diff --git a/lib/models/message.dart b/lib/models/message.dart index abd06828..1d5bafd3 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -3,6 +3,7 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; import '../main.dart'; +import '../model.dart'; import 'messages/invitemessage.dart'; import 'messages/malformedmessage.dart'; import 'messages/quotedmessage.dart'; @@ -22,6 +23,8 @@ const TorV3ContactHandleLength = 56; // Defines the length of a Cwtch v2 Group. const GroupConversationHandleLength = 32; + + abstract class Message { MessageMetadata getMetadata(); Widget getWidget(BuildContext context); diff --git a/lib/views/contactsview.dart b/lib/views/contactsview.dart index d52709be..6d5bfc1f 100644 --- a/lib/views/contactsview.dart +++ b/lib/views/contactsview.dart @@ -12,6 +12,8 @@ import 'addcontactview.dart'; import '../model.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'messageview.dart'; + class ContactsView extends StatefulWidget { const ContactsView({Key? key}) : super(key: key); @@ -19,6 +21,38 @@ class ContactsView extends StatefulWidget { _ContactsViewState createState() => _ContactsViewState(); } +// selectConversation can be called from anywhere to set the active conversation +void selectConversation(BuildContext context, String handle) { + // requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts + Provider.of(context, listen: false).contactList.getContact(handle)!.unreadMessages = 0; + // triggers update in Double/TripleColumnView + Provider.of(context, listen: false).selectedConversation = handle; + Provider.of(context, listen: false).selectedIndex = null; + // if in singlepane mode, push to the stack + var isLandscape = Provider.of(context, listen: false).isLandscape(context); + if (Provider.of(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(context, handle); +} + +void _pushMessageView(BuildContext context, String handle) { + var profileOnion = Provider.of(context, listen: false).onion; + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext builderContext) { + // assert we have an actual profile... + // We need to listen for updates to the profile in order to update things like invitation message bubbles. + var profile = Provider.of(builderContext).profs.getProfile(profileOnion)!; + return MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: profile), + ChangeNotifierProvider.value(value: profile.contactList.getContact(handle)!), + ], + builder: (context, child) => MessageView(), + ); + }, + ), + ); +} + class _ContactsViewState extends State { late TextEditingController ctrlrFilter; bool showSearchBar = false; diff --git a/lib/views/peersettingsview.dart b/lib/views/peersettingsview.dart index cdcfc707..0f71eb2e 100644 --- a/lib/views/peersettingsview.dart +++ b/lib/views/peersettingsview.dart @@ -114,7 +114,7 @@ class _PeerSettingsViewState extends State { value: Provider.of(context).isBlocked, onChanged: (bool blocked) { // Save local blocked status - Provider.of(context, listen: false).isBlocked = blocked; + Provider.of(context, listen: false).authorization = ContactAuthorization.blocked; // Save New peer authorization var profileOnion = Provider.of(context, listen: false).profileOnion; @@ -216,7 +216,7 @@ class _PeerSettingsViewState extends State { showAlertDialog(BuildContext context) { // set up the buttons Widget cancelButton = TextButton( - child: Text("Cancel"), + child: Text(AppLocalizations.of(context)!.cancel), style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))), onPressed: () { Navigator.of(context).pop(); // dismiss dialog diff --git a/lib/widgets/contactrow.dart b/lib/widgets/contactrow.dart index 0c844b00..f8c6f476 100644 --- a/lib/widgets/contactrow.dart +++ b/lib/widgets/contactrow.dart @@ -1,3 +1,4 @@ +import 'package:cwtch/views/contactsview.dart'; import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; import 'package:cwtch/views/messageview.dart'; @@ -93,39 +94,12 @@ class _ContactRowState extends State { ), ]), onTap: () { - setState(() { - // requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts - Provider.of(context, listen: false).contactList.getContact(contact.onion)!.unreadMessages = 0; - // triggers update in Double/TripleColumnView - Provider.of(context, listen: false).selectedConversation = contact.onion; - Provider.of(context, listen: false).selectedIndex = null; - // if in singlepane mode, push to the stack - var isLandscape = Provider.of(context, listen: false).isLandscape(context); - if (Provider.of(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(contact.onion); - }); + selectConversation(context, contact.onion); }, )); } - void _pushMessageView(String handle) { - var profileOnion = Provider.of(context, listen: false).onion; - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext builderContext) { - // assert we have an actual profile... - // We need to listen for updates to the profile in order to update things like invitation message bubbles. - var profile = Provider.of(builderContext).profs.getProfile(profileOnion)!; - return MultiProvider( - providers: [ - ChangeNotifierProvider.value(value: profile), - ChangeNotifierProvider.value(value: profile.contactList.getContact(handle)!), - ], - builder: (context, child) => MessageView(), - ); - }, - ), - ); - } + void _btnApprove() { Provider.of(context, listen: false) diff --git a/lib/widgets/messagerow.dart b/lib/widgets/messagerow.dart index cb007acc..a1d90482 100644 --- a/lib/widgets/messagerow.dart +++ b/lib/widgets/messagerow.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:cwtch/models/message.dart'; +import 'package:cwtch/views/contactsview.dart'; import 'package:flutter/material.dart'; import 'package:cwtch/widgets/profileimage.dart'; import 'package:provider/provider.dart'; @@ -24,6 +25,17 @@ class MessageRowState extends State { @override Widget build(BuildContext context) { var fromMe = Provider.of(context).senderHandle == Provider.of(context).onion; + var isContact = Provider.of(context).getContact(Provider.of(context).senderHandle) != null; + + var senderDisplayStr = ""; + if (!fromMe) { + ContactInfoState? contact = Provider.of(context).contactList.getContact(Provider.of(context).senderHandle); + if (contact != null) { + senderDisplayStr = contact.nickname; + } else { + senderDisplayStr = Provider.of(context).senderHandle; + } + } Widget wdgIcons = Visibility( visible: this.showMenu, @@ -45,7 +57,7 @@ class MessageRowState extends State { } else { var contact = Provider.of(context); Widget wdgPortrait = GestureDetector( - onTap: _btnAdd, + onTap: isContact ? _btnGoto : _btnAdd, child: Padding( padding: EdgeInsets.all(4.0), child: ProfileImage( @@ -54,6 +66,7 @@ class MessageRowState extends State { //maskOut: contact.status != "Authenticated", border: contact.status == "Authenticated" ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor(), badgeTextColor: Colors.red, badgeColor: Colors.red, + tooltip: isContact ? AppLocalizations.of(context)!.contactGoto.replaceFirst("%1", senderDisplayStr) : AppLocalizations.of(context)!.addContact, ))); widgetRow = [ @@ -86,25 +99,62 @@ class MessageRowState extends State { child: Padding(padding: EdgeInsets.all(2), child: Row(mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, children: widgetRow)))); } + void _btnGoto() { + selectConversation(context, Provider.of(context, listen: false).senderHandle); + } + void _btnAdd() { var sender = Provider.of(context, listen: false).senderHandle; if (sender == null || sender == "") { print("sender not yet loaded"); return; } - var profileOnion = Provider.of(context, listen: false).onion; - final setPeerAttribute = { - "EventType": "AddContact", - "Data": {"ImportString": sender}, - }; - final setPeerAttributeJson = jsonEncode(setPeerAttribute); - Provider.of(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson); - final snackBar = SnackBar( - content: Text(AppLocalizations.of(context)!.successfullAddedContact), - duration: Duration(seconds: 2), + showAddContactConfirmAlertDialog(context, profileOnion, sender); + } + + showAddContactConfirmAlertDialog(BuildContext context, String profileOnion, String senderOnion) { + // set up the buttons + Widget cancelButton = TextButton( + child: Text(AppLocalizations.of(context)!.cancel), + style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))), + onPressed: () { + Navigator.of(context).pop(); // dismiss dialog + }, + ); + Widget continueButton = TextButton( + style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))), + child: Text(AppLocalizations.of(context)!.addContact), + onPressed: () { + Provider + .of(context, listen: false) + .cwtch + .ImportBundle(profileOnion, senderOnion); + final snackBar = SnackBar( + content: Text(AppLocalizations.of(context)!.successfullAddedContact), + duration: Duration(seconds: 2), + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + Navigator.of(context).pop(); // dismiss dialog + }, + ); + + // set up the AlertDialog + AlertDialog alert = AlertDialog( + title: Text(AppLocalizations.of(context)!.addContactConfirm.replaceFirst("%1", senderOnion)), + actions: [ + cancelButton, + continueButton, + ], + ); + + // show the dialog + showDialog( + context: context, + builder: (BuildContext context) { + return alert; + }, ); - ScaffoldMessenger.of(context).showSnackBar(snackBar); } } diff --git a/lib/widgets/profileimage.dart b/lib/widgets/profileimage.dart index 5b1468bf..68e9ae11 100644 --- a/lib/widgets/profileimage.dart +++ b/lib/widgets/profileimage.dart @@ -5,7 +5,7 @@ import 'package:provider/provider.dart'; import '../settings.dart'; class ProfileImage extends StatefulWidget { - ProfileImage({required this.imagePath, required this.diameter, required this.border, this.badgeCount = 0, required this.badgeColor, required this.badgeTextColor, this.maskOut = false}); + ProfileImage({required this.imagePath, required this.diameter, required this.border, this.badgeCount = 0, required this.badgeColor, required this.badgeTextColor, this.maskOut = false, this.tooltip = ""}); final double diameter; final String imagePath; final Color border; @@ -13,6 +13,7 @@ class ProfileImage extends StatefulWidget { final Color badgeColor; final Color badgeTextColor; final bool maskOut; + final String tooltip; @override _ProfileImageState createState() => _ProfileImageState(); @@ -21,6 +22,21 @@ class ProfileImage extends StatefulWidget { class _ProfileImageState extends State { @override Widget build(BuildContext context) { + var image = Image( + image: AssetImage("assets/" + widget.imagePath), + filterQuality: FilterQuality.medium, + // We need some theme specific blending here...we might want to consider making this a theme level attribute + colorBlendMode: !widget.maskOut + ? Provider.of(context).theme.identifier() == "dark" + ? BlendMode.softLight + : BlendMode.darken + : BlendMode.srcOut, + color: Provider.of(context).theme.backgroundHilightElementColor(), + isAntiAlias: true, + width: widget.diameter, + height: widget.diameter, + ); + return RepaintBoundary( child: Stack(children: [ ClipOval( @@ -33,20 +49,9 @@ class _ProfileImageState extends State { padding: const EdgeInsets.all(2.0), //border size child: ClipOval( clipBehavior: Clip.antiAlias, - child: Image( - image: AssetImage("assets/" + widget.imagePath), - filterQuality: FilterQuality.medium, - // We need some theme specific blending here...we might want to consider making this a theme level attribute - colorBlendMode: !widget.maskOut - ? Provider.of(context).theme.identifier() == "dark" - ? BlendMode.softLight - : BlendMode.darken - : BlendMode.srcOut, - color: Provider.of(context).theme.backgroundHilightElementColor(), - isAntiAlias: true, - width: widget.diameter, - height: widget.diameter, - ))))), + child: widget.tooltip == "" ? image : Tooltip( + message: widget.tooltip, + child: image))))), Visibility( visible: widget.badgeCount > 0, child: Positioned(