model contact uses authorization now; add tooltips to contact pics in chat; dual action: add or goto #105
|
@ -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":
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
{
|
||||
"@@locale": "de",
|
||||
"@@last_modified": "2021-07-07T23:42:20+02:00",
|
||||
"@@last_modified": "2021-07-10T17:32:01+02:00",
|
||||
dan marked this conversation as resolved
Outdated
|
||||
"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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages = 0;
|
||||
// triggers update in Double/TripleColumnView
|
||||
Provider.of<AppState>(context, listen: false).selectedConversation = handle;
|
||||
Provider.of<AppState>(context, listen: false).selectedIndex = null;
|
||||
// if in singlepane mode, push to the stack
|
||||
var isLandscape = Provider.of<AppState>(context, listen: false).isLandscape(context);
|
||||
if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(context, handle);
|
||||
}
|
||||
|
||||
void _pushMessageView(BuildContext context, String handle) {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
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<FlwtchState>(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<ContactsView> {
|
||||
late TextEditingController ctrlrFilter;
|
||||
bool showSearchBar = false;
|
||||
|
|
|
@ -114,7 +114,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
value: Provider.of<ContactInfoState>(context).isBlocked,
|
||||
onChanged: (bool blocked) {
|
||||
// Save local blocked status
|
||||
Provider.of<ContactInfoState>(context, listen: false).isBlocked = blocked;
|
||||
Provider.of<ContactInfoState>(context, listen: false).authorization = ContactAuthorization.blocked;
|
||||
|
||||
// Save New peer authorization
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
|
@ -216,7 +216,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
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
|
||||
|
|
|
@ -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<ContactRow> {
|
|||
),
|
||||
]),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
// requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(contact.onion)!.unreadMessages = 0;
|
||||
// triggers update in Double/TripleColumnView
|
||||
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
|
||||
var isLandscape = Provider.of<AppState>(context, listen: false).isLandscape(context);
|
||||
if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(contact.onion);
|
||||
});
|
||||
selectConversation(context, contact.onion);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
void _pushMessageView(String handle) {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
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<FlwtchState>(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<FlwtchState>(context, listen: false)
|
||||
|
|
|
@ -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<MessageRow> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||
var isContact = Provider.of<ContactListState>(context).getContact(Provider.of<MessageMetadata>(context).senderHandle) != null;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Widget wdgIcons = Visibility(
|
||||
visible: this.showMenu,
|
||||
|
@ -45,7 +57,7 @@ class MessageRowState extends State<MessageRow> {
|
|||
} else {
|
||||
var contact = Provider.of<ContactInfoState>(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<MessageRow> {
|
|||
//maskOut: contact.status != "Authenticated",
|
||||
border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor(),
|
||||
badgeTextColor: Colors.red, badgeColor: Colors.red,
|
||||
tooltip: isContact ? AppLocalizations.of(context)!.contactGoto.replaceFirst("%1", senderDisplayStr) : AppLocalizations.of(context)!.addContact,
|
||||
)));
|
||||
|
||||
widgetRow = <Widget>[
|
||||
|
@ -86,25 +99,62 @@ class MessageRowState extends State<MessageRow> {
|
|||
child: Padding(padding: EdgeInsets.all(2), child: Row(mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, children: widgetRow))));
|
||||
}
|
||||
|
||||
void _btnGoto() {
|
||||
selectConversation(context, Provider.of<MessageMetadata>(context, listen: false).senderHandle);
|
||||
}
|
||||
|
||||
void _btnAdd() {
|
||||
var sender = Provider.of<MessageMetadata>(context, listen: false).senderHandle;
|
||||
if (sender == null || sender == "") {
|
||||
print("sender not yet loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
final setPeerAttribute = {
|
||||
"EventType": "AddContact",
|
||||
"Data": {"ImportString": sender},
|
||||
};
|
||||
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
|
||||
Provider.of<FlwtchState>(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<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
sarah
commented
Use cwtch.ImportBundle(profileOnion, senderOnion) instead of constructing the Event here, as it is more robust. Use cwtch.ImportBundle(profileOnion, senderOnion) instead of constructing the Event here, as it is more robust.
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ProfileImage> {
|
||||
@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<Settings>(context).theme.identifier() == "dark"
|
||||
? BlendMode.softLight
|
||||
: BlendMode.darken
|
||||
: BlendMode.srcOut,
|
||||
color: Provider.of<Settings>(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<ProfileImage> {
|
|||
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<Settings>(context).theme.identifier() == "dark"
|
||||
? BlendMode.softLight
|
||||
: BlendMode.darken
|
||||
: BlendMode.srcOut,
|
||||
color: Provider.of<Settings>(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(
|
||||
|
|
Loading…
Reference in New Issue
Download these in last updated order so it is more obvious what has changed.