diff --git a/src/components/left/main/LeftMainHeader.tsx b/src/components/left/main/LeftMainHeader.tsx index 86dfede1d..8330563ce 100644 --- a/src/components/left/main/LeftMainHeader.tsx +++ b/src/components/left/main/LeftMainHeader.tsx @@ -7,7 +7,9 @@ import { GlobalActions } from '../../../global/types'; import { LeftColumnContent, ISettings } from '../../../types'; import { ApiChat } from '../../../api/types'; -import { APP_INFO, FEEDBACK_URL } from '../../../config'; +import { + APP_INFO, DEFAULT_PATTERN_COLOR, FEEDBACK_URL, DARK_THEME_BG_COLOR, DARK_THEME_PATTERN_COLOR, +} from '../../../config'; import { IS_MOBILE_SCREEN } from '../../../util/environment'; import buildClassName from '../../../util/buildClassName'; import { pick } from '../../../util/iteratees'; @@ -125,10 +127,12 @@ const LeftMainHeader: FC = ({ const handleDarkModeToggle = useCallback((e: React.SyntheticEvent) => { e.stopPropagation(); const newTheme = theme === 'light' ? 'dark' : 'light'; + const isNewThemeDark = newTheme === 'dark'; setSettingOption({ theme: newTheme, - customBackground: newTheme === 'dark' ? '#0F0F0F' : undefined, + customBackground: isNewThemeDark ? DARK_THEME_BG_COLOR : undefined, + patternColor: isNewThemeDark ? DARK_THEME_PATTERN_COLOR : DEFAULT_PATTERN_COLOR, }); switchTheme(newTheme, animationLevel > 0); }, [animationLevel, setSettingOption, theme]); diff --git a/src/components/left/settings/SettingsGeneralBackground.tsx b/src/components/left/settings/SettingsGeneralBackground.tsx index 04ae269f0..5b08687bb 100644 --- a/src/components/left/settings/SettingsGeneralBackground.tsx +++ b/src/components/left/settings/SettingsGeneralBackground.tsx @@ -7,9 +7,11 @@ import { GlobalActions } from '../../../global/types'; import { SettingsScreens, UPLOADING_WALLPAPER_SLUG } from '../../../types'; import { ApiWallpaper } from '../../../api/types'; +import { DEFAULT_PATTERN_COLOR } from '../../../config'; import { pick } from '../../../util/iteratees'; import { throttle } from '../../../util/schedulers'; import { openSystemFilesDialog } from '../../../util/systemFilesDialog'; +import { getAverageColor, getPatternColor } from '../../../util/colors'; import useLang from '../../../hooks/useLang'; import ListItem from '../../ui/ListItem'; @@ -69,12 +71,19 @@ const SettingsGeneralBackground: FC = ({ }, [onScreenSelect]); const handleResetToDefault = useCallback(() => { - setSettingOption({ customBackground: undefined }); + setSettingOption({ customBackground: undefined, patternColor: DEFAULT_PATTERN_COLOR }); }, [setSettingOption]); const handleWallPaperSelect = useCallback((slug: string) => { setSettingOption({ customBackground: slug }); - }, [setSettingOption]); + const currentWallpaper = loadedWallpapers && loadedWallpapers.find((wallpaper) => wallpaper.slug === slug); + if (currentWallpaper && currentWallpaper.document.thumbnail) { + getAverageColor(currentWallpaper.document.thumbnail.dataUri) + .then((color) => { + setSettingOption({ patternColor: getPatternColor(color) }); + }); + } + }, [loadedWallpapers, setSettingOption]); const handleWallPaperBlurChange = useCallback((e: React.ChangeEvent) => { setSettingOption({ isBackgroundBlurred: e.target.checked }); diff --git a/src/components/left/settings/SettingsGeneralBackgroundColor.tsx b/src/components/left/settings/SettingsGeneralBackgroundColor.tsx index 6e7c42d4e..2f34fc15b 100644 --- a/src/components/left/settings/SettingsGeneralBackgroundColor.tsx +++ b/src/components/left/settings/SettingsGeneralBackgroundColor.tsx @@ -9,7 +9,7 @@ import { SettingsScreens } from '../../../types'; import { pick } from '../../../util/iteratees'; import { - hex2rgb, hsb2rgb, rgb2hex, rgb2hsb, + getPatternColor, hex2rgb, hsb2rgb, rgb2hex, rgb2hsb, } from '../../../util/colors'; import { captureEvents, RealTouchEvent } from '../../../util/captureEvents'; import useFlag from '../../../hooks/useFlag'; @@ -139,7 +139,10 @@ const SettingsGeneralBackground: FC = ({ setHexInput(color); if (!isFirstRunRef.current) { - setSettingOption({ customBackground: color }); + setSettingOption({ + customBackground: color, + patternColor: getPatternColor(rgb), + }); } isFirstRunRef.current = false; }, [hsb, setSettingOption]); diff --git a/src/components/middle/MessageList.scss b/src/components/middle/MessageList.scss index d10766ca7..3d5f9c6b6 100644 --- a/src/components/middle/MessageList.scss +++ b/src/components/middle/MessageList.scss @@ -131,7 +131,7 @@ > span { display: inline-block; - background: rgba(var(--color-text-secondary-rgb), 0.45); + background: var(--pattern-color); color: white; font-size: 0.9375rem; font-weight: 500; diff --git a/src/components/middle/MiddleColumn.tsx b/src/components/middle/MiddleColumn.tsx index 94fc41753..083ed3294 100644 --- a/src/components/middle/MiddleColumn.tsx +++ b/src/components/middle/MiddleColumn.tsx @@ -59,6 +59,7 @@ type StateProps = { messageSendingRestrictionReason?: string; hasPinnedOrAudioMessage?: boolean; customBackground?: string; + patternColor?: string; isCustomBackgroundColor?: boolean; isRightColumnShown?: boolean; isBackgroundBlurred?: boolean; @@ -85,6 +86,7 @@ const MiddleColumn: FC = ({ messageSendingRestrictionReason, hasPinnedOrAudioMessage, customBackground, + patternColor, isCustomBackgroundColor, isRightColumnShown, isBackgroundBlurred, @@ -203,6 +205,7 @@ const MiddleColumn: FC = ({ --toolbar-unpin-hidden-scale: ${toolbarForUnpinHiddenScale}; --composer-translate-x: ${composerTranslateX}px; --toolbar-translate-x: ${toolbarTranslateX}px; + --pattern-color: ${patternColor}; `} >
= ({ export default memo(withGlobal( (global): StateProps => { - const { isBackgroundBlurred, customBackground } = global.settings.byKey; + const { isBackgroundBlurred, customBackground, patternColor } = global.settings.byKey; const isCustomBackgroundColor = Boolean((customBackground || '').match(/^#[a-f\d]{6,8}$/i)); const currentMessageList = selectCurrentMessageList(global); @@ -329,6 +332,7 @@ export default memo(withGlobal( messageSendingRestrictionReason: chat && getMessageSendingRestrictionReason(chat), hasPinnedOrAudioMessage: Boolean(pinnedIds && pinnedIds.length) || Boolean(audioChatId && audioMessageId), customBackground, + patternColor, isCustomBackgroundColor, isRightColumnShown: selectIsRightColumnShown(global), isBackgroundBlurred, diff --git a/src/components/middle/message/InlineButtons.scss b/src/components/middle/message/InlineButtons.scss index c8788d56f..1a3954175 100644 --- a/src/components/middle/message/InlineButtons.scss +++ b/src/components/middle/message/InlineButtons.scss @@ -11,13 +11,32 @@ flex: 1; width: auto; margin: 0.125rem; - background: rgba(102, 102, 102, 0.4); + background: var(--pattern-color); border-radius: var(--border-radius-messages-small); font-weight: 500; text-transform: none; + &::before { + content: ''; + background-color: var(--color-white); + opacity: 0; + + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: var(--border-radius-messages-small); + z-index: var(--z-below); + transition: opacity 200ms; + } + &:hover { - background: rgba(130, 130, 130, 0.4) !important; + background: var(--pattern-color) !important; + + &::before { + opacity: .4; + } } &:first-of-type { diff --git a/src/config.ts b/src/config.ts index ecf134f56..f52473d16 100644 --- a/src/config.ts +++ b/src/config.ts @@ -119,3 +119,6 @@ export const DEFAULT_LANG_PACK = 'android'; export const LANG_PACKS = ['android', 'ios']; export const TIPS_USERNAME = 'TelegramTips'; export const FEEDBACK_URL = 'https://bugs.telegram.org/?tag_ids=41&sort=time'; +export const DARK_THEME_BG_COLOR = '#0F0F0F'; +export const DARK_THEME_PATTERN_COLOR = 'hsla(0, 0%, 3.82353%, 0.55)'; +export const DEFAULT_PATTERN_COLOR = 'rgba(90, 110, 70, 0.6)'; diff --git a/src/global/initial.ts b/src/global/initial.ts index 93e1916ed..f6269c3a7 100644 --- a/src/global/initial.ts +++ b/src/global/initial.ts @@ -1,6 +1,6 @@ import { GlobalState } from './types'; -import { ANIMATION_LEVEL_DEFAULT, DEFAULT_MESSAGE_TEXT_SIZE_PX } from '../config'; +import { ANIMATION_LEVEL_DEFAULT, DEFAULT_MESSAGE_TEXT_SIZE_PX, DEFAULT_PATTERN_COLOR } from '../config'; export const INITIAL_STATE: GlobalState = { isLeftColumnShown: true, @@ -101,6 +101,7 @@ export const INITIAL_STATE: GlobalState = { byKey: { messageTextSize: DEFAULT_MESSAGE_TEXT_SIZE_PX, isBackgroundBlurred: true, + patternColor: DEFAULT_PATTERN_COLOR, animationLevel: ANIMATION_LEVEL_DEFAULT, messageSendKeyCombo: 'enter', theme: 'light', diff --git a/src/types/index.ts b/src/types/index.ts index 9f8ce25c8..5bba2f05c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -23,6 +23,7 @@ export interface IAlbum { export interface ISettings extends Record { messageTextSize: number; customBackground?: string; + patternColor?: string; isBackgroundBlurred?: boolean; animationLevel: 0 | 1 | 2; messageSendKeyCombo: 'enter' | 'ctrl-enter'; diff --git a/src/util/colors.ts b/src/util/colors.ts index 37aed35d4..7d9a55849 100644 --- a/src/util/colors.ts +++ b/src/util/colors.ts @@ -5,6 +5,8 @@ /* eslint-disable one-var */ /* eslint-disable one-var-declaration-per-line */ +import { preloadImage } from './files'; + /** * HEX > RGB * input: 'xxxxxx' (ex. 'ed15fa') case-insensitive @@ -132,3 +134,65 @@ export function hsb2rgb([h, s, v]: [number, number, number]): [number, number, n Math.round(b * 255), ]; } + +export async function getAverageColor(url: string): Promise<[number, number, number]> { + // Only visit every 5 pixels + const blockSize = 5; + const defaultRGB: [number, number, number] = [0, 0, 0]; + let data; + let width; + let height; + let i = -4; + let length; + let rgb: [number, number, number] = [0, 0, 0]; + let count = 0; + + const canvas = document.createElement('canvas'); + const context = canvas.getContext && canvas.getContext('2d'); + if (!context) { + return defaultRGB; + } + + const image = await preloadImage(url); + height = image.naturalHeight || image.offsetHeight || image.height; + width = image.naturalWidth || image.offsetWidth || image.width; + canvas.height = height; + canvas.width = width; + + context.drawImage(image, 0, 0); + + try { + data = context.getImageData(0, 0, width, height); + } catch (e) { + return defaultRGB; + } + + length = data.data.length; + + // eslint-disable-next-line no-cond-assign + while ((i += blockSize * 4) < length) { + ++count; + rgb[0] += data.data[i]; + rgb[1] += data.data[i + 1]; + rgb[2] += data.data[i + 2]; + } + + rgb[0] = Math.floor(rgb[0] / count); + rgb[1] = Math.floor(rgb[1] / count); + rgb[2] = Math.floor(rgb[2] / count); + + return rgb; +} + +// eslint-disable-next-line max-len +// Function was adapted from https://github.com/telegramdesktop/tdesktop/blob/35ff621b5b52f7e3553fb0f990ea13ade7101b8e/Telegram/SourceFiles/data/data_wall_paper.cpp#L518 +export function getPatternColor(rgbColor: [number, number, number]) { + let [hue, saturation, value] = rgb2hsb(rgbColor); + + saturation = Math.min(1, saturation + 0.05 + 0.1 * (1 - saturation)); + value = value > 0.5 + ? Math.max(0, value * 0.65) + : Math.max(0, Math.min(1, 1 - value * 0.65)); + + return `hsla(${hue * 360}, ${saturation * 100}%, ${value * 100}%, .4)`; +}