From 95b8cc0142c969776711227ddd47eca633c8a965 Mon Sep 17 00:00:00 2001 From: Arkaprabha Chakraborty Date: Sun, 15 Mar 2026 01:00:16 +0530 Subject: [PATCH] feat: black theme and other UI fixes --- android/app/build.gradle | 2 +- app.json | 2 +- ios/Expensso.xcodeproj/project.pbxproj | 4 +- package.json | 2 +- scripts/version.js | 15 ++ src/components/CustomBottomSheet.tsx | 23 ++- src/components/FloatingModal.tsx | 89 +++++++++ src/components/SettingsSelectionSheet.tsx | 137 ++++++++++++++ src/components/index.ts | 3 + src/constants/appVersion.ts | 3 + src/constants/index.ts | 2 + src/screens/ExpensesScreen.tsx | 5 +- src/screens/SettingsScreen.tsx | 104 ++--------- src/store/mmkv.ts | 2 +- src/theme/ThemeProvider.tsx | 3 +- src/theme/index.ts | 2 + src/theme/md3.ts | 208 ++++++++++++---------- src/types/index.ts | 2 +- version.config.json | 2 +- 19 files changed, 416 insertions(+), 194 deletions(-) create mode 100644 src/components/FloatingModal.tsx create mode 100644 src/components/SettingsSelectionSheet.tsx create mode 100644 src/constants/appVersion.ts diff --git a/android/app/build.gradle b/android/app/build.gradle index 8b3b76b..73626a7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -92,7 +92,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "0.1.2" + versionName "0.1.4" } signingConfigs { debug { diff --git a/app.json b/app.json index 6123caa..938a267 100644 --- a/app.json +++ b/app.json @@ -1,7 +1,7 @@ { "name": "Expensso", "displayName": "Expensso", - "version": "0.1.2", + "version": "0.1.4", "android": { "versionCode": 1 }, diff --git a/ios/Expensso.xcodeproj/project.pbxproj b/ios/Expensso.xcodeproj/project.pbxproj index 2d4505c..7fbae8c 100644 --- a/ios/Expensso.xcodeproj/project.pbxproj +++ b/ios/Expensso.xcodeproj/project.pbxproj @@ -265,7 +265,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.1.2; + MARKETING_VERSION = 0.1.4; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -294,7 +294,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.1.2; + MARKETING_VERSION = 0.1.4; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/package.json b/package.json index 79e1dd4..a3d64ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "expensso", - "version": "0.1.2", + "version": "0.1.4", "private": true, "scripts": { "android": "react-native run-android", diff --git a/scripts/version.js b/scripts/version.js index f7c0cdd..cbcd8e8 100644 --- a/scripts/version.js +++ b/scripts/version.js @@ -9,6 +9,7 @@ const packagePath = path.join(rootDir, 'package.json'); const appJsonPath = path.join(rootDir, 'app.json'); const androidGradlePath = path.join(rootDir, 'android', 'app', 'build.gradle'); const iosPbxprojPath = path.join(rootDir, 'ios', 'Expensso.xcodeproj', 'project.pbxproj'); +const appVersionTsPath = path.join(rootDir, 'src', 'constants', 'appVersion.ts'); function readJson(filePath) { return JSON.parse(fs.readFileSync(filePath, 'utf8')); @@ -69,6 +70,18 @@ function replaceOrThrow(content, regex, replacement, targetName) { return updated; } +function writeAppVersionTs({version, buildNumber}) { + const content = [ + `export const APP_VERSION = '${version}';`, + `export const APP_BUILD_NUMBER = ${buildNumber};`, + 'export const APP_VERSION_LABEL = `${APP_VERSION} (${APP_BUILD_NUMBER})`;', + '', + ].join('\n'); + + fs.mkdirSync(path.dirname(appVersionTsPath), {recursive: true}); + fs.writeFileSync(appVersionTsPath, content, 'utf8'); +} + function syncToTargets({version, buildNumber}) { const packageJson = readJson(packagePath); packageJson.version = version; @@ -109,6 +122,8 @@ function syncToTargets({version, buildNumber}) { 'iOS CURRENT_PROJECT_VERSION', ); fs.writeFileSync(iosPbxprojPath, pbxproj, 'utf8'); + + writeAppVersionTs({version, buildNumber}); } function saveConfig(config) { diff --git a/src/components/CustomBottomSheet.tsx b/src/components/CustomBottomSheet.tsx index 142cc1a..3b9c0fa 100644 --- a/src/components/CustomBottomSheet.tsx +++ b/src/components/CustomBottomSheet.tsx @@ -91,8 +91,14 @@ const CustomBottomSheetInner = forwardRef(null); + const [sheetIndex, setSheetIndex] = React.useState(-1); const snapPoints = useMemo(() => snapPointsProp ?? ['60%'], [snapPointsProp]); + const handleClose = useCallback(() => { + setSheetIndex(-1); + onDismiss?.(); + }, [onDismiss]); + // Imperative handle useImperativeHandle(ref, () => ({ present: () => { @@ -138,7 +144,8 @@ const CustomBottomSheetInner = forwardRef - {children} - + {sheetIndex >= 0 && ( + + {children} + + )} ); }, diff --git a/src/components/FloatingModal.tsx b/src/components/FloatingModal.tsx new file mode 100644 index 0000000..94b7f79 --- /dev/null +++ b/src/components/FloatingModal.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { + Modal, + View, + Text, + Pressable, + StyleSheet, +} from 'react-native'; +import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; +import {useTheme} from '../theme'; + +export interface FloatingModalProps { + visible: boolean; + onClose: () => void; + title?: string; + children: React.ReactNode; +} + +export const FloatingModal: React.FC = ({ + visible, + onClose, + title, + children, +}) => { + const theme = useTheme(); + const {colors, typography, spacing, shape, elevation} = theme; + + return ( + + + + + + {(title || onClose) && ( + + {title ?? ''} + + + + + )} + + {children} + + + + ); +}; + +const styles = StyleSheet.create({ + backdrop: { + flex: 1, + justifyContent: 'center', + }, + card: { + maxHeight: '72%', + borderWidth: 1, + overflow: 'hidden', + }, + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + borderBottomWidth: 1, + }, +}); diff --git a/src/components/SettingsSelectionSheet.tsx b/src/components/SettingsSelectionSheet.tsx new file mode 100644 index 0000000..258a644 --- /dev/null +++ b/src/components/SettingsSelectionSheet.tsx @@ -0,0 +1,137 @@ +import React, {forwardRef, useImperativeHandle, useMemo, useState} from 'react'; +import {Pressable, ScrollView, StyleSheet, Text, View} from 'react-native'; +import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; +import type {CustomBottomSheetHandle} from './CustomBottomSheet'; +import {triggerHaptic} from './CustomBottomSheet'; +import {FloatingModal} from './FloatingModal'; +import {useTheme} from '../theme'; + +export interface SettingsSelectionOption { + label: string; + value: T; + icon?: string; +} + +export interface SettingsSelectionSheetProps { + title: string; + options: SettingsSelectionOption[]; + selectedValue: T; + onSelect: (value: T) => void; + enableDynamicSizing?: boolean; + snapPoints?: (string | number)[]; +} + +const SettingsSelectionSheetInner = ( + { + title, + options, + selectedValue, + onSelect, + }: SettingsSelectionSheetProps, + ref: React.ForwardedRef, +) => { + const theme = useTheme(); + const {colors, typography, spacing, shape} = theme; + const [visible, setVisible] = useState(false); + + useImperativeHandle(ref, () => ({ + present: () => { + triggerHaptic('impactLight'); + setVisible(true); + }, + dismiss: () => { + triggerHaptic('impactLight'); + setVisible(false); + }, + })); + + const selectedOption = useMemo( + () => options.find(option => option.value === selectedValue), + [options, selectedValue], + ); + + return ( + setVisible(false)} + title={title}> + + {options.map(option => { + const selected = option.value === selectedValue; + return ( + { + triggerHaptic('selection'); + onSelect(option.value); + setVisible(false); + }} + style={{ + ...styles.optionRow, + paddingHorizontal: spacing.md, + paddingVertical: spacing.md, + borderRadius: shape.medium, + borderColor: selected ? colors.primary : colors.outlineVariant, + backgroundColor: selected + ? colors.primaryContainer + : colors.surfaceContainerLowest, + marginBottom: spacing.sm, + }}> + {option.icon ? ( + + ) : null} + + {option.label} + + {selected ? ( + + ) : null} + + ); + })} + + {!selectedOption && options.length > 0 ? ( + + + Current selection is unavailable. Pick a new value. + + + ) : null} + + + ); +}; + +export const SettingsSelectionSheet = forwardRef(SettingsSelectionSheetInner) as ( + props: SettingsSelectionSheetProps & React.RefAttributes, +) => React.ReactElement; + +const styles = StyleSheet.create({ + optionRow: { + flexDirection: 'row', + alignItems: 'center', + borderWidth: 1, + }, + optionLabel: { + flex: 1, + }, + optionLabelWithIcon: { + marginLeft: 8, + }, + hintContainer: { + marginTop: 0, + paddingHorizontal: 0, + }, +}); diff --git a/src/components/index.ts b/src/components/index.ts index c83d4d4..79fc448 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -2,6 +2,8 @@ export {SummaryCard} from './SummaryCard'; export {TransactionItem} from './TransactionItem'; export {EmptyState} from './EmptyState'; export {SectionHeader} from './SectionHeader'; +export {FloatingModal} from './FloatingModal'; +export {SettingsSelectionSheet} from './SettingsSelectionSheet'; export { CustomBottomSheet, BottomSheetInput, @@ -9,3 +11,4 @@ export { triggerHaptic, } from './CustomBottomSheet'; export type {CustomBottomSheetHandle} from './CustomBottomSheet'; +export type {SettingsSelectionOption} from './SettingsSelectionSheet'; diff --git a/src/constants/appVersion.ts b/src/constants/appVersion.ts new file mode 100644 index 0000000..e1cf110 --- /dev/null +++ b/src/constants/appVersion.ts @@ -0,0 +1,3 @@ +export const APP_VERSION = '0.1.4'; +export const APP_BUILD_NUMBER = 1; +export const APP_VERSION_LABEL = `${APP_VERSION} (${APP_BUILD_NUMBER})`; diff --git a/src/constants/index.ts b/src/constants/index.ts index 3f4d63e..f1da65a 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,5 +1,7 @@ import {Category, ExchangeRates, PaymentMethod} from '../types'; +export * from './appVersion'; + // ─── Default Expense Categories (Indian Context) ──────────────────── export const DEFAULT_EXPENSE_CATEGORIES: Omit[] = [ diff --git a/src/screens/ExpensesScreen.tsx b/src/screens/ExpensesScreen.tsx index d34094d..6d074ab 100644 --- a/src/screens/ExpensesScreen.tsx +++ b/src/screens/ExpensesScreen.tsx @@ -480,7 +480,7 @@ function makeStyles(theme: MD3Theme) { flexDirection: 'row', gap: spacing.md, marginTop: spacing.sm, - marginBottom: spacing.lg, + marginBottom: spacing.xs, }, summaryItem: { flex: 1, @@ -504,7 +504,8 @@ function makeStyles(theme: MD3Theme) { flex: 1, backgroundColor: colors.surface, marginHorizontal: spacing.xl, - marginTop: spacing.md, + marginTop: spacing.xs, + marginBottom: spacing.md, borderRadius: shape.large, borderWidth: 1, borderColor: colors.outlineVariant, diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index 0adbc96..fe6443c 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -19,25 +19,27 @@ import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import Animated, {FadeIn, FadeInDown} from 'react-native-reanimated'; import { - CustomBottomSheet, + SettingsSelectionSheet, triggerHaptic, } from '../components'; -import type {CustomBottomSheetHandle} from '../components'; +import type {CustomBottomSheetHandle, SettingsSelectionOption} from '../components'; +import {APP_VERSION_LABEL} from '../constants'; import {useSettingsStore} from '../store'; import {useTheme} from '../theme'; import type {MD3Theme} from '../theme'; import {Currency} from '../types'; -const CURRENCIES: {label: string; value: Currency; icon: string}[] = [ +const CURRENCIES: SettingsSelectionOption[] = [ {label: 'Indian Rupee (\u20B9)', value: 'INR', icon: 'currency-inr'}, {label: 'US Dollar ($)', value: 'USD', icon: 'currency-usd'}, {label: 'Euro (\u20AC)', value: 'EUR', icon: 'currency-eur'}, {label: 'British Pound (\u00A3)', value: 'GBP', icon: 'currency-gbp'}, ]; -const THEMES: {label: string; value: 'light' | 'dark' | 'system'; icon: string}[] = [ +const THEMES: SettingsSelectionOption<'light' | 'dark' | 'black' | 'system'>[] = [ {label: 'Light', value: 'light', icon: 'white-balance-sunny'}, {label: 'Dark', value: 'dark', icon: 'moon-waning-crescent'}, + {label: 'Black', value: 'black', icon: 'theme-light-dark'}, {label: 'System', value: 'system', icon: 'theme-light-dark'}, ]; @@ -228,7 +230,7 @@ const SettingsScreen: React.FC = () => { icon="information" iconColor="#7E57C2" label={t('settings.version')} - value="0.1.1-alpha" + value={APP_VERSION_LABEL} /> @@ -238,79 +240,25 @@ const SettingsScreen: React.FC = () => { - {/* Currency Selection Bottom Sheet */} - - {CURRENCIES.map(c => ( - { - triggerHaptic('selection'); - setBaseCurrency(c.value); - currencySheetRef.current?.dismiss(); - }}> - - - {c.label} - - {c.value === baseCurrency && ( - - )} - - ))} - + snapPoints={['40%']} + /> - {/* Theme Selection Bottom Sheet */} - - {THEMES.map(th => ( - { - triggerHaptic('selection'); - setTheme(th.value); - themeSheetRef.current?.dismiss(); - }}> - - - {th.label} - - {th.value === themeSetting && ( - - )} - - ))} - + snapPoints={['35%']} + /> ); }; @@ -363,19 +311,5 @@ function makeStyles(theme: MD3Theme) { marginTop: spacing.xxxl, marginBottom: spacing.xxxl + 8, }, - selectionRow: { - flexDirection: 'row', - alignItems: 'center', - gap: spacing.md, - paddingVertical: spacing.lg, - paddingHorizontal: spacing.lg, - borderRadius: shape.medium, - marginBottom: spacing.xs, - }, - selectionLabel: { - flex: 1, - ...typography.bodyLarge, - color: colors.onSurface, - }, }); } diff --git a/src/store/mmkv.ts b/src/store/mmkv.ts index 6bc664b..0dd1da8 100644 --- a/src/store/mmkv.ts +++ b/src/store/mmkv.ts @@ -37,7 +37,7 @@ export const mmkvStorage = { getStorage().set(KEYS.LOCALE, locale); }, - getTheme: (): 'light' | 'dark' | 'system' => { + getTheme: (): 'light' | 'dark' | 'black' | 'system' => { return (getStorage().getString(KEYS.THEME) as AppSettings['theme']) || 'light'; }, setTheme: (theme: AppSettings['theme']) => { diff --git a/src/theme/ThemeProvider.tsx b/src/theme/ThemeProvider.tsx index b1f9074..08f5882 100644 --- a/src/theme/ThemeProvider.tsx +++ b/src/theme/ThemeProvider.tsx @@ -8,7 +8,7 @@ import React, {createContext, useContext, useMemo} from 'react'; import {useColorScheme} from 'react-native'; import {useSettingsStore} from '../store/settingsStore'; -import {LightTheme, DarkTheme} from './md3'; +import {LightTheme, DarkTheme, BlackTheme} from './md3'; import type {MD3Theme} from './md3'; const ThemeContext = createContext(LightTheme); @@ -18,6 +18,7 @@ export const ThemeProvider: React.FC<{children: React.ReactNode}> = ({children}) const systemScheme = useColorScheme(); const resolvedTheme = useMemo(() => { + if (themeSetting === 'black') return BlackTheme; if (themeSetting === 'dark') return DarkTheme; if (themeSetting === 'light') return LightTheme; return systemScheme === 'dark' ? DarkTheme : LightTheme; diff --git a/src/theme/index.ts b/src/theme/index.ts index 459a3ce..f003303 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -1,12 +1,14 @@ export { MD3LightColors, MD3DarkColors, + MD3BlackColors, MD3Typography, MD3Elevation, MD3Shape, Spacing, LightTheme, DarkTheme, + BlackTheme, } from './md3'; export type {MD3Theme, MD3ColorScheme} from './md3'; export {ThemeProvider, useTheme} from './ThemeProvider'; diff --git a/src/theme/md3.ts b/src/theme/md3.ts index c69f1d9..8c2279e 100644 --- a/src/theme/md3.ts +++ b/src/theme/md3.ts @@ -9,64 +9,64 @@ export const MD3LightColors = { // Primary - primary: '#6750A4', + primary: '#4078F2', onPrimary: '#FFFFFF', - primaryContainer: '#EADDFF', - onPrimaryContainer: '#21005D', + primaryContainer: '#DCE6FF', + onPrimaryContainer: '#1E3E8A', // Secondary - secondary: '#625B71', + secondary: '#A626A4', onSecondary: '#FFFFFF', - secondaryContainer: '#E8DEF8', - onSecondaryContainer: '#1D192B', + secondaryContainer: '#F3E5F5', + onSecondaryContainer: '#4E1A4C', // Tertiary (Fintech Teal) - tertiary: '#00897B', + tertiary: '#0184BC', onTertiary: '#FFFFFF', - tertiaryContainer: '#A7F3D0', - onTertiaryContainer: '#00382E', + tertiaryContainer: '#D8F1FC', + onTertiaryContainer: '#004B6C', // Error - error: '#B3261E', + error: '#E45649', onError: '#FFFFFF', - errorContainer: '#F9DEDC', - onErrorContainer: '#410E0B', + errorContainer: '#FCE3E1', + onErrorContainer: '#7A211C', // Success (custom MD3 extension) - success: '#1B873B', + success: '#50A14F', onSuccess: '#FFFFFF', - successContainer: '#D4EDDA', - onSuccessContainer: '#0A3D1B', + successContainer: '#DDF3D1', + onSuccessContainer: '#1F4D1E', // Warning (custom MD3 extension) - warning: '#E65100', + warning: '#C18401', onWarning: '#FFFFFF', - warningContainer: '#FFE0B2', - onWarningContainer: '#3E2723', + warningContainer: '#F8EACB', + onWarningContainer: '#5C4300', // Surface - background: '#FFFBFE', - onBackground: '#1C1B1F', - surface: '#FFFBFE', - onSurface: '#1C1B1F', - surfaceVariant: '#E7E0EC', - onSurfaceVariant: '#49454F', - surfaceDim: '#DED8E1', - surfaceBright: '#FFF8FF', + background: '#FAFAFA', + onBackground: '#383A42', + surface: '#FAFAFA', + onSurface: '#383A42', + surfaceVariant: '#E9ECF3', + onSurfaceVariant: '#4B5260', + surfaceDim: '#EDEFF2', + surfaceBright: '#FFFFFF', surfaceContainerLowest: '#FFFFFF', - surfaceContainerLow: '#F7F2FA', - surfaceContainer: '#F3EDF7', - surfaceContainerHigh: '#ECE6F0', - surfaceContainerHighest: '#E6E0E9', + surfaceContainerLow: '#F4F5F7', + surfaceContainer: '#EEF0F4', + surfaceContainerHigh: '#E8EBF0', + surfaceContainerHighest: '#E2E6EC', // Outline - outline: '#79747E', - outlineVariant: '#CAC4D0', + outline: '#7F8693', + outlineVariant: '#BCC3CF', // Inverse - inverseSurface: '#313033', - inverseOnSurface: '#F4EFF4', - inversePrimary: '#D0BCFF', + inverseSurface: '#383A42', + inverseOnSurface: '#FAFAFA', + inversePrimary: '#8FB1FF', // Scrim & Shadow scrim: '#000000', @@ -74,97 +74,114 @@ export const MD3LightColors = { // ─── App-Specific Semantic Colors ───────────────────────────── - income: '#1B873B', - expense: '#B3261E', - asset: '#6750A4', - liability: '#E65100', + income: '#50A14F', + expense: '#E45649', + asset: '#4078F2', + liability: '#C18401', // Chart palette (MD3 tonal) chartColors: [ - '#6750A4', '#00897B', '#1E88E5', '#E65100', - '#8E24AA', '#00ACC1', '#43A047', '#F4511E', - '#5C6BC0', '#FFB300', + '#4078F2', '#A626A4', '#0184BC', '#50A14F', + '#E45649', '#C18401', '#56B6C2', '#C678DD', + '#61AFEF', '#D19A66', ], }; export const MD3DarkColors: typeof MD3LightColors = { // Primary - primary: '#D0BCFF', - onPrimary: '#381E72', - primaryContainer: '#4F378B', - onPrimaryContainer: '#EADDFF', + primary: '#61AFEF', + onPrimary: '#1B2838', + primaryContainer: '#2C3E55', + onPrimaryContainer: '#D6E9FF', // Secondary - secondary: '#CCC2DC', - onSecondary: '#332D41', - secondaryContainer: '#4A4458', - onSecondaryContainer: '#E8DEF8', + secondary: '#C678DD', + onSecondary: '#2F1B36', + secondaryContainer: '#4B2F55', + onSecondaryContainer: '#F0D7F6', // Tertiary - tertiary: '#4DB6AC', - onTertiary: '#003730', - tertiaryContainer: '#005048', - onTertiaryContainer: '#A7F3D0', + tertiary: '#56B6C2', + onTertiary: '#102D31', + tertiaryContainer: '#1F4A50', + onTertiaryContainer: '#CDEFF3', // Error - error: '#F2B8B5', - onError: '#601410', - errorContainer: '#8C1D18', - onErrorContainer: '#F9DEDC', + error: '#E06C75', + onError: '#3A1519', + errorContainer: '#5A232A', + onErrorContainer: '#FFD9DC', // Success - success: '#81C784', - onSuccess: '#0A3D1B', - successContainer: '#1B5E20', - onSuccessContainer: '#D4EDDA', + success: '#98C379', + onSuccess: '#1B2A17', + successContainer: '#2D4524', + onSuccessContainer: '#DDF3D1', // Warning - warning: '#FFB74D', - onWarning: '#3E2723', - warningContainer: '#BF360C', - onWarningContainer: '#FFE0B2', + warning: '#E5C07B', + onWarning: '#342915', + warningContainer: '#5A4728', + onWarningContainer: '#FFE8C1', // Surface - background: '#141218', - onBackground: '#E6E0E9', - surface: '#141218', - onSurface: '#E6E0E9', - surfaceVariant: '#49454F', - onSurfaceVariant: '#CAC4D0', - surfaceDim: '#141218', - surfaceBright: '#3B383E', - surfaceContainerLowest: '#0F0D13', - surfaceContainerLow: '#1D1B20', - surfaceContainer: '#211F26', - surfaceContainerHigh: '#2B2930', - surfaceContainerHighest: '#36343B', + background: '#282C34', + onBackground: '#E6EAF0', + surface: '#282C34', + onSurface: '#E6EAF0', + surfaceVariant: '#3A404B', + onSurfaceVariant: '#BCC3CF', + surfaceDim: '#21252B', + surfaceBright: '#353B45', + surfaceContainerLowest: '#1E2228', + surfaceContainerLow: '#2B3038', + surfaceContainer: '#313741', + surfaceContainerHigh: '#393F4A', + surfaceContainerHighest: '#424955', // Outline - outline: '#938F99', - outlineVariant: '#49454F', + outline: '#8E97A8', + outlineVariant: '#5A6271', // Inverse - inverseSurface: '#E6E0E9', - inverseOnSurface: '#313033', - inversePrimary: '#6750A4', + inverseSurface: '#ABB2BF', + inverseOnSurface: '#282C34', + inversePrimary: '#4078F2', // Scrim & Shadow scrim: '#000000', shadow: '#000000', // App-Specific - income: '#81C784', - expense: '#F2B8B5', - asset: '#D0BCFF', - liability: '#FFB74D', + income: '#98C379', + expense: '#E06C75', + asset: '#61AFEF', + liability: '#E5C07B', chartColors: [ - '#D0BCFF', '#4DB6AC', '#64B5F6', '#FFB74D', - '#CE93D8', '#4DD0E1', '#81C784', '#FF8A65', - '#9FA8DA', '#FFD54F', + '#61AFEF', '#C678DD', '#56B6C2', '#98C379', + '#E06C75', '#E5C07B', '#D19A66', '#ABB2BF', + '#7F848E', '#8BE9FD', ], }; +export const MD3BlackColors: typeof MD3LightColors = { + ...MD3DarkColors, + background: '#000000', + surface: '#000000', + surfaceDim: '#000000', + surfaceBright: '#0D0D0D', + surfaceContainerLowest: '#000000', + surfaceContainerLow: '#070707', + surfaceContainer: '#0E0E0E', + surfaceContainerHigh: '#141414', + surfaceContainerHighest: '#1B1B1B', + inverseSurface: '#F1F1F1', + inverseOnSurface: '#000000', + outline: '#767676', + outlineVariant: '#333333', +}; + // ─── MD3 Typography Scale ──────────────────────────────────────────── const fontFamily = 'JetBrainsMono-Regular'; @@ -381,3 +398,12 @@ export const DarkTheme: MD3Theme = { spacing: Spacing, isDark: true, }; + +export const BlackTheme: MD3Theme = { + colors: MD3BlackColors, + typography: MD3Typography, + elevation: MD3Elevation, + shape: MD3Shape, + spacing: Spacing, + isDark: true, +}; diff --git a/src/types/index.ts b/src/types/index.ts index 151962d..b3988b4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -88,7 +88,7 @@ export interface NetWorthSnapshot { export interface AppSettings { baseCurrency: Currency; locale: string; - theme: 'light' | 'dark' | 'system'; + theme: 'light' | 'dark' | 'black' | 'system'; biometricEnabled: boolean; onboardingComplete: boolean; } diff --git a/version.config.json b/version.config.json index a74135f..d71a066 100644 --- a/version.config.json +++ b/version.config.json @@ -1,4 +1,4 @@ { - "version": "0.1.2", + "version": "0.1.4", "buildNumber": 1 }