forked from cwtch.im/cwtch-ui
Merge pull request 'Profile Images' (#355) from custom_profile_images into trunk
Reviewed-on: cwtch.im/cwtch-ui#355 Reviewed-by: Dan Ballard <dan@openprivacy.ca> Reviewed-by: erinn <erinn@openprivacy.ca>
This commit is contained in:
commit
729ff6811e
|
@ -1 +1 @@
|
|||
2022-02-04-16-57-v1.5.4-28-g4e4e331
|
||||
2022-02-07-17-39-v1.5.4-31-g17acc3b
|
|
@ -1 +1 @@
|
|||
2022-02-04-21-58-v1.5.4-28-g4e4e331
|
||||
2022-02-07-22-31-v1.5.4-31-g17acc3b
|
|
@ -77,7 +77,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
}
|
||||
|
||||
val loader = FlutterInjector.instance().flutterLoader()
|
||||
val key = loader.getLookupKeyForAsset("assets/" + data.getString("Picture"))//"assets/profiles/001-centaur.png")
|
||||
val key = loader.getLookupKeyForAsset("assets/" + data.getString("picture"))//"assets/profiles/001-centaur.png")
|
||||
val fh = applicationContext.assets.open(key)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
const int MaxImageFileSharingSize = 20971520;
|
||||
|
||||
const int MaxGeneralFileSharingSize = 10737418240;
|
|
@ -0,0 +1,30 @@
|
|||
import 'dart:io';
|
||||
import 'package:cwtch/models/appstate.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
void showFilePicker(BuildContext ctx, int maxBytes, Function(File) onSuccess, Function onError, Function onCancel) async {
|
||||
// only allow one file picker at a time
|
||||
// note: ideally we would destroy file picker when leaving a conversation
|
||||
// but we don't currently have that option.
|
||||
// we need to store AppState in a variable because ctx might be destroyed
|
||||
// while awaiting for pickFiles.
|
||||
var appstate = Provider.of<AppState>(ctx, listen: false);
|
||||
appstate.disableFilePicker = true;
|
||||
// currently lockParentWindow only works on Windows...
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(lockParentWindow: true);
|
||||
appstate.disableFilePicker = false;
|
||||
if (result != null && result.files.first.path != null) {
|
||||
File file = File(result.files.first.path!);
|
||||
// We have a maximum number of bytes we can represent in terms of
|
||||
// a manifest (see : https://git.openprivacy.ca/cwtch.im/cwtch/src/branch/master/protocol/files/manifest.go#L25)
|
||||
if (file.lengthSync() <= maxBytes) {
|
||||
onSuccess(file);
|
||||
} else {
|
||||
onError();
|
||||
}
|
||||
} else {
|
||||
onCancel();
|
||||
}
|
||||
}
|
|
@ -55,7 +55,7 @@ class CwtchNotifier {
|
|||
}
|
||||
EnvironmentConfig.debugLog("NewPeer $data");
|
||||
// if tag != v1-defaultPassword then it is either encrypted OR it is an unencrypted account created during pre-beta...
|
||||
profileCN.add(data["Identity"], data["name"], data["picture"], data["ContactsJson"], data["ServerList"], data["Online"] == "true", data["tag"] != "v1-defaultPassword");
|
||||
profileCN.add(data["Identity"], data["name"], data["picture"], data["defaultPicture"], data["ContactsJson"], data["ServerList"], data["Online"] == "true", data["tag"] != "v1-defaultPassword");
|
||||
break;
|
||||
case "ContactCreated":
|
||||
EnvironmentConfig.debugLog("ContactCreated $data");
|
||||
|
@ -67,6 +67,7 @@ class CwtchNotifier {
|
|||
nickname: data["nick"],
|
||||
status: data["status"],
|
||||
imagePath: data["picture"],
|
||||
defaultImagePath: data["defaultPicture"],
|
||||
blocked: data["blocked"] == "true",
|
||||
accepted: data["accepted"] == "true",
|
||||
savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"],
|
||||
|
@ -106,7 +107,8 @@ class CwtchNotifier {
|
|||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], int.parse(data["ConversationID"]), data["GroupID"],
|
||||
blocked: false, // we created
|
||||
accepted: true, // we created
|
||||
imagePath: data["PicturePath"],
|
||||
imagePath: data["picture"],
|
||||
defaultImagePath: data["picture"],
|
||||
nickname: data["GroupName"],
|
||||
status: status,
|
||||
server: data["GroupServer"],
|
||||
|
@ -147,7 +149,7 @@ class CwtchNotifier {
|
|||
var messageID = int.parse(data["Index"]);
|
||||
var timestamp = DateTime.tryParse(data['TimestampReceived'])!;
|
||||
var senderHandle = data['RemotePeer'];
|
||||
var senderImage = data['Picture'];
|
||||
var senderImage = data['picture'];
|
||||
var isAuto = data['Auto'] == "true";
|
||||
String? contenthash = data['ContentHash'];
|
||||
var selectedProfile = appState.selectedProfile == data["ProfileOnion"];
|
||||
|
@ -199,7 +201,7 @@ class CwtchNotifier {
|
|||
if (data["ProfileOnion"] != data["RemotePeer"]) {
|
||||
var idx = int.parse(data["Index"]);
|
||||
var senderHandle = data['RemotePeer'];
|
||||
var senderImage = data['Picture'];
|
||||
var senderImage = data['picture'];
|
||||
var timestampSent = DateTime.tryParse(data['TimestampSent'])!;
|
||||
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier);
|
||||
var currentTotal = contact!.totalMessages;
|
||||
|
@ -301,7 +303,7 @@ class CwtchNotifier {
|
|||
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], identifier, groupInvite["GroupID"],
|
||||
blocked: false, // NewGroup only issued on accepting invite
|
||||
accepted: true, // NewGroup only issued on accepting invite
|
||||
imagePath: data["PicturePath"],
|
||||
imagePath: data["picture"],
|
||||
nickname: groupInvite["GroupName"],
|
||||
server: groupInvite["ServerHost"],
|
||||
status: status,
|
||||
|
@ -322,15 +324,28 @@ class CwtchNotifier {
|
|||
profileCN.getProfile(data["ProfileOnion"])?.contactList.resort();
|
||||
break;
|
||||
case "NewRetValMessageFromPeer":
|
||||
if (data["Path"] == "profile.name") {
|
||||
if (data["Path"] == "profile.name" && data["Exists"] == "true") {
|
||||
if (data["Data"].toString().trim().length > 0) {
|
||||
// Update locally on the UI...
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]) != null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"])!.nickname = data["Data"];
|
||||
}
|
||||
}
|
||||
} else if (data['Path'] == "profile.picture") {
|
||||
// Not yet..
|
||||
} else if (data['Path'] == "profile.custom-profile-image" && data["Exists"] == "true") {
|
||||
EnvironmentConfig.debugLog("received ret val of custom profile image: $data");
|
||||
String fileKey = data['Data'];
|
||||
String filePath = data['FilePath'];
|
||||
bool downloaded = data['FileDownloadFinished'] == "true";
|
||||
if (downloaded) {
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]) != null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"])!.imagePath = filePath;
|
||||
}
|
||||
} else {
|
||||
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]);
|
||||
if (contact != null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.waitForDownloadComplete(contact.identifier, fileKey);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
EnvironmentConfig.debugLog("unhandled ret val event: ${data['Path']}");
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "de",
|
||||
"@@last_modified": "2022-01-28T19:57:41+01:00",
|
||||
"@@last_modified": "2022-02-07T21:17:01+01:00",
|
||||
"tooltipSelectACustomProfileImage": "Select a Custom Profile Image",
|
||||
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||
"labelTorNetwork": "Tor Network",
|
||||
|
@ -162,7 +163,7 @@
|
|||
"newPassword": "Neues Passwort",
|
||||
"yesLeave": "Ja, diese Unterhaltung beenden",
|
||||
"reallyLeaveThisGroupPrompt": "Bist du sicher, dass du diese Unterhaltung beenden möchtest? Alle Nachrichten und Attribute werden gelöscht.",
|
||||
"leaveGroup": "Unterhaltung beenden",
|
||||
"leaveConversation": "Unterhaltung beenden",
|
||||
"inviteToGroup": "Du wurdest eingeladen einer Gruppe beizutreten:",
|
||||
"titleManageServers": "Server verwalten",
|
||||
"dateNever": "Nie",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "en",
|
||||
"@@last_modified": "2022-01-28T19:57:41+01:00",
|
||||
"@@last_modified": "2022-02-07T21:17:01+01:00",
|
||||
"tooltipSelectACustomProfileImage": "Select a Custom Profile Image",
|
||||
"editProfile": "Edit Profile",
|
||||
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||
|
@ -164,7 +165,7 @@
|
|||
"newPassword": "New Password",
|
||||
"yesLeave": "Yes, Leave This Conversation",
|
||||
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
|
||||
"leaveGroup": "Leave This Conversation",
|
||||
"leaveConversation": "Leave This Conversation",
|
||||
"inviteToGroup": "You have been invited to join a group:",
|
||||
"pasteAddressToAddContact": "Paste a cwtch address, invitation or key bundle here to add a new conversation",
|
||||
"tooltipAddContact": "Add a new contact or conversation",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "es",
|
||||
"@@last_modified": "2022-01-28T19:57:41+01:00",
|
||||
"@@last_modified": "2022-02-07T21:17:01+01:00",
|
||||
"tooltipSelectACustomProfileImage": "Select a Custom Profile Image",
|
||||
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||
"labelTorNetwork": "Tor Network",
|
||||
|
@ -148,7 +149,7 @@
|
|||
"newPassword": "New Password",
|
||||
"yesLeave": "Yes, Leave This Conversation",
|
||||
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
|
||||
"leaveGroup": "Leave This Conversation",
|
||||
"leaveConversation": "Leave This Conversation",
|
||||
"inviteToGroup": "You have been invited to join a group:",
|
||||
"titleManageServers": "Manage Servers",
|
||||
"dateNever": "Never",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "fr",
|
||||
"@@last_modified": "2022-01-28T19:57:41+01:00",
|
||||
"@@last_modified": "2022-02-07T21:17:01+01:00",
|
||||
"tooltipSelectACustomProfileImage": "Select a Custom Profile Image",
|
||||
"editProfile": "Modifier le profil",
|
||||
"settingTheme": "Utilisez des thèmes clairs",
|
||||
"torSettingsUseCustomTorServiceConfiguration": "Utiliser une configuration personnalisée du service Tor (torrc)",
|
||||
|
@ -213,7 +214,7 @@
|
|||
"dateNever": "Jamais",
|
||||
"titleManageServers": "Gérer les serveurs",
|
||||
"inviteToGroup": "Vous avez été invité à rejoindre un groupe :",
|
||||
"leaveGroup": "Quittez cette conversation",
|
||||
"leaveConversation": "Quittez cette conversation",
|
||||
"reallyLeaveThisGroupPrompt": "Êtes-vous sûr de vouloir quitter cette conversation ? Tous les messages et attributs seront supprimés.",
|
||||
"yesLeave": "Oui, quittez cette conversation",
|
||||
"noPasswordWarning": "Ne pas utiliser de mot de passe sur ce compte signifie que toutes les données stockées localement ne seront pas chiffrées.",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "it",
|
||||
"@@last_modified": "2022-01-28T19:57:41+01:00",
|
||||
"@@last_modified": "2022-02-07T21:17:01+01:00",
|
||||
"tooltipSelectACustomProfileImage": "Select a Custom Profile Image",
|
||||
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||
"labelTorNetwork": "Tor Network",
|
||||
|
@ -38,7 +39,7 @@
|
|||
"copiedToClipboardNotification": "Copiato negli Appunti",
|
||||
"groupNameLabel": "Nome del gruppo",
|
||||
"titleManageServers": "Gestisci i Server",
|
||||
"leaveGroup": "Lascia Questa Conversazione",
|
||||
"leaveConversation": "Lascia Questa Conversazione",
|
||||
"yesLeave": "Sì, Lascia Questa Conversazione",
|
||||
"newPassword": "Nuova Password",
|
||||
"sendMessage": "Invia Messaggio",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "pl",
|
||||
"@@last_modified": "2022-01-28T19:57:41+01:00",
|
||||
"@@last_modified": "2022-02-07T21:17:01+01:00",
|
||||
"tooltipSelectACustomProfileImage": "Select a Custom Profile Image",
|
||||
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||
"labelTorNetwork": "Tor Network",
|
||||
|
@ -209,7 +210,7 @@
|
|||
"chatHistoryDefault": "Ta konwersacja zostanie usunięta gdy zamkniesz Cwtch! Możesz włączyć zapisywanie wiadomości dla każdej konwersacji osobno w menu w prawym górnym rogu.",
|
||||
"yesLeave": "Opuść",
|
||||
"reallyLeaveThisGroupPrompt": "Na pewno chcesz opuścić tę grupę? Wszystkie wiadomości i atrybuty zostaną usunięte.",
|
||||
"leaveGroup": "Opuść grupę",
|
||||
"leaveConversation": "Opuść grupę",
|
||||
"inviteToGroup": "Zaproszono Cię do grupy:",
|
||||
"dateNever": "Nigdy",
|
||||
"dateLastYear": "Rok temu",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "pt",
|
||||
"@@last_modified": "2022-01-28T19:57:41+01:00",
|
||||
"@@last_modified": "2022-02-07T21:17:01+01:00",
|
||||
"tooltipSelectACustomProfileImage": "Select a Custom Profile Image",
|
||||
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||
"labelTorNetwork": "Tor Network",
|
||||
|
@ -148,7 +149,7 @@
|
|||
"newPassword": "New Password",
|
||||
"yesLeave": "Yes, Leave This Conversation",
|
||||
"reallyLeaveThisGroupPrompt": "Are you sure you want to leave this conversation? All messages and attributes will be deleted.",
|
||||
"leaveGroup": "Leave This Conversation",
|
||||
"leaveConversation": "Leave This Conversation",
|
||||
"inviteToGroup": "You have been invited to join a group:",
|
||||
"titleManageServers": "Manage Servers",
|
||||
"dateNever": "Never",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"@@locale": "ru",
|
||||
"@@last_modified": "2022-01-28T19:57:41+01:00",
|
||||
"@@last_modified": "2022-02-07T21:17:01+01:00",
|
||||
"tooltipSelectACustomProfileImage": "Select a Custom Profile Image",
|
||||
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||
"labelTorNetwork": "Tor Network",
|
||||
|
@ -164,7 +165,7 @@
|
|||
"newPassword": "Новый пароль",
|
||||
"yesLeave": "Да, оставить этот чат",
|
||||
"reallyLeaveThisGroupPrompt": "Вы уверены, что хотите закончить этот разговор? Все сообщения будут удалены.",
|
||||
"leaveGroup": "Да, оставить этот чат",
|
||||
"leaveConversation": "Да, оставить этот чат",
|
||||
"inviteToGroup": "Вас пригласили присоединиться к группе:",
|
||||
"titleManageServers": "Управление серверами",
|
||||
"dateNever": "Никогда",
|
||||
|
|
|
@ -14,6 +14,7 @@ class ContactInfoState extends ChangeNotifier {
|
|||
late bool _blocked;
|
||||
late String _status;
|
||||
late String _imagePath;
|
||||
late String _defaultImagePath;
|
||||
late String _savePeerHistory;
|
||||
late int _unreadMessages = 0;
|
||||
late int _totalMessages = 0;
|
||||
|
@ -37,6 +38,7 @@ class ContactInfoState extends ChangeNotifier {
|
|||
blocked = false,
|
||||
status = "",
|
||||
imagePath = "",
|
||||
defaultImagePath = "",
|
||||
savePeerHistory = "DeleteHistoryConfirmed",
|
||||
numMessages = 0,
|
||||
numUnread = 0,
|
||||
|
@ -49,6 +51,7 @@ class ContactInfoState extends ChangeNotifier {
|
|||
this._blocked = blocked;
|
||||
this._status = status;
|
||||
this._imagePath = imagePath;
|
||||
this._defaultImagePath = defaultImagePath;
|
||||
this._totalMessages = numMessages;
|
||||
this._unreadMessages = numUnread;
|
||||
this._savePeerHistory = savePeerHistory;
|
||||
|
@ -166,6 +169,13 @@ class ContactInfoState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
String get defaultImagePath => this._defaultImagePath;
|
||||
|
||||
set defaultImagePath(String newVal) {
|
||||
this._defaultImagePath = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
DateTime get lastMessageTime => this._lastMessageTime;
|
||||
|
||||
set lastMessageTime(DateTime newVal) {
|
||||
|
|
|
@ -14,9 +14,11 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
final String onion;
|
||||
String _nickname = "";
|
||||
String _imagePath = "";
|
||||
String _defaultImagePath = "";
|
||||
int _unreadMessages = 0;
|
||||
bool _online = false;
|
||||
Map<String, FileDownloadProgress> _downloads = Map<String, FileDownloadProgress>();
|
||||
Map<String, int> _downloadTriggers = Map<String, int>();
|
||||
|
||||
// assume profiles are encrypted...this will be set to false
|
||||
// in the constructor if the profile is encrypted with the defacto password.
|
||||
|
@ -26,14 +28,17 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
required this.onion,
|
||||
nickname = "",
|
||||
imagePath = "",
|
||||
defaultImagePath = "",
|
||||
unreadMessages = 0,
|
||||
contactsJson = "",
|
||||
serversJson = "",
|
||||
online = false,
|
||||
encrypted = true,
|
||||
String,
|
||||
}) {
|
||||
this._nickname = nickname;
|
||||
this._imagePath = imagePath;
|
||||
this._defaultImagePath = defaultImagePath;
|
||||
this._unreadMessages = unreadMessages;
|
||||
this._online = online;
|
||||
this._encrypted = encrypted;
|
||||
|
@ -49,6 +54,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
nickname: contact["name"],
|
||||
status: contact["status"],
|
||||
imagePath: contact["picture"],
|
||||
defaultImagePath: contact["isGroup"] ? contact["picture"] : contact["defaultPicture"],
|
||||
accepted: contact["accepted"],
|
||||
blocked: contact["blocked"],
|
||||
savePeerHistory: contact["saveConversationHistory"],
|
||||
|
@ -114,6 +120,12 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
String get defaultImagePath => this._defaultImagePath;
|
||||
set defaultImagePath(String newVal) {
|
||||
this._defaultImagePath = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
int get unreadMessages => this._unreadMessages;
|
||||
set unreadMessages(int newVal) {
|
||||
this._unreadMessages = newVal;
|
||||
|
@ -160,6 +172,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
contact["identifier"],
|
||||
contact["onion"],
|
||||
nickname: contact["name"],
|
||||
defaultImagePath: contact["defaultPicture"],
|
||||
status: contact["status"],
|
||||
imagePath: contact["picture"],
|
||||
accepted: contact["accepted"],
|
||||
|
@ -178,22 +191,14 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
this._contacts.resort();
|
||||
}
|
||||
|
||||
void newMessage(int identifier, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data, String? contenthash, bool selectedProfile, bool selectedConversation) {
|
||||
void newMessage(
|
||||
int identifier, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data, String? contenthash, bool selectedProfile, bool selectedConversation) {
|
||||
if (!selectedProfile) {
|
||||
unreadMessages++;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
contactList.newMessage(
|
||||
identifier,
|
||||
messageID,
|
||||
timestamp,
|
||||
senderHandle,
|
||||
senderImage,
|
||||
isAuto,
|
||||
data,
|
||||
contenthash,
|
||||
selectedConversation);
|
||||
contactList.newMessage(identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data, contenthash, selectedConversation);
|
||||
}
|
||||
|
||||
void downloadInit(String fileKey, int numChunks) {
|
||||
|
@ -232,6 +237,15 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
// so setting numChunks correctly shouldn't matter
|
||||
this.downloadInit(fileKey, 1);
|
||||
}
|
||||
|
||||
// Update the contact with a custom profile image if we are
|
||||
// waiting for one...
|
||||
if (this._downloadTriggers.containsKey(fileKey)) {
|
||||
int identifier = this._downloadTriggers[fileKey]!;
|
||||
this.contactList.getContact(identifier)!.imagePath = finalPath;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// only update if different
|
||||
if (!this._downloads[fileKey]!.complete) {
|
||||
this._downloads[fileKey]!.timeEnd = DateTime.now();
|
||||
|
@ -308,4 +322,9 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
}
|
||||
return prettyBytes((bytes / seconds).round()) + "/s";
|
||||
}
|
||||
|
||||
void waitForDownloadComplete(int identifier, String fileKey) {
|
||||
_downloadTriggers[fileKey] = identifier;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,11 @@ class ProfileListState extends ChangeNotifier {
|
|||
List<ProfileInfoState> _profiles = [];
|
||||
int get num => _profiles.length;
|
||||
|
||||
void add(String onion, String name, String picture, String contactsJson, String serverJson, bool online, bool encrypted) {
|
||||
void add(String onion, String name, String picture, String defaultPicture, String contactsJson, String serverJson, bool online, bool encrypted) {
|
||||
var idx = _profiles.indexWhere((element) => element.onion == onion);
|
||||
if (idx == -1) {
|
||||
_profiles.add(ProfileInfoState(onion: onion, nickname: name, imagePath: picture, contactsJson: contactsJson, serversJson: serverJson, online: online, encrypted: encrypted));
|
||||
_profiles.add(ProfileInfoState(
|
||||
onion: onion, nickname: name, imagePath: picture, defaultImagePath: defaultPicture, contactsJson: contactsJson, serversJson: serverJson, online: online, encrypted: encrypted));
|
||||
} else {
|
||||
_profiles[idx].updateFrom(onion, name, picture, contactsJson, serverJson, online);
|
||||
}
|
||||
|
@ -28,7 +29,5 @@ class ProfileListState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
int generateUnreadCount(String selectedProfile) => _profiles.where( (p) => p.onion != selectedProfile ).fold(0, (i, p) => i + p.unreadMessages);
|
||||
|
||||
|
||||
int generateUnreadCount(String selectedProfile) => _profiles.where((p) => p.onion != selectedProfile).fold(0, (i, p) => i + p.unreadMessages);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cwtch/config.dart';
|
||||
import 'package:cwtch/cwtch/cwtch.dart';
|
||||
import 'package:cwtch/models/appstate.dart';
|
||||
import 'package:cwtch/models/profile.dart';
|
||||
import 'package:cwtch/controllers/filesharing.dart' as filesharing;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cwtch/widgets/buttontextfield.dart';
|
||||
|
@ -15,6 +15,7 @@ import 'package:cwtch/widgets/textfield.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import '../constants.dart';
|
||||
import '../cwtch_icons_icons.dart';
|
||||
import '../errorHandler.dart';
|
||||
import '../main.dart';
|
||||
|
@ -89,14 +90,39 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
Visibility(
|
||||
visible: Provider.of<ProfileInfoState>(context).onion.isNotEmpty,
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
ProfileImage(
|
||||
imagePath: Provider.of<ProfileInfoState>(context).imagePath,
|
||||
diameter: 120,
|
||||
maskOut: false,
|
||||
border: theme.theme.portraitOnlineBorderColor,
|
||||
badgeTextColor: Colors.red,
|
||||
badgeColor: Colors.red,
|
||||
)
|
||||
MouseRegion(
|
||||
cursor: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment) ? SystemMouseCursors.click : SystemMouseCursors.basic,
|
||||
child: GestureDetector(
|
||||
// don't allow setting of profile images if the image previews experiment is disabled.
|
||||
onTap: Provider.of<AppState>(context, listen: false).disableFilePicker || !Provider.of<Settings>(context, listen: false).isExperimentEnabled(ImagePreviewsExperiment)
|
||||
? null
|
||||
: () {
|
||||
filesharing.showFilePicker(context, MaxImageFileSharingSize, (File file) {
|
||||
var profile = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
// Share this image publicly (conversation handle == -1)
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.ShareFile(profile, -1, file.path);
|
||||
// update the image cache locally
|
||||
Provider.of<ProfileInfoState>(context, listen: false).imagePath = file.path;
|
||||
}, () {
|
||||
final snackBar = SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.msgFileTooBig),
|
||||
duration: Duration(seconds: 4),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}, () {});
|
||||
},
|
||||
child: ProfileImage(
|
||||
imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment)
|
||||
? Provider.of<ProfileInfoState>(context).imagePath
|
||||
: Provider.of<ProfileInfoState>(context).defaultImagePath,
|
||||
diameter: 120,
|
||||
tooltip:
|
||||
Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment) ? AppLocalizations.of(context)!.tooltipSelectACustomProfileImage : "",
|
||||
maskOut: false,
|
||||
border: theme.theme.portraitOnlineBorderColor,
|
||||
badgeTextColor: theme.theme.portraitContactBadgeTextColor,
|
||||
badgeColor: theme.theme.portraitContactBadgeColor,
|
||||
badgeEdit: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment))))
|
||||
])),
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel),
|
||||
|
|
|
@ -86,11 +86,10 @@ class _ContactsViewState extends State<ContactsView> {
|
|||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
|
||||
StreamBuilder<bool>(
|
||||
stream: Provider.of<AppState>(context).getUnreadProfileNotifyStream(),
|
||||
builder: (BuildContext context, AsyncSnapshot<bool> unreadCountSnapshot) {
|
||||
int unreadCount = Provider.of<ProfileListState>(context).generateUnreadCount(Provider.of<AppState>(context).selectedProfile ?? "") ;
|
||||
int unreadCount = Provider.of<ProfileListState>(context).generateUnreadCount(Provider.of<AppState>(context).selectedProfile ?? "");
|
||||
|
||||
return Visibility(
|
||||
visible: unreadCount > 0,
|
||||
|
@ -104,7 +103,9 @@ class _ContactsViewState extends State<ContactsView> {
|
|||
title: RepaintBoundary(
|
||||
child: Row(children: [
|
||||
ProfileImage(
|
||||
imagePath: Provider.of<ProfileInfoState>(context).imagePath,
|
||||
imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment)
|
||||
? Provider.of<ProfileInfoState>(context).imagePath
|
||||
: Provider.of<ProfileInfoState>(context).defaultImagePath,
|
||||
diameter: 42,
|
||||
border: Provider.of<Settings>(context).current().portraitOnlineBorderColor,
|
||||
badgeTextColor: Colors.red,
|
||||
|
|
|
@ -251,6 +251,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
settings.enableExperiment(FileSharingExperiment);
|
||||
} else {
|
||||
settings.disableExperiment(FileSharingExperiment);
|
||||
settings.disableExperiment(ImagePreviewsExperiment);
|
||||
}
|
||||
saveSettings(context);
|
||||
},
|
||||
|
|
|
@ -159,7 +159,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
|||
),
|
||||
Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||
Tooltip(
|
||||
message: AppLocalizations.of(context)!.leaveGroup,
|
||||
message: AppLocalizations.of(context)!.leaveConversation,
|
||||
child: TextButton.icon(
|
||||
onPressed: () {
|
||||
showAlertDialog(context);
|
||||
|
@ -167,7 +167,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
|||
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.transparent)),
|
||||
icon: Icon(CwtchIcons.leave_group),
|
||||
label: Text(
|
||||
AppLocalizations.of(context)!.leaveGroup,
|
||||
AppLocalizations.of(context)!.leaveConversation,
|
||||
style: TextStyle(decoration: TextDecoration.underline),
|
||||
),
|
||||
))
|
||||
|
|
|
@ -11,7 +11,7 @@ import 'package:cwtch/models/profile.dart';
|
|||
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||
import 'package:cwtch/widgets/messageloadingbubble.dart';
|
||||
import 'package:cwtch/widgets/profileimage.dart';
|
||||
|
||||
import 'package:cwtch/controllers/filesharing.dart' as filesharing;
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
@ -23,6 +23,7 @@ import 'package:provider/provider.dart';
|
|||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
import '../constants.dart';
|
||||
import '../main.dart';
|
||||
import '../settings.dart';
|
||||
import '../widgets/messagelist.dart';
|
||||
|
@ -92,7 +93,16 @@ class _MessageViewState extends State<MessageView> {
|
|||
onPressed: Provider.of<AppState>(context).disableFilePicker
|
||||
? null
|
||||
: () {
|
||||
_showFilePicker(context);
|
||||
imagePreview = null;
|
||||
filesharing.showFilePicker(context, MaxGeneralFileSharingSize, (File file) {
|
||||
_confirmFileSend(context, file.path);
|
||||
}, () {
|
||||
final snackBar = SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.msgFileTooBig),
|
||||
duration: Duration(seconds: 4),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}, () {});
|
||||
},
|
||||
));
|
||||
}
|
||||
|
@ -129,7 +139,9 @@ class _MessageViewState extends State<MessageView> {
|
|||
leading: Provider.of<Settings>(context).uiColumns(appState.isLandscape(context)).length > 1 ? Container() : null,
|
||||
title: Row(children: [
|
||||
ProfileImage(
|
||||
imagePath: Provider.of<ContactInfoState>(context).imagePath,
|
||||
imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment)
|
||||
? Provider.of<ContactInfoState>(context).imagePath
|
||||
: Provider.of<ContactInfoState>(context).defaultImagePath,
|
||||
diameter: 42,
|
||||
border: Provider.of<Settings>(context).current().portraitOnlineBorderColor,
|
||||
badgeTextColor: Colors.red,
|
||||
|
@ -415,36 +427,6 @@ class _MessageViewState extends State<MessageView> {
|
|||
});
|
||||
}
|
||||
|
||||
void _showFilePicker(BuildContext ctx) async {
|
||||
imagePreview = null;
|
||||
|
||||
// only allow one file picker at a time
|
||||
// note: ideally we would destroy file picker when leaving a conversation
|
||||
// but we don't currently have that option.
|
||||
// we need to store AppState in a variable because ctx might be destroyed
|
||||
// while awaiting for pickFiles.
|
||||
var appstate = Provider.of<AppState>(ctx, listen: false);
|
||||
appstate.disableFilePicker = true;
|
||||
// currently lockParentWindow only works on Windows...
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(lockParentWindow: true);
|
||||
appstate.disableFilePicker = false;
|
||||
if (result != null && result.files.first.path != null) {
|
||||
File file = File(result.files.first.path!);
|
||||
// We have a maximum number of bytes we can represent in terms of
|
||||
// a manifest (see : https://git.openprivacy.ca/cwtch.im/cwtch/src/branch/master/protocol/files/manifest.go#L25)
|
||||
if (file.lengthSync() <= 10737418240) {
|
||||
print("Sending " + file.path);
|
||||
_confirmFileSend(ctx, file.path);
|
||||
} else {
|
||||
final snackBar = SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.msgFileTooBig),
|
||||
duration: Duration(seconds: 4),
|
||||
);
|
||||
ScaffoldMessenger.of(ctx).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _confirmFileSend(BuildContext ctx, String path) async {
|
||||
showModalBottomSheet<void>(
|
||||
context: ctx,
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:ui';
|
|||
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||
import 'package:cwtch/models/appstate.dart';
|
||||
import 'package:cwtch/models/contact.dart';
|
||||
import 'package:cwtch/models/profile.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cwtch/widgets/buttontextfield.dart';
|
||||
import 'package:cwtch/widgets/cwtchlabel.dart';
|
||||
|
@ -216,11 +217,29 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
|
||||
});
|
||||
},
|
||||
icon: Icon(CwtchIcons.leave_chat),
|
||||
icon: Icon(Icons.archive),
|
||||
label: Text(AppLocalizations.of(context)!.archiveConversation),
|
||||
))
|
||||
]),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||
Tooltip(
|
||||
message: AppLocalizations.of(context)!.leaveConversation,
|
||||
child: TextButton.icon(
|
||||
onPressed: () {
|
||||
showAlertDialog(context);
|
||||
},
|
||||
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.transparent)),
|
||||
icon: Icon(CwtchIcons.leave_group),
|
||||
label: Text(
|
||||
AppLocalizations.of(context)!.leaveConversation,
|
||||
style: TextStyle(decoration: TextDecoration.underline),
|
||||
),
|
||||
))
|
||||
])
|
||||
]),
|
||||
])
|
||||
])))));
|
||||
});
|
||||
});
|
||||
|
@ -246,10 +265,10 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
|
|||
child: Text(AppLocalizations.of(context)!.yesLeave),
|
||||
onPressed: () {
|
||||
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
|
||||
var handle = Provider.of<ContactInfoState>(context, listen: false).identifier;
|
||||
var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
|
||||
// locally update cache...
|
||||
Provider.of<ContactInfoState>(context, listen: false).isArchived = true;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteContact(profileOnion, handle);
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactList.removeContact(identifier);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteContact(profileOnion, identifier);
|
||||
Future.delayed(Duration(milliseconds: 500), () {
|
||||
Provider.of<AppState>(context, listen: false).selectedConversation = null;
|
||||
Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
|
||||
|
|
|
@ -50,7 +50,7 @@ class _ContactRowState extends State<ContactRow> {
|
|||
badgeColor: Provider.of<Settings>(context).theme.portraitContactBadgeColor,
|
||||
badgeTextColor: Provider.of<Settings>(context).theme.portraitContactBadgeTextColor,
|
||||
diameter: 64.0,
|
||||
imagePath: contact.imagePath,
|
||||
imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment) ? contact.imagePath : contact.defaultImagePath,
|
||||
maskOut: !contact.isOnline(),
|
||||
border: contact.isOnline()
|
||||
? Provider.of<Settings>(context).theme.portraitOnlineBorderColor
|
||||
|
|
|
@ -152,6 +152,12 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
];
|
||||
} else {
|
||||
var contact = Provider.of<ContactInfoState>(context);
|
||||
ContactInfoState? sender = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||
|
||||
String imagePath = Provider.of<MessageMetadata>(context).senderImage!;
|
||||
if (sender != null) {
|
||||
imagePath = Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment) ? sender.imagePath : sender.defaultImagePath;
|
||||
}
|
||||
Widget wdgPortrait = GestureDetector(
|
||||
onTap: !isGroup
|
||||
? null
|
||||
|
@ -162,7 +168,8 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
padding: EdgeInsets.all(4.0),
|
||||
child: ProfileImage(
|
||||
diameter: 48.0,
|
||||
imagePath: Provider.of<MessageMetadata>(context).senderImage ?? contact.imagePath,
|
||||
// default to the contact image...otherwise use a derived sender image...
|
||||
imagePath: imagePath,
|
||||
border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor : Provider.of<Settings>(context).theme.portraitOfflineBorderColor,
|
||||
badgeTextColor: Colors.red,
|
||||
badgeColor: Colors.red,
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cwtch/themes/opaque.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -6,7 +9,15 @@ 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, this.tooltip = ""});
|
||||
{required this.imagePath,
|
||||
required this.diameter,
|
||||
required this.border,
|
||||
this.badgeCount = 0,
|
||||
required this.badgeColor,
|
||||
required this.badgeTextColor,
|
||||
this.maskOut = false,
|
||||
this.tooltip = "",
|
||||
this.badgeEdit = false});
|
||||
final double diameter;
|
||||
final String imagePath;
|
||||
final Color border;
|
||||
|
@ -14,6 +25,7 @@ class ProfileImage extends StatefulWidget {
|
|||
final Color badgeColor;
|
||||
final Color badgeTextColor;
|
||||
final bool maskOut;
|
||||
final bool badgeEdit;
|
||||
final String tooltip;
|
||||
|
||||
@override
|
||||
|
@ -23,8 +35,11 @@ class ProfileImage extends StatefulWidget {
|
|||
class _ProfileImageState extends State<ProfileImage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var image = Image(
|
||||
image: AssetImage("assets/" + widget.imagePath),
|
||||
var file = new File(widget.imagePath);
|
||||
var image = Image.file(
|
||||
file,
|
||||
cacheWidth: 512,
|
||||
cacheHeight: 512,
|
||||
filterQuality: FilterQuality.medium,
|
||||
// We need some theme specific blending here...we might want to consider making this a theme level attribute
|
||||
colorBlendMode: !widget.maskOut
|
||||
|
@ -36,6 +51,21 @@ class _ProfileImageState extends State<ProfileImage> {
|
|||
isAntiAlias: true,
|
||||
width: widget.diameter,
|
||||
height: widget.diameter,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
// on android the above will fail for asset images, in which case try to load them the original way
|
||||
return Image.asset(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.mode == mode_dark
|
||||
? BlendMode.softLight
|
||||
: BlendMode.darken
|
||||
: BlendMode.srcOut,
|
||||
color: Provider.of<Settings>(context).theme.portraitBackgroundColor,
|
||||
isAntiAlias: true,
|
||||
width: widget.diameter,
|
||||
height: widget.diameter);
|
||||
},
|
||||
);
|
||||
|
||||
return RepaintBoundary(
|
||||
|
@ -50,14 +80,19 @@ class _ProfileImageState extends State<ProfileImage> {
|
|||
padding: const EdgeInsets.all(2.0), //border size
|
||||
child: ClipOval(clipBehavior: Clip.antiAlias, child: widget.tooltip == "" ? image : Tooltip(message: widget.tooltip, child: image))))),
|
||||
Visibility(
|
||||
visible: widget.badgeCount > 0,
|
||||
visible: widget.badgeEdit || widget.badgeCount > 0,
|
||||
child: Positioned(
|
||||
bottom: 0.0,
|
||||
right: 0.0,
|
||||
child: CircleAvatar(
|
||||
radius: 10.0,
|
||||
radius: max(10.0, widget.diameter / 6.0),
|
||||
backgroundColor: widget.badgeColor,
|
||||
child: Text(widget.badgeCount > 99 ? "99+" : widget.badgeCount.toString(), style: TextStyle(color: widget.badgeTextColor, fontSize: 8.0)),
|
||||
child: widget.badgeEdit
|
||||
? Icon(
|
||||
Icons.edit,
|
||||
color: widget.badgeTextColor,
|
||||
)
|
||||
: Text(widget.badgeCount > 99 ? "99+" : widget.badgeCount.toString(), style: TextStyle(color: widget.badgeTextColor, fontSize: 8.0)),
|
||||
),
|
||||
)),
|
||||
]));
|
||||
|
|
|
@ -38,7 +38,7 @@ class _ProfileRowState extends State<ProfileRow> {
|
|||
badgeColor: Provider.of<Settings>(context).theme.portraitProfileBadgeColor,
|
||||
badgeTextColor: Provider.of<Settings>(context).theme.portraitProfileBadgeTextColor,
|
||||
diameter: 64.0,
|
||||
imagePath: profile.imagePath,
|
||||
imagePath: Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment) ? profile.imagePath : profile.defaultImagePath,
|
||||
border: profile.isOnline ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor : Provider.of<Settings>(context).theme.portraitOfflineBorderColor)),
|
||||
Expanded(
|
||||
child: Column(
|
||||
|
@ -105,11 +105,12 @@ class _ProfileRowState extends State<ProfileRow> {
|
|||
void _pushEditProfile({onion: "", displayName: "", profileImage: "", encrypted: true}) {
|
||||
Provider.of<ErrorHandler>(context, listen: false).reset();
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
builder: (BuildContext bcontext) {
|
||||
var profile = Provider.of<FlwtchState>(bcontext).profs.getProfile(onion)!;
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider<ProfileInfoState>(
|
||||
create: (_) => ProfileInfoState(onion: onion, nickname: displayName, imagePath: profileImage, encrypted: encrypted),
|
||||
ChangeNotifierProvider<ProfileInfoState>.value(
|
||||
value: profile,
|
||||
),
|
||||
],
|
||||
builder: (context, widget) => AddEditProfileView(),
|
||||
|
|
|
@ -8,7 +8,8 @@ doNothing(String x) {}
|
|||
// Provides a styled Text Field for use in Form Widgets.
|
||||
// Callers must provide a text controller, label helper text and a validator.
|
||||
class CwtchTextField extends StatefulWidget {
|
||||
CwtchTextField({required this.controller, this.hintText = "", this.validator, this.autofocus = false, this.onChanged = doNothing, this.number = false, this.multiLine = false, this.key, this.testKey});
|
||||
CwtchTextField(
|
||||
{required this.controller, this.hintText = "", this.validator, this.autofocus = false, this.onChanged = doNothing, this.number = false, this.multiLine = false, this.key, this.testKey});
|
||||
final TextEditingController controller;
|
||||
final String hintText;
|
||||
final FormFieldValidator? validator;
|
||||
|
|
Loading…
Reference in New Issue