From 03c14c84cbb85780ca7504aff30ec3d3ed2b2b84 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Mon, 2 Aug 2021 14:36:19 +1000 Subject: [PATCH 01/19] added styled component and conditional button for sending pausing recording. --- ts/components/dialog/ModeratorsAddDialog.tsx | 1 - .../session/conversation/SessionRecording.tsx | 58 +++++++++++++++---- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/ts/components/dialog/ModeratorsAddDialog.tsx b/ts/components/dialog/ModeratorsAddDialog.tsx index 07d6aac5b..9a3e209f8 100644 --- a/ts/components/dialog/ModeratorsAddDialog.tsx +++ b/ts/components/dialog/ModeratorsAddDialog.tsx @@ -2,7 +2,6 @@ import React, { useState } from 'react'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton'; import { PubKey } from '../../session/types'; import { ToastUtils } from '../../session/utils'; -import { useTheme } from 'styled-components'; import { SessionSpinner } from '../session/SessionSpinner'; import { Flex } from '../basic/Flex'; import { ApiV2 } from '../../opengroup/opengroupV2'; diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index 0efc3161c..a38b31626 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -8,6 +8,7 @@ import { Constants } from '../../../session'; import { ToastUtils } from '../../../session/utils'; import autoBind from 'auto-bind'; import MicRecorder from 'mic-recorder-to-mp3'; +import styled, { useTheme } from 'styled-components'; interface Props { onExitVoiceNoteView: any; @@ -33,6 +34,23 @@ function getTimestamp(asInt = false) { return asInt ? Math.floor(timestamp) : timestamp; } +export interface StyledFlexWrapperProps { + flexDirection: 'row' | 'column'; + marginHorizontal: string; +} + +/** + * Generic wrapper for quickly passing in theme constant values. + */ +export const StyledFlexWrapper = styled.div` + display: flex; + flex-direction: ${(props: StyledFlexWrapperProps) => props.flexDirection}; + + .session-button { + margin: ${(props: StyledFlexWrapperProps) => props.marginHorizontal}; + } +`; + class SessionRecordingInner extends React.Component { private recorder: any; private audioBlobMp3?: Blob; @@ -98,13 +116,14 @@ class SessionRecordingInner extends React.Component { const displayTimeMs = isRecording ? (nowTimestamp - startTimestamp) * 1000 : (this.audioElement && - (this.audioElement?.currentTime * 1000 || this.audioElement?.duration)) || - 0; + (this.audioElement?.currentTime * 1000 || this.audioElement?.duration)) || + 0; const displayTimeString = moment.utc(displayTimeMs).format('m:ss'); - const actionPauseFn = isPlaying ? this.pauseAudio : this.stopRecordingStream; + const themeToUse = useTheme(); + return (
{
{isRecording ? ( ) : ( - + + + + )}
@@ -271,6 +303,9 @@ class SessionRecordingInner extends React.Component { this.props.onExitVoiceNoteView(); } + /** + * Sends the recorded voice message + */ private async onSendVoiceMessage() { if (!this.audioBlobMp3 || !this.audioBlobMp3.size) { window?.log?.info('Empty audio blob'); @@ -305,6 +340,9 @@ class SessionRecordingInner extends React.Component { }); } + /** + * Stops recording audio, sets recording state to stopped. + */ private async stopRecordingStream() { if (!this.recorder) { return; From 637b604d0baa6ac3e3a145f584b3743b2aceab76 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Tue, 3 Aug 2021 15:45:44 +1000 Subject: [PATCH 02/19] Adding fraction timer display, exaggerating recording animation, moving delete button to toolbar, display full recording duration immediately after stop recording. --- stylesheets/_session_constants.scss | 39 ++++++ stylesheets/_session_conversation.scss | 18 ++- .../session/conversation/SessionRecording.tsx | 126 +++++++++++------- 3 files changed, 134 insertions(+), 49 deletions(-) diff --git a/stylesheets/_session_constants.scss b/stylesheets/_session_constants.scss index 364edc36e..2bae44ea0 100644 --- a/stylesheets/_session_constants.scss +++ b/stylesheets/_session_constants.scss @@ -153,6 +153,28 @@ $session-font-h4: 16px; color: subtle($color); } +@mixin pulse-color($color) { + + animation: pulseColor 2s infinite; + + @keyframes pulseColor { + 0% { + transform: scale(0.95); + box-shadow: 0 0 0 0 rgba($color, 0.7); + } + + 70% { + transform: scale(1); + box-shadow: 0 0 0 10px rgba($color, 0); + } + + 100% { + transform: scale(0.95); + box-shadow: 0 0 0 0 rgba($color, 0); + } + } +} + $session-subtle-factor: 0.6; @function subtle($color) { @@ -195,6 +217,23 @@ $session-fadein-duration: 0.1s; } } +@keyframes pulse-shadow { + 0% { + transform: scale(0.95); + box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7); + } + + 70% { + transform: scale(1); + box-shadow: 0 0 0 10px rgba(0, 0, 0, 0); + } + + 100% { + transform: scale(0.95); + box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); + } +} + // ////////////////////////////////////////////// // ///////////////// Various //////////////////// // ////////////////////////////////////////////// diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss index 75011e6c3..7119ea5f8 100644 --- a/stylesheets/_session_conversation.scss +++ b/stylesheets/_session_conversation.scss @@ -329,6 +329,10 @@ } } +.send-message-button { + animation: fadein $session-transition-duration; +} + .session-recording { height: $composition-container-height; display: flex; @@ -342,11 +346,14 @@ &--actions { display: flex; align-items: center; - justify-content: center; width: $actions-element-size; height: $actions-element-size; border-radius: 50%; + .session-button { + animation: fadein $session-transition-duration; + } + .session-icon-button { animation: fadein $session-transition-duration; opacity: 1; @@ -415,6 +422,11 @@ &.playback-timer { margin-right: $session-margin-sm; + animation: fadein $session-transition-duration; + + @media (-webkit-min-device-pixel-ratio: 1.6) { + margin-left: auto; + } } &-light { @@ -422,9 +434,9 @@ width: $session-margin-sm; border-radius: 50%; background-color: $session-color-danger-alt; - margin-left: $session-margin-sm; + margin: 0 $session-margin-sm; - animation: pulseLight 4s infinite; + @include pulse-color($session-color-danger-alt); } } } diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index a38b31626..0392b35de 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -45,6 +45,7 @@ export interface StyledFlexWrapperProps { export const StyledFlexWrapper = styled.div` display: flex; flex-direction: ${(props: StyledFlexWrapperProps) => props.flexDirection}; + align-items: center; .session-button { margin: ${(props: StyledFlexWrapperProps) => props.marginHorizontal}; @@ -104,10 +105,11 @@ class SessionRecordingInner extends React.Component { nowTimestamp, } = this.state; - const actionStopRecording = actionHover && isRecording; - const actionPlayAudio = !isRecording && !isPlaying; + const hasRecordingAndPaused = !isRecording && !isPlaying; const actionPauseAudio = !isRecording && !isPaused && isPlaying; - const actionDefault = !actionStopRecording && !actionPlayAudio && !actionPauseAudio; + + // const actionDefault = !actionStopRecording && !actionPlayAudio && !actionPauseAudio; + const actionDefault = !isRecording && !hasRecordingAndPaused && !actionPauseAudio; // if we are recording, we base the time recording on our state values // if we are playing ( audioElement?.currentTime is !== 0, use that instead) @@ -118,11 +120,19 @@ class SessionRecordingInner extends React.Component { : (this.audioElement && (this.audioElement?.currentTime * 1000 || this.audioElement?.duration)) || 0; + let displayTimeString = moment.utc(displayTimeMs).format('m:ss'); - const displayTimeString = moment.utc(displayTimeMs).format('m:ss'); - const actionPauseFn = isPlaying ? this.pauseAudio : this.stopRecordingStream; + const recordingLengthMs = this.audioElement?.duration ? this.audioElement?.duration * 1000 : undefined; - const themeToUse = useTheme(); + let remainingTimeString; + if (recordingLengthMs !== undefined) { + console.log({ recordingLengthMs }); + console.log({ displayTimeMs }); + remainingTimeString = ` / ${moment.utc(recordingLengthMs).format('m:ss')}`; + displayTimeString += remainingTimeString; + } + + const actionPauseFn = isPlaying ? this.pauseAudio : this.stopRecordingStream; return (
@@ -131,7 +141,7 @@ class SessionRecordingInner extends React.Component { onMouseEnter={this.handleHoverActions} onMouseLeave={this.handleUnhoverActions} > - {actionStopRecording && ( + {isRecording && ( { onClick={actionPauseFn} /> )} - {actionPlayAudio && ( - + {hasRecordingAndPaused && ( + + + + )} {actionDefault && ( @@ -162,10 +183,14 @@ class SessionRecordingInner extends React.Component { )}
-
- {displayTimeString} - {isRecording &&
} -
+ {isRecording || (!isRecording && remainingTimeString) ? +
+ {displayTimeString} + {isRecording &&
} +
+ : + null + } {!isRecording && (
@@ -178,35 +203,6 @@ class SessionRecordingInner extends React.Component {
)} -
- {isRecording ? ( - - ) : ( - - - - - )} -
); } @@ -351,10 +347,48 @@ class SessionRecordingInner extends React.Component { this.recorder = undefined; this.audioBlobMp3 = blob; + this.updateAudioElementAndDuration(); + // Stop recording this.stopRecordingState(); } + private async onStopRecordingClick() { + this.stopRecordingStream(); + this.updateAudioElementAndDuration(); + this.stopRecordingState(); + } + + + + /** + * Creates an audio element using the recorded audio blob. + * Updates the duration for displaying audio duration. + */ + private updateAudioElementAndDuration() { + // init audio element + const audioURL = window.URL.createObjectURL(this.audioBlobMp3); + this.audioElement = new Audio(audioURL); + + // ww adding record duration + this.setState({ + recordDuration: this.audioElement.duration + }) + + this.audioElement.loop = false; + this.audioElement.onended = () => { + this.pauseAudio(); + }; + + this.audioElement.oncanplaythrough = async () => { + const duration = this.state.recordDuration; + + if (duration && this.audioElement && this.audioElement.currentTime < duration) { + await this.audioElement?.play(); + } + }; + } + private async onKeyDown(event: any) { if (event.key === 'Escape') { await this.onDeleteVoiceMessage(); From cd7a1233fa37e3e89eb4ee2761784b1b644a8c22 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Tue, 3 Aug 2021 16:18:38 +1000 Subject: [PATCH 03/19] Adding small hover effect to message composition icon buttons. Removing extra method call. --- stylesheets/_session_constants.scss | 21 ++----------------- stylesheets/_session_conversation.scss | 10 +++++++-- .../session/conversation/SessionRecording.tsx | 9 ++++---- 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/stylesheets/_session_constants.scss b/stylesheets/_session_constants.scss index 2bae44ea0..f4c199274 100644 --- a/stylesheets/_session_constants.scss +++ b/stylesheets/_session_constants.scss @@ -153,9 +153,9 @@ $session-font-h4: 16px; color: subtle($color); } -@mixin pulse-color($color) { +@mixin pulse-color($color, $repetition) { - animation: pulseColor 2s infinite; + animation: pulseColor 2s $repetition; @keyframes pulseColor { 0% { @@ -217,23 +217,6 @@ $session-fadein-duration: 0.1s; } } -@keyframes pulse-shadow { - 0% { - transform: scale(0.95); - box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7); - } - - 70% { - transform: scale(1); - box-shadow: 0 0 0 10px rgba(0, 0, 0, 0); - } - - 100% { - transform: scale(0.95); - box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); - } -} - // ////////////////////////////////////////////// // ///////////////// Various //////////////////// // ////////////////////////////////////////////// diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss index 7119ea5f8..4435bd6dc 100644 --- a/stylesheets/_session_conversation.scss +++ b/stylesheets/_session_conversation.scss @@ -187,7 +187,13 @@ display: flex; justify-content: center; align-items: center; - opacity: 0.8; + opacity: 0.7; + + &:hover { + opacity: 1; + transform: scale(0.95); + transition: $session-transition-duration; + } .send { padding: $session-margin-xs; @@ -436,7 +442,7 @@ background-color: $session-color-danger-alt; margin: 0 $session-margin-sm; - @include pulse-color($session-color-danger-alt); + @include pulse-color($session-color-danger-alt, infinite); } } } diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index 0392b35de..4e288337c 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -132,7 +132,7 @@ class SessionRecordingInner extends React.Component { displayTimeString += remainingTimeString; } - const actionPauseFn = isPlaying ? this.pauseAudio : this.stopRecordingStream; + const actionPauseFn = isPlaying ? this.pauseAudio : this.onStopRecordingClick; return (
@@ -347,20 +347,19 @@ class SessionRecordingInner extends React.Component { this.recorder = undefined; this.audioBlobMp3 = blob; - this.updateAudioElementAndDuration(); // Stop recording this.stopRecordingState(); } + /** + * Stops recording and sets up audio element, updates recording state. + */ private async onStopRecordingClick() { this.stopRecordingStream(); this.updateAudioElementAndDuration(); - this.stopRecordingState(); } - - /** * Creates an audio element using the recorded audio blob. * Updates the duration for displaying audio duration. From 9eb1847da52f655172bf91179ca974e8f01c3a9d Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Tue, 3 Aug 2021 17:07:44 +1000 Subject: [PATCH 04/19] WIP removing NaN dsiaply timer text. --- stylesheets/_session_conversation.scss | 3 +- .../session/conversation/SessionRecording.tsx | 38 ++++++++++--------- ts/components/session/icon/Icons.tsx | 21 ++++++++++ 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss index 4435bd6dc..8a8e97b37 100644 --- a/stylesheets/_session_conversation.scss +++ b/stylesheets/_session_conversation.scss @@ -180,7 +180,8 @@ border-top: themed('sessionBorder'); } - & > .session-icon-button { + .session-icon-button { + // & > .session-icon-button { margin-right: $session-margin-sm; } .session-icon-button { diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index 4e288337c..ef1d439b2 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -106,6 +106,7 @@ class SessionRecordingInner extends React.Component { } = this.state; const hasRecordingAndPaused = !isRecording && !isPlaying; + const hasRecording = this.audioElement?.duration && this.audioElement?.duration > 0; const actionPauseAudio = !isRecording && !isPaused && isPlaying; // const actionDefault = !actionStopRecording && !actionPlayAudio && !actionPauseAudio; @@ -122,17 +123,16 @@ class SessionRecordingInner extends React.Component { 0; let displayTimeString = moment.utc(displayTimeMs).format('m:ss'); - const recordingLengthMs = this.audioElement?.duration ? this.audioElement?.duration * 1000 : undefined; + const recordingDurationMs = this.audioElement?.duration ? this.audioElement?.duration * 1000 : undefined; let remainingTimeString; - if (recordingLengthMs !== undefined) { - console.log({ recordingLengthMs }); - console.log({ displayTimeMs }); - remainingTimeString = ` / ${moment.utc(recordingLengthMs).format('m:ss')}`; + if (recordingDurationMs !== undefined) { + remainingTimeString = ` / ${moment.utc(recordingDurationMs).format('m:ss')}`; displayTimeString += remainingTimeString; } - const actionPauseFn = isPlaying ? this.pauseAudio : this.onStopRecordingClick; + const actionPauseFn = isPlaying ? this.pauseAudio : this.stopRecordingStream; + // const actionPauseFn = isPlaying ? this.pauseAudio : this.onStopRecordingClick; return (
@@ -156,24 +156,25 @@ class SessionRecordingInner extends React.Component { onClick={actionPauseFn} /> )} - {hasRecordingAndPaused && ( - + + {hasRecordingAndPaused && ( - - - )} + )} + {actionDefault && ( { )}
- {isRecording || (!isRecording && remainingTimeString) ? + {isRecording || (!isRecording && hasRecording) ?
{displayTimeString} {isRecording &&
} @@ -347,6 +348,7 @@ class SessionRecordingInner extends React.Component { this.recorder = undefined; this.audioBlobMp3 = blob; + this.updateAudioElementAndDuration(); // Stop recording this.stopRecordingState(); diff --git a/ts/components/session/icon/Icons.tsx b/ts/components/session/icon/Icons.tsx index 4c4967d54..fc18eff79 100644 --- a/ts/components/session/icon/Icons.tsx +++ b/ts/components/session/icon/Icons.tsx @@ -12,6 +12,9 @@ export enum SessionIconType { CirclePlus = 'circlePlus', CircleElipses = 'circleElipses', Contacts = 'contacts', + Delete = 'delete', + Delete2 = 'delete2', + Delete3 = 'delete3', Ellipses = 'ellipses', Emoji = 'emoji', Error = 'error', @@ -126,6 +129,24 @@ export const icons = { viewBox: '4 4 7 7', ratio: 1, }, + [SessionIconType.Delete]: { + path: + 'M1.63 97.99l36.55-36.55L1.63 24.89c-2.17-2.17-2.17-5.73 0-7.9L16.99 1.63c2.17-2.17 5.73-2.17 7.9 0l36.55 36.55L97.99 1.63c2.17-2.17 5.73-2.17 7.9 0l15.36 15.36c2.17 2.17 2.17 5.73 0 7.9L84.7 61.44l36.55 36.55c2.17 2.17 2.17 5.73 0 7.9l-15.36 15.36c-2.17 2.17-5.73 2.17-7.9 0L61.44 84.7l-36.55 36.55c-2.17 2.17-5.73 2.17-7.9 0L1.63 105.89c-2.17-2.17-2.17-5.73 0-7.9z', + viewBox: '0 0 122.88 122.88', + ratio: 1, + }, + [SessionIconType.Delete2]: { + path: + 'M11.17 37.16h83.48a8.4 8.4 0 012 .16 5.93 5.93 0 012.88 1.56 5.43 5.43 0 011.64 3.34 7.65 7.65 0 01-.06 1.44L94 117.31V117.72a7.06 7.06 0 01-.2.9v.06a5.89 5.89 0 01-5.47 4.07H17.32a6.17 6.17 0 01-1.25-.19 6.17 6.17 0 01-1.16-.48 6.18 6.18 0 01-3.08-4.88l-7-73.49a7.69 7.69 0 01-.06-1.66 5.37 5.37 0 011.63-3.29 6 6 0 013-1.58 8.94 8.94 0 011.79-.13zM5.65 8.8h31.47V6a2.44 2.44 0 010-.27 6 6 0 011.76-4A6 6 0 0143.09 0h19.67a6 6 0 015.7 6v2.8h32.39a4.7 4.7 0 014.31 4.43v10.36a2.59 2.59 0 01-2.59 2.59H2.59A2.59 2.59 0 010 23.62V13.53a1.56 1.56 0 010-.31 4.72 4.72 0 013.88-4.34 10.4 10.4 0 011.77-.08zm42.1 52.7a4.77 4.77 0 019.49 0v37a4.77 4.77 0 01-9.49 0v-37zm23.73-.2a4.58 4.58 0 015-4.06 4.47 4.47 0 014.51 4.46l-2 37a4.57 4.57 0 01-5 4.06 4.47 4.47 0 01-4.51-4.46l2-37zM25 61.7a4.46 4.46 0 014.5-4.46 4.58 4.58 0 015 4.06l2 37a4.47 4.47 0 01-4.51 4.46 4.57 4.57 0 01-5-4.06l-2-37z', + viewBox: '0 0 105.16 122.88', + ratio: 1, + }, + [SessionIconType.Delete3]: { + path: + 'M4.873 9.058h33.35V6.187c0-.095.002-.186.014-.279.075-1.592.762-3.037 1.816-4.086l-.007-.007C41.15.711 42.683.025 44.371.009l.023.002V0H64.325c.106 0 .207.009.309.022 1.583.084 3.019.76 4.064 1.81 1.102 1.104 1.786 2.635 1.803 4.315l-.003.021h.014V9.057h32.926c.138 0 .268.014.401.034 1.182.106 2.254.625 3.034 1.41l.004.007.005-.007c.851.857 1.386 2.048 1.401 3.368l-.002.032h.014v10.861c0 1.472-1.195 2.665-2.667 2.665H2.667C1.195 27.426 0 26.233 0 24.762V13.919c0-.106.004-.211.018-.315v-.021c.089-1.207.624-2.304 1.422-3.098l-.007-.002c.862-.861 2.057-1.396 3.377-1.414l.032.002v-.013h.031zM77.79 49.097h-5.945v56.093h5.945V49.097zm-19.33 0h-5.948v56.093h5.948V49.097zm-19.33 0h-5.946v56.093h5.946V49.097zM10.837 31.569h87.385l.279.018.127.007.134.011h.009l.163.023c1.363.163 2.638.789 3.572 1.708 1.04 1.025 1.705 2.415 1.705 3.964 0 .098-.009.193-.019.286l-.002.068-.014.154-7.393 79.335-.007.043h.007l-.016.139-.051.283-.002.005-.002.018c-.055.331-.12.646-.209.928l-.007.022-.002.005-.009.018-.023.062-.004.021c-.118.354-.264.698-.432 1.009-1.009 1.88-2.879 3.187-5.204 3.187H18.13l-.247-.014v.003l-.011-.003-.032-.004c-.46-.023-.889-.091-1.288-.202-.415-.116-.818-.286-1.197-.495l-.009-.002-.002.002c-1.785-.977-2.975-2.882-3.17-5.022L4.88 37.79l-.011-.125-.011-.247-.004-.116h-.005c0-1.553.664-2.946 1.707-3.971.976-.955 2.32-1.599 3.756-1.726l.122-.004v-.007l.3-.013.104.002v-.014h-.001zm87.386 5.334H10.837v-.007l-.116.004c-.163.022-.322.106-.438.222-.063.063-.104.132-.104.179h-.007l.007.118 7.282 79.244h-.002l.002.012c.032.376.202.691.447.825l-.002.004.084.032.063.012H90.825c.207 0 .399-.157.518-.377l.084-.197.054-.216.014-.138h.005l7.384-79.21-.003-.11c0-.045-.041-.111-.103-.172-.12-.118-.286-.202-.451-.227l-.104.002zm.111-.002h-.016.016zm.549.512v-.004.004zm5.297.377l-.002.018.002-.018zM40.887 14.389H5.332v7.706h97.63v-7.706H67.844c-1.472 0-2.664-1.192-2.664-2.664V6.168h.007c-.007-.22-.106-.433-.259-.585-.137-.141-.324-.229-.521-.252H44.394v-.006c-.213.007-.422.104-.576.259l-.004-.004-.007.004c-.131.134-.231.313-.259.501l.007.102v5.537c-.001 1.472-1.196 2.665-2.668 2.665z', + viewBox: '0 0 108.294 122.88', + ratio: 1, + }, [SessionIconType.DoubleCheckCircleFilled]: { path: 'M7.91731278,0.313257194 C6.15053376,1.58392424 5,3.65760134 5,6 C5,6.343797 5.0247846,6.68180525 5.07266453,7.01233547 L5,7.085 L3.205,5.295 L2.5,6 L5,8.5 L5.33970233,8.16029767 C5.80439817,9.59399486 6.71914823,10.8250231 7.91731278,11.6867428 C7.31518343,11.8898758 6.67037399,12 6,12 C2.688,12 0,9.312 0,6 C0,2.688 2.688,0 6,0 C6.67037399,0 7.31518343,0.110124239 7.91731278,0.313257194 Z M12,0 C15.312,0 18,2.688 18,6 C18,9.312 15.312,12 12,12 C8.688,12 6,9.312 6,6 C6,2.688 8.688,0 12,0 Z M11,8.5 L15.5,4 L14.795,3.29 L11,7.085 L9.205,5.295 L8.5,6 L11,8.5 Z', From 8fca989736f94501faa31fee472c26bf3c78e665 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Thu, 5 Aug 2021 09:41:50 +1000 Subject: [PATCH 05/19] Fixing delete button JSX evaluating to NaN --- stylesheets/_session_conversation.scss | 1 - .../session/conversation/SessionRecording.tsx | 47 +++++++++++-------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss index 8a8e97b37..c0224777b 100644 --- a/stylesheets/_session_conversation.scss +++ b/stylesheets/_session_conversation.scss @@ -428,7 +428,6 @@ flex-shrink: 0; &.playback-timer { - margin-right: $session-margin-sm; animation: fadein $session-transition-duration; @media (-webkit-min-device-pixel-ratio: 1.6) { diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index ef1d439b2..9b471b4a8 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -106,7 +106,7 @@ class SessionRecordingInner extends React.Component { } = this.state; const hasRecordingAndPaused = !isRecording && !isPlaying; - const hasRecording = this.audioElement?.duration && this.audioElement?.duration > 0; + const hasRecording = !!this.audioElement?.duration && this.audioElement?.duration > 0; const actionPauseAudio = !isRecording && !isPaused && isPlaying; // const actionDefault = !actionStopRecording && !actionPlayAudio && !actionPauseAudio; @@ -123,16 +123,14 @@ class SessionRecordingInner extends React.Component { 0; let displayTimeString = moment.utc(displayTimeMs).format('m:ss'); - const recordingDurationMs = this.audioElement?.duration ? this.audioElement?.duration * 1000 : undefined; + const recordingDurationMs = this.audioElement?.duration ? this.audioElement?.duration * 1000 : 1; - let remainingTimeString; + let remainingTimeString = ''; if (recordingDurationMs !== undefined) { remainingTimeString = ` / ${moment.utc(recordingDurationMs).format('m:ss')}`; - displayTimeString += remainingTimeString; } const actionPauseFn = isPlaying ? this.pauseAudio : this.stopRecordingStream; - // const actionPauseFn = isPlaying ? this.pauseAudio : this.onStopRecordingClick; return (
@@ -141,7 +139,10 @@ class SessionRecordingInner extends React.Component { onMouseEnter={this.handleHoverActions} onMouseLeave={this.handleUnhoverActions} > - {isRecording && ( + {isRecording && ( { onClick={actionPauseFn} /> )} - {actionPauseAudio && ( - - )} - + {actionPauseAudio && ( + + )} {hasRecordingAndPaused && ( { onClick={this.playAudio} /> )} - {hasRecording && ( + { hasRecording && ( { )}
- {isRecording || (!isRecording && hasRecording) ? + { hasRecording && !isRecording ?
+ { + displayTimeString + remainingTimeString + } +
+ : + null + } + + {isRecording ? +
{displayTimeString} - {isRecording &&
} +
: null From 81ffb96530b452ef73da82262791bdb232cdb98f Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Thu, 5 Aug 2021 10:47:35 +1000 Subject: [PATCH 06/19] adjusting alignment of buttons. increasing pulse for recording light, increasing hover over effect. --- stylesheets/_session_constants.scss | 4 ++-- stylesheets/_session_conversation.scss | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/stylesheets/_session_constants.scss b/stylesheets/_session_constants.scss index f4c199274..0867f186c 100644 --- a/stylesheets/_session_constants.scss +++ b/stylesheets/_session_constants.scss @@ -153,9 +153,9 @@ $session-font-h4: 16px; color: subtle($color); } -@mixin pulse-color($color, $repetition) { +@mixin pulse-color($color,$duration, $repetition) { - animation: pulseColor 2s $repetition; + animation: pulseColor $duration $repetition; @keyframes pulseColor { 0% { diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss index c0224777b..fd242ddb7 100644 --- a/stylesheets/_session_conversation.scss +++ b/stylesheets/_session_conversation.scss @@ -192,7 +192,7 @@ &:hover { opacity: 1; - transform: scale(0.95); + transform: scale(0.93); transition: $session-transition-duration; } @@ -348,7 +348,7 @@ flex-grow: 1; outline: none; - $actions-element-size: 45px; + $actions-element-size: 30px; &--actions { display: flex; @@ -442,7 +442,7 @@ background-color: $session-color-danger-alt; margin: 0 $session-margin-sm; - @include pulse-color($session-color-danger-alt, infinite); + @include pulse-color($session-color-danger-alt, 1s, infinite); } } } From 32f7dcc811c25d2a2fdb2d14e7b4c91a76601267 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Thu, 5 Aug 2021 10:47:53 +1000 Subject: [PATCH 07/19] testing shake addition to send button on finished recording. --- stylesheets/_session_constants.scss | 23 +++++++++++++++++++ stylesheets/_session_conversation.scss | 5 +++- .../session/conversation/SessionRecording.tsx | 7 ++++-- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/stylesheets/_session_constants.scss b/stylesheets/_session_constants.scss index 0867f186c..0526845cf 100644 --- a/stylesheets/_session_constants.scss +++ b/stylesheets/_session_constants.scss @@ -175,6 +175,29 @@ $session-font-h4: 16px; } } +@mixin subtle-shake() { + animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both; + transform: translate3d(0, 0, 0); + + @keyframes shake { + 10%, 90% { + transform: translate3d(-1px, 0, 0); + } + + 20%, 80% { + transform: translate3d(2px, 0, 0); + } + + 30%, 50%, 70% { + transform: translate3d(-4px, 0, 0); + } + + 40%, 60% { + transform: translate3d(4px, 0, 0); + } + } +} + $session-subtle-factor: 0.6; @function subtle($color) { diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss index fd242ddb7..eae7d8cfe 100644 --- a/stylesheets/_session_conversation.scss +++ b/stylesheets/_session_conversation.scss @@ -338,6 +338,10 @@ .send-message-button { animation: fadein $session-transition-duration; + + &---shake { + @include subtle-shake(); + } } .session-recording { @@ -363,7 +367,6 @@ .session-icon-button { animation: fadein $session-transition-duration; - opacity: 1; border-radius: 50%; width: $actions-element-size; height: $actions-element-size; diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index 9b471b4a8..231a5a366 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -200,8 +200,11 @@ class SessionRecordingInner extends React.Component { null } - {!isRecording && ( -
+ {!isRecording && ( +
Date: Thu, 5 Aug 2021 11:18:18 +1000 Subject: [PATCH 08/19] Adding animation to briefly scale up send button after recording is made. --- stylesheets/_session_constants.scss | 22 ------------------- stylesheets/_session_conversation.scss | 17 ++++++++++++-- .../session/conversation/SessionRecording.tsx | 2 +- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/stylesheets/_session_constants.scss b/stylesheets/_session_constants.scss index 0526845cf..30d0350de 100644 --- a/stylesheets/_session_constants.scss +++ b/stylesheets/_session_constants.scss @@ -175,28 +175,6 @@ $session-font-h4: 16px; } } -@mixin subtle-shake() { - animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both; - transform: translate3d(0, 0, 0); - - @keyframes shake { - 10%, 90% { - transform: translate3d(-1px, 0, 0); - } - - 20%, 80% { - transform: translate3d(2px, 0, 0); - } - - 30%, 50%, 70% { - transform: translate3d(-4px, 0, 0); - } - - 40%, 60% { - transform: translate3d(4px, 0, 0); - } - } -} $session-subtle-factor: 0.6; diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss index eae7d8cfe..6144fccf8 100644 --- a/stylesheets/_session_conversation.scss +++ b/stylesheets/_session_conversation.scss @@ -339,9 +339,22 @@ .send-message-button { animation: fadein $session-transition-duration; - &---shake { - @include subtle-shake(); + &---scale { + animation: scaling 2s ease-in-out; + + @keyframes scaling { + 0% { + transform: scale(1); + } + 80% { + transform: scale(1.3); + } + 100% { + transform: scale(1); + } + } } + } .session-recording { diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index 231a5a366..f9e83d6e7 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -203,7 +203,7 @@ class SessionRecordingInner extends React.Component { {!isRecording && (
Date: Thu, 5 Aug 2021 11:32:55 +1000 Subject: [PATCH 09/19] applying linting and formatting. --- stylesheets/_session_constants.scss | 8 +- stylesheets/_session_conversation.scss | 5 +- .../session/conversation/SessionRecording.tsx | 75 ++++++++----------- 3 files changed, 36 insertions(+), 52 deletions(-) diff --git a/stylesheets/_session_constants.scss b/stylesheets/_session_constants.scss index 30d0350de..725ed6ce5 100644 --- a/stylesheets/_session_constants.scss +++ b/stylesheets/_session_constants.scss @@ -153,8 +153,7 @@ $session-font-h4: 16px; color: subtle($color); } -@mixin pulse-color($color,$duration, $repetition) { - +@mixin pulse-color($color, $duration, $repetition) { animation: pulseColor $duration $repetition; @keyframes pulseColor { @@ -162,12 +161,12 @@ $session-font-h4: 16px; transform: scale(0.95); box-shadow: 0 0 0 0 rgba($color, 0.7); } - + 70% { transform: scale(1); box-shadow: 0 0 0 10px rgba($color, 0); } - + 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba($color, 0); @@ -175,7 +174,6 @@ $session-font-h4: 16px; } } - $session-subtle-factor: 0.6; @function subtle($color) { diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss index 6144fccf8..bb388b03e 100644 --- a/stylesheets/_session_conversation.scss +++ b/stylesheets/_session_conversation.scss @@ -180,8 +180,8 @@ border-top: themed('sessionBorder'); } - .session-icon-button { - // & > .session-icon-button { + .session-icon-button { + // & > .session-icon-button { margin-right: $session-margin-sm; } .session-icon-button { @@ -354,7 +354,6 @@ } } } - } .session-recording { diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index f9e83d6e7..7d932621f 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -119,11 +119,13 @@ class SessionRecordingInner extends React.Component { const displayTimeMs = isRecording ? (nowTimestamp - startTimestamp) * 1000 : (this.audioElement && - (this.audioElement?.currentTime * 1000 || this.audioElement?.duration)) || - 0; - let displayTimeString = moment.utc(displayTimeMs).format('m:ss'); + (this.audioElement?.currentTime * 1000 || this.audioElement?.duration)) || + 0; - const recordingDurationMs = this.audioElement?.duration ? this.audioElement?.duration * 1000 : 1; + const displayTimeString = moment.utc(displayTimeMs).format('m:ss'); + const recordingDurationMs = this.audioElement?.duration + ? this.audioElement?.duration * 1000 + : 1; let remainingTimeString = ''; if (recordingDurationMs !== undefined) { @@ -139,17 +141,15 @@ class SessionRecordingInner extends React.Component { onMouseEnter={this.handleHoverActions} onMouseLeave={this.handleUnhoverActions} > - {isRecording && ( - - )} + + {isRecording && ( + + )} {actionPauseAudio && ( { onClick={this.playAudio} /> )} - { hasRecording && ( + {hasRecording && ( { )}
- { hasRecording && !isRecording ? + {hasRecording && !isRecording ? (
- { - displayTimeString + remainingTimeString - } + {displayTimeString + remainingTimeString}
- : - null - } + ) : null} - {isRecording ? + {isRecording ? (
{displayTimeString}
- : - null - } + ) : null} - {!isRecording && ( -
+ {!isRecording && ( +
{ />
)} -
); } @@ -365,15 +360,7 @@ class SessionRecordingInner extends React.Component { } /** - * Stops recording and sets up audio element, updates recording state. - */ - private async onStopRecordingClick() { - this.stopRecordingStream(); - this.updateAudioElementAndDuration(); - } - - /** - * Creates an audio element using the recorded audio blob. + * Creates an audio element using the recorded audio blob. * Updates the duration for displaying audio duration. */ private updateAudioElementAndDuration() { @@ -383,8 +370,8 @@ class SessionRecordingInner extends React.Component { // ww adding record duration this.setState({ - recordDuration: this.audioElement.duration - }) + recordDuration: this.audioElement.duration, + }); this.audioElement.loop = false; this.audioElement.onended = () => { From bfed4a88dd898fe0c95e425e8429a5ca5ab7121f Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Thu, 5 Aug 2021 11:37:18 +1000 Subject: [PATCH 10/19] removing comments --- stylesheets/_session.scss | 2 -- stylesheets/_session_conversation.scss | 10 ---------- .../session/conversation/SessionRecording.tsx | 2 -- 3 files changed, 14 deletions(-) diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index 11e6ffa17..4385acff8 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -224,7 +224,6 @@ textarea { border-radius: 2px; height: 33px; padding: 0px 18px; - // line-height: 33px; font-size: $session-font-sm; } @@ -1248,7 +1247,6 @@ input { margin: 15px calc(100% / 2 - 1px); width: 1px; - // z-index: -1; } } diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss index bb388b03e..0a85590c0 100644 --- a/stylesheets/_session_conversation.scss +++ b/stylesheets/_session_conversation.scss @@ -462,16 +462,6 @@ } } -// box-sizing: border-box; -// position: absolute; -// z-index: 3; -// width: 4px; -// height: 5px; -// margin-left: 0px; -// top: 0px; -// background: #00f782; -// border-radius: 50px; - /* ************ */ /* AUDIO PLAYER */ /* ************ */ diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index 7d932621f..58d8a2059 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -108,8 +108,6 @@ class SessionRecordingInner extends React.Component { const hasRecordingAndPaused = !isRecording && !isPlaying; const hasRecording = !!this.audioElement?.duration && this.audioElement?.duration > 0; const actionPauseAudio = !isRecording && !isPaused && isPlaying; - - // const actionDefault = !actionStopRecording && !actionPlayAudio && !actionPauseAudio; const actionDefault = !isRecording && !hasRecordingAndPaused && !actionPauseAudio; // if we are recording, we base the time recording on our state values From b0a8c6c3ebf48975d03c9ea7f0b469f3a4f1d6d6 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Thu, 5 Aug 2021 13:15:03 +1000 Subject: [PATCH 11/19] wip: adding recovery phrase prompt. --- _locales/en/messages.json | 9 +- ts/components/dialog/DeleteAccountModal.tsx | 1 + .../session/LeftPaneSectionHeader.tsx | 253 ++++++++++++++++-- .../session/LeftPaneSettingSection.tsx | 8 +- ts/components/session/SessionInboxView.tsx | 1 + .../session/conversation/SessionRecording.tsx | 6 +- ts/components/session/icon/Icons.tsx | 14 - ts/state/ducks/SessionTheme.tsx | 19 +- ts/state/ducks/userConfig.tsx | 9 +- ts/state/selectors/userConfig.ts | 6 + ts/styled.d.ts | 4 + ts/window.d.ts | 1 + 12 files changed, 286 insertions(+), 45 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 442a633ef..cbfa1aab0 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -426,5 +426,12 @@ "dialogClearAllDataDeletionFailedMultiple": "Data not deleted by those Service Nodes: $snodes$", "dialogClearAllDataDeletionQuestion": "Would you like to clear only this device, or delete your entire account?", "deviceOnly": "Device Only", - "entireAccount": "Entire Account" + "entireAccount": "Entire Account", + "recoveryPhraseSecureMessage": "Secure your account by saving your recovery phrase", + "recoveryPhraseSecureTitle": "You're almost finished!", + "recoveryPhraseSecureButtonText": "Secure your account", + "recoveryPhraseRevealMessage": "Reveal your recovery phrase then store it safely to secure it", + "recoveryPhraseRevealButtonText": "Reveal Recovery Phrase", + "recoveryPhraseInfoMessage": "Meet your recovery phrase Your recovery phrase is the master key to your session ID - you can use it to restore your Session ID if you lose access to your device, Store your recovery phrase in a safe place, and don't give it to anyone.", + "recoveryPhraseCompleteTitle": "Account secured!" } diff --git a/ts/components/dialog/DeleteAccountModal.tsx b/ts/components/dialog/DeleteAccountModal.tsx index b0e948019..40c4226f5 100644 --- a/ts/components/dialog/DeleteAccountModal.tsx +++ b/ts/components/dialog/DeleteAccountModal.tsx @@ -11,6 +11,7 @@ import { SessionWrapperModal } from '../session/SessionWrapperModal'; const deleteDbLocally = async () => { window?.log?.info('configuration message sent successfully. Deleting everything'); + window.persistStore?.purge(); await window.Signal.Logs.deleteAll(); await window.Signal.Data.removeAll(); await window.Signal.Data.close(); diff --git a/ts/components/session/LeftPaneSectionHeader.tsx b/ts/components/session/LeftPaneSectionHeader.tsx index b72d95285..9d32acdf6 100644 --- a/ts/components/session/LeftPaneSectionHeader.tsx +++ b/ts/components/session/LeftPaneSectionHeader.tsx @@ -1,8 +1,13 @@ -import React from 'react'; +import React, { useState } from 'react'; import classNames from 'classnames'; import { SessionIcon, SessionIconSize, SessionIconType } from './icon'; -import { DefaultTheme, useTheme } from 'styled-components'; -import { SessionButton } from './SessionButton'; +import styled, { DefaultTheme, useTheme } from 'styled-components'; +import { SessionButton, SessionButtonColor, SessionButtonType } from './SessionButton'; +import { Constants } from '../../session'; +import { UserUtils } from '../../session/utils'; +import { useDispatch, useSelector } from 'react-redux'; +import { disableRecoveryPhrasePrompt } from '../../state/ducks/userConfig'; +import { getShowRecoveryPhrasePrompt } from '../../state/selectors/userConfig'; const Tab = ({ isSelected, @@ -17,8 +22,8 @@ const Tab = ({ }) => { const handleClick = onSelect ? () => { - onSelect(type); - } + onSelect(type); + } : undefined; return ( @@ -40,22 +45,232 @@ type Props = { export const LeftPaneSectionHeader = (props: Props) => { const { label, buttonIcon, buttonClicked } = props; - const theme = useTheme(); + const showRecoveryPhrasePrompt = useSelector(getShowRecoveryPhrasePrompt); return ( -
- {label && } - {buttonIcon && ( - - - - )} -
+ +
+ {label && } + {buttonIcon && ( + + + + )} +
+ {showRecoveryPhrasePrompt && } +
); }; + +export const LeftPaneBanner = (Props: any) => { + + const [completion, setCompletion] = useState(80); + const [bodyText, setBodyText] = useState(window.i18n('recoveryPhraseSecureMessage')); + const [buttonText, setButtonText] = useState(window.i18n('recoveryPhraseSecureButtonText')); + const [recoveryPhraseHidden, setRecoveryPhraseHidden] = useState(true); + const [isCompleted, setIsCompleted] = useState(false); + const [bannerTitle, setBannerTitle] = useState(window.i18n("recoveryPhraseSecureTitle")); + const recoveryPhrase = UserUtils.getCurrentRecoveryPhrase(); + const secondsBeforeRemoval = 2 * 1000; + const completionText = `${completion}%`; + + const handleShowRecoveryClick = () => { + setRecoveryPhraseHidden(false); + setBodyText(window.i18n('recoveryPhraseInfoMessage')); + setButtonText(window.i18n('copy')); + } + + const handleSecureClick = () => { + if (completion === 80) { + setCompletion(90); + setBodyText(window.i18n('recoveryPhraseRevealMessage')); + setButtonText(window.i18n('recoveryPhraseRevealButtonText')); + } + } + + const BannerInner = (props: any) => { + const dispatch = useDispatch(); + + const handleCopyPhraseClick = async () => { + await navigator.clipboard.writeText(recoveryPhrase); + setCompletion(100) + setBannerTitle(window.i18n('recoveryPhraseCompleteTitle')); + setBodyText(''); + setRecoveryPhraseHidden(true); + setIsCompleted(true); + + // remove banner after a small delay + setTimeout(() => { + dispatch(disableRecoveryPhrasePrompt()); + }, secondsBeforeRemoval); + } + + let onClick = + completion === 80 ? handleSecureClick : + completion === 90 ? + recoveryPhraseHidden ? + handleShowRecoveryClick : + handleCopyPhraseClick + : null; + + // TODO: This can be refactored down. all returns have p tag + button, final has conditional phrase element. + return ( + +

+ {bodyText} +

+ {!recoveryPhraseHidden && + + {recoveryPhrase} + + } + {!isCompleted && + + } +
+ ) + } + + const useColumn = completion === 90 && handleShowRecoveryClick; + const theme = useTheme(); + + return ( + + + + + + {bannerTitle} {completionText} + + + + + + ) +} + +interface StyledProgressBarContainerProps { + theme: DefaultTheme; +} +const StyledProgressBarContainer = styled.div` + width: 100%; + height: 5px; + flex-direction: row; + background: ${(p: StyledProgressBarContainerProps) => p.theme.colors.sessionBorderColor}; +`; + +interface StyledProgressBarProps { + width: string; + color?: string; +} + +const StyledProgressBarInner = styled.div` + background: ${(p: StyledProgressBarProps) => p.color}; + width: ${(p: StyledProgressBarProps) => p.width}; + transition: width 0.5s ease-in; + height: 100%; +`; + +interface StyledBannerTitle { + theme: DefaultTheme; +} +export const StyledBannerTitle = styled.div` + line-height: 1.3; + font-size: ${(p: StyledBannerTitle) => p.theme.common.fonts.md}; + font-weight: bold; + margin: + ${Constants.UI.SPACING.marginSm} + ${Constants.UI.SPACING.marginSm} + 0 + ${Constants.UI.SPACING.marginSm}; + + span { + color: ${(p: StyledBannerTitle) => p.theme.colors.textAccent}; + } +`; + +interface StyledLeftPaneBannerProps { + isCompleted?: boolean; + border: string; + theme: DefaultTheme; +} +export const StyledLeftPaneBanner = styled.div` + background: ${(p: StyledLeftPaneBannerProps) => p.theme.colors.recoveryPhraseBannerBackground}; + display: flex; + flex-direction: column; + border-bottom: ${(p: StyledLeftPaneBannerProps) => p.border}; + opacity: 1; + transition: opacity 2s; + ${(p: StyledLeftPaneBannerProps) => p.isCompleted === true ? + ` + opacity: 0; + ` + : + null + } +`; + +const StyledBannerInner = styled.div` + p { + margin: 0; + } + + .left-pane-banner___phrase { + margin-top: ${Constants.UI.SPACING.marginMd}; + } + + .session-button { + margin-top: ${Constants.UI.SPACING.marginSm}; + } +`; + +interface StyledRecoveryPhraseProps { + theme: DefaultTheme; +} +const StyledRecoveryPhrase = styled.p` + margin: ${Constants.UI.SPACING.marginXs}; + border-radius: 5px; + padding: 5px; + border: ${(props: StyledRecoveryPhraseProps) => props.theme.colors.sessionBorderHighContrast}; +`; + + +// 90% + +// tap and hold the redacted words to reveal your recovery phrase then store it safely to secure + +// meet your recovery phrase +// your recovery phrase is the master key to your session ID - you can use it to restore your Session ID if you lose access to your device, Store your recovery phrase in a safe place, and don't give it to anyone. + +interface StyledBannerContainerProps { + flexDirection?: string; +} +export const StyledBannerContainer = styled.div` + display: flex; + flex-direction: ${(p: StyledBannerContainerProps) => p.flexDirection}; + justify-content: space-between; + padding: ${Constants.UI.SPACING.marginSm} +`; + +export const StyledLeftPaneHeaderContainer = styled.div` + display: flex; + flex-direction: column; +`; diff --git a/ts/components/session/LeftPaneSettingSection.tsx b/ts/components/session/LeftPaneSettingSection.tsx index fb71e90c2..4f6617089 100644 --- a/ts/components/session/LeftPaneSettingSection.tsx +++ b/ts/components/session/LeftPaneSettingSection.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import classNames from 'classnames'; import { SessionButton, SessionButtonColor, SessionButtonType } from './SessionButton'; @@ -13,6 +13,12 @@ import { getFocusedSettingsSection } from '../../state/selectors/section'; import { getTheme } from '../../state/selectors/theme'; import { recoveryPhraseModal, updateDeleteAccountModal } from '../../state/ducks/modalDialog'; +type Props = { + settingsCategory: SessionSettingCategory; + showSettingsSection: (category: SessionSettingCategory) => void; + theme: DefaultTheme; +}; + const getCategories = () => { return [ { diff --git a/ts/components/session/SessionInboxView.tsx b/ts/components/session/SessionInboxView.tsx index 9357b30bc..defd3845b 100644 --- a/ts/components/session/SessionInboxView.tsx +++ b/ts/components/session/SessionInboxView.tsx @@ -52,6 +52,7 @@ export class SessionInboxView extends React.Component { } const persistor = persistStore(this.store); + window.persistStore = persistor; return ( diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index 58d8a2059..94d3a9beb 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -3,12 +3,11 @@ import classNames from 'classnames'; import moment from 'moment'; import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon'; -import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton'; import { Constants } from '../../../session'; import { ToastUtils } from '../../../session/utils'; import autoBind from 'auto-bind'; import MicRecorder from 'mic-recorder-to-mp3'; -import styled, { useTheme } from 'styled-components'; +import styled from 'styled-components'; interface Props { onExitVoiceNoteView: any; @@ -164,7 +163,7 @@ class SessionRecordingInner extends React.Component { )} {hasRecording && ( @@ -366,7 +365,6 @@ class SessionRecordingInner extends React.Component { const audioURL = window.URL.createObjectURL(this.audioBlobMp3); this.audioElement = new Audio(audioURL); - // ww adding record duration this.setState({ recordDuration: this.audioElement.duration, }); diff --git a/ts/components/session/icon/Icons.tsx b/ts/components/session/icon/Icons.tsx index fc18eff79..c95a62543 100644 --- a/ts/components/session/icon/Icons.tsx +++ b/ts/components/session/icon/Icons.tsx @@ -13,8 +13,6 @@ export enum SessionIconType { CircleElipses = 'circleElipses', Contacts = 'contacts', Delete = 'delete', - Delete2 = 'delete2', - Delete3 = 'delete3', Ellipses = 'ellipses', Emoji = 'emoji', Error = 'error', @@ -130,23 +128,11 @@ export const icons = { ratio: 1, }, [SessionIconType.Delete]: { - path: - 'M1.63 97.99l36.55-36.55L1.63 24.89c-2.17-2.17-2.17-5.73 0-7.9L16.99 1.63c2.17-2.17 5.73-2.17 7.9 0l36.55 36.55L97.99 1.63c2.17-2.17 5.73-2.17 7.9 0l15.36 15.36c2.17 2.17 2.17 5.73 0 7.9L84.7 61.44l36.55 36.55c2.17 2.17 2.17 5.73 0 7.9l-15.36 15.36c-2.17 2.17-5.73 2.17-7.9 0L61.44 84.7l-36.55 36.55c-2.17 2.17-5.73 2.17-7.9 0L1.63 105.89c-2.17-2.17-2.17-5.73 0-7.9z', - viewBox: '0 0 122.88 122.88', - ratio: 1, - }, - [SessionIconType.Delete2]: { path: 'M11.17 37.16h83.48a8.4 8.4 0 012 .16 5.93 5.93 0 012.88 1.56 5.43 5.43 0 011.64 3.34 7.65 7.65 0 01-.06 1.44L94 117.31V117.72a7.06 7.06 0 01-.2.9v.06a5.89 5.89 0 01-5.47 4.07H17.32a6.17 6.17 0 01-1.25-.19 6.17 6.17 0 01-1.16-.48 6.18 6.18 0 01-3.08-4.88l-7-73.49a7.69 7.69 0 01-.06-1.66 5.37 5.37 0 011.63-3.29 6 6 0 013-1.58 8.94 8.94 0 011.79-.13zM5.65 8.8h31.47V6a2.44 2.44 0 010-.27 6 6 0 011.76-4A6 6 0 0143.09 0h19.67a6 6 0 015.7 6v2.8h32.39a4.7 4.7 0 014.31 4.43v10.36a2.59 2.59 0 01-2.59 2.59H2.59A2.59 2.59 0 010 23.62V13.53a1.56 1.56 0 010-.31 4.72 4.72 0 013.88-4.34 10.4 10.4 0 011.77-.08zm42.1 52.7a4.77 4.77 0 019.49 0v37a4.77 4.77 0 01-9.49 0v-37zm23.73-.2a4.58 4.58 0 015-4.06 4.47 4.47 0 014.51 4.46l-2 37a4.57 4.57 0 01-5 4.06 4.47 4.47 0 01-4.51-4.46l2-37zM25 61.7a4.46 4.46 0 014.5-4.46 4.58 4.58 0 015 4.06l2 37a4.47 4.47 0 01-4.51 4.46 4.57 4.57 0 01-5-4.06l-2-37z', viewBox: '0 0 105.16 122.88', ratio: 1, }, - [SessionIconType.Delete3]: { - path: - 'M4.873 9.058h33.35V6.187c0-.095.002-.186.014-.279.075-1.592.762-3.037 1.816-4.086l-.007-.007C41.15.711 42.683.025 44.371.009l.023.002V0H64.325c.106 0 .207.009.309.022 1.583.084 3.019.76 4.064 1.81 1.102 1.104 1.786 2.635 1.803 4.315l-.003.021h.014V9.057h32.926c.138 0 .268.014.401.034 1.182.106 2.254.625 3.034 1.41l.004.007.005-.007c.851.857 1.386 2.048 1.401 3.368l-.002.032h.014v10.861c0 1.472-1.195 2.665-2.667 2.665H2.667C1.195 27.426 0 26.233 0 24.762V13.919c0-.106.004-.211.018-.315v-.021c.089-1.207.624-2.304 1.422-3.098l-.007-.002c.862-.861 2.057-1.396 3.377-1.414l.032.002v-.013h.031zM77.79 49.097h-5.945v56.093h5.945V49.097zm-19.33 0h-5.948v56.093h5.948V49.097zm-19.33 0h-5.946v56.093h5.946V49.097zM10.837 31.569h87.385l.279.018.127.007.134.011h.009l.163.023c1.363.163 2.638.789 3.572 1.708 1.04 1.025 1.705 2.415 1.705 3.964 0 .098-.009.193-.019.286l-.002.068-.014.154-7.393 79.335-.007.043h.007l-.016.139-.051.283-.002.005-.002.018c-.055.331-.12.646-.209.928l-.007.022-.002.005-.009.018-.023.062-.004.021c-.118.354-.264.698-.432 1.009-1.009 1.88-2.879 3.187-5.204 3.187H18.13l-.247-.014v.003l-.011-.003-.032-.004c-.46-.023-.889-.091-1.288-.202-.415-.116-.818-.286-1.197-.495l-.009-.002-.002.002c-1.785-.977-2.975-2.882-3.17-5.022L4.88 37.79l-.011-.125-.011-.247-.004-.116h-.005c0-1.553.664-2.946 1.707-3.971.976-.955 2.32-1.599 3.756-1.726l.122-.004v-.007l.3-.013.104.002v-.014h-.001zm87.386 5.334H10.837v-.007l-.116.004c-.163.022-.322.106-.438.222-.063.063-.104.132-.104.179h-.007l.007.118 7.282 79.244h-.002l.002.012c.032.376.202.691.447.825l-.002.004.084.032.063.012H90.825c.207 0 .399-.157.518-.377l.084-.197.054-.216.014-.138h.005l7.384-79.21-.003-.11c0-.045-.041-.111-.103-.172-.12-.118-.286-.202-.451-.227l-.104.002zm.111-.002h-.016.016zm.549.512v-.004.004zm5.297.377l-.002.018.002-.018zM40.887 14.389H5.332v7.706h97.63v-7.706H67.844c-1.472 0-2.664-1.192-2.664-2.664V6.168h.007c-.007-.22-.106-.433-.259-.585-.137-.141-.324-.229-.521-.252H44.394v-.006c-.213.007-.422.104-.576.259l-.004-.004-.007.004c-.131.134-.231.313-.259.501l.007.102v5.537c-.001 1.472-1.196 2.665-2.668 2.665z', - viewBox: '0 0 108.294 122.88', - ratio: 1, - }, [SessionIconType.DoubleCheckCircleFilled]: { path: 'M7.91731278,0.313257194 C6.15053376,1.58392424 5,3.65760134 5,6 C5,6.343797 5.0247846,6.68180525 5.07266453,7.01233547 L5,7.085 L3.205,5.295 L2.5,6 L5,8.5 L5.33970233,8.16029767 C5.80439817,9.59399486 6.71914823,10.8250231 7.91731278,11.6867428 C7.31518343,11.8898758 6.67037399,12 6,12 C2.688,12 0,9.312 0,6 C0,2.688 2.688,0 6,0 C6.67037399,0 7.31518343,0.110124239 7.91731278,0.313257194 Z M12,0 C15.312,0 18,2.688 18,6 C18,9.312 15.312,12 12,12 C8.688,12 6,9.312 6,6 C6,2.688 8.688,0 12,0 Z M11,8.5 L15.5,4 L14.795,3.29 L11,7.085 L9.205,5.295 L8.5,6 L11,8.5 Z', diff --git a/ts/state/ducks/SessionTheme.tsx b/ts/state/ducks/SessionTheme.tsx index 6a4c43f93..575a8ce0a 100644 --- a/ts/state/ducks/SessionTheme.tsx +++ b/ts/state/ducks/SessionTheme.tsx @@ -3,7 +3,6 @@ import React from 'react'; // import 'reset-css/reset.css'; import { DefaultTheme, ThemeProvider } from 'styled-components'; -import { pushToastWarning } from '../../session/utils/Toast'; const white = '#ffffff'; const black = '#000000'; @@ -11,8 +10,10 @@ const warning = '#e7b100'; const destructive = '#ff453a'; const accentLightTheme = '#00e97b'; const accentDarkTheme = '#00f782'; -const borderLightTheme = '#f1f1f1'; -const borderDarkTheme = '#ffffff0F'; +const borderLightThemeColor = '#f1f1f1'; +const borderDarkThemeColor = '#ffffff0F'; +const borderHighContrastLightTheme = '#afafaf'; +const borderHighContrastDarkTheme = '#484848'; const borderAvatarColor = '#00000059'; const common = { @@ -55,6 +56,7 @@ export const lightTheme: DefaultTheme = { textColorSubtleNoOpacity: '#52514f', textColorOpposite: white, textHighlight: `${black}33`, + textAccent: '#00c769', // inbox inboxBackground: white, // buttons @@ -75,9 +77,12 @@ export const lightTheme: DefaultTheme = { conversationItemHasUnread: '#fcfcfc', conversationItemSelected: '#f0f0f0', clickableHovered: '#dfdfdf', - sessionBorder: `1px solid ${borderLightTheme}`, + sessionBorder: `1px solid ${borderLightThemeColor}`, + sessionBorderColor: borderLightThemeColor, + sessionBorderHighContrast: `1px solid ${borderHighContrastLightTheme}`, sessionUnreadBorder: `4px solid ${accentLightTheme}`, leftpaneOverlayBackground: white, + recoveryPhraseBannerBackground: white, // scrollbars scrollBarTrack: '#fcfcfc', scrollBarThumb: '#474646', @@ -111,6 +116,7 @@ export const darkTheme = { textColorSubtleNoOpacity: '#7f7d7d', textColorOpposite: black, textHighlight: `${accentDarkTheme}99`, + textAccent: accentDarkTheme, // inbox inboxBackground: 'linear-gradient(180deg, #171717 0%, #121212 100%)', // buttons @@ -131,9 +137,12 @@ export const darkTheme = { conversationItemHasUnread: '#2c2c2c', conversationItemSelected: '#404040', clickableHovered: '#414347', - sessionBorder: `1px solid ${borderDarkTheme}`, + sessionBorder: `1px solid ${borderDarkThemeColor}`, + sessionBorderColor: borderDarkThemeColor, + sessionBorderHighContrast: `1px solid ${borderHighContrastDarkTheme}`, sessionUnreadBorder: `4px solid ${accentDarkTheme}`, leftpaneOverlayBackground: 'linear-gradient(180deg, #171717 0%, #121212 100%)', + recoveryPhraseBannerBackground: '#1f1f1f', // scrollbars scrollBarTrack: '#1b1b1b', scrollBarThumb: '#474646', diff --git a/ts/state/ducks/userConfig.tsx b/ts/state/ducks/userConfig.tsx index 1c704e2b4..20316fcdf 100644 --- a/ts/state/ducks/userConfig.tsx +++ b/ts/state/ducks/userConfig.tsx @@ -6,10 +6,12 @@ import { createSlice } from '@reduxjs/toolkit'; export interface UserConfigState { audioAutoplay: boolean; + showRecoveryPhrasePrompt: boolean; } export const initialUserConfigState = { audioAutoplay: false, + showRecoveryPhrasePrompt: true, }; const userConfigSlice = createSlice({ @@ -19,9 +21,14 @@ const userConfigSlice = createSlice({ toggleAudioAutoplay: state => { state.audioAutoplay = !state.audioAutoplay; }, + disableRecoveryPhrasePrompt: state => { + console.log('setting recovery phrase state'); + state.showRecoveryPhrasePrompt = false + }, + }, }); const { actions, reducer } = userConfigSlice; -export const { toggleAudioAutoplay } = actions; +export const { toggleAudioAutoplay, disableRecoveryPhrasePrompt } = actions; export const userConfigReducer = reducer; diff --git a/ts/state/selectors/userConfig.ts b/ts/state/selectors/userConfig.ts index 084f8d9ac..1e3de4c7c 100644 --- a/ts/state/selectors/userConfig.ts +++ b/ts/state/selectors/userConfig.ts @@ -8,3 +8,9 @@ export const getAudioAutoplay = createSelector( getUserConfig, (state: UserConfigState): boolean => state.audioAutoplay ); + + +export const getShowRecoveryPhrasePrompt = createSelector( + getUserConfig, + (state: UserConfigState): boolean => state.showRecoveryPhrasePrompt +); diff --git a/ts/styled.d.ts b/ts/styled.d.ts index 905375e34..953f901d3 100644 --- a/ts/styled.d.ts +++ b/ts/styled.d.ts @@ -39,6 +39,7 @@ declare module 'styled-components' { textColorSubtleNoOpacity: string; textColorOpposite: string; textHighlight: string; + textAccent: string; // inbox inboxBackground: string; // buttons @@ -60,8 +61,11 @@ declare module 'styled-components' { conversationItemSelected: string; clickableHovered: string; sessionBorder: string; + sessionBorderColor: string; + sessionBorderHighContrast: string; sessionUnreadBorder: string; leftpaneOverlayBackground: string; + recoveryPhraseBannerBackground: string; // scrollbars scrollBarTrack: string; scrollBarThumb: string; diff --git a/ts/window.d.ts b/ts/window.d.ts index c4a8c17ea..2ba243d01 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -52,6 +52,7 @@ declare global { }; lokiSnodeAPI: LokiSnodeAPI; onLogin: any; + persistStore?: Persistor; resetDatabase: any; restart: any; getSeedNodeList: () => Array | undefined; From d13a6824ee67631290d0f66893b45ec1bc8a7573 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Thu, 12 Aug 2021 09:07:54 +1000 Subject: [PATCH 12/19] running yarn ready --- .../session/LeftPaneSectionHeader.tsx | 101 +++++++----------- ts/state/ducks/userConfig.tsx | 4 +- ts/state/selectors/userConfig.ts | 1 - 3 files changed, 42 insertions(+), 64 deletions(-) diff --git a/ts/components/session/LeftPaneSectionHeader.tsx b/ts/components/session/LeftPaneSectionHeader.tsx index 9d32acdf6..d530d25a4 100644 --- a/ts/components/session/LeftPaneSectionHeader.tsx +++ b/ts/components/session/LeftPaneSectionHeader.tsx @@ -22,8 +22,8 @@ const Tab = ({ }) => { const handleClick = onSelect ? () => { - onSelect(type); - } + onSelect(type); + } : undefined; return ( @@ -68,14 +68,13 @@ export const LeftPaneSectionHeader = (props: Props) => { ); }; -export const LeftPaneBanner = (Props: any) => { - +export const LeftPaneBanner = () => { const [completion, setCompletion] = useState(80); const [bodyText, setBodyText] = useState(window.i18n('recoveryPhraseSecureMessage')); const [buttonText, setButtonText] = useState(window.i18n('recoveryPhraseSecureButtonText')); const [recoveryPhraseHidden, setRecoveryPhraseHidden] = useState(true); const [isCompleted, setIsCompleted] = useState(false); - const [bannerTitle, setBannerTitle] = useState(window.i18n("recoveryPhraseSecureTitle")); + const [bannerTitle, setBannerTitle] = useState(window.i18n('recoveryPhraseSecureTitle')); const recoveryPhrase = UserUtils.getCurrentRecoveryPhrase(); const secondsBeforeRemoval = 2 * 1000; const completionText = `${completion}%`; @@ -84,7 +83,7 @@ export const LeftPaneBanner = (Props: any) => { setRecoveryPhraseHidden(false); setBodyText(window.i18n('recoveryPhraseInfoMessage')); setButtonText(window.i18n('copy')); - } + }; const handleSecureClick = () => { if (completion === 80) { @@ -92,14 +91,14 @@ export const LeftPaneBanner = (Props: any) => { setBodyText(window.i18n('recoveryPhraseRevealMessage')); setButtonText(window.i18n('recoveryPhraseRevealButtonText')); } - } + }; const BannerInner = (props: any) => { const dispatch = useDispatch(); const handleCopyPhraseClick = async () => { await navigator.clipboard.writeText(recoveryPhrase); - setCompletion(100) + setCompletion(100); setBannerTitle(window.i18n('recoveryPhraseCompleteTitle')); setBodyText(''); setRecoveryPhraseHidden(true); @@ -109,63 +108,57 @@ export const LeftPaneBanner = (Props: any) => { setTimeout(() => { dispatch(disableRecoveryPhrasePrompt()); }, secondsBeforeRemoval); - } + }; - let onClick = - completion === 80 ? handleSecureClick : - completion === 90 ? - recoveryPhraseHidden ? - handleShowRecoveryClick : - handleCopyPhraseClick - : null; + const onClick = + completion === 80 + ? handleSecureClick + : completion === 90 + ? recoveryPhraseHidden + ? handleShowRecoveryClick + : handleCopyPhraseClick + : null; - // TODO: This can be refactored down. all returns have p tag + button, final has conditional phrase element. return ( -

- {bodyText} -

- {!recoveryPhraseHidden && +

{bodyText}

+ {!recoveryPhraseHidden && ( + className="left-pane-banner___phrase" + onClick={handleShowRecoveryClick} + > {recoveryPhrase} - } - {!isCompleted && + )} + {!isCompleted && ( - } + )}
- ) - } + ); + }; - const useColumn = completion === 90 && handleShowRecoveryClick; + const flexDirection = completion === 90 && handleShowRecoveryClick ? 'column' : 'row'; const theme = useTheme(); return ( - + - + - + {bannerTitle} {completionText} - + - ) -} + ); +}; interface StyledProgressBarContainerProps { theme: DefaultTheme; @@ -196,15 +189,12 @@ export const StyledBannerTitle = styled.div` line-height: 1.3; font-size: ${(p: StyledBannerTitle) => p.theme.common.fonts.md}; font-weight: bold; - margin: - ${Constants.UI.SPACING.marginSm} - ${Constants.UI.SPACING.marginSm} - 0 + margin: ${Constants.UI.SPACING.marginSm} ${Constants.UI.SPACING.marginSm} 0 ${Constants.UI.SPACING.marginSm}; span { - color: ${(p: StyledBannerTitle) => p.theme.colors.textAccent}; - } + color: ${(p: StyledBannerTitle) => p.theme.colors.textAccent}; + } `; interface StyledLeftPaneBannerProps { @@ -219,13 +209,12 @@ export const StyledLeftPaneBanner = styled.div` border-bottom: ${(p: StyledLeftPaneBannerProps) => p.border}; opacity: 1; transition: opacity 2s; - ${(p: StyledLeftPaneBannerProps) => p.isCompleted === true ? - ` + ${(p: StyledLeftPaneBannerProps) => + p.isCompleted === true + ? ` opacity: 0; ` - : - null - } + : null} `; const StyledBannerInner = styled.div` @@ -252,14 +241,6 @@ const StyledRecoveryPhrase = styled.p` border: ${(props: StyledRecoveryPhraseProps) => props.theme.colors.sessionBorderHighContrast}; `; - -// 90% - -// tap and hold the redacted words to reveal your recovery phrase then store it safely to secure - -// meet your recovery phrase -// your recovery phrase is the master key to your session ID - you can use it to restore your Session ID if you lose access to your device, Store your recovery phrase in a safe place, and don't give it to anyone. - interface StyledBannerContainerProps { flexDirection?: string; } @@ -267,7 +248,7 @@ export const StyledBannerContainer = styled.div` display: flex; flex-direction: ${(p: StyledBannerContainerProps) => p.flexDirection}; justify-content: space-between; - padding: ${Constants.UI.SPACING.marginSm} + padding: ${Constants.UI.SPACING.marginSm}; `; export const StyledLeftPaneHeaderContainer = styled.div` diff --git a/ts/state/ducks/userConfig.tsx b/ts/state/ducks/userConfig.tsx index 20316fcdf..af64b2cb4 100644 --- a/ts/state/ducks/userConfig.tsx +++ b/ts/state/ducks/userConfig.tsx @@ -22,10 +22,8 @@ const userConfigSlice = createSlice({ state.audioAutoplay = !state.audioAutoplay; }, disableRecoveryPhrasePrompt: state => { - console.log('setting recovery phrase state'); - state.showRecoveryPhrasePrompt = false + state.showRecoveryPhrasePrompt = false; }, - }, }); diff --git a/ts/state/selectors/userConfig.ts b/ts/state/selectors/userConfig.ts index 1e3de4c7c..9c8641cb2 100644 --- a/ts/state/selectors/userConfig.ts +++ b/ts/state/selectors/userConfig.ts @@ -9,7 +9,6 @@ export const getAudioAutoplay = createSelector( (state: UserConfigState): boolean => state.audioAutoplay ); - export const getShowRecoveryPhrasePrompt = createSelector( getUserConfig, (state: UserConfigState): boolean => state.showRecoveryPhrasePrompt From af298936d95bcab4921e7ad7d908bb95cffbc7f0 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Tue, 17 Aug 2021 10:05:12 +1000 Subject: [PATCH 13/19] Combining and 80 and 90 completion stages. --- _locales/en/messages.json | 4 +-- .../session/LeftPaneSectionHeader.tsx | 29 +++++++++---------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index cbfa1aab0..13971d932 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -427,10 +427,8 @@ "dialogClearAllDataDeletionQuestion": "Would you like to clear only this device, or delete your entire account?", "deviceOnly": "Device Only", "entireAccount": "Entire Account", - "recoveryPhraseSecureMessage": "Secure your account by saving your recovery phrase", "recoveryPhraseSecureTitle": "You're almost finished!", - "recoveryPhraseSecureButtonText": "Secure your account", - "recoveryPhraseRevealMessage": "Reveal your recovery phrase then store it safely to secure it", + "recoveryPhraseRevealMessage": "Secure your account by saving your recovery phrase. Reveal your recovery phrase then store it safely to secure it", "recoveryPhraseRevealButtonText": "Reveal Recovery Phrase", "recoveryPhraseInfoMessage": "Meet your recovery phrase Your recovery phrase is the master key to your session ID - you can use it to restore your Session ID if you lose access to your device, Store your recovery phrase in a safe place, and don't give it to anyone.", "recoveryPhraseCompleteTitle": "Account secured!" diff --git a/ts/components/session/LeftPaneSectionHeader.tsx b/ts/components/session/LeftPaneSectionHeader.tsx index d530d25a4..bd98b5aba 100644 --- a/ts/components/session/LeftPaneSectionHeader.tsx +++ b/ts/components/session/LeftPaneSectionHeader.tsx @@ -8,6 +8,7 @@ import { UserUtils } from '../../session/utils'; import { useDispatch, useSelector } from 'react-redux'; import { disableRecoveryPhrasePrompt } from '../../state/ducks/userConfig'; import { getShowRecoveryPhrasePrompt } from '../../state/selectors/userConfig'; +import { recoveryPhraseModal } from '../../state/ducks/modalDialog'; const Tab = ({ isSelected, @@ -69,9 +70,9 @@ export const LeftPaneSectionHeader = (props: Props) => { }; export const LeftPaneBanner = () => { - const [completion, setCompletion] = useState(80); - const [bodyText, setBodyText] = useState(window.i18n('recoveryPhraseSecureMessage')); - const [buttonText, setButtonText] = useState(window.i18n('recoveryPhraseSecureButtonText')); + const [completion, setCompletion] = useState(90); + const [bodyText, setBodyText] = useState(window.i18n('recoveryPhraseRevealMessage')); + const [buttonText, setButtonText] = useState(window.i18n('recoveryPhraseRevealButtonText')); const [recoveryPhraseHidden, setRecoveryPhraseHidden] = useState(true); const [isCompleted, setIsCompleted] = useState(false); const [bannerTitle, setBannerTitle] = useState(window.i18n('recoveryPhraseSecureTitle')); @@ -79,20 +80,18 @@ export const LeftPaneBanner = () => { const secondsBeforeRemoval = 2 * 1000; const completionText = `${completion}%`; + const dispatch = useDispatch(); + const handleShowRecoveryClick = () => { - setRecoveryPhraseHidden(false); - setBodyText(window.i18n('recoveryPhraseInfoMessage')); - setButtonText(window.i18n('copy')); - }; + // setRecoveryPhraseHidden(false); + // setBodyText(window.i18n('recoveryPhraseInfoMessage')); + // setButtonText(window.i18n('copy')); - const handleSecureClick = () => { - if (completion === 80) { - setCompletion(90); - setBodyText(window.i18n('recoveryPhraseRevealMessage')); - setButtonText(window.i18n('recoveryPhraseRevealButtonText')); - } + // show a modal + dispatch(recoveryPhraseModal({})) }; + const BannerInner = (props: any) => { const dispatch = useDispatch(); @@ -111,9 +110,7 @@ export const LeftPaneBanner = () => { }; const onClick = - completion === 80 - ? handleSecureClick - : completion === 90 + completion === 90 ? recoveryPhraseHidden ? handleShowRecoveryClick : handleCopyPhraseClick From 1783c706b5f156d7271646da884f0b0ca8df5ea2 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Tue, 17 Aug 2021 13:08:44 +1000 Subject: [PATCH 14/19] refactoring seed modal --- ts/components/dialog/SessionSeedModal.tsx | 172 +++++++++------------- 1 file changed, 72 insertions(+), 100 deletions(-) diff --git a/ts/components/dialog/SessionSeedModal.tsx b/ts/components/dialog/SessionSeedModal.tsx index 3a48bac69..87bc05263 100644 --- a/ts/components/dialog/SessionSeedModal.tsx +++ b/ts/components/dialog/SessionSeedModal.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { SessionButton } from '../session/SessionButton'; import { ToastUtils, UserUtils } from '../../session/utils'; @@ -9,73 +9,34 @@ import { QRCode } from 'react-qr-svg'; import { mn_decode } from '../../session/crypto/mnemonic'; import { SessionWrapperModal } from '../session/SessionWrapperModal'; import { SpacerLG, SpacerSM, SpacerXS } from '../basic/Text'; -import autoBind from 'auto-bind'; import { recoveryPhraseModal } from '../../state/ducks/modalDialog'; +import { useEffect } from 'react'; -interface State { - error: string; - loadingPassword: boolean; - loadingSeed: boolean; - recoveryPhrase: string; - hasPassword: boolean | null; - passwordHash: string; - passwordValid: boolean; +interface Props { + onClickOk?: () => any; } -class SessionSeedModalInner extends React.Component<{}, State> { - constructor(props: any) { - super(props); - - this.state = { - error: '', - loadingPassword: true, - loadingSeed: true, - recoveryPhrase: '', - hasPassword: null, - passwordHash: '', - passwordValid: false, - }; - - autoBind(this); - } +const SessionSeedModalInner = (props: Props) => { + const [error, setError] = useState(''); + const [loadingPassword, setLoadingPassword] = useState(true); + const [loadingSeed, setLoadingSeed] = useState(true); + const [recoveryPhrase, setRecoveryPhrase] = useState(''); + const [hasPassword, setHasPassword] = useState(null); + const [passwordHash, setPasswordHash] = useState(''); + const [passwordValid, setPasswordValid] = useState(false); - public componentDidMount() { + useEffect(() => { setTimeout(() => ($('#seed-input-password') as any).focus(), 100); + void checkHasPassword(); + void getRecoveryPhrase(); + }, []); - void this.checkHasPassword(); - void this.getRecoveryPhrase(); - } - public render() { - const i18n = window.i18n; + const i18n = window.i18n; - const { hasPassword, passwordValid } = this.state; - const loading = this.state.loadingPassword || this.state.loadingSeed; - const onClose = () => window.inboxStore?.dispatch(recoveryPhraseModal(null)); + const onClose = () => window.inboxStore?.dispatch(recoveryPhraseModal(null)); - return ( - <> - {!loading && ( - - - - {hasPassword && !passwordValid ? ( - <>{this.renderPasswordView()} - ) : ( - <>{this.renderSeedView()} - )} - - )} - - ); - } - - private renderPasswordView() { - const error = this.state.error; + const renderPasswordView = () => { const i18n = window.i18n; const onClose = () => window.inboxStore?.dispatch(recoveryPhraseModal(null)); @@ -87,7 +48,7 @@ class SessionSeedModalInner extends React.Component<{}, State> { type="password" id="seed-input-password" placeholder={i18n('password')} - onKeyUp={this.onEnter} + onKeyUp={onEnter} /> {error && ( @@ -100,7 +61,7 @@ class SessionSeedModalInner extends React.Component<{}, State> {
- +
@@ -108,12 +69,12 @@ class SessionSeedModalInner extends React.Component<{}, State> { ); } - private renderSeedView() { + const renderSeedView = () => { const i18n = window.i18n; const bgColor = '#FFFFFF'; const fgColor = '#1B1B1B'; - const hexEncodedSeed = mn_decode(this.state.recoveryPhrase, 'english'); + const hexEncodedSeed = mn_decode(recoveryPhrase, 'english'); return ( <> @@ -121,7 +82,7 @@ class SessionSeedModalInner extends React.Component<{}, State> {

{i18n('recoveryPhraseSavePromptMain')}

- {this.state.recoveryPhrase} + {recoveryPhrase}
@@ -132,7 +93,7 @@ class SessionSeedModalInner extends React.Component<{}, State> { { - this.copyRecoveryPhrase(this.state.recoveryPhrase); + copyRecoveryPhrase(recoveryPhrase); }} />
@@ -140,77 +101,88 @@ class SessionSeedModalInner extends React.Component<{}, State> { ); } - private confirmPassword() { - const passwordHash = this.state.passwordHash; + const confirmPassword = () => { const passwordValue = jQuery('#seed-input-password').val(); const isPasswordValid = PasswordUtil.matchesHash(passwordValue as string, passwordHash); if (!passwordValue) { - this.setState({ - error: window.i18n('noGivenPassword'), - }); - + setError('noGivenPassword'); return false; } if (passwordHash && !isPasswordValid) { - this.setState({ - error: window.i18n('invalidPassword'), - }); - + setError('invalidPassword'); return false; } - this.setState({ - passwordValid: true, - error: '', - }); - - window.removeEventListener('keyup', this.onEnter); + setPasswordValid(true); + setError(''); + window.removeEventListener('keyup', onEnter); return true; } - private async checkHasPassword() { - if (!this.state.loadingPassword) { + const checkHasPassword = async () => { + if (!loadingPassword) { return; } const hash = await getPasswordHash(); - this.setState({ - hasPassword: !!hash, - passwordHash: hash || '', - loadingPassword: false, - }); + setHasPassword(!!hash); + setPasswordHash(hash || ''); + setLoadingPassword(false); } - private async getRecoveryPhrase() { - if (this.state.recoveryPhrase) { + const getRecoveryPhrase = async () => { + if (recoveryPhrase) { return false; } - - const recoveryPhrase = UserUtils.getCurrentRecoveryPhrase(); - - this.setState({ - recoveryPhrase, - loadingSeed: false, - }); + const newRecoveryPhrase = UserUtils.getCurrentRecoveryPhrase(); + setRecoveryPhrase(newRecoveryPhrase); + setLoadingSeed(false); return true; } - private copyRecoveryPhrase(recoveryPhrase: string) { + const copyRecoveryPhrase = (recoveryPhrase: string) => { window.clipboard.writeText(recoveryPhrase); ToastUtils.pushCopiedToClipBoard(); window.inboxStore?.dispatch(recoveryPhraseModal(null)); } - private onEnter(event: any) { + const onEnter = (event: any) => { if (event.key === 'Enter') { - this.confirmPassword(); + confirmPassword(); } } + + if (Math.random() > 0.5) { + return null; + } + + return ( + <> + {!loadingSeed && ( + + + + {hasPassword && !passwordValid ? ( + <>{renderPasswordView()} + ) : ( + <>{renderSeedView()} + )} + + )} + : + + ); } -export const SessionSeedModal = withTheme(SessionSeedModalInner); +// withTheme(SessionSeedModalInner) +// export const SessionSeedModal = withTheme(SessionSeedModalInner); +export const SessionSeedModal = SessionSeedModalInner; From f255f674c8c1a3008864463d881d5e30576075f4 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Tue, 17 Aug 2021 15:11:40 +1000 Subject: [PATCH 15/19] Refactoring subcomponents to fix ts-lint errors. --- ts/components/dialog/SessionSeedModal.tsx | 230 +++++++++--------- .../session/LeftPaneSectionHeader.tsx | 89 +++---- 2 files changed, 148 insertions(+), 171 deletions(-) diff --git a/ts/components/dialog/SessionSeedModal.tsx b/ts/components/dialog/SessionSeedModal.tsx index 87bc05263..a8261c0f3 100644 --- a/ts/components/dialog/SessionSeedModal.tsx +++ b/ts/components/dialog/SessionSeedModal.tsx @@ -1,8 +1,7 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { SessionButton } from '../session/SessionButton'; import { ToastUtils, UserUtils } from '../../session/utils'; -import { withTheme } from 'styled-components'; import { PasswordUtil } from '../../util'; import { getPasswordHash } from '../../data/data'; import { QRCode } from 'react-qr-svg'; @@ -10,97 +9,19 @@ import { mn_decode } from '../../session/crypto/mnemonic'; import { SessionWrapperModal } from '../session/SessionWrapperModal'; import { SpacerLG, SpacerSM, SpacerXS } from '../basic/Text'; import { recoveryPhraseModal } from '../../state/ducks/modalDialog'; -import { useEffect } from 'react'; -interface Props { - onClickOk?: () => any; +interface PasswordProps { + setPasswordValid: (val: boolean) => any; + passwordHash: string; } -const SessionSeedModalInner = (props: Props) => { - const [error, setError] = useState(''); - const [loadingPassword, setLoadingPassword] = useState(true); - const [loadingSeed, setLoadingSeed] = useState(true); - const [recoveryPhrase, setRecoveryPhrase] = useState(''); - const [hasPassword, setHasPassword] = useState(null); - const [passwordHash, setPasswordHash] = useState(''); - const [passwordValid, setPasswordValid] = useState(false); - - useEffect(() => { - setTimeout(() => ($('#seed-input-password') as any).focus(), 100); - void checkHasPassword(); - void getRecoveryPhrase(); - }, []); - - +const Password = (props: PasswordProps) => { + const { setPasswordValid, passwordHash } = props; const i18n = window.i18n; + const [error, setError] = useState(''); const onClose = () => window.inboxStore?.dispatch(recoveryPhraseModal(null)); - const renderPasswordView = () => { - const i18n = window.i18n; - - const onClose = () => window.inboxStore?.dispatch(recoveryPhraseModal(null)); - - return ( - <> -

{i18n('showRecoveryPhrasePasswordRequest')}

- - - {error && ( - <> - -
{error}
- - )} - - - -
- - - -
- - ); - } - - const renderSeedView = () => { - const i18n = window.i18n; - const bgColor = '#FFFFFF'; - const fgColor = '#1B1B1B'; - - const hexEncodedSeed = mn_decode(recoveryPhrase, 'english'); - - return ( - <> -
-

{i18n('recoveryPhraseSavePromptMain')}

- - - {recoveryPhrase} -
- -
- -
- -
- { - copyRecoveryPhrase(recoveryPhrase); - }} - /> -
- - ); - } - const confirmPassword = () => { const passwordValue = jQuery('#seed-input-password').val(); const isPasswordValid = PasswordUtil.matchesHash(passwordValue as string, passwordHash); @@ -120,7 +41,111 @@ const SessionSeedModalInner = (props: Props) => { window.removeEventListener('keyup', onEnter); return true; - } + }; + + const onEnter = (event: any) => { + if (event.key === 'Enter') { + confirmPassword(); + } + }; + + return ( + <> +

{i18n('showRecoveryPhrasePasswordRequest')}

+ + + {error && ( + <> + +
{error}
+ + )} + + + +
+ + + +
+ + ); +}; + +interface SeedProps { + recoveryPhrase: string; + onClickCopy?: () => any; +} + +const Seed = (props: SeedProps) => { + const { recoveryPhrase, onClickCopy } = props; + const i18n = window.i18n; + const bgColor = '#FFFFFF'; + const fgColor = '#1B1B1B'; + + const hexEncodedSeed = mn_decode(recoveryPhrase, 'english'); + + const copyRecoveryPhrase = (recoveryPhraseToCopy: string) => { + window.clipboard.writeText(recoveryPhraseToCopy); + ToastUtils.pushCopiedToClipBoard(); + if (onClickCopy) { + onClickCopy(); + } + window.inboxStore?.dispatch(recoveryPhraseModal(null)); + }; + + return ( + <> +
+

{i18n('recoveryPhraseSavePromptMain')}

+ + + {recoveryPhrase} +
+ +
+ +
+ +
+ { + copyRecoveryPhrase(recoveryPhrase); + }} + /> +
+ + ); +}; + +interface ModalInnerProps { + onClickOk?: () => any; +} + +const SessionSeedModalInner = (props: ModalInnerProps) => { + const { onClickOk } = props; + const [loadingPassword, setLoadingPassword] = useState(true); + const [loadingSeed, setLoadingSeed] = useState(true); + const [recoveryPhrase, setRecoveryPhrase] = useState(''); + const [hasPassword, setHasPassword] = useState(null); + const [passwordValid, setPasswordValid] = useState(false); + const [passwordHash, setPasswordHash] = useState(''); + + useEffect(() => { + setTimeout(() => ($('#seed-input-password') as any).focus(), 100); + void checkHasPassword(); + void getRecoveryPhrase(); + }, []); + + const i18n = window.i18n; + + const onClose = () => window.inboxStore?.dispatch(recoveryPhraseModal(null)); const checkHasPassword = async () => { if (!loadingPassword) { @@ -131,7 +156,7 @@ const SessionSeedModalInner = (props: Props) => { setHasPassword(!!hash); setPasswordHash(hash || ''); setLoadingPassword(false); - } + }; const getRecoveryPhrase = async () => { if (recoveryPhrase) { @@ -142,24 +167,7 @@ const SessionSeedModalInner = (props: Props) => { setLoadingSeed(false); return true; - } - - const copyRecoveryPhrase = (recoveryPhrase: string) => { - window.clipboard.writeText(recoveryPhrase); - - ToastUtils.pushCopiedToClipBoard(); - window.inboxStore?.dispatch(recoveryPhraseModal(null)); - } - - const onEnter = (event: any) => { - if (event.key === 'Enter') { - confirmPassword(); - } - } - - if (Math.random() > 0.5) { - return null; - } + }; return ( <> @@ -172,17 +180,15 @@ const SessionSeedModalInner = (props: Props) => { {hasPassword && !passwordValid ? ( - <>{renderPasswordView()} + ) : ( - <>{renderSeedView()} + )} )} : - ); -} + ); +}; -// withTheme(SessionSeedModalInner) -// export const SessionSeedModal = withTheme(SessionSeedModalInner); export const SessionSeedModal = SessionSeedModalInner; diff --git a/ts/components/session/LeftPaneSectionHeader.tsx b/ts/components/session/LeftPaneSectionHeader.tsx index bd98b5aba..c27280053 100644 --- a/ts/components/session/LeftPaneSectionHeader.tsx +++ b/ts/components/session/LeftPaneSectionHeader.tsx @@ -2,13 +2,14 @@ import React, { useState } from 'react'; import classNames from 'classnames'; import { SessionIcon, SessionIconSize, SessionIconType } from './icon'; import styled, { DefaultTheme, useTheme } from 'styled-components'; -import { SessionButton, SessionButtonColor, SessionButtonType } from './SessionButton'; +import { SessionButton, SessionButtonType } from './SessionButton'; import { Constants } from '../../session'; import { UserUtils } from '../../session/utils'; import { useDispatch, useSelector } from 'react-redux'; import { disableRecoveryPhrasePrompt } from '../../state/ducks/userConfig'; import { getShowRecoveryPhrasePrompt } from '../../state/selectors/userConfig'; import { recoveryPhraseModal } from '../../state/ducks/modalDialog'; +import { Flex } from '../basic/Flex'; const Tab = ({ isSelected, @@ -50,7 +51,7 @@ export const LeftPaneSectionHeader = (props: Props) => { const showRecoveryPhrasePrompt = useSelector(getShowRecoveryPhrasePrompt); return ( - +
{label && } {buttonIcon && ( @@ -65,7 +66,7 @@ export const LeftPaneSectionHeader = (props: Props) => { )}
{showRecoveryPhrasePrompt && } -
+ ); }; @@ -82,49 +83,31 @@ export const LeftPaneBanner = () => { const dispatch = useDispatch(); - const handleShowRecoveryClick = () => { - // setRecoveryPhraseHidden(false); - // setBodyText(window.i18n('recoveryPhraseInfoMessage')); - // setButtonText(window.i18n('copy')); - - // show a modal - dispatch(recoveryPhraseModal({})) + const showRecoveryPhraseModal = () => { + dispatch( + recoveryPhraseModal({ + onClickOk: () => { + setCompletion(100); + setBannerTitle(window.i18n('recoveryPhraseCompleteTitle')); + setBodyText(''); + setRecoveryPhraseHidden(true); + setIsCompleted(true); + + // remove banner after a small delay + setTimeout(() => { + dispatch(disableRecoveryPhrasePrompt()); + }, secondsBeforeRemoval); + }, + }) + ); }; - - const BannerInner = (props: any) => { - const dispatch = useDispatch(); - - const handleCopyPhraseClick = async () => { - await navigator.clipboard.writeText(recoveryPhrase); - setCompletion(100); - setBannerTitle(window.i18n('recoveryPhraseCompleteTitle')); - setBodyText(''); - setRecoveryPhraseHidden(true); - setIsCompleted(true); - - // remove banner after a small delay - setTimeout(() => { - dispatch(disableRecoveryPhrasePrompt()); - }, secondsBeforeRemoval); - }; - - const onClick = - completion === 90 - ? recoveryPhraseHidden - ? handleShowRecoveryClick - : handleCopyPhraseClick - : null; - + const BannerInner = () => { return (

{bodyText}

{!recoveryPhraseHidden && ( - + {recoveryPhrase} )} @@ -132,14 +115,13 @@ export const LeftPaneBanner = () => { )}
); }; - const flexDirection = completion === 90 && handleShowRecoveryClick ? 'column' : 'row'; const theme = useTheme(); return ( @@ -150,9 +132,13 @@ export const LeftPaneBanner = () => { {bannerTitle} {completionText} - + - + ); }; @@ -237,18 +223,3 @@ const StyledRecoveryPhrase = styled.p` padding: 5px; border: ${(props: StyledRecoveryPhraseProps) => props.theme.colors.sessionBorderHighContrast}; `; - -interface StyledBannerContainerProps { - flexDirection?: string; -} -export const StyledBannerContainer = styled.div` - display: flex; - flex-direction: ${(p: StyledBannerContainerProps) => p.flexDirection}; - justify-content: space-between; - padding: ${Constants.UI.SPACING.marginSm}; -`; - -export const StyledLeftPaneHeaderContainer = styled.div` - display: flex; - flex-direction: column; -`; From 48b4aab63c8956c0b0ec622582f4ed4897894fd3 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Wed, 18 Aug 2021 11:17:42 +1000 Subject: [PATCH 16/19] applying PR fixes. --- _locales/en/messages.json | 1 - ts/components/session/LeftPaneSectionHeader.tsx | 3 +-- ts/components/session/conversation/SessionRecording.tsx | 1 - ts/state/ducks/SessionTheme.tsx | 7 ++++--- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 09586ad87..b68e6783c 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -432,6 +432,5 @@ "recoveryPhraseSecureTitle": "You're almost finished!", "recoveryPhraseRevealMessage": "Secure your account by saving your recovery phrase. Reveal your recovery phrase then store it safely to secure it", "recoveryPhraseRevealButtonText": "Reveal Recovery Phrase", - "recoveryPhraseInfoMessage": "Meet your recovery phrase Your recovery phrase is the master key to your session ID - you can use it to restore your Session ID if you lose access to your device, Store your recovery phrase in a safe place, and don't give it to anyone.", "recoveryPhraseCompleteTitle": "Account secured!" } diff --git a/ts/components/session/LeftPaneSectionHeader.tsx b/ts/components/session/LeftPaneSectionHeader.tsx index c27280053..33ad23ff2 100644 --- a/ts/components/session/LeftPaneSectionHeader.tsx +++ b/ts/components/session/LeftPaneSectionHeader.tsx @@ -73,7 +73,6 @@ export const LeftPaneSectionHeader = (props: Props) => { export const LeftPaneBanner = () => { const [completion, setCompletion] = useState(90); const [bodyText, setBodyText] = useState(window.i18n('recoveryPhraseRevealMessage')); - const [buttonText, setButtonText] = useState(window.i18n('recoveryPhraseRevealButtonText')); const [recoveryPhraseHidden, setRecoveryPhraseHidden] = useState(true); const [isCompleted, setIsCompleted] = useState(false); const [bannerTitle, setBannerTitle] = useState(window.i18n('recoveryPhraseSecureTitle')); @@ -114,7 +113,7 @@ export const LeftPaneBanner = () => { {!isCompleted && ( )} diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index 94d3a9beb..63ee29b35 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -96,7 +96,6 @@ class SessionRecordingInner extends React.Component { // tslint:disable-next-line: cyclomatic-complexity public render() { const { - actionHover, isPlaying, isPaused, isRecording, diff --git a/ts/state/ducks/SessionTheme.tsx b/ts/state/ducks/SessionTheme.tsx index c9848a9fc..587a9f5e3 100644 --- a/ts/state/ducks/SessionTheme.tsx +++ b/ts/state/ducks/SessionTheme.tsx @@ -14,9 +14,10 @@ const borderLightThemeColor = '#f1f1f1'; const borderDarkThemeColor = '#ffffff0F'; const borderHighContrastLightTheme = '#afafaf'; const borderHighContrastDarkTheme = '#484848'; -const borderAvatarColor = '#00000059'; -const borderLightTheme = '#f1f1f1'; -const borderDarkTheme = '#ffffff0F'; + +// const borderAvatarColor = '#00000059'; +// const borderLightTheme = '#f1f1f1'; +// const borderDarkTheme = '#ffffff0F'; const common = { fonts: { From b26ec1996dc953f7f9b80861c8448a79b907392c Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Wed, 18 Aug 2021 11:56:53 +1000 Subject: [PATCH 17/19] yarn ready --- .../session/conversation/SessionRecording.tsx | 8 +------- ts/receiver/attachments.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/ts/components/session/conversation/SessionRecording.tsx b/ts/components/session/conversation/SessionRecording.tsx index 63ee29b35..7b6cf12dd 100644 --- a/ts/components/session/conversation/SessionRecording.tsx +++ b/ts/components/session/conversation/SessionRecording.tsx @@ -95,13 +95,7 @@ class SessionRecordingInner extends React.Component { // tslint:disable-next-line: cyclomatic-complexity public render() { - const { - isPlaying, - isPaused, - isRecording, - startTimestamp, - nowTimestamp, - } = this.state; + const { isPlaying, isPaused, isRecording, startTimestamp, nowTimestamp } = this.state; const hasRecordingAndPaused = !isRecording && !isPlaying; const hasRecording = !!this.audioElement?.duration && this.audioElement?.duration > 0; diff --git a/ts/receiver/attachments.ts b/ts/receiver/attachments.ts index a9134a75e..a4b804fb0 100644 --- a/ts/receiver/attachments.ts +++ b/ts/receiver/attachments.ts @@ -171,12 +171,12 @@ async function processPreviews(message: MessageModel, convo: ConversationModel): const image = message.isTrustedForAttachmentDownload() ? await AttachmentDownloads.addJob(item.image, { - messageId: message.id, - type: 'preview', - index, - isOpenGroupV2, - openGroupV2Details, - }) + messageId: message.id, + type: 'preview', + index, + isOpenGroupV2, + openGroupV2Details, + }) : null; return { ...item, image }; From 0cbcc001800eebea88571c902d6a70a5a6fd1a0c Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Wed, 18 Aug 2021 14:18:09 +1000 Subject: [PATCH 18/19] Adding registration stages file. Somehow became untracked. --- .../registration/RegistrationStages.tsx | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 ts/components/session/registration/RegistrationStages.tsx diff --git a/ts/components/session/registration/RegistrationStages.tsx b/ts/components/session/registration/RegistrationStages.tsx new file mode 100644 index 000000000..1490ff002 --- /dev/null +++ b/ts/components/session/registration/RegistrationStages.tsx @@ -0,0 +1,215 @@ +import React, { createContext, useEffect, useState } from 'react'; +import { PromiseUtils, StringUtils, ToastUtils, UserUtils } from '../../../session/utils'; +import { getConversationController } from '../../../session/conversations'; +import { createOrUpdateItem, removeAll } from '../../../data/data'; +import { SignUpTab } from './SignUpTab'; +import { SignInTab } from './SignInTab'; +import { trigger } from '../../../shims/events'; +import { + generateMnemonic, + registerSingleDevice, + sessionGenerateKeyPair, + signInByLinkingDevice, +} from '../../../util/accountManager'; +import { fromHex } from '../../../session/utils/String'; +import { TaskTimedOutError } from '../../../session/utils/Promise'; +import { mn_decode } from '../../../session/crypto/mnemonic'; +import { getSwarmPollingInstance } from '../../../session/snode_api/swarmPolling'; + +export const MAX_USERNAME_LENGTH = 20; +// tslint:disable: use-simple-attributes + +export async function resetRegistration() { + await removeAll(); + await window.storage.reset(); + await window.storage.fetch(); + getConversationController().reset(); + await getConversationController().load(); +} + +/** + * Returns undefined if an error happened, or the trim userName. + * + * Be sure to use the trimmed userName for creating the account. + */ +const displayNameIsValid = (displayName: string): undefined | string => { + const trimName = displayName.trim(); + + if (!trimName) { + window?.log?.warn('invalid trimmed name for registration'); + ToastUtils.pushToastError('invalidDisplayName', window.i18n('displayNameEmpty')); + return undefined; + } + return trimName; +}; + +export async function signUp(signUpDetails: { + displayName: string; + generatedRecoveryPhrase: string; +}) { + const { displayName, generatedRecoveryPhrase } = signUpDetails; + window?.log?.info('SIGNING UP'); + + const trimName = displayNameIsValid(displayName); + // shows toast to user about the error + if (!trimName) { + return; + } + + try { + await resetRegistration(); + await registerSingleDevice(generatedRecoveryPhrase, 'english', trimName); + await createOrUpdateItem({ + id: 'hasSyncedInitialConfigurationItem', + value: true, + }); + trigger('openInbox'); + } catch (e) { + await resetRegistration(); + + ToastUtils.pushToastError('registrationError', `Error: ${e.message || 'Something went wrong'}`); + window?.log?.warn('exception during registration:', e); + } +} + +/** + * Sign in/restore from seed. + * Ask for a display name, as we will drop incoming ConfigurationMessages if any are saved on the swarm. + * We will handle a ConfigurationMessage + */ +export async function signInWithRecovery(signInDetails: { + displayName: string; + userRecoveryPhrase: string; +}) { + const { displayName, userRecoveryPhrase } = signInDetails; + window?.log?.info('RESTORING FROM SEED'); + const trimName = displayNameIsValid(displayName); + // shows toast to user about the error + if (!trimName) { + return; + } + + try { + await resetRegistration(); + + await registerSingleDevice(userRecoveryPhrase, 'english', trimName); + trigger('openInbox'); + } catch (e) { + await resetRegistration(); + ToastUtils.pushToastError('registrationError', `Error: ${e.message || 'Something went wrong'}`); + window?.log?.warn('exception during registration:', e); + } +} + +/** + * This is will try to sign in with the user recovery phrase. + * If no ConfigurationMessage is received in 60seconds, the loading will be canceled. + */ +export async function signInWithLinking(signInDetails: { userRecoveryPhrase: string }) { + const { userRecoveryPhrase } = signInDetails; + window?.log?.info('LINKING DEVICE'); + + try { + await resetRegistration(); + await signInByLinkingDevice(userRecoveryPhrase, 'english'); + let displayNameFromNetwork = ''; + await getSwarmPollingInstance().start(); + + await PromiseUtils.waitForTask(done => { + window.Whisper.events.on('configurationMessageReceived', (displayName: string) => { + window.Whisper.events.off('configurationMessageReceived'); + UserUtils.setSignInByLinking(false); + done(displayName); + + displayNameFromNetwork = displayName; + }); + }, 60000); + if (displayNameFromNetwork.length) { + // display name, avatars, groups and contacts should already be handled when this event was triggered. + window?.log?.info('We got a displayName from network: '); + } else { + window?.log?.info('Got a config message from network but without a displayName...'); + throw new Error('Got a config message from network but without a displayName...'); + } + // Do not set the lastProfileUpdateTimestamp. + // We expect to get a display name from a configuration message while we are loading messages of this user + trigger('openInbox'); + } catch (e) { + await resetRegistration(); + if (e instanceof TaskTimedOutError) { + ToastUtils.pushToastError( + 'registrationError', + 'Could not find your display name. Please Sign In by Restoring Your Account instead.' + ); + } else { + ToastUtils.pushToastError( + 'registrationError', + `Error: ${e.message || 'Something went wrong'}` + ); + } + window?.log?.warn('exception during registration:', e); + } +} + +export enum RegistrationPhase { + Start, + SignIn, + SignUp, +} + +interface RegistrationPhaseContext { + registrationPhase: RegistrationPhase; + setRegistrationPhase: (phase: RegistrationPhase) => void; +} + +export const RegistrationContext = createContext({ + registrationPhase: RegistrationPhase.Start, + setRegistrationPhase: () => undefined, +}); + +export const RegistrationStages = () => { + const [generatedRecoveryPhrase, setGeneratedRecoveryPhrase] = useState(''); + const [hexGeneratedPubKey, setHexGeneratedPubKey] = useState(''); + const [registrationPhase, setRegistrationPhase] = useState(RegistrationPhase.Start); + + useEffect(() => { + void generateMnemonicAndKeyPair(); + void resetRegistration(); + }, []); + + const generateMnemonicAndKeyPair = async () => { + if (generatedRecoveryPhrase === '') { + const mnemonic = await generateMnemonic(); + + let seedHex = mn_decode(mnemonic); + // handle shorter than 32 bytes seeds + const privKeyHexLength = 32 * 2; + if (seedHex.length !== privKeyHexLength) { + seedHex = seedHex.concat('0'.repeat(32)); + seedHex = seedHex.substring(0, privKeyHexLength); + } + const seed = fromHex(seedHex); + const keyPair = await sessionGenerateKeyPair(seed); + const newHexPubKey = StringUtils.decode(keyPair.pubKey, 'hex'); + + setGeneratedRecoveryPhrase(mnemonic); + setHexGeneratedPubKey(newHexPubKey); // our 'frontend' sessionID + } + }; + + return ( +
+ + {(registrationPhase === RegistrationPhase.Start || + registrationPhase === RegistrationPhase.SignUp) && ( + + )} + {(registrationPhase === RegistrationPhase.Start || + registrationPhase === RegistrationPhase.SignIn) && } + +
+ ); +}; From dbc19adeb8c280b98ab51747522acb4842cac754 Mon Sep 17 00:00:00 2001 From: Warrick Corfe-Tan Date: Thu, 19 Aug 2021 12:10:11 +1000 Subject: [PATCH 19/19] Adding PR changes, running yarn ready --- _locales/en/messages.json | 5 +- ts/components/dialog/SessionSeedModal.tsx | 10 +- .../session/LeftPaneSectionHeader.tsx | 119 +++++------------- 3 files changed, 38 insertions(+), 96 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index b68e6783c..51b752fd2 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -430,7 +430,6 @@ "deviceOnly": "Device Only", "entireAccount": "Entire Account", "recoveryPhraseSecureTitle": "You're almost finished!", - "recoveryPhraseRevealMessage": "Secure your account by saving your recovery phrase. Reveal your recovery phrase then store it safely to secure it", - "recoveryPhraseRevealButtonText": "Reveal Recovery Phrase", - "recoveryPhraseCompleteTitle": "Account secured!" + "recoveryPhraseRevealMessage": "Secure your account by saving your recovery phrase. Reveal your recovery phrase then store it safely to secure it.", + "recoveryPhraseRevealButtonText": "Reveal Recovery Phrase" } diff --git a/ts/components/dialog/SessionSeedModal.tsx b/ts/components/dialog/SessionSeedModal.tsx index a8261c0f3..3e61e716d 100644 --- a/ts/components/dialog/SessionSeedModal.tsx +++ b/ts/components/dialog/SessionSeedModal.tsx @@ -9,6 +9,7 @@ import { mn_decode } from '../../session/crypto/mnemonic'; import { SessionWrapperModal } from '../session/SessionWrapperModal'; import { SpacerLG, SpacerSM, SpacerXS } from '../basic/Text'; import { recoveryPhraseModal } from '../../state/ducks/modalDialog'; +import { useDispatch } from 'react-redux'; interface PasswordProps { setPasswordValid: (val: boolean) => any; @@ -19,8 +20,9 @@ const Password = (props: PasswordProps) => { const { setPasswordValid, passwordHash } = props; const i18n = window.i18n; const [error, setError] = useState(''); + const dispatch = useDispatch(); - const onClose = () => window.inboxStore?.dispatch(recoveryPhraseModal(null)); + const onClose = () => dispatch(recoveryPhraseModal(null)); const confirmPassword = () => { const passwordValue = jQuery('#seed-input-password').val(); @@ -87,6 +89,7 @@ const Seed = (props: SeedProps) => { const i18n = window.i18n; const bgColor = '#FFFFFF'; const fgColor = '#1B1B1B'; + const dispatch = useDispatch(); const hexEncodedSeed = mn_decode(recoveryPhrase, 'english'); @@ -96,7 +99,7 @@ const Seed = (props: SeedProps) => { if (onClickCopy) { onClickCopy(); } - window.inboxStore?.dispatch(recoveryPhraseModal(null)); + dispatch(recoveryPhraseModal(null)); }; return ( @@ -136,6 +139,7 @@ const SessionSeedModalInner = (props: ModalInnerProps) => { const [hasPassword, setHasPassword] = useState(null); const [passwordValid, setPasswordValid] = useState(false); const [passwordHash, setPasswordHash] = useState(''); + const dispatch = useDispatch(); useEffect(() => { setTimeout(() => ($('#seed-input-password') as any).focus(), 100); @@ -145,7 +149,7 @@ const SessionSeedModalInner = (props: ModalInnerProps) => { const i18n = window.i18n; - const onClose = () => window.inboxStore?.dispatch(recoveryPhraseModal(null)); + const onClose = () => dispatch(recoveryPhraseModal(null)); const checkHasPassword = async () => { if (!loadingPassword) { diff --git a/ts/components/session/LeftPaneSectionHeader.tsx b/ts/components/session/LeftPaneSectionHeader.tsx index 33ad23ff2..fcff680a3 100644 --- a/ts/components/session/LeftPaneSectionHeader.tsx +++ b/ts/components/session/LeftPaneSectionHeader.tsx @@ -1,10 +1,8 @@ -import React, { useState } from 'react'; +import React from 'react'; import classNames from 'classnames'; import { SessionIcon, SessionIconSize, SessionIconType } from './icon'; -import styled, { DefaultTheme, useTheme } from 'styled-components'; +import styled, { useTheme } from 'styled-components'; import { SessionButton, SessionButtonType } from './SessionButton'; -import { Constants } from '../../session'; -import { UserUtils } from '../../session/utils'; import { useDispatch, useSelector } from 'react-redux'; import { disableRecoveryPhrasePrompt } from '../../state/ducks/userConfig'; import { getShowRecoveryPhrasePrompt } from '../../state/selectors/userConfig'; @@ -51,11 +49,11 @@ export const LeftPaneSectionHeader = (props: Props) => { const showRecoveryPhrasePrompt = useSelector(getShowRecoveryPhrasePrompt); return ( - +
{label && } {buttonIcon && ( - + { }; export const LeftPaneBanner = () => { - const [completion, setCompletion] = useState(90); - const [bodyText, setBodyText] = useState(window.i18n('recoveryPhraseRevealMessage')); - const [recoveryPhraseHidden, setRecoveryPhraseHidden] = useState(true); - const [isCompleted, setIsCompleted] = useState(false); - const [bannerTitle, setBannerTitle] = useState(window.i18n('recoveryPhraseSecureTitle')); - const recoveryPhrase = UserUtils.getCurrentRecoveryPhrase(); - const secondsBeforeRemoval = 2 * 1000; - const completionText = `${completion}%`; - const dispatch = useDispatch(); const showRecoveryPhraseModal = () => { dispatch( recoveryPhraseModal({ onClickOk: () => { - setCompletion(100); - setBannerTitle(window.i18n('recoveryPhraseCompleteTitle')); - setBodyText(''); - setRecoveryPhraseHidden(true); - setIsCompleted(true); - - // remove banner after a small delay - setTimeout(() => { - dispatch(disableRecoveryPhrasePrompt()); - }, secondsBeforeRemoval); + dispatch(disableRecoveryPhrasePrompt()); }, }) ); @@ -104,19 +84,12 @@ export const LeftPaneBanner = () => { const BannerInner = () => { return ( -

{bodyText}

- {!recoveryPhraseHidden && ( - - {recoveryPhrase} - - )} - {!isCompleted && ( - - )} +

{window.i18n('recoveryPhraseRevealMessage')}

+
); }; @@ -124,17 +97,17 @@ export const LeftPaneBanner = () => { const theme = useTheme(); return ( - + - + - - {bannerTitle} {completionText} + + {window.i18n('recoveryPhraseSecureTitle')} 90% @@ -142,61 +115,37 @@ export const LeftPaneBanner = () => { ); }; -interface StyledProgressBarContainerProps { - theme: DefaultTheme; -} const StyledProgressBarContainer = styled.div` width: 100%; height: 5px; flex-direction: row; - background: ${(p: StyledProgressBarContainerProps) => p.theme.colors.sessionBorderColor}; + background: ${p => p.theme.colors.sessionBorderColor}; `; -interface StyledProgressBarProps { - width: string; - color?: string; -} - const StyledProgressBarInner = styled.div` - background: ${(p: StyledProgressBarProps) => p.color}; - width: ${(p: StyledProgressBarProps) => p.width}; + background: ${p => p.theme.colors.accent}; + width: 90%; transition: width 0.5s ease-in; height: 100%; `; -interface StyledBannerTitle { - theme: DefaultTheme; -} export const StyledBannerTitle = styled.div` line-height: 1.3; - font-size: ${(p: StyledBannerTitle) => p.theme.common.fonts.md}; + font-size: ${p => p.theme.common.fonts.md}; font-weight: bold; - margin: ${Constants.UI.SPACING.marginSm} ${Constants.UI.SPACING.marginSm} 0 - ${Constants.UI.SPACING.marginSm}; + margin: ${p => p.theme.common.margins.sm} ${p => p.theme.common.margins.sm} 0 + ${p => p.theme.common.margins.sm}; span { - color: ${(p: StyledBannerTitle) => p.theme.colors.textAccent}; + color: ${p => p.theme.colors.textAccent}; } `; -interface StyledLeftPaneBannerProps { - isCompleted?: boolean; - border: string; - theme: DefaultTheme; -} export const StyledLeftPaneBanner = styled.div` - background: ${(p: StyledLeftPaneBannerProps) => p.theme.colors.recoveryPhraseBannerBackground}; + background: ${p => p.theme.colors.recoveryPhraseBannerBackground}; display: flex; flex-direction: column; - border-bottom: ${(p: StyledLeftPaneBannerProps) => p.border}; - opacity: 1; - transition: opacity 2s; - ${(p: StyledLeftPaneBannerProps) => - p.isCompleted === true - ? ` - opacity: 0; - ` - : null} + border-bottom: ${p => p.theme.colors.sessionBorder}; `; const StyledBannerInner = styled.div` @@ -205,20 +154,10 @@ const StyledBannerInner = styled.div` } .left-pane-banner___phrase { - margin-top: ${Constants.UI.SPACING.marginMd}; + margin-top: ${props => props.theme.common.margins.md}; } .session-button { - margin-top: ${Constants.UI.SPACING.marginSm}; + margin-top: ${props => props.theme.common.margins.sm}; } `; - -interface StyledRecoveryPhraseProps { - theme: DefaultTheme; -} -const StyledRecoveryPhrase = styled.p` - margin: ${Constants.UI.SPACING.marginXs}; - border-radius: 5px; - padding: 5px; - border: ${(props: StyledRecoveryPhraseProps) => props.theme.colors.sessionBorderHighContrast}; -`;