mirror of
https://github.com/Jellify-Music/App.git
synced 2026-03-20 04:01:18 -05:00
Merge branch 'main' of github.com:Jellify-Music/App into feature/nitro-player
This commit is contained in:
18
App.tsx
18
App.tsx
@@ -10,12 +10,12 @@ import { queryClient } from './src/constants/query-client'
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
||||
import { NavigationContainer } from '@react-navigation/native'
|
||||
import { JellifyDarkTheme, JellifyLightTheme, JellifyOLEDTheme } from './src/components/theme'
|
||||
import { getJellifyNavTheme } from './src/components/theme'
|
||||
import ErrorBoundary from './src/components/ErrorBoundary'
|
||||
import OTAUpdateScreen from './src/components/OtaUpdates'
|
||||
import { usePerformanceMonitor } from './src/hooks/use-performance-monitor'
|
||||
import navigationRef from './navigation'
|
||||
import { useThemeSetting } from './src/stores/settings/app'
|
||||
import { useColorPresetSetting, useThemeSetting } from './src/stores/settings/app'
|
||||
import { getApi } from './src/stores'
|
||||
import CarPlayNavigation from './src/components/CarPlay/Navigation'
|
||||
import { CarPlay } from 'react-native-carplay'
|
||||
@@ -75,23 +75,15 @@ export default function App(): React.JSX.Element {
|
||||
|
||||
function Container(): React.JSX.Element {
|
||||
const [theme] = useThemeSetting()
|
||||
const [colorPreset] = useColorPresetSetting()
|
||||
|
||||
const isDarkMode = useColorScheme() === 'dark'
|
||||
const resolvedMode = theme === 'system' ? (isDarkMode ? 'dark' : 'light') : theme
|
||||
|
||||
return (
|
||||
<NavigationContainer
|
||||
ref={navigationRef}
|
||||
theme={
|
||||
theme === 'system'
|
||||
? isDarkMode
|
||||
? JellifyDarkTheme
|
||||
: JellifyLightTheme
|
||||
: theme === 'dark'
|
||||
? JellifyDarkTheme
|
||||
: theme === 'oled'
|
||||
? JellifyOLEDTheme
|
||||
: JellifyLightTheme
|
||||
}
|
||||
theme={getJellifyNavTheme(colorPreset, resolvedMode)}
|
||||
>
|
||||
<GestureHandlerRootView>
|
||||
<TamaguiProvider config={jellifyConfig}>
|
||||
|
||||
@@ -2,9 +2,6 @@ apply plugin: "com.android.application"
|
||||
apply plugin: "org.jetbrains.kotlin.android"
|
||||
apply plugin: "com.facebook.react"
|
||||
|
||||
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
|
||||
|
||||
|
||||
|
||||
def keystoreFile = file("./jellify.keystore")
|
||||
def keystoreExists = keystoreFile.exists()
|
||||
@@ -93,7 +90,6 @@ android {
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 173
|
||||
versionName "1.0.14"
|
||||
resValue "string", "build_config_package", "com.jellify"
|
||||
|
||||
}
|
||||
signingConfigs {
|
||||
|
||||
18
bun.lock
18
bun.lock
@@ -9,13 +9,13 @@
|
||||
"@jellyfin/sdk": "0.13.0",
|
||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||
"@react-native-community/cli": "20.0.0",
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-community/netinfo": "11.5.1",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-native-vector-icons/material-design-icons": "12.4.0",
|
||||
"@react-navigation/bottom-tabs": "7.10.1",
|
||||
"@react-navigation/material-top-tabs": "7.4.13",
|
||||
"@react-navigation/native": "7.1.28",
|
||||
"@react-navigation/native-stack": "7.10.1",
|
||||
"@react-navigation/native-stack": "7.11.0",
|
||||
"@sentry/react-native": "7.8.0",
|
||||
"@shopify/flash-list": "2.2.0",
|
||||
"@tamagui/config": "1.144.3",
|
||||
@@ -33,7 +33,6 @@
|
||||
"react-native-blob-util": "^0.22.2",
|
||||
"react-native-blurhash": "^2.1.3",
|
||||
"react-native-carplay": "^2.4.1-beta.0",
|
||||
"react-native-config": "1.5.6",
|
||||
"react-native-device-info": "15.0.1",
|
||||
"react-native-fs": "^2.20.0",
|
||||
"react-native-gesture-handler": "2.30.0",
|
||||
@@ -48,11 +47,12 @@
|
||||
"react-native-pager-view": "8.0.0",
|
||||
"react-native-reanimated": "4.1.6",
|
||||
"react-native-safe-area-context": "5.6.2",
|
||||
"react-native-screens": "4.20.0",
|
||||
"react-native-screens": "4.21.0",
|
||||
"react-native-sortables": "1.9.4",
|
||||
"react-native-superconfig": "^0.6.0",
|
||||
"react-native-text-ticker": "^1.15.0",
|
||||
"react-native-toast-message": "^2.3.3",
|
||||
"react-native-turbo-image": "^1.23.1",
|
||||
"react-native-turbo-image": "1.24.1",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-uuid": "^2.0.3",
|
||||
"react-native-worklets": "^0.7.1",
|
||||
@@ -576,7 +576,7 @@
|
||||
|
||||
"@react-navigation/native": ["@react-navigation/native@7.1.28", "", { "dependencies": { "@react-navigation/core": "^7.14.0", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-d1QDn+KNHfHGt3UIwOZvupvdsDdiHYZBEj7+wL2yDVo3tMezamYy60H9s3EnNVE1Ae1ty0trc7F2OKqo/RmsdQ=="],
|
||||
|
||||
"@react-navigation/native-stack": ["@react-navigation/native-stack@7.10.1", "", { "dependencies": { "@react-navigation/elements": "^2.9.5", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-8jt7olKysn07HuKKSjT/ahZZTV+WaZa96o9RI7gAwh7ATlUDY02rIRttwvCyjovhSjD9KCiuJ+Hd4kwLidHwJw=="],
|
||||
"@react-navigation/native-stack": ["@react-navigation/native-stack@7.11.0", "", { "dependencies": { "@react-navigation/elements": "^2.9.5", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.28", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-yNx9Wr4dfpOHpqjf2sGog4eH6KCYwTAEPlUPrKbvWlQbCRm5bglwPmaTXw9hTovX9v3HIa42yo7bXpbYfq4jzg=="],
|
||||
|
||||
"@react-navigation/routers": ["@react-navigation/routers@7.5.3", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg=="],
|
||||
|
||||
@@ -1898,8 +1898,6 @@
|
||||
|
||||
"react-native-cli-bump-version": ["react-native-cli-bump-version@1.5.1", "", {}, "sha512-C7Vss+BBD4iNMnn2YR00cU+GDDPZ+LDmIqWoh3FPwI/LBsJ/Vp5qanwtyVYRPcIe7Cg1PPB8WdeZ8XcnqF5Klw=="],
|
||||
|
||||
"react-native-config": ["react-native-config@1.5.6", "", { "peerDependencies": { "react-native-windows": ">=0.61" }, "optionalPeers": ["react-native-windows"] }, "sha512-UB3LEco0FGGbbGvS+DfH2VmGKiP/y5C2MkmfBmfsIaxHSbM1KOTMKYG7YRf6xFhZbJ/01BedHG7SIny5i7N9BQ=="],
|
||||
|
||||
"react-native-device-info": ["react-native-device-info@15.0.1", "", { "peerDependencies": { "react-native": "*" } }, "sha512-U5waZRXtT3l1SgZpZMlIvMKPTkFZPH8W7Ks6GrJhdH723aUIPxjVer7cRSij1mvQdOAAYFJV/9BDzlC8apG89A=="],
|
||||
|
||||
"react-native-fs": ["react-native-fs@2.20.0", "", { "dependencies": { "base-64": "^0.1.0", "utf8": "^3.0.0" }, "peerDependencies": { "react-native": "*", "react-native-windows": "*" }, "optionalPeers": ["react-native-windows"] }, "sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ=="],
|
||||
@@ -1930,10 +1928,12 @@
|
||||
|
||||
"react-native-safe-area-context": ["react-native-safe-area-context@5.6.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg=="],
|
||||
|
||||
"react-native-screens": ["react-native-screens@4.20.0", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-wg3ILSd8yHM2YMsWqDjr1+Rxj1qn9CrzZ8qAqDXYd+jf6p3GIMwi+NugFUbRBRZMXs3MNEXCS1vAkvc2ZwpaAA=="],
|
||||
"react-native-screens": ["react-native-screens@4.21.0", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-vUgbfKntx4LZ/1k+UU/miKohK0Ih6xoF3gYJr9QqZNOpPARksPxt4hq3HdCirvCLClieLYC9oLpGdizz/S+BGg=="],
|
||||
|
||||
"react-native-sortables": ["react-native-sortables@1.9.4", "", { "optionalDependencies": { "react-native-haptic-feedback": ">=2.0.0" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-gesture-handler": ">=2.0.0", "react-native-reanimated": ">=3.0.0" } }, "sha512-a6hxT+gl14HA5Sm8UiLXJqF8KMEQVa+mUJd75OnzoVsmrxUDtjAatlMdV0kI9qTQDT/ZSFLPRmdUhOR762IA4g=="],
|
||||
|
||||
"react-native-superconfig": ["react-native-superconfig@0.6.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "*" } }, "sha512-kW9SjpKmuB7F54JNzaWHX5Ncr7jM8852FcIcBvfIyR0ofACaGvqf59hLKg7i8DfSIi0f5DXOCXbvu88cr8PIVw=="],
|
||||
|
||||
"react-native-tab-view": ["react-native-tab-view@4.2.2", "", { "dependencies": { "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0" } }, "sha512-NXtrG6OchvbGjsvbySJGVocXxo4Y2vA17ph4rAaWtA2jh+AasD8OyikKBRg2SmllEfeQ+GEhcKe8kulHv8BhTg=="],
|
||||
|
||||
"react-native-text-ticker": ["react-native-text-ticker@1.15.0", "", {}, "sha512-d/uK+PIOhsYMy1r8h825iq/nADiHsabz3WMbRJSnkpQYn+K9aykUAXRRhu8ZbTAzk4CgnUWajJEFxS5ZDygsdg=="],
|
||||
|
||||
@@ -169,6 +169,36 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- NitroSuperconfig (0.6.0):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
- fmt
|
||||
- glog
|
||||
- hermes-engine
|
||||
- NitroModules
|
||||
- RCT-Folly
|
||||
- RCT-Folly/Fabric
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-callinvoker
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- PromisesObjC (2.4.0)
|
||||
- RCT-Folly (2024.11.18.00):
|
||||
- boost
|
||||
@@ -2114,10 +2144,6 @@ PODS:
|
||||
- Yoga
|
||||
- react-native-carplay (2.4.1-beta.0):
|
||||
- React
|
||||
- react-native-config (1.5.6):
|
||||
- react-native-config/App (= 1.5.6)
|
||||
- react-native-config/App (1.5.6):
|
||||
- React-Core
|
||||
- react-native-google-cast (4.9.1):
|
||||
- google-cast-sdk
|
||||
- PromisesObjC
|
||||
@@ -3090,7 +3116,7 @@ PODS:
|
||||
- RNWorklets
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- RNScreens (4.20.0):
|
||||
- RNScreens (4.21.0):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -3117,10 +3143,10 @@ PODS:
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- RNScreens/common (= 4.20.0)
|
||||
- RNScreens/common (= 4.21.0)
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- RNScreens/common (4.20.0):
|
||||
- RNScreens/common (4.21.0):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -3288,6 +3314,7 @@ DEPENDENCIES:
|
||||
- NitroModules (from `../node_modules/react-native-nitro-modules`)
|
||||
- NitroOta (from `../node_modules/react-native-nitro-ota`)
|
||||
- NitroPlayer (from `../node_modules/react-native-nitro-player`)
|
||||
- NitroSuperconfig (from `../node_modules/react-native-superconfig`)
|
||||
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
|
||||
- RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
|
||||
- RCTRequired (from `../node_modules/react-native/Libraries/Required`)
|
||||
@@ -3329,7 +3356,6 @@ DEPENDENCIES:
|
||||
- react-native-blob-util (from `../node_modules/react-native-blob-util`)
|
||||
- react-native-blurhash (from `../node_modules/react-native-blurhash`)
|
||||
- react-native-carplay (from `../node_modules/react-native-carplay`)
|
||||
- react-native-config (from `../node_modules/react-native-config`)
|
||||
- react-native-google-cast (from `../node_modules/react-native-google-cast`)
|
||||
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
|
||||
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
|
||||
@@ -3425,6 +3451,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-nitro-ota"
|
||||
NitroPlayer:
|
||||
:path: "../node_modules/react-native-nitro-player"
|
||||
NitroSuperconfig:
|
||||
:path: "../node_modules/react-native-superconfig"
|
||||
RCT-Folly:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
|
||||
RCTDeprecation:
|
||||
@@ -3505,8 +3533,6 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-blurhash"
|
||||
react-native-carplay:
|
||||
:path: "../node_modules/react-native-carplay"
|
||||
react-native-config:
|
||||
:path: "../node_modules/react-native-config"
|
||||
react-native-google-cast:
|
||||
:path: "../node_modules/react-native-google-cast"
|
||||
react-native-netinfo:
|
||||
@@ -3630,6 +3656,7 @@ SPEC CHECKSUMS:
|
||||
NitroOta: 92d4eb528566b6babf5e4a30adbda44bfa803a9b
|
||||
NitroOtaBundleManager: 8fad871db2daf6b9ee6f04a100c79605cfa81e8d
|
||||
NitroPlayer: 8bc7be5caa2240ed636e4c1128791473eaf07a8b
|
||||
NitroSuperconfig: 54d86ee90bb78cbca09d119ea775a53ffbedb0fc
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
|
||||
RCTDeprecation: a41bbdd9af30bf2e5715796b313e44ec43eefff1
|
||||
@@ -3671,7 +3698,6 @@ SPEC CHECKSUMS:
|
||||
react-native-blob-util: e2162ce4757849682559754bca954b65dc7eeb2f
|
||||
react-native-blurhash: 93b024ff78f7912d22b1cdba262f3c91d3e2002e
|
||||
react-native-carplay: 8f388f6f73e5e0f73ed154ad8794371343ee20c0
|
||||
react-native-config: f1dde39f8468ad922fc7e8bd4308c8e6223d5ee8
|
||||
react-native-google-cast: 7be68a5d0b7eeb95a5924c3ecef8d319ef6c0a44
|
||||
react-native-netinfo: 34238ef2d5902cd505de92671b36eb7c28a55184
|
||||
react-native-pager-view: d7d2aa47f54343bf55fdcee3973503dd27c2bd37
|
||||
@@ -3719,7 +3745,7 @@ SPEC CHECKSUMS:
|
||||
RNGestureHandler: cd4be101cfa17ea6bbd438710caa02e286a84381
|
||||
RNReactNativeHapticFeedback: be4f1b4bf0398c30b59b76ed92ecb0a2ff3a69c6
|
||||
RNReanimated: 942d757148da78f5663d1fdf9ab71d1e75946c22
|
||||
RNScreens: 714e10b6b554f7dc7ad9f78dcf36dc8e3fc73415
|
||||
RNScreens: e66f520506371042666e38a06d5d7e9580f0d3a0
|
||||
RNSentry: fdb39d5f294e492aa2f08ad80e510310dc223772
|
||||
RNWorklets: 01efdd402d236a13651ea5ea5437ca85a44e7afa
|
||||
Sentry: c643eb180df401dd8c734c5036ddd9dd9218daa6
|
||||
|
||||
@@ -16,6 +16,15 @@ jest.mock('../../src/api/info', () => {
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock('react-native-superconfig', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
OTA_UPDATE_ENABLED: 'false',
|
||||
IS_MAESTRO_BUILD: 'false',
|
||||
GLITCHTIP_DSN: '',
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter')
|
||||
|
||||
jest.mock('react-native-haptic-feedback', () => {
|
||||
|
||||
10
package.json
10
package.json
@@ -41,13 +41,13 @@
|
||||
"@jellyfin/sdk": "0.13.0",
|
||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||
"@react-native-community/cli": "20.0.0",
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-community/netinfo": "11.5.1",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-native-vector-icons/material-design-icons": "12.4.0",
|
||||
"@react-navigation/bottom-tabs": "7.10.1",
|
||||
"@react-navigation/material-top-tabs": "7.4.13",
|
||||
"@react-navigation/native": "7.1.28",
|
||||
"@react-navigation/native-stack": "7.10.1",
|
||||
"@react-navigation/native-stack": "7.11.0",
|
||||
"@sentry/react-native": "7.8.0",
|
||||
"@shopify/flash-list": "2.2.0",
|
||||
"@tamagui/config": "1.144.3",
|
||||
@@ -65,7 +65,6 @@
|
||||
"react-native-blob-util": "^0.22.2",
|
||||
"react-native-blurhash": "^2.1.3",
|
||||
"react-native-carplay": "^2.4.1-beta.0",
|
||||
"react-native-config": "1.5.6",
|
||||
"react-native-device-info": "15.0.1",
|
||||
"react-native-fs": "^2.20.0",
|
||||
"react-native-gesture-handler": "2.30.0",
|
||||
@@ -80,11 +79,12 @@
|
||||
"react-native-pager-view": "8.0.0",
|
||||
"react-native-reanimated": "4.1.6",
|
||||
"react-native-safe-area-context": "5.6.2",
|
||||
"react-native-screens": "4.20.0",
|
||||
"react-native-screens": "4.21.0",
|
||||
"react-native-sortables": "1.9.4",
|
||||
"react-native-superconfig": "^0.6.0",
|
||||
"react-native-text-ticker": "^1.15.0",
|
||||
"react-native-toast-message": "^2.3.3",
|
||||
"react-native-turbo-image": "^1.23.1",
|
||||
"react-native-turbo-image": "1.24.1",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-uuid": "^2.0.3",
|
||||
"react-native-worklets": "^0.7.1",
|
||||
|
||||
@@ -64,6 +64,16 @@ export default function Icon({
|
||||
|
||||
const pressStyle = animation ? { opacity: 0.6 } : undefined
|
||||
|
||||
// Tamagui theme keys are unprefixed (e.g. "primary" not "$primary"); resolve for token strings
|
||||
const themeColorKey =
|
||||
color && typeof color === 'string' && color.startsWith('$') ? color.slice(1) : color
|
||||
const resolvedColor =
|
||||
color && !disabled
|
||||
? (theme[themeColorKey as keyof typeof theme]?.val ?? theme.color.val)
|
||||
: disabled
|
||||
? theme.neutral.val
|
||||
: theme.color.val
|
||||
|
||||
return (
|
||||
<YStack
|
||||
animation={animation}
|
||||
@@ -76,13 +86,7 @@ export default function Icon({
|
||||
flex={flex}
|
||||
>
|
||||
<MaterialDesignIcon
|
||||
color={
|
||||
color && !disabled
|
||||
? theme[color]?.val
|
||||
: disabled
|
||||
? theme.neutral.val
|
||||
: theme.color.val
|
||||
}
|
||||
color={resolvedColor}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
name={name as any}
|
||||
size={size}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SizeTokens, XStack, Separator, Switch, styled, getToken } from 'tamagui'
|
||||
import { SizeTokens, XStack, Separator, Switch, styled } from 'tamagui'
|
||||
import { Label } from './text'
|
||||
import { triggerHaptic } from '../../../hooks/use-haptic-feedback'
|
||||
|
||||
@@ -10,9 +10,10 @@ interface SwitchWithLabelProps {
|
||||
width?: number | undefined
|
||||
}
|
||||
|
||||
// Use theme tokens so thumb colors follow the active color preset
|
||||
const JellifySliderThumb = styled(Switch.Thumb, {
|
||||
borderColor: getToken('$color.amethyst'),
|
||||
backgroundColor: getToken('$color.purpleDark'),
|
||||
borderColor: '$primary',
|
||||
backgroundColor: '$background',
|
||||
})
|
||||
|
||||
export function SwitchWithLabel(props: SwitchWithLabelProps) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { getToken, useTheme, View, YStack, ZStack } from 'tamagui'
|
||||
import { useTheme, View, YStack, ZStack } from 'tamagui'
|
||||
import { useWindowDimensions } from 'react-native'
|
||||
import LinearGradient from 'react-native-linear-gradient'
|
||||
import { getBlurhashFromDto } from '../../../utils/parsing/blurhash'
|
||||
@@ -26,13 +26,13 @@ export default function BlurredBackground(): React.JSX.Element {
|
||||
// Get blurhash safely
|
||||
const blurhash = nowPlaying?.item ? getBlurhashFromDto(nowPlaying.item) : null
|
||||
|
||||
// Define gradient colors
|
||||
const darkGradientColors = [getToken('$black'), getToken('$black25')]
|
||||
// Use theme colors so the gradient follows the active color preset
|
||||
const darkGradientColors = [theme.background.val, theme.background25.val]
|
||||
const darkGradientColors2 = [
|
||||
getToken('$black25'),
|
||||
getToken('$black75'),
|
||||
getToken('$black'),
|
||||
getToken('$black'),
|
||||
theme.background25.val,
|
||||
theme.background75.val,
|
||||
theme.background.val,
|
||||
theme.background.val,
|
||||
]
|
||||
|
||||
// Define styles
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { Progress, XStack, YStack } from 'tamagui'
|
||||
import { Progress, useTheme, XStack, YStack } from 'tamagui'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import TextTicker from 'react-native-text-ticker'
|
||||
@@ -35,6 +35,7 @@ export default function Miniplayer(): React.JSX.Element | null {
|
||||
console.log('nowPlaying', nowPlaying)
|
||||
const skip = useSkip()
|
||||
const previous = usePrevious()
|
||||
const theme = useTheme()
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
@@ -99,7 +100,7 @@ export default function Miniplayer(): React.JSX.Element | null {
|
||||
pressStyle={pressStyle}
|
||||
animation={'quick'}
|
||||
onPress={openPlayer}
|
||||
backgroundColor='$background'
|
||||
backgroundColor={theme.background.val}
|
||||
>
|
||||
<MiniPlayerProgress />
|
||||
<XStack alignItems='center' padding={'$2'}>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { Suspense, lazy } from 'react'
|
||||
import { useColorScheme } from 'react-native'
|
||||
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'
|
||||
import { getToken, getTokenValue, useTheme, Spinner, YStack } from 'tamagui'
|
||||
import { getTokenValue, useTheme, Spinner, YStack } from 'tamagui'
|
||||
import { useColorPresetSetting, useThemeSetting } from '../../stores/settings/app'
|
||||
import SettingsTabBar from './tab-bar'
|
||||
|
||||
// Lazy load tab components to improve initial render
|
||||
@@ -63,9 +65,16 @@ function LazyInfoTab() {
|
||||
|
||||
export default function Settings(): React.JSX.Element {
|
||||
const theme = useTheme()
|
||||
const [themeSetting] = useThemeSetting()
|
||||
const [colorPreset] = useColorPresetSetting()
|
||||
const isDarkMode = useColorScheme() === 'dark'
|
||||
const resolvedMode = themeSetting === 'system' ? (isDarkMode ? 'dark' : 'light') : themeSetting
|
||||
// Key forces navigator to remount when preset/mode changes so tab bar colors update
|
||||
const themeKey = `${colorPreset}_${resolvedMode}`
|
||||
|
||||
return (
|
||||
<SettingsTabsNavigator.Navigator
|
||||
key={themeKey}
|
||||
screenOptions={{
|
||||
tabBarIndicatorStyle: {
|
||||
borderColor: theme.background.val,
|
||||
|
||||
@@ -2,7 +2,9 @@ import { YStack, XStack, Paragraph, SizableText } from 'tamagui'
|
||||
import { SwitchWithLabel } from '../../Global/helpers/switch-with-label'
|
||||
import SettingsListGroup from './settings-list-group'
|
||||
import {
|
||||
ColorPreset,
|
||||
ThemeSetting,
|
||||
useColorPresetSetting,
|
||||
useHideRunTimesSetting,
|
||||
useReducedHapticsSetting,
|
||||
useSendMetricsSetting,
|
||||
@@ -18,6 +20,12 @@ type ThemeOptionConfig = {
|
||||
icon: string
|
||||
}
|
||||
|
||||
type ColorPresetOptionConfig = {
|
||||
value: ColorPreset
|
||||
label: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
const THEME_OPTIONS: ThemeOptionConfig[] = [
|
||||
{
|
||||
value: 'system',
|
||||
@@ -41,6 +49,34 @@ const THEME_OPTIONS: ThemeOptionConfig[] = [
|
||||
},
|
||||
]
|
||||
|
||||
const COLOR_PRESET_OPTIONS: ColorPresetOptionConfig[] = [
|
||||
{
|
||||
value: 'purple',
|
||||
label: 'Purple',
|
||||
icon: 'crown',
|
||||
},
|
||||
{
|
||||
value: 'ocean',
|
||||
label: 'Ocean',
|
||||
icon: 'waves',
|
||||
},
|
||||
{
|
||||
value: 'forest',
|
||||
label: 'Forest',
|
||||
icon: 'forest',
|
||||
},
|
||||
{
|
||||
value: 'sunset',
|
||||
label: 'Sunset',
|
||||
icon: 'weather-sunset',
|
||||
},
|
||||
{
|
||||
value: 'peanut',
|
||||
label: 'Peanut',
|
||||
icon: 'peanut',
|
||||
},
|
||||
]
|
||||
|
||||
function ActionChip({
|
||||
active,
|
||||
label,
|
||||
@@ -113,6 +149,42 @@ function ThemeOptionCard({
|
||||
)
|
||||
}
|
||||
|
||||
function ColorPresetOptionCard({
|
||||
option,
|
||||
isSelected,
|
||||
onPress,
|
||||
}: {
|
||||
option: ColorPresetOptionConfig
|
||||
isSelected: boolean
|
||||
onPress: () => void
|
||||
}) {
|
||||
return (
|
||||
<YStack
|
||||
onPress={onPress}
|
||||
pressStyle={{ scale: 0.97 }}
|
||||
animation='quick'
|
||||
borderWidth={'$1'}
|
||||
borderColor={isSelected ? '$primary' : '$borderColor'}
|
||||
backgroundColor={isSelected ? '$background25' : '$background'}
|
||||
borderRadius={'$9'}
|
||||
padding='$3'
|
||||
gap='$2'
|
||||
hitSlop={8}
|
||||
role='button'
|
||||
aria-label={`${option.label} color preset option`}
|
||||
aria-selected={isSelected}
|
||||
>
|
||||
<XStack alignItems='center' gap='$2'>
|
||||
<Icon small name={option.icon} color={isSelected ? '$primary' : '$borderColor'} />
|
||||
<SizableText size={'$4'} flex={1} fontWeight='600'>
|
||||
{option.label}
|
||||
</SizableText>
|
||||
{isSelected && <Icon small name='check-circle-outline' color={'$primary'} />}
|
||||
</XStack>
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
||||
function getThemeSubtitle(themeSetting: ThemeSetting): string {
|
||||
switch (themeSetting) {
|
||||
case 'light':
|
||||
@@ -126,10 +198,28 @@ function getThemeSubtitle(themeSetting: ThemeSetting): string {
|
||||
}
|
||||
}
|
||||
|
||||
function getColorPresetSubtitle(colorPreset: ColorPreset): string {
|
||||
switch (colorPreset) {
|
||||
case 'purple':
|
||||
return 'Purple vibes'
|
||||
case 'ocean':
|
||||
return 'Oceanic vibes'
|
||||
case 'forest':
|
||||
return 'Foresty vibes'
|
||||
case 'sunset':
|
||||
return 'Sunset vibes'
|
||||
case 'peanut':
|
||||
return 'Sandbox vibes'
|
||||
default:
|
||||
return 'Default vibes'
|
||||
}
|
||||
}
|
||||
|
||||
export default function PreferencesTab(): React.JSX.Element {
|
||||
const [sendMetrics, setSendMetrics] = useSendMetricsSetting()
|
||||
const [reducedHaptics, setReducedHaptics] = useReducedHapticsSetting()
|
||||
const [themeSetting, setThemeSetting] = useThemeSetting()
|
||||
const [colorPreset, setColorPreset] = useColorPresetSetting()
|
||||
const [hideRunTimes, setHideRunTimes] = useHideRunTimesSetting()
|
||||
|
||||
const left = useSwipeSettingsStore((s) => s.left)
|
||||
@@ -138,7 +228,7 @@ export default function PreferencesTab(): React.JSX.Element {
|
||||
const toggleRight = useSwipeSettingsStore((s) => s.toggleRight)
|
||||
|
||||
const themeSubtitle = getThemeSubtitle(themeSetting)
|
||||
|
||||
const colorPresetSubtitle = getColorPresetSubtitle(colorPreset)
|
||||
return (
|
||||
<SettingsListGroup
|
||||
settingsList={[
|
||||
@@ -160,6 +250,24 @@ export default function PreferencesTab(): React.JSX.Element {
|
||||
</YStack>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Color Preset',
|
||||
subTitle: colorPresetSubtitle && `${colorPresetSubtitle}`,
|
||||
iconName: 'palette',
|
||||
iconColor: '$primary',
|
||||
children: (
|
||||
<YStack gap='$2' paddingVertical='$2'>
|
||||
{COLOR_PRESET_OPTIONS.map((option) => (
|
||||
<ColorPresetOptionCard
|
||||
key={option.value}
|
||||
option={option}
|
||||
isSelected={colorPreset === option.value}
|
||||
onPress={() => setColorPreset(option.value)}
|
||||
/>
|
||||
))}
|
||||
</YStack>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Track Swipe Actions',
|
||||
subTitle: 'Choose actions for left/right swipes',
|
||||
|
||||
@@ -10,13 +10,17 @@ import {
|
||||
} from '@typedigital/telemetrydeck-react'
|
||||
import telemetryDeckConfig from '../../telemetrydeck.json'
|
||||
import * as Sentry from '@sentry/react-native'
|
||||
import { getToken, Theme, useTheme } from 'tamagui'
|
||||
import { getToken, Theme, ThemeName, useTheme } from 'tamagui'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import JellifyToastConfig from '../configs/toast.config'
|
||||
import { useColorScheme } from 'react-native'
|
||||
import { StorageProvider } from '../providers/Storage'
|
||||
import { useSelectPlayerEngine } from '../stores/player/engine'
|
||||
import { useSendMetricsSetting, useThemeSetting } from '../stores/settings/app'
|
||||
import {
|
||||
useColorPresetSetting,
|
||||
useSendMetricsSetting,
|
||||
useThemeSetting,
|
||||
} from '../stores/settings/app'
|
||||
import { GLITCHTIP_DSN } from '../configs/config'
|
||||
import useDownloadProcessor from '../hooks/use-download-processor'
|
||||
/**
|
||||
@@ -25,12 +29,17 @@ import useDownloadProcessor from '../hooks/use-download-processor'
|
||||
*/
|
||||
export default function Jellify(): React.JSX.Element {
|
||||
const [theme] = useThemeSetting()
|
||||
const [colorPreset] = useColorPresetSetting()
|
||||
|
||||
const isDarkMode = useColorScheme() === 'dark'
|
||||
|
||||
const resolvedMode = theme === 'system' ? (isDarkMode ? 'dark' : 'light') : theme
|
||||
const themeName = `${colorPreset}_${resolvedMode}` // e.g. 'purple_dark'
|
||||
|
||||
useSelectPlayerEngine()
|
||||
|
||||
return (
|
||||
<Theme name={theme === 'system' ? (isDarkMode ? 'dark' : 'light') : theme}>
|
||||
<Theme name={themeName as ThemeName | null}>
|
||||
<JellifyLoggingWrapper>
|
||||
<DisplayProvider>
|
||||
<App />
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { DarkTheme, DefaultTheme } from '@react-navigation/native'
|
||||
import { getToken, getTokens } from 'tamagui'
|
||||
import type { Theme } from '@react-navigation/native'
|
||||
import { PRESET_PALETTES } from '../../tamagui.config'
|
||||
import type { ColorPreset } from '../stores/settings/app'
|
||||
|
||||
interface Fonts {
|
||||
regular: FontStyle
|
||||
@@ -43,38 +45,36 @@ const JellifyFonts: Fonts = {
|
||||
},
|
||||
}
|
||||
|
||||
export const JellifyDarkTheme: ReactNavigation.Theme = {
|
||||
dark: true,
|
||||
colors: {
|
||||
...DarkTheme.colors,
|
||||
card: getTokens().color.$darkBackground.val,
|
||||
border: getTokens().color.$neutral.val,
|
||||
background: getTokens().color.$darkBackground.val,
|
||||
primary: getTokens().color.$primaryDark.val,
|
||||
},
|
||||
fonts: JellifyFonts,
|
||||
function paletteToNavTheme(
|
||||
palette: (typeof PRESET_PALETTES)['purple']['dark'],
|
||||
dark: boolean,
|
||||
): Theme {
|
||||
const base = dark ? DarkTheme : DefaultTheme
|
||||
return {
|
||||
...base,
|
||||
dark,
|
||||
colors: {
|
||||
...base.colors,
|
||||
background: palette.background,
|
||||
card: palette.background,
|
||||
border: palette.borderColor,
|
||||
primary: palette.primary,
|
||||
},
|
||||
fonts: JellifyFonts,
|
||||
}
|
||||
}
|
||||
|
||||
export const JellifyLightTheme = {
|
||||
...DefaultTheme,
|
||||
colors: {
|
||||
...DefaultTheme.colors,
|
||||
primary: getTokens().color.$primaryLight.val,
|
||||
border: getTokens().color.$neutral.val,
|
||||
background: getTokens().color.$white.val,
|
||||
card: getTokens().color.$white.val,
|
||||
},
|
||||
fonts: JellifyFonts,
|
||||
/** React Navigation theme for a given color preset and mode (purple_dark, ocean_light, etc.) */
|
||||
export function getJellifyNavTheme(preset: ColorPreset, mode: 'light' | 'dark' | 'oled'): Theme {
|
||||
const palette = PRESET_PALETTES[preset][mode]
|
||||
return paletteToNavTheme(palette, mode !== 'light')
|
||||
}
|
||||
|
||||
export const JellifyOLEDTheme: ReactNavigation.Theme = {
|
||||
dark: true,
|
||||
colors: {
|
||||
...DarkTheme.colors,
|
||||
card: getTokens().color.$black.val,
|
||||
border: getTokens().color.$neutral.val,
|
||||
background: getTokens().color.$black.val,
|
||||
primary: getTokens().color.$primaryDark.val,
|
||||
},
|
||||
fonts: JellifyFonts,
|
||||
}
|
||||
/** Purple dark — matches Tamagui purple_dark (current JellifyDarkTheme) */
|
||||
export const JellifyDarkTheme: Theme = paletteToNavTheme(PRESET_PALETTES.purple.dark, true)
|
||||
|
||||
/** Purple light — matches Tamagui purple_light (current JellifyLightTheme) */
|
||||
export const JellifyLightTheme: Theme = paletteToNavTheme(PRESET_PALETTES.purple.light, false)
|
||||
|
||||
/** Purple oled — matches Tamagui purple_oled (current JellifyOLEDTheme) */
|
||||
export const JellifyOLEDTheme: Theme = paletteToNavTheme(PRESET_PALETTES.purple.oled, true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Config from 'react-native-config'
|
||||
import Config from 'react-native-superconfig'
|
||||
|
||||
const OTA_UPDATE_ENABLED = Config.OTA_UPDATE_ENABLED === 'true'
|
||||
const IS_MAESTRO_BUILD = Config.IS_MAESTRO_BUILD === 'true'
|
||||
|
||||
@@ -28,6 +28,7 @@ export default function Tabs({ route, navigation }: TabProps): React.JSX.Element
|
||||
animation: 'shift',
|
||||
tabBarActiveTintColor: theme.primary.val,
|
||||
tabBarInactiveTintColor: theme.borderColor.val,
|
||||
tabBarStyle: { backgroundColor: theme.background.val },
|
||||
lazy: true,
|
||||
}}
|
||||
tabBar={(props) => <TabBar {...props} />}
|
||||
|
||||
@@ -1,17 +1,57 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import Miniplayer from '../../components/Player/mini-player'
|
||||
import InternetConnectionWatcher from '../../components/Network/internetConnectionWatcher'
|
||||
import { BottomTabBar, BottomTabBarProps } from '@react-navigation/bottom-tabs'
|
||||
import useIsMiniPlayerActive from '../../hooks/use-mini-player'
|
||||
import { useTheme } from 'tamagui'
|
||||
|
||||
export default function TabBar({ ...props }: BottomTabBarProps): React.JSX.Element {
|
||||
/**
|
||||
* Merge theme-driven tab bar options into the focused route's descriptor
|
||||
* so the bar updates immediately when color preset changes (the navigator
|
||||
* often does not re-pass updated screenOptions to the tab bar).
|
||||
*/
|
||||
export default function TabBar(props: BottomTabBarProps): React.JSX.Element {
|
||||
const isMiniPlayerActive = useIsMiniPlayerActive()
|
||||
const theme = useTheme()
|
||||
|
||||
const descriptorsWithTheme = useMemo(() => {
|
||||
const focusedRoute = props.state.routes[props.state.index]
|
||||
const focusedDescriptor = props.descriptors[focusedRoute.key]
|
||||
if (!focusedDescriptor) return props.descriptors
|
||||
return {
|
||||
...props.descriptors,
|
||||
[focusedRoute.key]: {
|
||||
...focusedDescriptor,
|
||||
options: {
|
||||
...focusedDescriptor.options,
|
||||
tabBarStyle: {
|
||||
...focusedDescriptor.options.tabBarStyle,
|
||||
backgroundColor: theme.background.val,
|
||||
},
|
||||
tabBarActiveTintColor: theme.primary.val,
|
||||
tabBarInactiveTintColor: theme.borderColor.val,
|
||||
},
|
||||
},
|
||||
}
|
||||
}, [
|
||||
props.descriptors,
|
||||
props.state.routes,
|
||||
props.state.index,
|
||||
theme.background.val,
|
||||
theme.primary.val,
|
||||
theme.borderColor.val,
|
||||
])
|
||||
|
||||
// Key forces mini-player to remount when theme changes so colors update
|
||||
// (avoids stale styles from Reanimated/Progress when preset changes without interaction)
|
||||
const themeKey = `${theme.background.val}-${theme.primary.val}`
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMiniPlayerActive && <Miniplayer />}
|
||||
{isMiniPlayerActive && <Miniplayer key={themeKey} />}
|
||||
<InternetConnectionWatcher />
|
||||
|
||||
<BottomTabBar {...props} />
|
||||
<BottomTabBar {...props} descriptors={descriptorsWithTheme} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createJSONStorage, devtools, persist } from 'zustand/middleware'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
|
||||
export type ThemeSetting = 'system' | 'light' | 'dark' | 'oled'
|
||||
export type ColorPreset = 'purple' | 'ocean' | 'forest' | 'sunset' | 'peanut'
|
||||
|
||||
type AppSettingsStore = {
|
||||
sendMetrics: boolean
|
||||
@@ -17,6 +18,9 @@ type AppSettingsStore = {
|
||||
|
||||
theme: ThemeSetting
|
||||
setTheme: (theme: ThemeSetting) => void
|
||||
|
||||
colorPreset: ColorPreset
|
||||
setColorPreset: (colorPreset: ColorPreset) => void
|
||||
}
|
||||
|
||||
export const useAppSettingsStore = create<AppSettingsStore>()(
|
||||
@@ -34,6 +38,9 @@ export const useAppSettingsStore = create<AppSettingsStore>()(
|
||||
|
||||
theme: 'system',
|
||||
setTheme: (theme: ThemeSetting) => set({ theme }),
|
||||
|
||||
colorPreset: 'purple',
|
||||
setColorPreset: (colorPreset: ColorPreset) => set({ colorPreset }),
|
||||
}),
|
||||
{
|
||||
name: 'app-settings-storage',
|
||||
@@ -50,6 +57,18 @@ export const useThemeSetting: () => [ThemeSetting, (theme: ThemeSetting) => void
|
||||
|
||||
return [theme, setTheme]
|
||||
}
|
||||
|
||||
export const useColorPresetSetting: () => [
|
||||
ColorPreset,
|
||||
(colorPreset: ColorPreset) => void,
|
||||
] = () => {
|
||||
const colorPreset = useAppSettingsStore((state) => state.colorPreset)
|
||||
|
||||
const setColorPreset = useAppSettingsStore((state) => state.setColorPreset)
|
||||
|
||||
return [colorPreset, setColorPreset]
|
||||
}
|
||||
|
||||
export const useReducedHapticsSetting: () => [boolean, (reducedHaptics: boolean) => void] = () => {
|
||||
const reducedHaptics = useAppSettingsStore((state) => state.reducedHaptics)
|
||||
|
||||
|
||||
2
src/types/react-native-config.d.ts
vendored
2
src/types/react-native-config.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
declare module 'react-native-config' {
|
||||
declare module 'react-native-superconfig' {
|
||||
export interface NativeConfig {
|
||||
OTA_UPDATE_ENABLED?: string
|
||||
IS_MAESTRO_BUILD?: string
|
||||
|
||||
@@ -51,6 +51,275 @@ const tokens = createTokens({
|
||||
},
|
||||
})
|
||||
|
||||
/** Theme mode palette: semantic keys used by Tamagui and React Navigation */
|
||||
type PresetModePalette = {
|
||||
background: string
|
||||
background75: string
|
||||
background50: string
|
||||
background25: string
|
||||
borderColor: string
|
||||
color: string
|
||||
success: string
|
||||
secondary: string
|
||||
primary: string
|
||||
danger: string
|
||||
warning: string
|
||||
neutral: string
|
||||
translucent: string
|
||||
}
|
||||
|
||||
/** Palettes per preset (purple = current Jellify themes), for Tamagui + nav */
|
||||
export const PRESET_PALETTES: Record<
|
||||
'purple' | 'ocean' | 'forest' | 'sunset' | 'peanut',
|
||||
{ light: PresetModePalette; dark: PresetModePalette; oled: PresetModePalette }
|
||||
> = {
|
||||
purple: {
|
||||
// Matches current JellifyDarkTheme / JellifyLightTheme / JellifyOLEDTheme
|
||||
dark: {
|
||||
background: 'rgba(25, 24, 28, 1)',
|
||||
background75: 'rgba(25, 24, 28, 0.75)',
|
||||
background50: 'rgba(25, 24, 28, 0.5)',
|
||||
background25: 'rgba(25, 24, 28, 0.25)',
|
||||
borderColor: '#77748E',
|
||||
color: '#ffffff',
|
||||
success: 'rgba(87, 233, 201, 1)',
|
||||
secondary: 'rgba(75, 125, 215, 1)',
|
||||
primary: '#887BFF',
|
||||
danger: '#FF066F',
|
||||
warning: '#FF6625',
|
||||
neutral: '#77748E',
|
||||
translucent: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
light: {
|
||||
background: '#ffffff',
|
||||
background75: 'rgba(235, 221, 255, 0.75)',
|
||||
background50: 'rgba(235, 221, 255, 0.5)',
|
||||
background25: 'rgba(235, 221, 255, 0.25)',
|
||||
borderColor: '#77748E',
|
||||
color: '#0C0622',
|
||||
success: 'rgba(16, 175, 141, 1)',
|
||||
secondary: 'rgba(0, 58, 159, 1)',
|
||||
primary: '#4b0fd6ff',
|
||||
danger: '#B30077',
|
||||
warning: '#a93300ff',
|
||||
neutral: '#77748E',
|
||||
translucent: 'rgba(255, 255, 255, 0.75)',
|
||||
},
|
||||
oled: {
|
||||
background: '#000000',
|
||||
background75: 'rgba(0, 0, 0, 0.75)',
|
||||
background50: 'rgba(0, 0, 0, 0.5)',
|
||||
background25: 'rgba(0, 0, 0, 0.25)',
|
||||
borderColor: '#77748E',
|
||||
color: '#ffffff',
|
||||
success: 'rgba(87, 233, 201, 1)',
|
||||
secondary: 'rgba(75, 125, 215, 1)',
|
||||
primary: '#887BFF',
|
||||
danger: '#FF066F',
|
||||
warning: '#FF6625',
|
||||
neutral: '#77748E',
|
||||
translucent: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
ocean: {
|
||||
dark: {
|
||||
background: 'rgba(25, 24, 28, 1)',
|
||||
background75: 'rgba(25, 24, 28, 0.75)',
|
||||
background50: 'rgba(25, 24, 28, 0.5)',
|
||||
background25: 'rgba(25, 24, 28, 0.25)',
|
||||
borderColor: '#78909C',
|
||||
color: '#ffffff',
|
||||
success: '#4DD0E1',
|
||||
secondary: '#81D4FA',
|
||||
primary: '#4FC3F7',
|
||||
danger: '#FF7043',
|
||||
warning: '#FFB74D',
|
||||
neutral: '#78909C',
|
||||
translucent: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
light: {
|
||||
background: '#E1F5FE',
|
||||
background75: 'rgba(225, 245, 254, 0.75)',
|
||||
background50: 'rgba(225, 245, 254, 0.5)',
|
||||
background25: 'rgba(225, 245, 254, 0.25)',
|
||||
borderColor: '#546E7A',
|
||||
color: '#01579B',
|
||||
success: '#00838F',
|
||||
secondary: '#0277BD',
|
||||
primary: '#0288D1',
|
||||
danger: '#D84315',
|
||||
warning: '#EF6C00',
|
||||
neutral: '#546E7A',
|
||||
translucent: 'rgba(255, 255, 255, 0.75)',
|
||||
},
|
||||
oled: {
|
||||
background: '#000000',
|
||||
background75: 'rgba(0, 0, 0, 0.75)',
|
||||
background50: 'rgba(0, 0, 0, 0.5)',
|
||||
background25: 'rgba(0, 0, 0, 0.25)',
|
||||
borderColor: '#78909C',
|
||||
color: '#ffffff',
|
||||
success: '#4DD0E1',
|
||||
secondary: '#81D4FA',
|
||||
primary: '#4FC3F7',
|
||||
danger: '#FF7043',
|
||||
warning: '#FFB74D',
|
||||
neutral: '#78909C',
|
||||
translucent: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
forest: {
|
||||
dark: {
|
||||
background: 'rgb(35, 47, 35)',
|
||||
background75: 'rgba(35, 47, 35, 0.75)',
|
||||
background50: 'rgba(35, 47, 35, 0.5)',
|
||||
background25: 'rgba(35, 47, 35, 0.25)',
|
||||
borderColor: '#8D9E8C',
|
||||
color: '#ffffff',
|
||||
success: '#66BB6A',
|
||||
secondary: '#9CCC65',
|
||||
primary: 'rgb(56, 105, 56)',
|
||||
danger: '#E57373',
|
||||
warning: '#FFB74D',
|
||||
neutral: '#8D9E8C',
|
||||
translucent: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
light: {
|
||||
background: '#E8F5E9',
|
||||
background75: 'rgba(232, 245, 233, 0.75)',
|
||||
background50: 'rgba(232, 245, 233, 0.5)',
|
||||
background25: 'rgba(232, 245, 233, 0.25)',
|
||||
borderColor: '#558B2F',
|
||||
color: '#1B5E20',
|
||||
success: '#2E7D32',
|
||||
secondary: '#43A047',
|
||||
primary: 'rgb(14, 143, 21)',
|
||||
danger: '#C62828',
|
||||
warning: '#E65100',
|
||||
neutral: '#558B2F',
|
||||
translucent: 'rgba(255, 255, 255, 0.75)',
|
||||
},
|
||||
oled: {
|
||||
background: '#000000',
|
||||
background75: 'rgba(0, 0, 0, 0.75)',
|
||||
background50: 'rgba(0, 0, 0, 0.5)',
|
||||
background25: 'rgba(0, 0, 0, 0.25)',
|
||||
borderColor: '#8D9E8C',
|
||||
color: '#ffffff',
|
||||
success: '#66BB6A',
|
||||
secondary: '#9CCC65',
|
||||
primary: 'rgb(11, 128, 17)',
|
||||
danger: '#E57373',
|
||||
warning: '#FFB74D',
|
||||
neutral: '#8D9E8C',
|
||||
translucent: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
sunset: {
|
||||
dark: {
|
||||
background: 'rgb(52, 34, 28)',
|
||||
background75: 'rgba(52, 34, 28, 0.75)',
|
||||
background50: 'rgba(52, 34, 28, 0.5)',
|
||||
background25: 'rgba(52, 34, 28, 0.25)',
|
||||
borderColor: '#A1887F',
|
||||
color: '#ffffff',
|
||||
success: '#FFAB91',
|
||||
secondary: '#FF8A65',
|
||||
primary: '#FF7043',
|
||||
danger: '#EF5350',
|
||||
warning: '#FFCA28',
|
||||
neutral: '#A1887F',
|
||||
translucent: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
light: {
|
||||
background: '#FFF3E0',
|
||||
background75: 'rgba(255, 243, 224, 0.75)',
|
||||
background50: 'rgba(255, 243, 224, 0.5)',
|
||||
background25: 'rgba(255, 243, 224, 0.25)',
|
||||
borderColor: '#BF360C',
|
||||
color: '#3E2723',
|
||||
success: '#E64A19',
|
||||
secondary: '#FF5722',
|
||||
primary: '#FF5722',
|
||||
danger: '#B71C1C',
|
||||
warning: '#F57C00',
|
||||
neutral: '#BF360C',
|
||||
translucent: 'rgba(255, 255, 255, 0.75)',
|
||||
},
|
||||
oled: {
|
||||
background: '#000000',
|
||||
background75: 'rgba(0, 0, 0, 0.75)',
|
||||
background50: 'rgba(0, 0, 0, 0.5)',
|
||||
background25: 'rgba(0, 0, 0, 0.25)',
|
||||
borderColor: '#A1887F',
|
||||
color: '#ffffff',
|
||||
success: '#FFAB91',
|
||||
secondary: '#FF8A65',
|
||||
primary: '#FF7043',
|
||||
danger: '#EF5350',
|
||||
warning: '#FFCA28',
|
||||
neutral: '#A1887F',
|
||||
translucent: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
peanut: {
|
||||
dark: {
|
||||
background: 'rgba(62, 39, 22, 1)',
|
||||
background75: 'rgba(62, 39, 22, 0.75)',
|
||||
background50: 'rgba(62, 39, 22, 0.5)',
|
||||
background25: 'rgba(62, 39, 22, 0.25)',
|
||||
borderColor: '#BCAAA4',
|
||||
color: '#ffffff',
|
||||
success: '#D7CCC8',
|
||||
secondary: '#A1887F',
|
||||
primary: '#D7CCC8',
|
||||
danger: '#8D6E63',
|
||||
warning: '#FFAB91',
|
||||
neutral: '#BCAAA4',
|
||||
translucent: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
light: {
|
||||
background: '#EFEBE9',
|
||||
background75: 'rgba(239, 235, 233, 0.75)',
|
||||
background50: 'rgba(239, 235, 233, 0.5)',
|
||||
background25: 'rgba(239, 235, 233, 0.25)',
|
||||
borderColor: '#6D4C41',
|
||||
color: '#3E2723',
|
||||
success: '#5D4037',
|
||||
secondary: '#795548',
|
||||
primary: '#8D6E63',
|
||||
danger: '#4E342E',
|
||||
warning: '#BF360C',
|
||||
neutral: '#6D4C41',
|
||||
translucent: 'rgba(255, 255, 255, 0.75)',
|
||||
},
|
||||
oled: {
|
||||
background: '#000000',
|
||||
background75: 'rgba(0, 0, 0, 0.75)',
|
||||
background50: 'rgba(0, 0, 0, 0.5)',
|
||||
background25: 'rgba(0, 0, 0, 0.25)',
|
||||
borderColor: '#BCAAA4',
|
||||
color: '#ffffff',
|
||||
success: '#D7CCC8',
|
||||
secondary: '#A1887F',
|
||||
primary: '#D7CCC8',
|
||||
danger: '#8D6E63',
|
||||
warning: '#FFAB91',
|
||||
neutral: '#BCAAA4',
|
||||
translucent: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const presetNames = ['purple', 'ocean', 'forest', 'sunset', 'peanut'] as const
|
||||
|
||||
const themes: Record<string, PresetModePalette> = {}
|
||||
for (const preset of presetNames) {
|
||||
for (const mode of ['light', 'dark', 'oled'] as const) {
|
||||
themes[`${preset}_${mode}`] = PRESET_PALETTES[preset][mode]
|
||||
}
|
||||
}
|
||||
|
||||
const jellifyConfig = createTamagui({
|
||||
animations,
|
||||
fonts: {
|
||||
@@ -60,89 +329,7 @@ const jellifyConfig = createTamagui({
|
||||
media,
|
||||
shorthands,
|
||||
tokens,
|
||||
themes: {
|
||||
dark: {
|
||||
background: tokens.color.darkBackground,
|
||||
background75: tokens.color.darkBackground75,
|
||||
background50: tokens.color.darkBackground50,
|
||||
background25: tokens.color.darkBackground25,
|
||||
borderColor: tokens.color.neutral,
|
||||
color: tokens.color.white,
|
||||
success: tokens.color.tealDark,
|
||||
secondary: tokens.color.secondaryDark,
|
||||
primary: tokens.color.primaryDark,
|
||||
danger: tokens.color.dangerDark,
|
||||
warning: tokens.color.warningDark,
|
||||
neutral: tokens.color.neutral,
|
||||
|
||||
translucent: tokens.color.darkTranslucent,
|
||||
},
|
||||
oled: {
|
||||
// True black OLED theme
|
||||
background: tokens.color.black,
|
||||
background75: tokens.color.black75,
|
||||
background50: tokens.color.black50,
|
||||
background25: tokens.color.black25,
|
||||
borderColor: tokens.color.neutral,
|
||||
color: tokens.color.white,
|
||||
success: tokens.color.tealDark,
|
||||
secondary: tokens.color.secondaryDark,
|
||||
primary: tokens.color.primaryDark,
|
||||
danger: tokens.color.dangerDark,
|
||||
warning: tokens.color.warningDark,
|
||||
neutral: tokens.color.neutral,
|
||||
|
||||
translucent: tokens.color.darkTranslucent,
|
||||
},
|
||||
dark_inverted_purple: {
|
||||
color: tokens.color.purpleDark,
|
||||
borderColor: tokens.color.amethyst,
|
||||
background: tokens.color.amethyst,
|
||||
background25: tokens.color.amethyst25,
|
||||
background50: tokens.color.amethyst50,
|
||||
background75: tokens.color.amethyst75,
|
||||
success: tokens.color.tealDark,
|
||||
secondary: tokens.color.secondaryDark,
|
||||
primary: tokens.color.primaryDark,
|
||||
danger: tokens.color.dangerDark,
|
||||
warning: tokens.color.warningDark,
|
||||
neutral: tokens.color.neutral,
|
||||
|
||||
translucent: tokens.color.darkTranslucent,
|
||||
},
|
||||
light: {
|
||||
background: tokens.color.white,
|
||||
background75: tokens.color.lightBackground75,
|
||||
background50: tokens.color.lightBackground50,
|
||||
background25: tokens.color.lightBackground25,
|
||||
borderColor: tokens.color.neutral,
|
||||
color: tokens.color.purpleDark,
|
||||
success: tokens.color.tealLight,
|
||||
secondary: tokens.color.secondaryLight,
|
||||
primary: tokens.color.primaryLight,
|
||||
danger: tokens.color.dangerLight,
|
||||
warning: tokens.color.warningLight,
|
||||
neutral: tokens.color.neutral,
|
||||
|
||||
translucent: tokens.color.lightTranslucent,
|
||||
},
|
||||
light_inverted_purple: {
|
||||
color: tokens.color.purpleDark,
|
||||
borderColor: tokens.color.neutral,
|
||||
background: tokens.color.amethyst,
|
||||
background25: tokens.color.amethyst25,
|
||||
background50: tokens.color.amethyst50,
|
||||
background75: tokens.color.amethyst75,
|
||||
success: tokens.color.tealLight,
|
||||
secondary: tokens.color.secondaryLight,
|
||||
primary: tokens.color.primaryLight,
|
||||
danger: tokens.color.dangerLight,
|
||||
warning: tokens.color.warningLight,
|
||||
neutral: tokens.color.neutral,
|
||||
|
||||
translucent: tokens.color.lightTranslucent,
|
||||
},
|
||||
},
|
||||
themes,
|
||||
})
|
||||
|
||||
export type JellifyConfig = typeof jellifyConfig
|
||||
|
||||
Reference in New Issue
Block a user