QR Code responds to theme changes

pull/714/head
Vincent 5 years ago
parent 7b6e28eaab
commit 2d01275ede

@ -2021,7 +2021,7 @@
},
"copyPublicKey": {
"message": "Copy public key",
"message": "Copy Public Key",
"description":
"Button action that the user can click to copy their public keys"
},
@ -2086,7 +2086,7 @@
"A toast message telling the user that the message text was copied"
},
"editProfile": {
"message": "Edit profile",
"message": "Edit Profile",
"description": "Button action that the user can click to edit their profile"
},
@ -2131,11 +2131,11 @@
"description": "Description given to QRCode modal"
},
"showQRCode": {
"message": "Show QR code",
"message": "Show QR Code",
"description": "Button action that the user can click to view their QR code"
},
"showAddServer": {
"message": "Add public server",
"message": "Add Public Server",
"description":
"Button action that the user can click to connect to a new public server"
},

@ -67,6 +67,15 @@ const {
const {
SessionSeedModal,
} = require('../../ts/components/session/SessionSeedModal');
const {
SessionPasswordChangeModal,
} = require('../../ts/components/session/SessionPasswordChangeModal');
const {
SessionPasswordRemoveModal,
} = require('../../ts/components/session/SessionPasswordRemoveModal');
const {
SessionConfirm,
} = require('../../ts/components/session/SessionConfirm');
@ -281,6 +290,8 @@ exports.setup = (options = {}) => {
SessionModal,
SessionQRModal,
SessionSeedModal,
SessionPasswordChangeModal,
SessionPasswordRemoveModal,
SessionDropdown,
MediaGallery,
Message,

@ -196,8 +196,8 @@
this.el.append(dialog.el);
dialog.focusInput();
},
showPasswordDialog({ type, resolve, reject }) {
const dialog = Whisper.getPasswordDialogView(type, resolve, reject);
showPasswordDialog() {
const dialog = Whisper.PasswordDialogView();
this.el.append(dialog.el);
},
showSeedDialog(seed) {

@ -1,4 +1,4 @@
/* global Whisper, i18n, _, Signal, passwordUtil */
/* global Whisper */
// eslint-disable-next-line func-names
(function() {
@ -6,223 +6,28 @@
window.Whisper = window.Whisper || {};
const PasswordDialogView = Whisper.View.extend({
Whisper.PasswordDialogView = Whisper.View.extend({
className: 'loki-dialog password-dialog modal',
templateName: 'password-dialog',
initialize(options) {
this.type = options.type;
this.resolve = options.resolve;
this.okText = options.okText || i18n('ok');
this.reject = options.reject;
this.cancelText = options.cancelText || i18n('cancel');
this.title = options.title;
initialize() {
this.close = this.close.bind(this);
this.render();
this.updateUI();
},
events: {
keyup: 'onKeyup',
'click .ok': 'ok',
'click .cancel': 'cancel',
},
render_attributes() {
return {
showCancel: !this.hideCancel,
cancel: this.cancelText,
ok: this.okText,
title: this.title,
};
},
async updateUI() {
if (this.disableOkButton()) {
this.$('.ok').prop('disabled', true);
} else {
this.$('.ok').prop('disabled', false);
}
},
disableOkButton() {
const password = this.$('#password').val();
return _.isEmpty(password);
},
async validate() {
const password = this.$('#password').val();
const passwordConfirmation = this.$('#password-confirmation').val();
const pairValidation = this.validatePasswordPair(
password,
passwordConfirmation
);
const hashValidation = await this.validatePasswordHash(password);
return pairValidation || hashValidation;
},
async validatePasswordHash(password) {
// Check if the password matches the hash we have stored
const hash = await Signal.Data.getPasswordHash();
if (hash && !passwordUtil.matchesHash(password, hash)) {
return i18n('invalidPassword');
}
return null;
},
validatePasswordPair(password, passwordConfirmation) {
if (!_.isEmpty(password)) {
// Check if the password is first valid
const passwordValidation = passwordUtil.validatePassword(
password,
i18n
);
if (passwordValidation) {
return passwordValidation;
}
render() {
this.dialogView = new Whisper.ReactWrapperView({
className: 'password-dialog-wrapper',
Component: window.Signal.Components.SessionPasswordChangeModal,
props: {
onClose: this.close,
},
});
// Check if the confirmation password is the same
if (
!passwordConfirmation ||
password.trim() !== passwordConfirmation.trim()
) {
return i18n('passwordsDoNotMatch');
}
}
return null;
},
okPressed() {
const password = this.$('#password').val();
if (this.type === 'set') {
window.setPassword(password.trim());
} else if (this.type === 'remove') {
window.setPassword(null, password.trim());
}
this.$el.append(this.dialogView.el);
return this;
},
okErrored() {
if (this.type === 'set') {
this.showError(i18n('setPasswordFail'));
} else if (this.type === 'remove') {
this.showError(i18n('removePasswordFail'));
}
},
async ok() {
const error = await this.validate();
if (error) {
this.showError(error);
return;
}
// Clear any errors
this.showError(null);
try {
this.okPressed();
this.remove();
if (this.resolve) {
this.resolve();
}
} catch (e) {
this.okErrored();
}
},
cancel() {
close() {
this.remove();
if (this.reject) {
this.reject();
}
},
onKeyup(event) {
this.updateUI();
switch (event.key) {
case 'Enter':
this.ok();
break;
case 'Escape':
case 'Esc':
this.cancel();
break;
default:
return;
}
event.preventDefault();
},
focusCancel() {
this.$('.cancel').focus();
},
showError(message) {
if (_.isEmpty(message)) {
this.$('.error').text('');
this.$('.error').hide();
} else {
this.$('.error').text(`Error: ${message}`);
this.$('.error').show();
}
},
});
const ChangePasswordDialogView = PasswordDialogView.extend({
templateName: 'password-change-dialog',
disableOkButton() {
const oldPassword = this.$('#old-password').val();
const newPassword = this.$('#new-password').val();
return _.isEmpty(oldPassword) || _.isEmpty(newPassword);
},
async validate() {
const oldPassword = this.$('#old-password').val();
// Validate the old password
if (!_.isEmpty(oldPassword)) {
const oldPasswordValidation = passwordUtil.validatePassword(
oldPassword,
i18n
);
if (oldPasswordValidation) {
return oldPasswordValidation;
}
} else {
return i18n('typeInOldPassword');
}
const password = this.$('#new-password').val();
const passwordConfirmation = this.$('#new-password-confirmation').val();
const pairValidation = this.validatePasswordPair(
password,
passwordConfirmation
);
const hashValidation = await this.validatePasswordHash(oldPassword);
return pairValidation || hashValidation;
},
okPressed() {
const oldPassword = this.$('#old-password').val();
const newPassword = this.$('#new-password').val();
window.setPassword(newPassword.trim(), oldPassword.trim());
},
okErrored() {
this.showError(i18n('changePasswordFail'));
},
});
Whisper.getPasswordDialogView = (type, resolve, reject) => {
// This is a differently styled dialog
if (type === 'change') {
return new ChangePasswordDialogView({
title: i18n('changePassword'),
okTitle: i18n('change'),
resolve,
reject,
});
}
// Set and Remove is basically the same UI
const title =
type === 'remove' ? i18n('removePassword') : i18n('setPassword');
const okTitle = type === 'remove' ? i18n('remove') : i18n('set');
return new PasswordDialogView({
title,
okTitle,
type,
resolve,
reject,
});
};
})();
})();

@ -0,0 +1,228 @@
/* global Whisper, i18n, _, Signal, passwordUtil */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
const PasswordDialogView = Whisper.View.extend({
className: 'loki-dialog password-dialog modal',
templateName: 'password-dialog',
initialize(options) {
this.type = options.type;
this.resolve = options.resolve;
this.okText = options.okText || i18n('ok');
this.reject = options.reject;
this.cancelText = options.cancelText || i18n('cancel');
this.title = options.title;
this.render();
this.updateUI();
},
events: {
keyup: 'onKeyup',
'click .ok': 'ok',
'click .cancel': 'cancel',
},
render_attributes() {
return {
showCancel: !this.hideCancel,
cancel: this.cancelText,
ok: this.okText,
title: this.title,
};
},
async updateUI() {
if (this.disableOkButton()) {
this.$('.ok').prop('disabled', true);
} else {
this.$('.ok').prop('disabled', false);
}
},
disableOkButton() {
const password = this.$('#password').val();
return _.isEmpty(password);
},
async validate() {
const password = this.$('#password').val();
const passwordConfirmation = this.$('#password-confirmation').val();
const pairValidation = this.validatePasswordPair(
password,
passwordConfirmation
);
const hashValidation = await this.validatePasswordHash(password);
return pairValidation || hashValidation;
},
async validatePasswordHash(password) {
// Check if the password matches the hash we have stored
const hash = await Signal.Data.getPasswordHash();
if (hash && !passwordUtil.matchesHash(password, hash)) {
return i18n('invalidPassword');
}
return null;
},
validatePasswordPair(password, passwordConfirmation) {
if (!_.isEmpty(password)) {
// Check if the password is first valid
const passwordValidation = passwordUtil.validatePassword(
password,
i18n
);
if (passwordValidation) {
return passwordValidation;
}
// Check if the confirmation password is the same
if (
!passwordConfirmation ||
password.trim() !== passwordConfirmation.trim()
) {
return i18n('passwordsDoNotMatch');
}
}
return null;
},
okPressed() {
const password = this.$('#password').val();
if (this.type === 'set') {
window.setPassword(password.trim());
} else if (this.type === 'remove') {
window.setPassword(null, password.trim());
}
},
okErrored() {
if (this.type === 'set') {
this.showError(i18n('setPasswordFail'));
} else if (this.type === 'remove') {
this.showError(i18n('removePasswordFail'));
}
},
async ok() {
const error = await this.validate();
if (error) {
this.showError(error);
return;
}
// Clear any errors
this.showError(null);
try {
this.okPressed();
this.remove();
if (this.resolve) {
this.resolve();
}
} catch (e) {
this.okErrored();
}
},
cancel() {
this.remove();
if (this.reject) {
this.reject();
}
},
onKeyup(event) {
this.updateUI();
switch (event.key) {
case 'Enter':
this.ok();
break;
case 'Escape':
case 'Esc':
this.cancel();
break;
default:
return;
}
event.preventDefault();
},
focusCancel() {
this.$('.cancel').focus();
},
showError(message) {
if (_.isEmpty(message)) {
this.$('.error').text('');
this.$('.error').hide();
} else {
this.$('.error').text(`Error: ${message}`);
this.$('.error').show();
}
},
});
const ChangePasswordDialogView = PasswordDialogView.extend({
templateName: 'password-change-dialog',
disableOkButton() {
const oldPassword = this.$('#old-password').val();
const newPassword = this.$('#new-password').val();
return _.isEmpty(oldPassword) || _.isEmpty(newPassword);
},
async validate() {
const oldPassword = this.$('#old-password').val();
// Validate the old password
if (!_.isEmpty(oldPassword)) {
const oldPasswordValidation = passwordUtil.validatePassword(
oldPassword,
i18n
);
if (oldPasswordValidation) {
return oldPasswordValidation;
}
} else {
return i18n('typeInOldPassword');
}
const password = this.$('#new-password').val();
const passwordConfirmation = this.$('#new-password-confirmation').val();
const pairValidation = this.validatePasswordPair(
password,
passwordConfirmation
);
const hashValidation = await this.validatePasswordHash(oldPassword);
return pairValidation || hashValidation;
},
okPressed() {
const oldPassword = this.$('#old-password').val();
const newPassword = this.$('#new-password').val();
window.setPassword(newPassword.trim(), oldPassword.trim());
},
okErrored() {
this.showError(i18n('changePasswordFail'));
},
});
Whisper.getPasswordDialogView = (type, resolve, reject) => {
// This is a differently styled dialog
if (type === 'change') {
return new ChangePasswordDialogView({
title: i18n('changePassword'),
okTitle: i18n('change'),
resolve,
reject,
});
}
// Set and Remove is basically the same UI
const title =
type === 'remove' ? i18n('removePassword') : i18n('setPassword');
const okTitle = type === 'remove' ? i18n('remove') : i18n('set');
return new PasswordDialogView({
title,
okTitle,
type,
resolve,
reject,
});
};
})();

@ -106,7 +106,7 @@ div.spacer-lg {
}
@mixin text-highlight($color) {
background-color: rgba($color, 0.8);
background-color: rgba($color, 0.4);
padding: $session-margin-xs;
border-radius: 3px;
display: inline-block;
@ -474,6 +474,12 @@ label {
}
}
.user-details-dialog {
.message {
word-break: break-all;
}
}
#session-toast-container {
position: fixed;
right: $session-margin-lg;
@ -819,7 +825,6 @@ label {
#qr svg {
width: $session-modal-size-sm;
height: $session-modal-size-sm;
border: 6px solid white;
border-radius: 3px;
}

@ -36,7 +36,9 @@ export class AddServerDialog extends React.Component<Props, State> {
this.attemptConnection = this.attemptConnection.bind(this);
this.closeDialog = this.closeDialog.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
this.onEnter = this.onEnter.bind(this);
window.addEventListener('keyup', this.onEnter);
}
public render() {
@ -191,23 +193,16 @@ export class AddServerDialog extends React.Component<Props, State> {
);
}
private onKeyUp(event: any) {
switch (event.key) {
case 'Enter':
if (this.state.view === 'default') {
this.showView('connecting');
}
break;
case 'Esc':
case 'Escape':
this.closeDialog();
break;
default:
private onEnter(event: any) {
if (event.key === 'Enter') {
if ($('#server-url').is(':focus')) {
this.showView('connecting');
}
}
}
private closeDialog() {
window.removeEventListener('keyup', this.onKeyUp);
window.removeEventListener('keyup', this.onEnter);
this.props.onClose();
}

@ -59,6 +59,14 @@ export class DevicePairingDialog extends React.Component<Props, State> {
const waitingForRequest = this.state.view === 'waitingForRequest';
const nothingPaired = this.state.data.length === 0;
const theme = window.Events.getThemeSetting();
// Foreground equivalent to .session-modal background color
const bgColor = 'rgba(0, 0, 0, 0)';
const fgColor = theme === 'dark'
? '#FFFFFF'
: '#1B1B1B';
// const renderPairedDevices = this.state.data.map((pubKey: any) => {
// const pubKeyInfo = this.getPubkeyName(pubKey);
// const isFinalItem =
@ -96,8 +104,8 @@ export class DevicePairingDialog extends React.Component<Props, State> {
<div id="qr">
<QRCode
value={window.textsecure.storage.user.getNumber()}
bgColor="#FFFFFF"
fgColor="#000000"
bgColor={bgColor}
fgColor={fgColor}
level="L"
/>
</div>

@ -13,10 +13,17 @@ export class SessionQRModal extends React.Component<Props> {
constructor(props: any) {
super(props);
}
public render() {
const { value, onClose } = this.props;
const theme = window.Events.getThemeSetting();
// Foreground equivalent to .session-modal background color
const bgColor = 'rgba(0, 0, 0, 0)';
const fgColor = theme === 'dark'
? '#FFFFFF'
: '#1B1B1B';
return (
<SessionModal
title={window.i18n('QRCodeTitle')}
@ -31,7 +38,7 @@ export class SessionQRModal extends React.Component<Props> {
<div className="spacer-lg" />
<div id="qr">
<QRCode value={value} bgColor="#FFFFFF" fgColor="#000000" level="L" />
<QRCode value={value} bgColor={bgColor} fgColor={fgColor} level="L" />
</div>
<div className="spacer-lg" />

1
ts/global.d.ts vendored

@ -1,4 +1,5 @@
interface Window {
Events: any;
getAccountManager: any;
mnemonic: any;
clipboard: any;

Loading…
Cancel
Save