Auth / QR Code: New animated design (#1778)
This commit is contained in:
parent
41a67c9baf
commit
b2ad6a0ae4
9015
package-lock.json
generated
9015
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -110,7 +110,10 @@
|
|||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
"pako": "^2.0.4",
|
"pako": "^2.0.4",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"qr-creator": "^1.0.0",
|
"qr-code-styling": "github:zubiden/qr-code-styling#10f7cf3",
|
||||||
"websocket": "^1.0.34"
|
"websocket": "^1.0.34"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
src/assets/blank.png
Normal file
BIN
src/assets/blank.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 B |
BIN
src/assets/tgs/auth/QrPlane.tgs
Normal file
BIN
src/assets/tgs/auth/QrPlane.tgs
Normal file
Binary file not shown.
@ -113,22 +113,34 @@
|
|||||||
height: 280px;
|
height: 280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-container {
|
.qr-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
transition: transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 300ms;
|
transition: transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 300ms;
|
||||||
|
|
||||||
&.pre-animate {
|
&.pre-animate {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
transform: scale(0.8);
|
transform: scale(0.5);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
canvas {
|
.qr-plane {
|
||||||
padding: 0.625rem;
|
position: absolute;
|
||||||
border-radius: var(--border-radius-default);
|
top: 50%;
|
||||||
background: var(--color-white);
|
left: 50%;
|
||||||
margin-top: -0.625rem;
|
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 {
|
h3 {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import QrCreator from 'qr-creator';
|
import QrCodeStyling from 'qr-code-styling';
|
||||||
import React, {
|
import React, {
|
||||||
FC, useEffect, useRef, memo, useCallback,
|
FC, useEffect, useRef, memo, useCallback, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
@ -10,13 +10,17 @@ import { LangCode } from '../../types';
|
|||||||
import { DEFAULT_LANG_CODE } from '../../config';
|
import { DEFAULT_LANG_CODE } from '../../config';
|
||||||
import { setLanguage } from '../../util/langProvider';
|
import { setLanguage } from '../../util/langProvider';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
|
import { getSuggestedLanguage } from './helpers/getSuggestedLanguage';
|
||||||
|
import getAnimationData from '../common/helpers/animatedAssets';
|
||||||
|
|
||||||
import useLangString from '../../hooks/useLangString';
|
import useLangString from '../../hooks/useLangString';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import { getSuggestedLanguage } from './helpers/getSuggestedLanguage';
|
|
||||||
|
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
|
import AnimatedSticker from '../common/AnimatedSticker';
|
||||||
|
import blankUrl from '../../assets/blank.png';
|
||||||
|
|
||||||
type StateProps =
|
type StateProps =
|
||||||
Pick<GlobalState, 'connectionState' | 'authState' | 'authQrCode'>
|
Pick<GlobalState, 'connectionState' | 'authState' | 'authQrCode'>
|
||||||
@ -25,6 +29,29 @@ type StateProps =
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DATA_PREFIX = 'tg://login?token=';
|
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> = ({
|
const AuthCode: FC<StateProps> = ({
|
||||||
connectionState,
|
connectionState,
|
||||||
@ -43,25 +70,45 @@ const AuthCode: FC<StateProps> = ({
|
|||||||
const qrCodeRef = useRef<HTMLDivElement>(null);
|
const qrCodeRef = useRef<HTMLDivElement>(null);
|
||||||
const continueText = useLangString(suggestedLanguage, 'ContinueOnThisLanguage');
|
const continueText = useLangString(suggestedLanguage, 'ContinueOnThisLanguage');
|
||||||
const [isLoading, markIsLoading, unmarkIsLoading] = useFlag();
|
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(() => {
|
useEffect(() => {
|
||||||
if (!authQrCode || connectionState !== 'connectionStateReady') {
|
if (!animationData) {
|
||||||
return;
|
getAnimationData('QrPlane').then(setAnimationData);
|
||||||
|
}
|
||||||
|
}, [animationData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!authQrCode) {
|
||||||
|
return () => {
|
||||||
|
unmarkQrMounted();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connectionState !== 'connectionStateReady') {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = qrCodeRef.current!;
|
const container = qrCodeRef.current!;
|
||||||
|
|
||||||
container.innerHTML = '';
|
container.parentElement!.classList.remove('pre-animate');
|
||||||
container.classList.remove('pre-animate');
|
|
||||||
|
|
||||||
QrCreator.render({
|
const data = `${DATA_PREFIX}${authQrCode.token}`;
|
||||||
text: `${DATA_PREFIX}${authQrCode.token}`,
|
|
||||||
radius: 0.5,
|
QR_CODE.update({
|
||||||
ecLevel: 'M',
|
data,
|
||||||
fill: '#4E96D4',
|
});
|
||||||
size: 280,
|
|
||||||
}, container);
|
if (!isQrMounted) {
|
||||||
}, [connectionState, authQrCode]);
|
QR_CODE.append(container);
|
||||||
|
markQrMounted();
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, [connectionState, authQrCode, isQrMounted, markQrMounted, unmarkQrMounted]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (connectionState === 'connectionStateReady') {
|
if (connectionState === 'connectionStateReady') {
|
||||||
@ -85,7 +132,25 @@ const AuthCode: FC<StateProps> = ({
|
|||||||
<div id="auth-qr-form" className="custom-scroll">
|
<div id="auth-qr-form" className="custom-scroll">
|
||||||
<div className="auth-form qr">
|
<div className="auth-form qr">
|
||||||
{authQrCode ? (
|
{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>
|
<div key="qr-loading" className="qr-loading"><Loading /></div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -27,6 +27,8 @@ import Cumshot from '../../../assets/tgs/animatedEmojis/Cumshot.tgs';
|
|||||||
import JoinRequest from '../../../assets/tgs/invites/Requests.tgs';
|
import JoinRequest from '../../../assets/tgs/invites/Requests.tgs';
|
||||||
import Invite from '../../../assets/tgs/invites/Invite.tgs';
|
import Invite from '../../../assets/tgs/invites/Invite.tgs';
|
||||||
|
|
||||||
|
import QrPlane from '../../../assets/tgs/auth/QrPlane.tgs';
|
||||||
|
|
||||||
export const ANIMATED_STICKERS_PATHS = {
|
export const ANIMATED_STICKERS_PATHS = {
|
||||||
MonkeyIdle,
|
MonkeyIdle,
|
||||||
MonkeyTracking,
|
MonkeyTracking,
|
||||||
@ -48,6 +50,7 @@ export const ANIMATED_STICKERS_PATHS = {
|
|||||||
Cumshot,
|
Cumshot,
|
||||||
JoinRequest,
|
JoinRequest,
|
||||||
Invite,
|
Invite,
|
||||||
|
QrPlane,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function getAnimationData(name: keyof typeof ANIMATED_STICKERS_PATHS) {
|
export default function getAnimationData(name: keyof typeof ANIMATED_STICKERS_PATHS) {
|
||||||
|
|||||||
@ -165,6 +165,8 @@ $color-message-reaction-own-hover: #b5e0a4;
|
|||||||
--color-skeleton-background: rgba(33, 33, 33, 0.15);
|
--color-skeleton-background: rgba(33, 33, 33, 0.15);
|
||||||
--color-skeleton-foreground: rgba(232, 232, 232, 0.2);
|
--color-skeleton-foreground: rgba(232, 232, 232, 0.2);
|
||||||
|
|
||||||
|
--color-telegram-blue: #{$color-primary};
|
||||||
|
|
||||||
--vh: 1vh;
|
--vh: 1vh;
|
||||||
|
|
||||||
--border-radius-default: 0.75rem;
|
--border-radius-default: 0.75rem;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user