Merge pull request #68 from Mikunj/ui/profile-icons

Automatically generate profile icons
pull/70/head
sachaaaaa 6 years ago committed by GitHub
commit 5212edcee2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,65 @@
const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
const Identicon = require('identicon.js');
const sha224 = require('js-sha512').sha512_224;
const { app } = require('electron').remote;
const userDataPath = app.getPath('userData');
const PATH = path.join(userDataPath, 'profileImages');
mkdirp.sync(PATH);
const hasImage = pubKey => fs.existsSync(getImagePath(pubKey));
const getImagePath = pubKey => `${PATH}/${pubKey}.png`;
const getOrCreateImagePath = pubKey => {
// If the image doesn't exist then create it
if (!hasImage(pubKey))
return generateImage(pubKey);
return getImagePath(pubKey);
};
const removeImage = pubKey => {
if (hasImage(pubKey)) {
fs.unlinkSync(getImagePath(pubKey));
}
}
const removeImagesNotInArray = pubKeyArray => {
fs.readdirSync(PATH)
// Get all files that end with png
.filter(file => file.includes('.png'))
// Strip the extension
.map(i => path.basename(i, '.png'))
// Get any file that is not in the pubKeyArray
.filter(i => !pubKeyArray.includes(i))
// Remove them
.forEach(i => removeImage(i));
}
const generateImage = pubKey => {
const imagePath = getImagePath(pubKey);
/*
We hash the pubKey and then pass that into Identicon.
This is to avoid getting the same image
if 2 public keys start with the same 15 characters.
*/
const png = new Identicon(sha224(pubKey), {
margin: 0.2,
background: [0,0,0,0],
}).toString();
fs.writeFileSync(imagePath, png, 'base64');
return imagePath
}
module.exports = {
generateImage,
getOrCreateImagePath,
getImagePath,
hasImage,
removeImage,
removeImagesNotInArray,
};

@ -161,7 +161,7 @@
{{ #title }} {{ #title }}
<h4>{{ title }}</h4> <h4>{{ title }}</h4>
{{ /title }} {{ /title }}
<input type='text' name='name' class='name' placeholder='Type a name' autofocus maxlength="25"> <input type='text' name='name' class='name' placeholder='Type a name' autofocus maxlength='25'>
{{ #message }} {{ #message }}
<div class='message'>{{ message }}</div> <div class='message'>{{ message }}</div>
{{ /message }} {{ /message }}
@ -594,6 +594,9 @@
<div class='step-body'> <div class='step-body'>
<div class='header'>Create your Loki Messenger Account</div> <div class='header'>Create your Loki Messenger Account</div>
<input class='form-control' type='text' id='display-name' placeholder='Display Name (optional)' autocomplete='off' spellcheck='false' maxlength='25'>
<hr>
<input class='form-control' type='text' id='mnemonic' placeholder='Mnemonic' autocomplete='off' spellcheck='false'> <input class='form-control' type='text' id='mnemonic' placeholder='Mnemonic' autocomplete='off' spellcheck='false'>
<select id='mnemonic-language'></select> <select id='mnemonic-language'></select>
@ -603,7 +606,7 @@
</div> </div>
<div id='error' class='collapse'></div> <div id='error' class='collapse'></div>
<div id=status></div> <div id=status></div>
<hr>
</div> </div>
<div class='nav'> <div class='nav'>
<a class='button' id='register' data-loading-text='Please wait...'>Register</a> <a class='button' id='register' data-loading-text='Please wait...'>Register</a>

@ -579,27 +579,15 @@
message: window.i18n('editProfileDisplayNameWarning'), message: window.i18n('editProfileDisplayNameWarning'),
nickname: displayName, nickname: displayName,
onOk: async (newName) => { onOk: async (newName) => {
// Update our profiles accordingly' await storage.setProfileName(newName);
const trimmed = newName && newName.trim();
// If we get an empty name then unset the name property
// Otherwise update it
const newProfile = profile || {};
if (_.isEmpty(trimmed)) {
delete newProfile.name;
} else {
newProfile.name = {
displayName: trimmed,
}
}
await storage.saveLocalProfile(newProfile);
appView.inboxView.trigger('updateProfile'); appView.inboxView.trigger('updateProfile');
// Update the conversation if we have it // Update the conversation if we have it
const conversation = ConversationController.get(ourNumber); const conversation = ConversationController.get(ourNumber);
if (conversation) if (conversation) {
const newProfile = storage.getLocalProfile();
conversation.setProfile(newProfile); conversation.setProfile(newProfile);
}
}, },
}) })
} }

