From 2f0b106264393d2e7b1d12f8e27a5798f306e424 Mon Sep 17 00:00:00 2001 From: WardPearce Date: Tue, 7 Apr 2026 18:42:12 +1200 Subject: [PATCH] Started new color theme system --- .../lib/components/settings/Interface.svelte | 108 ----------- .../lib/components/settings/Settings.svelte | 5 +- .../src/lib/components/settings/Theme.svelte | 180 ++++++++++++++++++ materialious/src/lib/css/global.css | 2 +- materialious/src/lib/i18n/locales/en.json | 3 +- materialious/src/lib/store.ts | 6 + materialious/src/lib/theme.ts | 107 ++++++++++- 7 files changed, 294 insertions(+), 117 deletions(-) create mode 100644 materialious/src/lib/components/settings/Theme.svelte diff --git a/materialious/src/lib/components/settings/Interface.svelte b/materialious/src/lib/components/settings/Interface.svelte index 6c55af03..80850964 100644 --- a/materialious/src/lib/components/settings/Interface.svelte +++ b/materialious/src/lib/components/settings/Interface.svelte @@ -1,24 +1,18 @@ + + + + +{#if colorPickerOpen} +
+ +
+ +
+{/if} + +
+ +{#if $darkModeStore} +
+ +
+{/if} + +
{$_('layout.theme.advanced')}
+
+ +{#if currentThemeColors} + {#each Object.entries(currentThemeColors) as [themeVar, themeColor] (themeVar)} +
+ await colorOnInput(color, themeVar as ThemeKey)} + /> +
+ {/each} +{/if} + + diff --git a/materialious/src/lib/css/global.css b/materialious/src/lib/css/global.css index 08a08186..aa68c71a 100644 --- a/materialious/src/lib/css/global.css +++ b/materialious/src/lib/css/global.css @@ -103,7 +103,7 @@ menu { right: 0; } -.color-picker .picker { +.global-color-picker .picker { display: none !important; } diff --git a/materialious/src/lib/i18n/locales/en.json b/materialious/src/lib/i18n/locales/en.json index 686dfe62..7df36eb9 100644 --- a/materialious/src/lib/i18n/locales/en.json +++ b/materialious/src/lib/i18n/locales/en.json @@ -199,7 +199,8 @@ "darkMode": "Dark mode", "lightMode": "Light mode", "AmoledTheme": "Amoled theme", - "color": "Color" + "color": "Color", + "advanced": "Advanced theming" }, "searchSuggestions": "Search suggestions", "previewVideoOnHover": "Preview video on hover", diff --git a/materialious/src/lib/store.ts b/materialious/src/lib/store.ts index 075f43d2..c4ce8d6c 100644 --- a/materialious/src/lib/store.ts +++ b/materialious/src/lib/store.ts @@ -24,6 +24,7 @@ import { ensureNoTrailingSlash, getPublicEnv } from './misc'; import type { EngineFallback } from './api/misc'; import type z from 'zod'; import type { zFilterSchema } from './filtering/index'; +import type { ThemeColors } from './theme'; function createListenerFunctions(): { callListeners: (eventKey: string, newValue: any) => void; @@ -260,6 +261,11 @@ export const interfaceAndroidUseNativeShare = persist( createStorage(), 'androidUseNativeShare' ); +export const interfaceAdvancedThemingStore: Writable = persist( + writable({}), + createStorage(), + 'advancedTheming' +); export const sponsorBlockStore = persist(writable(true), createStorage(), 'sponsorBlock'); export const sponsorBlockUrlStore: Writable = persist( diff --git a/materialious/src/lib/theme.ts b/materialious/src/lib/theme.ts index 331515e1..4ac0f303 100644 --- a/materialious/src/lib/theme.ts +++ b/materialious/src/lib/theme.ts @@ -4,16 +4,61 @@ import { get } from 'svelte/store'; import { SystemBars, SystemBarsStyle } from '@capacitor/core'; import { darkModeStore, interfaceAmoledTheme } from './store'; -export async function getDynamicTheme(mode?: string): Promise> { +export type ThemeKey = + | '--primary' + | '--on-primary' + | '--primary-container' + | '--on-primary-container' + | '--secondary' + | '--on-secondary' + | '--secondary-container' + | '--on-secondary-container' + | '--tertiary' + | '--on-tertiary' + | '--tertiary-container' + | '--on-tertiary-container' + | '--error' + | '--on-error' + | '--error-container' + | '--on-error-container' + | '--background' + | '--on-background' + | '--surface' + | '--on-surface' + | '--surface-variant' + | '--on-surface-variant' + | '--outline' + | '--outline-variant' + | '--shadow' + | '--scrim' + | '--inverse-surface' + | '--inverse-on-surface' + | '--inverse-primary' + | '--surface-dim' + | '--surface-bright' + | '--surface-container-lowest' + | '--surface-container-low' + | '--surface-container' + | '--surface-container-high' + | '--surface-container-highest'; + +export type ThemeColors = Partial>; + +export async function getDynamicTheme(mode?: string): Promise { const givenSettings = await ui('theme'); + if (typeof givenSettings !== 'object') return {}; + // @ts-expect-error Works as expected const themes: string = givenSettings[mode ? mode : (ui('mode') as string)]; const themeVars: Record = {}; - themes.split(';').forEach((keyVar) => { + for (const keyVar of themes.split(';')) { const [key, value] = keyVar.split(':'); - themeVars[key] = value; - }); + themeVars[key] = window.getComputedStyle(document.body).getPropertyValue(key) ?? value; + } + + delete themeVars['']; + return themeVars; } @@ -35,7 +80,7 @@ export function setAmoledTheme() { const isDark = get(darkModeStore); if (isAmoled && isDark) { - const rootVars = [ + const rootVars: ThemeKey[] = [ '--surface-container', '--surface', '--surface-container-lowest', @@ -44,7 +89,7 @@ export function setAmoledTheme() { '--surface-container-highest' ]; rootVars.forEach((varName) => { - document.body.style.setProperty(varName, '#000'); + setThemeColor(varName, '#000'); }); } else { setTheme(); @@ -70,3 +115,53 @@ export function setTheme() { } } } + +export const SUPPORTED_THEME_KEYS: ThemeKey[] = [ + '--primary', + '--on-primary', + '--primary-container', + '--on-primary-container', + '--secondary', + '--on-secondary', + '--secondary-container', + '--on-secondary-container', + '--tertiary', + '--on-tertiary', + '--tertiary-container', + '--on-tertiary-container', + '--error', + '--on-error', + '--error-container', + '--on-error-container', + '--background', + '--on-background', + '--surface', + '--on-surface', + '--surface-variant', + '--on-surface-variant', + '--outline', + '--outline-variant', + '--shadow', + '--scrim', + '--inverse-surface', + '--inverse-on-surface', + '--inverse-primary', + '--surface-dim', + '--surface-bright', + '--surface-container-lowest', + '--surface-container-low', + '--surface-container', + '--surface-container-high', + '--surface-container-highest' +] as const; + +export function setThemeColors(theme: ThemeColors) { + for (const [themeKey, themeValue] of Object.entries(theme)) { + setThemeColor(themeKey as ThemeKey, themeValue.trim()); + } +} + +export function setThemeColor(theme: ThemeKey, color: string) { + document.documentElement.style.setProperty(theme, color.trim()); + document.body.style.setProperty(theme, color.trim()); +}