diff --git a/images/session/qr/brand.svg b/images/session/qr/brand.svg
deleted file mode 100644
index f54264a60..000000000
--- a/images/session/qr/brand.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/images/session/qr/shield.svg b/images/session/qr/shield.svg
deleted file mode 100644
index 7272b6baa..000000000
--- a/images/session/qr/shield.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/ts/components/SessionQRCode.tsx b/ts/components/SessionQRCode.tsx
index bcaaeb1b8..3fcd56f87 100644
--- a/ts/components/SessionQRCode.tsx
+++ b/ts/components/SessionQRCode.tsx
@@ -1,22 +1,20 @@
-import { MouseEvent, useRef } from 'react';
+import { MouseEvent, useEffect, useRef, useState } from 'react';
import { QRCode } from 'react-qrcode-logo';
import { CSSProperties } from 'styled-components';
-import { ThemeStateType } from '../themes/constants/colors';
-import { THEME_GLOBALS } from '../themes/globals';
-import { AnimatedFlex } from './basic/Flex';
+import { COLORS } from '../themes/constants/colors';
+import { saveQRCode } from '../util/saveQRCode';
+import { Flex } from './basic/Flex';
export type SessionQRCodeProps = {
id: string;
value: string;
size: number;
+ hasLogo: boolean;
backgroundColor?: string;
foregroundColor?: string;
logoImage?: string;
- logoWidth?: number;
- logoHeight?: number;
- logoIsSVG?: boolean;
- theme?: ThemeStateType;
- ignoreTheme?: boolean;
+ logoSize?: number;
+ loading: boolean;
ariaLabel?: string;
dataTestId?: string;
style?: CSSProperties;
@@ -27,68 +25,35 @@ export function SessionQRCode(props: SessionQRCodeProps) {
id,
value,
size,
- backgroundColor = 'white',
- foregroundColor = 'black',
+ backgroundColor = COLORS.WHITE,
+ foregroundColor = COLORS.BLACK,
+ hasLogo,
logoImage,
- logoWidth,
- logoHeight,
- logoIsSVG,
- theme,
- ignoreTheme,
+ logoSize,
ariaLabel,
dataTestId,
+ loading,
style,
} = props;
+ const [logo, setLogo] = useState(logoImage);
+ const [bgColor, setBgColor] = useState(backgroundColor);
+ const [fgColor, setFgColor] = useState(foregroundColor);
const qrRef = useRef(null);
-
- // const [svgDataURL, setSvgDataURL] = useState('');
- // const [currentTheme, setCurrentTheme] = useState(theme);
- // const [loading, setLoading] = useState(false);
-
- // const loadLogoImage = useCallback(async () => {
- // if (logoImage && logoIsSVG) {
- // setLoading(true);
- // try {
- // const response = await fetch(logoImage);
- // let svgString = await response.text();
-
- // if (!ignoreTheme && theme && !isEmpty(theme)) {
- // svgString = svgString.replaceAll(
- // 'black',
- // getThemeValue(
- // checkDarkTheme(theme) ? '--background-primary-color' : '--text-primary-color'
- // )
- // );
- // }
-
- // setSvgDataURL(`data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`);
- // } catch (error) {
- // window.log.error('Error fetching the QR Code logo which is an svg:', error);
- // }
- // setLoading(false);
- // }
- // }, [ignoreTheme, logoImage, logoIsSVG, theme]);
-
- // useMount(() => {
- // void loadLogoImage();
- // });
-
- // useEffect(() => {
- // if (theme && theme !== currentTheme) {
- // setCurrentTheme(theme);
- // void loadLogoImage();
- // }
- // }, [currentTheme, loadLogoImage, theme]);
-
const qrCanvasSize = 1000;
- const canvasLogoWidth =
- logoWidth && logoHeight ? (qrCanvasSize * 0.25 * logoWidth) / logoHeight : undefined;
- const canvasLogoHeight = logoHeight ? (qrCanvasSize * 0.25 * logoHeight) / logoHeight : undefined;
+ const canvasLogoSize = logoSize ? (qrCanvasSize * 0.25 * logoSize) / logoSize : 250;
+
+ useEffect(() => {
+ // Don't pass the component props to the QR component directly instead update it's props in the next render cycle to prevent janky renders
+ if (!loading && hasLogo && logo !== logoImage) {
+ setBgColor(backgroundColor);
+ setFgColor(foregroundColor);
+ setLogo(logoImage);
+ }
+ }, [backgroundColor, foregroundColor, hasLogo, loading, logo, logoImage]);
- // We use an AnimatedFlex because we fade in the QR code to hide the logo flickering on first render
return (
- ) => {
+ // event.preventDefault();
+ // const fileName = `${id}-${new Date().toISOString()}.png`;
+ // try {
+ // qrRef.current?.download('png', fileName);
+ // } catch (e) {
+ // window.log.error(`Error downloading QR code: ${fileName}\n${e}`);
+ // }
+ // }}
onClick={(event: MouseEvent) => {
event.preventDefault();
- qrRef.current?.download('jpg', id);
+ void saveQRCode(id, {
+ ...props,
+ id: `temp-${props.id}`,
+ style: { display: 'none' },
+ });
}}
data-testId={dataTestId || 'session-qr-code'}
style={style}
@@ -113,14 +88,13 @@ export function SessionQRCode(props: SessionQRCodeProps) {
value={value}
ecLevel={'Q'}
size={qrCanvasSize}
- bgColor={backgroundColor}
- fgColor={foregroundColor}
+ bgColor={bgColor}
+ fgColor={fgColor}
quietZone={40}
- logoImage={logoImage}
- logoWidth={canvasLogoWidth}
- logoHeight={canvasLogoHeight}
+ logoImage={logo}
+ logoWidth={canvasLogoSize}
+ logoHeight={canvasLogoSize}
removeQrCodeBehindLogo={true}
- logoOnLoad={e => window.log.debug(`WIP: [SessionQRCode] logo loaded`, e)}
style={{
borderRadius: '10px',
cursor: 'pointer',
@@ -129,6 +103,6 @@ export function SessionQRCode(props: SessionQRCodeProps) {
height: size,
}}
/>
-
+
);
}
diff --git a/ts/components/dialog/edit-profile/components.tsx b/ts/components/dialog/edit-profile/components.tsx
index 9df6de5ed..80e7904e1 100644
--- a/ts/components/dialog/edit-profile/components.tsx
+++ b/ts/components/dialog/edit-profile/components.tsx
@@ -1,6 +1,5 @@
import styled from 'styled-components';
-import { useIsDarkTheme, useTheme } from '../../../state/selectors/theme';
-import { getThemeValue } from '../../../themes/globals';
+import { useIconToImageURL } from '../../../hooks/useIconToImageURL';
import { SessionQRCode } from '../../SessionQRCode';
import { Avatar, AvatarSize } from '../../avatar/Avatar';
import { Flex } from '../../basic/Flex';
@@ -8,25 +7,22 @@ import { SpacerSM } from '../../basic/Text';
import { SessionIconButton } from '../../icon';
export const QRView = ({ sessionID }: { sessionID: string }) => {
- const theme = useTheme();
- const isDarkTheme = useIsDarkTheme();
+ const { dataURL, iconSize, iconColor, backgroundColor, loading } = useIconToImageURL({
+ iconType: 'brand',
+ iconSize: 40,
+ });
return (
{
const hideRecoveryPassword = useHideRecoveryPasswordEnabled();
+ const isDarkTheme = useIsDarkTheme();
+ const { dataURL, iconSize, iconColor, backgroundColor, loading } = useIconToImageURL({
+ iconType: 'shield',
+ iconSize: 56,
+ });
+
const dispatch = useDispatch();
const { hasPassword, passwordValid } = usePasswordModal({
@@ -61,8 +68,6 @@ export const SettingsCategoryRecoveryPassword = () => {
dispatch(showSettingsSection('privacy'));
},
});
- const theme = useTheme();
- const isDarkTheme = useIsDarkTheme();
const fetchRecoverPhrase = () => {
const newRecoveryPhrase = getCurrentRecoveryPhrase();
@@ -103,17 +108,12 @@ export const SettingsCategoryRecoveryPassword = () => {
id={'session-recovery-password'}
value={hexEncodedSeed}
size={260}
- backgroundColor={getThemeValue(
- isDarkTheme ? '--text-primary-color' : '--background-primary-color'
- )}
- foregroundColor={getThemeValue(
- isDarkTheme ? '--background-primary-color' : '--text-primary-color'
- )}
- logoImage={'./images/session/qr/shield.svg'}
- logoWidth={56}
- logoHeight={56}
- logoIsSVG={true}
- theme={theme}
+ hasLogo={true}
+ backgroundColor={backgroundColor}
+ foregroundColor={iconColor}
+ logoImage={dataURL}
+ logoSize={iconSize}
+ loading={loading}
ariaLabel={'Recovery Password QR Code'}
dataTestId={'session-recovery-password'}
/>
diff --git a/ts/hooks/useIconToImageURL.tsx b/ts/hooks/useIconToImageURL.tsx
new file mode 100644
index 000000000..b72acf62d
--- /dev/null
+++ b/ts/hooks/useIconToImageURL.tsx
@@ -0,0 +1,116 @@
+import { useCallback, useEffect, useState } from 'react';
+import { createRoot } from 'react-dom/client';
+import useMount from 'react-use/lib/useMount';
+import { SessionIcon, SessionIconProps, SessionIconType } from '../components/icon';
+import { sleepFor } from '../session/utils/Promise';
+import { useIsDarkTheme } from '../state/selectors/theme';
+import { COLORS } from '../themes/constants/colors';
+import { getThemeValue } from '../themes/globals';
+import { ThemeColorVariables } from '../themes/variableColors';
+
+const convertIconToImageURL = async (
+ props: Pick
+) => {
+ const { iconType, iconSize, iconColor = COLORS.BLACK, backgroundColor = COLORS.WHITE } = props;
+
+ const root = document.querySelector('#root');
+ const divElement = document.createElement('div');
+ divElement.id = 'icon-to-image-url';
+ root?.appendChild(divElement);
+
+ const reactRoot = createRoot(divElement!);
+ reactRoot.render(
+
+ );
+ // wait for it to render
+ await sleepFor(100);
+ const svg = root?.querySelector(`#icon-to-image-url svg`);
+ svg?.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
+ const svgString = svg?.outerHTML;
+ reactRoot?.unmount();
+ root?.removeChild(divElement);
+
+ if (svgString) {
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
+ }
+
+ return null;
+};
+
+export const useIconToImageURL = ({
+ iconType,
+ iconSize,
+ theming = true,
+}: {
+ iconType: SessionIconType;
+ iconSize: number;
+ theming?: boolean;
+}) => {
+ const isDarkTheme = useIsDarkTheme();
+ const [dataURL, setDataURL] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [mounted, setMounted] = useState(false);
+ const [inDarkTheme, setInDarkTheme] = useState(false);
+ const [backgroundColor, setBackgroundColor] = useState('');
+ const [iconColor, setIconColor] = useState('');
+
+ const chooseColor = useCallback(
+ (darkColor: keyof ThemeColorVariables, lightColor: keyof ThemeColorVariables) => {
+ return theming ? getThemeValue(isDarkTheme ? darkColor : lightColor) : '';
+ },
+ [isDarkTheme, theming]
+ );
+
+ const loadURL = useCallback(async () => {
+ setLoading(true);
+ setDataURL('');
+ try {
+ const bgColor = chooseColor('--text-primary-color', '--background-primary-color');
+ const fgColor = chooseColor('--background-primary-color', '--text-primary-color');
+
+ setBackgroundColor(bgColor);
+ setIconColor(fgColor);
+
+ const newURL = await convertIconToImageURL({
+ iconType,
+ iconSize,
+ iconColor: fgColor,
+ backgroundColor: bgColor,
+ });
+
+ if (!newURL) {
+ throw new Error('[useIconToImageURL] Failed to convert icon to URL');
+ }
+ setDataURL(newURL);
+ setInDarkTheme(!!theming && isDarkTheme);
+
+ if (!mounted) {
+ setMounted(true);
+ }
+ setLoading(false);
+ } catch (error) {
+ window.log.error('[useIconToImageURL] Error fetching icon data url', error);
+ }
+ }, [chooseColor, iconSize, iconType, isDarkTheme, mounted, theming]);
+
+ useMount(() => {
+ void loadURL();
+ });
+
+ useEffect(() => {
+ if (mounted && theming && isDarkTheme !== inDarkTheme) {
+ void loadURL();
+ }
+ }, [inDarkTheme, isDarkTheme, loadURL, mounted, theming]);
+
+ const returnProps = { dataURL, iconSize, iconColor, backgroundColor, loading };
+
+ window.log.debug(`WIP: [useIconToImageURL] returnProps: ${JSON.stringify(returnProps)}`);
+
+ return returnProps;
+};