@ -192,7 +192,7 @@
return conversation; return conversation;
}; };
conversation.initialPromise = create(); conversation.initialPromise = create().then(() => conversation.updateProfileAvatar());
return conversation; return conversation;
}, },
@ -251,12 +251,19 @@
conversations.add(collection.models); conversations.add(collection.models);
this._initialFetchComplete = true; this._initialFetchComplete = true;
await Promise.all( const promises = [];
conversations.map(conversation => conversation.updateLastMessage()) conversations.forEach(conversation => {
); promises.concat([
conversation.updateLastMessage(),
conversation.updateProfile(),
conversation.updateProfileAvatar(),
]);
});
await Promise.all(promises);
// Remove any unused images
window.profileImages.removeImagesNotInArray(conversations.map(c => c.id));
// Update profiles
conversations.map(conversation => conversation.updateProfile());
window.log.info('ConversationController: done with initial fetch'); window.log.info('ConversationController: done with initial fetch');
} catch (error) { } catch (error) {
window.log.error( window.log.error(

@ -3,7 +3,7 @@
/* global BlockedNumberController: false */ /* global BlockedNumberController: false */
/* global ConversationController: false */ /* global ConversationController: false */
/* global i18n: false */ /* global i18n: false */
/* global libsignal: false */ /* global profileImages: false */
/* global storage: false */ /* global storage: false */
/* global textsecure: false */ /* global textsecure: false */
/* global Whisper: false */ /* global Whisper: false */
@ -174,6 +174,12 @@
deleteAttachmentData, deleteAttachmentData,
} }
); );
profileImages.removeImage(this.id);
},
async updateProfileAvatar() {
const path = profileImages.getOrCreateImagePath(this.id);
await this.setProfileAvatar(path);
}, },
async updateAndMerge(message) { async updateAndMerge(message) {
@ -1730,34 +1736,12 @@
} }
}, },
async setProfileAvatar(avatarPath) { async setProfileAvatar(avatarPath) {
if (!avatarPath) { const profileAvatar = this.get('profileAvatar');
return; if (profileAvatar !== avatarPath) {
} this.set({ profileAvatar: avatarPath });
await window.Signal.Data.updateConversation(this.id, this.attributes, {
const avatar = await textsecure.messaging.getAvatar(avatarPath); Conversation: Whisper.Conversation,
const key = this.get('profileKey'); });
if (!key) {
return;
}
const keyBuffer = window.Signal.Crypto.base64ToArrayBuffer(key);
// decrypt
const decrypted = await textsecure.crypto.decryptProfile(
avatar,
keyBuffer
);
// update the conversation avatar only if hash differs
if (decrypted) {
const newAttributes = await window.Signal.Types.Conversation.maybeUpdateProfileAvatar(
this.attributes,
decrypted,
{
writeNewAttachmentData,
deleteAttachmentData,
}
);
this.set(newAttributes);
} }
}, },
async setProfileKey(profileKey) { async setProfileKey(profileKey) {
@ -1959,8 +1943,9 @@
getAvatarPath() { getAvatarPath() {
const avatar = this.get('avatar') || this.get('profileAvatar'); const avatar = this.get('avatar') || this.get('profileAvatar');
if (avatar && avatar.path) { if (avatar) {
return getAbsoluteAttachmentPath(avatar.path); if (avatar.path) return getAbsoluteAttachmentPath(avatar.path);
return avatar;
} }
return null; return null;
@ -1970,8 +1955,10 @@
const color = this.getColor(); const color = this.getColor();
const avatar = this.get('avatar') || this.get('profileAvatar'); const avatar = this.get('avatar') || this.get('profileAvatar');
if (avatar && avatar.path) { const url = avatar && avatar.path ? getAbsoluteAttachmentPath(avatar.path) : avatar;
return { url: getAbsoluteAttachmentPath(avatar.path), color };
if (url) {
return { url, color };
} else if (this.isPrivate()) { } else if (this.isPrivate()) {
const symbol = this.isValid() ? '#' : '!'; const symbol = this.isValid() ? '#' : '!';
return { return {

@ -16,6 +16,25 @@
return profile; return profile;
} }
storage.setProfileName = async (newName) => {
// Update our profiles accordingly'
const trimmed = newName && newName.trim();
// If we get an empty name then unset the name property
// Otherwise update it
const profile = storage.getLocalProfile();
const newProfile = profile || {};
if (_.isEmpty(trimmed)) {
delete newProfile.name;
} else {
newProfile.name = {
displayName: trimmed,
}
}
await storage.saveLocalProfile(newProfile);
}
storage.saveLocalProfile = async (profile) => { storage.saveLocalProfile = async (profile) => {
const storedProfile = storage.get(PROFILE_ID, null); const storedProfile = storage.get(PROFILE_ID, null);

@ -334,6 +334,7 @@
openConversation(conversation) { openConversation(conversation) {
this.searchView.hideHints(); this.searchView.hideHints();
if (conversation) { if (conversation) {
conversation.updateProfile();
ConversationController.markAsSelected(conversation); ConversationController.markAsSelected(conversation);
this.conversation_stack.open( this.conversation_stack.open(
ConversationController.get(conversation.id) ConversationController.get(conversation.id)

@ -1,4 +1,4 @@
/* global Whisper, $, getAccountManager, textsecure */ /* global Whisper, $, getAccountManager, textsecure, storage, ConversationController */
/* eslint-disable more/no-then */ /* eslint-disable more/no-then */
@ -47,7 +47,8 @@
this.accountManager this.accountManager
.registerSingleDevice( .registerSingleDevice(
this.$('#mnemonic').val(), this.$('#mnemonic').val(),
this.$('#mnemonic-language').val() this.$('#mnemonic-language').val(),
this.$('#display-name').val()
) )
.then(() => { .then(() => {
this.$el.trigger('openInbox'); this.$el.trigger('openInbox');

@ -9,7 +9,7 @@
dcodeIO, dcodeIO,
StringView, StringView,
log, log,
libphonenumber, storage,
Event, Event,
ConversationController ConversationController
*/ */
@ -49,7 +49,7 @@
requestSMSVerification(number) { requestSMSVerification(number) {
// return this.server.requestVerificationSMS(number); // return this.server.requestVerificationSMS(number);
}, },
registerSingleDevice(mnemonic, mnemonicLanguage) { registerSingleDevice(mnemonic, mnemonicLanguage, profileName) {
const createAccount = this.createAccount.bind(this); const createAccount = this.createAccount.bind(this);
const clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.bind(this); const clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.bind(this);
const generateKeys = this.generateKeys.bind(this, 0); const generateKeys = this.generateKeys.bind(this, 0);
@ -77,7 +77,7 @@
.then(confirmKeys) .then(confirmKeys)
.then(() => { .then(() => {
const pubKeyString = StringView.arrayBufferToHex(identityKeyPair.pubKey); const pubKeyString = StringView.arrayBufferToHex(identityKeyPair.pubKey);
registrationDone(pubKeyString) registrationDone(pubKeyString, profileName);
}); });
} }
) )
@ -446,11 +446,17 @@
); );
}); });
}, },
async registrationDone(number) { async registrationDone(number, profileName) {
window.log.info('registration done'); window.log.info('registration done');
// Ensure that we always have a conversation for ourself // Ensure that we always have a conversation for ourself
await ConversationController.getOrCreateAndWait(number, 'private'); const conversation = await ConversationController.getOrCreateAndWait(number, 'private');
await storage.setProfileName(profileName);
// Update the conversation if we have it
const newProfile = storage.getLocalProfile();
await conversation.setProfile(newProfile);
this.dispatchEvent(new Event('registration')); this.dispatchEvent(new Event('registration'));
}, },

@ -69,6 +69,7 @@
"glob": "^7.1.2", "glob": "^7.1.2",
"google-libphonenumber": "^3.0.7", "google-libphonenumber": "^3.0.7",
"got": "^8.2.0", "got": "^8.2.0",
"identicon.js": "^2.3.3",
"intl-tel-input": "^12.1.15", "intl-tel-input": "^12.1.15",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"js-sha512": "^0.8.0", "js-sha512": "^0.8.0",
@ -118,7 +119,7 @@
"bower": "^1.8.2", "bower": "^1.8.2",
"chai": "^4.1.2", "chai": "^4.1.2",
"electron": "2.0.8", "electron": "2.0.8",
"electron-builder": "^20.13.5", "electron-builder": "20.13.5",
"electron-icon-maker": "0.0.3", "electron-icon-maker": "0.0.3",
"eslint": "^4.14.0", "eslint": "^4.14.0",
"eslint-config-airbnb-base": "^12.1.0", "eslint-config-airbnb-base": "^12.1.0",

@ -271,6 +271,7 @@ window.libphonenumber = require('google-libphonenumber').PhoneNumberUtil.getInst
window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').PhoneNumberFormat; window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').PhoneNumberFormat;
window.loadImage = require('blueimp-load-image'); window.loadImage = require('blueimp-load-image');
window.getGuid = require('uuid/v4'); window.getGuid = require('uuid/v4');
window.profileImages = require('./app/profile_images');
window.React = require('react'); window.React = require('react');
window.ReactDOM = require('react-dom'); window.ReactDOM = require('react-dom');

@ -647,6 +647,10 @@ textarea {
max-width: 35em; max-width: 35em;
} }
#display-name {
margin-bottom: 12px;
}
.inner { .inner {
display: flex; display: flex;
align-items: center; align-items: center;

@ -1835,6 +1835,10 @@
&:hover { &:hover {
background-color: $color-dark-70; background-color: $color-dark-70;
} }
.module-avatar {
background-color: $color-dark-85;
}
} }
.module-conversation-list-item--has-unread { .module-conversation-list-item--has-unread {

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save