Auth / QR Code: New animated design (#1778)

This commit is contained in:
Alexander Zinchuk 2022-03-25 13:14:51 +01:00
parent 41a67c9baf
commit b2ad6a0ae4
8 changed files with 2493 additions and 6659 deletions

9015
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -110,7 +110,10 @@
"os-browserify": "^0.3.0",
"pako": "^2.0.4",
"path-browserify": "^1.0.1",
"qr-creator": "^1.0.0",
"qr-code-styling": "github:zubiden/qr-code-styling#10f7cf3",
"websocket": "^1.0.34"
},
"optionalDependencies": {
"fsevents": "2.1.2"
}
}

BIN
src/assets/blank.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

View File

@ -113,22 +113,34 @@
height: 280px;
}
.qr-container {
.qr-wrapper {
display: flex;
justify-content: center;
position: relative;
opacity: 1;
transform: scale(1);
transition: transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 300ms;
&.pre-animate {
opacity: 0.5;
transform: scale(0.8);
transform: scale(0.5);
}
}
canvas {
padding: 0.625rem;
border-radius: var(--border-radius-default);
background: var(--color-white);
margin-top: -0.625rem;
}
.qr-plane {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
border-radius: 50%;
background-color: var(--color-telegram-blue);
}
.qr-container {
background-color: white;
border-radius: var(--border-radius-default);
overflow: hidden;
}
h3 {

View File

@ -1,6 +1,6 @@
import QrCreator from 'qr-creator';
import QrCodeStyling from 'qr-code-styling';
import React, {
FC, useEffect, useRef, memo, useCallback,
FC, useEffect, useRef, memo, useCallback, useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
@ -10,13 +10,17 @@ import { LangCode } from '../../types';
import { DEFAULT_LANG_CODE } from '../../config';
import { setLanguage } from '../../util/langProvider';
import renderText from '../common/helpers/renderText';
import { getSuggestedLanguage } from './helpers/getSuggestedLanguage';
import getAnimationData from '../common/helpers/animatedAssets';
import useLangString from '../../hooks/useLangString';
import useFlag from '../../hooks/useFlag';
import useLang from '../../hooks/useLang';
import { getSuggestedLanguage } from './helpers/getSuggestedLanguage';
import Loading from '../ui/Loading';
import Button from '../ui/Button';
import AnimatedSticker from '../common/AnimatedSticker';
import blankUrl from '../../assets/blank.png';
type StateProps =
Pick<GlobalState, 'connectionState' | 'authState' | 'authQrCode'>
@ -25,6 +29,29 @@ type StateProps =
};
const DATA_PREFIX = 'tg://login?token=';
const QR_SIZE = 280;
const QR_PLANE_SIZE = 54;
const QR_CODE = new QrCodeStyling({
width: QR_SIZE,
height: QR_SIZE,
image: blankUrl,
margin: 10,
type: 'svg',
dotsOptions: {
type: 'rounded',
},
cornersSquareOptions: {
type: 'extra-rounded',
},
imageOptions: {
imageSize: 0.4,
margin: 8,
},
qrOptions: {
errorCorrectionLevel: 'M',
},
});
const AuthCode: FC<StateProps> = ({
connectionState,
@ -43,25 +70,45 @@ const AuthCode: FC<StateProps> = ({
const qrCodeRef = useRef<HTMLDivElement>(null);
const continueText = useLangString(suggestedLanguage, 'ContinueOnThisLanguage');
const [isLoading, markIsLoading, unmarkIsLoading] = useFlag();
const [isQrMounted, markQrMounted, unmarkQrMounted] = useFlag();
const [animationData, setAnimationData] = useState<string>();
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
useEffect(() => {
if (!authQrCode || connectionState !== 'connectionStateReady') {
return;
if (!animationData) {
getAnimationData('QrPlane').then(setAnimationData);
}
}, [animationData]);
useEffect(() => {
if (!authQrCode) {
return () => {
unmarkQrMounted();
};
}
if (connectionState !== 'connectionStateReady') {
return undefined;
}
const container = qrCodeRef.current!;
container.innerHTML = '';
container.classList.remove('pre-animate');
container.parentElement!.classList.remove('pre-animate');
QrCreator.render({
text: `${DATA_PREFIX}${authQrCode.token}`,
radius: 0.5,
ecLevel: 'M',
fill: '#4E96D4',
size: 280,
}, container);
}, [connectionState, authQrCode]);
const data = `${DATA_PREFIX}${authQrCode.token}`;
QR_CODE.update({
data,
});
if (!isQrMounted) {
QR_CODE.append(container);
markQrMounted();
}
return undefined;
}, [connectionState, authQrCode, isQrMounted, markQrMounted, unmarkQrMounted]);
useEffect(() => {
if (connectionState === 'connectionStateReady') {
@ -85,7 +132,25 @@ const AuthCode: FC<StateProps> = ({
<div id="auth-qr-form" className="custom-scroll">
<div className="auth-form qr">
{authQrCode ? (
<div key="qr-container" className="qr-container pre-animate" ref={qrCodeRef} />
<div className="qr-wrapper pre-animate" key="qr-wrapper">
<div
key="qr-container"
className="qr-container"
ref={qrCodeRef}
style={`width: ${QR_SIZE}px; height: ${QR_SIZE}px`}
/>
{animationData && (
<AnimatedSticker
id="qrPlane"
className="qr-plane"
size={QR_PLANE_SIZE}
animationData={animationData}
play={isAnimationLoaded}
onLoad={handleAnimationLoad}
key="qrPlane"
/>
)}
</div>
) : (
<div key="qr-loading" className="qr-loading"><Loading /></div>
)}

View File

@ -27,6 +27,8 @@ import Cumshot from '../../../assets/tgs/animatedEmojis/Cumshot.tgs';
import JoinRequest from '../../../assets/tgs/invites/Requests.tgs';
import Invite from '../../../assets/tgs/invites/Invite.tgs';
import QrPlane from '../../../assets/tgs/auth/QrPlane.tgs';
export const ANIMATED_STICKERS_PATHS = {
MonkeyIdle,
MonkeyTracking,
@ -48,6 +50,7 @@ export const ANIMATED_STICKERS_PATHS = {
Cumshot,
JoinRequest,
Invite,
QrPlane,
};
export default function getAnimationData(name: keyof typeof ANIMATED_STICKERS_PATHS) {

View File

@ -165,6 +165,8 @@ $color-message-reaction-own-hover: #b5e0a4;
--color-skeleton-background: rgba(33, 33, 33, 0.15);
--color-skeleton-foreground: rgba(232, 232, 232, 0.2);
--color-telegram-blue: #{$color-primary};
--vh: 1vh;
--border-radius-default: 0.75rem;