1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

Implemented new dark theme

This commit is contained in:
Zhiyuan Zheng
2022-02-12 14:51:01 +01:00
parent 50141b2963
commit 6f0c318d06
108 changed files with 863 additions and 571 deletions

View File

@ -1,21 +1,20 @@
import { ContextsV0 } from './v0'
import { ContextsV1 } from './v1'
import { ContextsV2 } from './v2'
const contextsMigration = {
1: (state: ContextsV0) => {
return (state = {
1: (state: ContextsV0): ContextsV1 => {
return {
...state,
// @ts-ignore
mePage: {
lists: { shown: false },
announcements: { shown: false, unread: 0 }
}
})
}
},
2: (state: ContextsV1) => {
// @ts-ignore
delete state.mePage
return state
2: (state: ContextsV1): ContextsV2 => {
const { mePage, ...rest } = state
return rest
}
}

View File

@ -0,0 +1,13 @@
export type ContextsV2 = {
storeReview: {
context: Readonly<number>
current: number
shown: boolean
}
publicRemoteNotice: {
context: Readonly<number>
current: number
hidden: boolean
}
previousTab: 'Tab-Local' | 'Tab-Public' | 'Tab-Notifications' | 'Tab-Me'
}

View File

@ -2,15 +2,15 @@ import { InstanceV3 } from './v3'
import { InstanceV4 } from './v4'
import { InstanceV5 } from './v5'
import { InstanceV6 } from './v6'
import { InstanceV7 } from './v7'
const instancesMigration = {
4: (state: InstanceV3) => {
4: (state: InstanceV3): InstanceV4 => {
return {
instances: state.local.instances.map((instance, index) => {
// @ts-ignore
delete instance.notification
const { notification, ...rest } = instance
return {
...instance,
...rest,
active: state.local.activeIndex === index,
push: {
global: { loading: false, value: false },
@ -28,35 +28,42 @@ const instancesMigration = {
})
}
},
5: (state: InstanceV4) => {
5: (state: InstanceV4): InstanceV5 => {
// @ts-ignore
if (state.instances.length && !state.instances[0].notifications_filter) {
return {
// @ts-ignore
instances: state.instances.map(instance => {
// @ts-ignore
instance.notifications_filter = {
follow: true,
favourite: true,
reblog: true,
mention: true,
poll: true,
follow_request: true
return {
...instance,
notifications_filter: {
follow: true,
favourite: true,
reblog: true,
mention: true,
poll: true,
follow_request: true
}
}
return instance
})
}
} else {
// @ts-ignore
return state
}
},
6: (state: InstanceV5) => {
6: (state: InstanceV5): InstanceV6 => {
return {
// @ts-ignore
instances: state.instances.map(instance => {
return { ...instance, configuration: undefined }
return {
...instance,
configuration: undefined
}
})
}
},
7: (state: InstanceV6) => {
7: (state: InstanceV6): InstanceV7 => {
return {
instances: state.instances.map(instance => {
return {

View File

@ -0,0 +1,77 @@
import { ComposeStateDraft } from '@screens/Compose/utils/types'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
type Instance = {
active: boolean
appData: {
clientId: string
clientSecret: string
}
url: string
token: string
uri: Mastodon.Instance['uri']
urls: Mastodon.Instance['urls']
account: {
id: Mastodon.Account['id']
acct: Mastodon.Account['acct']
avatarStatic: Mastodon.Account['avatar_static']
preferences: Mastodon.Preferences
}
max_toot_chars?: number // To be deprecated in v4
configuration?: Mastodon.Instance['configuration']
filters: Mastodon.Filter[]
notifications_filter: {
follow: boolean
favourite: boolean
reblog: boolean
mention: boolean
poll: boolean
follow_request: boolean
}
push: {
global: { loading: boolean; value: boolean }
decode: { loading: boolean; value: boolean }
alerts: {
follow: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['follow']
}
favourite: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['favourite']
}
reblog: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['reblog']
}
mention: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['mention']
}
poll: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['poll']
}
}
keys: {
auth?: string
public?: string // legacy
private?: string // legacy
}
}
timelinesLookback?: {
[key: string]: {
queryKey: QueryKeyTimeline
ids: Mastodon.Status['id'][]
}
}
mePage: {
lists: { shown: boolean }
announcements: { shown: boolean; unread: number }
}
drafts: ComposeStateDraft[]
}
export type InstanceV7 = {
instances: Instance[]
}

View File

@ -0,0 +1,13 @@
import { SettingsV0 } from './v0'
import { SettingsV1 } from './v1'
const settingsMigration = {
1: (state: SettingsV0): SettingsV1 => {
return {
...state,
darkTheme: 'lighter'
}
}
}
export default settingsMigration

View File

@ -0,0 +1,7 @@
export type SettingsV0 = {
fontsize: -1 | 0 | 1 | 2 | 3
language: string
theme: 'light' | 'dark' | 'auto'
browser: 'internal' | 'external'
analytics: boolean
}

View File

@ -0,0 +1,8 @@
export type SettingsV1 = {
fontsize: -1 | 0 | 1 | 2 | 3
language: string
theme: 'light' | 'dark' | 'auto'
darkTheme: 'lighter' | 'darker'
browser: 'internal' | 'external'
analytics: boolean
}

View File

@ -17,7 +17,7 @@ export interface Params {
const pushUseConnect = ({ t, instances }: Params) => {
const dispatch = useDispatch()
const { mode } = useTheme()
const { theme } = useTheme()
return useEffect(() => {
const connect = async () => {
@ -36,7 +36,7 @@ const pushUseConnect = ({ t, instances }: Params) => {
}).catch(error => {
if (error.status == 404) {
displayMessage({
mode,
theme,
type: 'error',
duration: 'long',
message: t('pushError.message'),

View File

@ -2,6 +2,7 @@ import apiInstance from '@api/instance'
import haptics from '@components/haptics'
import { displayMessage } from '@components/Message'
import queryClient from '@helpers/queryClient'
import { Theme } from '@utils/styles/themes'
import { AxiosError } from 'axios'
import i18next from 'i18next'
import { RefObject } from 'react'
@ -51,7 +52,7 @@ type MutationVarsProfileBase =
}
type MutationVarsProfile = MutationVarsProfileBase & {
mode: 'light' | 'dark'
theme: Theme
messageRef: RefObject<FlashMessage>
message: {
text: string
@ -133,7 +134,7 @@ const useProfileMutation = () => {
type: i18next.t(`screenTabs:${variables.message.text}`)
}),
...(err && { description: err.message }),
mode: variables.mode,
theme: variables.theme,
type: 'error'
})
}
@ -146,7 +147,7 @@ const useProfileMutation = () => {
message: i18next.t('screenTabs:me.profile.feedback.succeed', {
type: i18next.t(`screenTabs:${variables.message.text}`)
}),
mode: variables.mode,
theme: variables.theme,
type: 'success'
})
}

View File

@ -17,6 +17,7 @@ export type SettingsState = {
fontsize: -1 | 0 | 1 | 2 | 3
language: string
theme: 'light' | 'dark' | 'auto'
darkTheme: 'lighter' | 'darker'
browser: 'internal' | 'external'
analytics: boolean
}
@ -34,6 +35,7 @@ export const settingsInitialState = {
)[0]
: 'en',
theme: 'auto',
darkTheme: 'lighter',
browser: 'internal',
analytics: true
}
@ -60,6 +62,12 @@ const settingsSlice = createSlice({
) => {
state.theme = action.payload
},
changeDarkTheme: (
state,
action: PayloadAction<NonNullable<SettingsState['darkTheme']>>
) => {
state.darkTheme = action.payload
},
changeBrowser: (
state,
action: PayloadAction<NonNullable<SettingsState['browser']>>
@ -78,10 +86,17 @@ export const getSettingsFontsize = (state: RootState) =>
state.settings.fontsize || 0
export const getSettingsLanguage = (state: RootState) => state.settings.language
export const getSettingsTheme = (state: RootState) => state.settings.theme
export const getSettingsDarkTheme = (state: RootState) =>
state.settings.darkTheme
export const getSettingsBrowser = (state: RootState) => state.settings.browser
export const getSettingsAnalytics = (state: RootState) =>
state.settings.analytics
export const { changeFontsize, changeLanguage, changeTheme, changeBrowser } =
settingsSlice.actions
export const {
changeFontsize,
changeLanguage,
changeTheme,
changeDarkTheme,
changeBrowser
} = settingsSlice.actions
export default settingsSlice.reducer

View File

@ -1,20 +1,30 @@
import React, { createContext, useContext, useEffect, useState } from 'react'
import React, {
createContext,
useContext,
useEffect,
useRef,
useState
} from 'react'
import { Appearance } from 'react-native'
import { useSelector } from 'react-redux'
import { ColorDefinitions, getTheme } from '@utils/styles/themes'
import { getSettingsTheme } from '@utils/slices/settingsSlice'
import { ColorDefinitions, getColors, Theme } from '@utils/styles/themes'
import {
getSettingsDarkTheme,
getSettingsTheme,
SettingsState
} from '@utils/slices/settingsSlice'
import { throttle } from 'lodash'
type ContextType = {
mode: 'light' | 'dark'
theme: { [key in ColorDefinitions]: string }
setTheme: (theme: 'light' | 'dark') => void
theme: Theme
colors: { [key in ColorDefinitions]: string }
}
const ManageThemeContext = createContext<ContextType>({
mode: 'light',
theme: getTheme('light'),
setTheme: () => {}
theme: 'light',
colors: getColors('light')
})
export const useTheme = () => useContext(ManageThemeContext)
@ -45,26 +55,54 @@ const useColorSchemeDelay = (delay = 500) => {
return colorScheme
}
const determineTheme = (
osTheme: 'light' | 'dark' | null | undefined,
userTheme: SettingsState['theme'],
darkTheme: SettingsState['darkTheme']
): 'light' | 'dark_lighter' | 'dark_darker' => {
enum DarkTheme {
lighter = 'dark_lighter',
darker = 'dark_darker'
}
const determineDarkTheme = DarkTheme[darkTheme]
switch (userTheme) {
case 'auto':
switch (osTheme) {
case 'dark':
return determineDarkTheme
default:
return 'light'
}
case 'light':
return 'light'
case 'dark':
return determineDarkTheme
}
}
const ThemeManager: React.FC = ({ children }) => {
const osTheme = useColorSchemeDelay()
const userTheme = useSelector(getSettingsTheme)
const currentMode =
userTheme === 'auto' ? (osTheme as 'light' | 'dark') : userTheme
const darkTheme = useSelector(getSettingsDarkTheme)
const [mode, setMode] = useState(currentMode)
const setTheme = (theme: 'light' | 'dark') => setMode(theme)
const mode = useRef(userTheme === 'auto' ? osTheme || 'light' : userTheme)
const [theme, setTheme] = useState<Theme>(
determineTheme(osTheme, userTheme, darkTheme)
)
useEffect(() => {
setMode(currentMode)
}, [currentMode])
mode.current = userTheme === 'auto' ? osTheme || 'light' : userTheme
}, [userTheme])
useEffect(() => {
setTheme(determineTheme(osTheme, userTheme, darkTheme))
}, [osTheme, userTheme, darkTheme])
return (
<ManageThemeContext.Provider
value={{
mode: mode,
theme: getTheme(mode),
setTheme: setTheme
mode: mode.current,
theme,
colors: getColors(theme)
}}
>
{children}

View File

@ -1,5 +1,7 @@
import { DefaultTheme, DarkTheme } from '@react-navigation/native'
export type Theme = 'light' | 'dark_lighter' | 'dark_darker'
export type ColorDefinitions =
| 'primaryDefault'
| 'primaryOverlay'
@ -20,82 +22,98 @@ export type ColorDefinitions =
const themeColors: {
[key in ColorDefinitions]: {
light: string
dark: string
dark_lighter: string
dark_darker: string
}
} = {
primaryDefault: {
light: 'rgb(18, 18, 18)',
dark: 'rgb(180, 180, 180)'
dark_lighter: 'rgb(250, 250, 250)',
dark_darker: 'rgb(180, 180, 180)'
},
primaryOverlay: {
light: 'rgb(250, 250, 250)',
dark: 'rgb(200, 200, 200)'
dark_lighter: 'rgb(200, 200, 200)',
dark_darker: 'rgb(200, 200, 200)'
},
secondary: {
light: 'rgb(135, 135, 135)',
dark: 'rgb(130, 130, 130)'
dark_lighter: 'rgb(160, 160, 160)',
dark_darker: 'rgb(130, 130, 130)'
},
disabled: {
light: 'rgb(200, 200, 200)',
dark: 'rgb(66, 66, 66)'
dark_lighter: 'rgb(120, 120, 120)',
dark_darker: 'rgb(66, 66, 66)'
},
blue: {
light: 'rgb(43, 144, 221)',
dark: 'rgb(43, 144, 221)'
dark_lighter: 'rgb(43, 144, 221)',
dark_darker: 'rgb(43, 144, 221)'
},
red: {
light: 'rgb(225, 45, 35)',
dark: 'rgb(225, 78, 79)'
dark_lighter: 'rgb(230, 60, 60)',
dark_darker: 'rgb(225, 78, 79)'
},
green: {
light: 'rgb(18, 158, 80)',
dark: 'rgb(18, 158, 80)'
dark_lighter: 'rgb(20, 185, 80)',
dark_darker: 'rgb(18, 158, 80)'
},
yellow: {
light: 'rgb(230, 166, 30)',
dark: 'rgb(200, 145, 25)'
dark_lighter: 'rgb(220, 160, 25)',
dark_darker: 'rgb(200, 145, 25)'
},
backgroundDefault: {
light: 'rgb(250, 250, 250)',
dark: 'rgb(18, 18, 18)'
dark_lighter: 'rgb(44, 44, 44)',
dark_darker: 'rgb(18, 18, 18)'
},
backgroundDefaultTransparent: {
light: 'rgba(250, 250, 250, 0)',
dark: 'rgba(18, 18, 18, 0)'
dark_lighter: 'rgba(18, 18, 18, 0)',
dark_darker: 'rgba(18, 18, 18, 0)'
},
backgroundOverlayDefault: {
light: 'rgba(250, 250, 250, 0.5)',
dark: 'rgba(0, 0, 0, 0.5)'
dark_lighter: 'rgba(44, 44, 44, 0.5)',
dark_darker: 'rgba(18, 18, 18, 0.5)'
},
backgroundOverlayInvert: {
light: 'rgba(25, 25, 25, 0.5)',
dark: 'rgba(0, 0, 0, 0.5)'
dark_lighter: 'rgba(0, 0, 0, 0.5)',
dark_darker: 'rgba(0, 0, 0, 0.5)'
},
border: {
light: 'rgba(25, 25, 25, 0.3)',
dark: 'rgba(255, 255, 255, 0.3)'
dark_lighter: 'rgba(255, 255, 255, 0.3)',
dark_darker: 'rgba(255, 255, 255, 0.3)'
},
shimmerDefault: {
light: 'rgba(25, 25, 25, 0.05)',
dark: 'rgba(250, 250, 250, 0.05)'
dark_lighter: 'rgba(250, 250, 250, 0.05)',
dark_darker: 'rgba(250, 250, 250, 0.05)'
},
shimmerHighlight: {
light: 'rgba(25, 25, 25, 0.15)',
dark: 'rgba(250, 250, 250, 0.15)'
dark_lighter: 'rgba(250, 250, 250, 0.15)',
dark_darker: 'rgba(250, 250, 250, 0.15)'
}
}
const getTheme = (mode: 'light' | 'dark') => {
let Theme = {} as {
const getColors = (theme: Theme): { [key in ColorDefinitions]: string } => {
let colors = {} as {
[key in ColorDefinitions]: string
}
const keys = Object.keys(themeColors) as ColorDefinitions[]
keys.forEach(key => (Theme[key] = themeColors[key][mode]))
keys.forEach(key => (colors[key] = themeColors[key][theme]))
return Theme
return colors
}
const themes = {
@ -111,18 +129,30 @@ const themes = {
notification: themeColors.red.light
}
},
dark: {
dark_lighter: {
...DarkTheme,
colors: {
...DarkTheme.colors,
primary: themeColors.primaryDefault.dark,
background: themeColors.backgroundDefault.dark,
card: themeColors.backgroundDefault.dark,
text: themeColors.primaryDefault.dark,
border: themeColors.border.dark,
notification: themeColors.red.dark
primary: themeColors.primaryDefault.dark_lighter,
background: themeColors.backgroundDefault.dark_lighter,
card: themeColors.backgroundDefault.dark_lighter,
text: themeColors.primaryDefault.dark_lighter,
border: themeColors.border.dark_lighter,
notification: themeColors.red.dark_lighter
}
},
dark_darker: {
...DarkTheme,
colors: {
...DarkTheme.colors,
primary: themeColors.primaryDefault.dark_darker,
background: themeColors.backgroundDefault.dark_darker,
card: themeColors.backgroundDefault.dark_darker,
text: themeColors.primaryDefault.dark_darker,
border: themeColors.border.dark_darker,
notification: themeColors.red.dark_darker
}
}
}
export { themeColors, getTheme, themes }
export { themeColors, getColors, themes }