1
0
mirror of https://github.com/tooot-app/app synced 2025-02-22 06:37:57 +01:00

Merge pull request #229 from tooot-app/main

Test v3.5
This commit is contained in:
xmflsct 2022-02-13 22:21:12 +01:00 committed by GitHub
commit f873e12bcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
137 changed files with 1274 additions and 679 deletions

View File

@ -27,11 +27,9 @@ jobs:
token: ${{ secrets.EXPO_TOKEN }} token: ${{ secrets.EXPO_TOKEN }}
- name: -- Step 4 -- Install node dependencies - name: -- Step 4 -- Install node dependencies
run: yarn install run: yarn install
- name: -- Step 5 -- Install native dependencies - name: -- Step 5 -- Install ruby dependencies
run: npx pod-install
- name: -- Step 6 -- Install ruby dependencies
run: bundle install run: bundle install
- name: -- Step 7 -- Run fastlane - name: -- Step 6 -- Run fastlane
env: env:
DEVELOPER_DIR: /Applications/Xcode_13.2.1.app/Contents/Developer DEVELOPER_DIR: /Applications/Xcode_13.2.1.app/Contents/Developer
ENVIRONMENT: ${{ steps.branch.outputs.branch }} ENVIRONMENT: ${{ steps.branch.outputs.branch }}

View File

@ -17,17 +17,17 @@ GEM
artifactory (3.0.15) artifactory (3.0.15)
atomos (0.1.3) atomos (0.1.3)
aws-eventstream (1.2.0) aws-eventstream (1.2.0)
aws-partitions (1.551.0) aws-partitions (1.554.0)
aws-sdk-core (3.125.5) aws-sdk-core (3.126.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0) aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
jmespath (~> 1.0) jmespath (~> 1.0)
aws-sdk-kms (1.53.0) aws-sdk-kms (1.54.0)
aws-sdk-core (~> 3, >= 3.125.0) aws-sdk-core (~> 3, >= 3.126.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.111.3) aws-sdk-s3 (1.112.0)
aws-sdk-core (~> 3, >= 3.125.0) aws-sdk-core (~> 3, >= 3.126.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4) aws-sigv4 (~> 1.4)
aws-sigv4 (1.4.0) aws-sigv4 (1.4.0)
@ -86,7 +86,7 @@ GEM
escape (0.0.4) escape (0.0.4)
ethon (0.15.0) ethon (0.15.0)
ffi (>= 1.15.0) ffi (>= 1.15.0)
excon (0.90.0) excon (0.91.0)
faraday (1.9.3) faraday (1.9.3)
faraday-em_http (~> 1.0) faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0) faraday-em_synchrony (~> 1.0)
@ -116,7 +116,7 @@ GEM
faraday_middleware (1.2.0) faraday_middleware (1.2.0)
faraday (~> 1.0) faraday (~> 1.0)
fastimage (2.2.6) fastimage (2.2.6)
fastlane (2.203.0) fastlane (2.204.2)
CFPropertyList (>= 2.3, < 4.0.0) CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0) addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0) artifactory (~> 3.0)
@ -155,8 +155,8 @@ GEM
xcodeproj (>= 1.13.0, < 2.0.0) xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0) xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3) xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-json (1.0.0) fastlane-plugin-json (1.1.0)
fastlane-plugin-sentry (1.11.0) fastlane-plugin-sentry (1.11.1)
fastlane-plugin-versioning_android (0.1.0) fastlane-plugin-versioning_android (0.1.0)
fastlane-plugin-yarn (1.2) fastlane-plugin-yarn (1.2)
ffi (1.15.4) ffi (1.15.4)

View File

@ -224,6 +224,7 @@ lane :build do
puts("Release #{GITHUB_RELEASE} exists. Continue with building React Native only.") puts("Release #{GITHUB_RELEASE} exists. Continue with building React Native only.")
else else
puts("Release #{GITHUB_RELEASE} does not exist. Create new release as well as new native build.") puts("Release #{GITHUB_RELEASE} does not exist. Create new release as well as new native build.")
cocoapods(clean_install: true, podfile: "./ios/Podfile", deployment: true)
build_ios build_ios
build_android build_android
case ENVIRONMENT case ENVIRONMENT

View File

@ -14,6 +14,7 @@ import pushUseConnect from '@utils/push/useConnect'
import pushUseReceive from '@utils/push/useReceive' import pushUseReceive from '@utils/push/useReceive'
import pushUseRespond from '@utils/push/useRespond' import pushUseRespond from '@utils/push/useRespond'
import { updatePreviousTab } from '@utils/slices/contextsSlice' import { updatePreviousTab } from '@utils/slices/contextsSlice'
import { checkEmojis } from '@utils/slices/instances/checkEmojis'
import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences' import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences'
import { updateConfiguration } from '@utils/slices/instances/updateConfiguration' import { updateConfiguration } from '@utils/slices/instances/updateConfiguration'
import { updateFilters } from '@utils/slices/instances/updateFilters' import { updateFilters } from '@utils/slices/instances/updateFilters'
@ -26,7 +27,6 @@ import { addScreenshotListener } from 'expo-screen-capture'
import React, { useCallback, useEffect, useRef, useState } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Alert, Platform, StatusBar } from 'react-native' import { Alert, Platform, StatusBar } from 'react-native'
import { useQueryClient } from 'react-query'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import * as Sentry from 'sentry-expo' import * as Sentry from 'sentry-expo'
@ -40,7 +40,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
const { t } = useTranslation('screens') const { t } = useTranslation('screens')
const dispatch = useDispatch() const dispatch = useDispatch()
const instanceActive = useSelector(getInstanceActive) const instanceActive = useSelector(getInstanceActive)
const { mode, theme } = useTheme() const { colors, mode, theme } = useTheme()
enum barStyle { enum barStyle {
light = 'dark-content', light = 'dark-content',
dark = 'light-content' dark = 'light-content'
@ -53,7 +53,6 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
getInstances, getInstances,
(prev, next) => prev.length === next.length (prev, next) => prev.length === next.length
) )
const queryClient = useQueryClient()
pushUseConnect({ t, instances }) pushUseConnect({ t, instances })
pushUseReceive({ instances }) pushUseReceive({ instances })
pushUseRespond({ instances }) pushUseRespond({ instances })
@ -77,8 +76,9 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
message: t('localCorrupt.message'), message: t('localCorrupt.message'),
description: localCorrupt.length ? localCorrupt : undefined, description: localCorrupt.length ? localCorrupt : undefined,
type: 'error', type: 'error',
mode theme
}) })
// @ts-ignore
navigationRef.navigate('Screen-Tabs', { navigationRef.navigate('Screen-Tabs', {
screen: 'Tab-Me' screen: 'Tab-Me'
}) })
@ -93,6 +93,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
dispatch(updateConfiguration()) dispatch(updateConfiguration())
dispatch(updateFilters()) dispatch(updateFilters())
dispatch(updateAccountPreferences()) dispatch(updateAccountPreferences())
dispatch(checkEmojis())
} }
}, [instanceActive]) }, [instanceActive])
@ -164,11 +165,11 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
<> <>
<StatusBar <StatusBar
barStyle={barStyle[mode]} barStyle={barStyle[mode]}
backgroundColor={theme.backgroundDefault} backgroundColor={colors.backgroundDefault}
/> />
<NavigationContainer <NavigationContainer
ref={navigationRef} ref={navigationRef}
theme={themes[mode]} theme={themes[theme]}
onReady={navigationContainerOnReady} onReady={navigationContainerOnReady}
onStateChange={navigationContainerOnStateChange} onStateChange={navigationContainerOnStateChange}
> >

View File

@ -19,7 +19,7 @@ export type Params = {
export const TOOOT_API_DOMAIN = mapEnvironment({ export const TOOOT_API_DOMAIN = mapEnvironment({
release: 'api.tooot.app', release: 'api.tooot.app',
candidate: 'api-candidate.tooot.app', candidate: 'api.tooot.app',
development: 'api-development.tooot.app' development: 'api-development.tooot.app'
}) })

View File

@ -20,10 +20,9 @@ const ComponentAccount: React.FC<Props> = ({
onPress: customOnPress, onPress: customOnPress,
origin origin
}) => { }) => {
const { theme } = useTheme() const { colors } = useTheme()
const navigation = useNavigation< const navigation =
StackNavigationProp<TabLocalStackParamList> useNavigation<StackNavigationProp<TabLocalStackParamList>>()
>()
const onPress = useCallback(() => { const onPress = useCallback(() => {
analytics('search_account_press', { page: origin }) analytics('search_account_press', { page: origin })
@ -37,7 +36,7 @@ const ComponentAccount: React.FC<Props> = ({
onPress={customOnPress || onPress} onPress={customOnPress || onPress}
> >
<GracefullyImage <GracefullyImage
uri={{ original: account.avatar_static }} uri={{ original: account.avatar, static: account.avatar_static }}
style={styles.itemAccountAvatar} style={styles.itemAccountAvatar}
/> />
<View> <View>
@ -51,7 +50,7 @@ const ComponentAccount: React.FC<Props> = ({
</Text> </Text>
<Text <Text
numberOfLines={1} numberOfLines={1}
style={[styles.itemAccountAcct, { color: theme.secondary }]} style={[styles.itemAccountAcct, { color: colors.secondary }]}
> >
@{account.acct} @{account.acct}
</Text> </Text>

View File

@ -54,7 +54,7 @@ const Button: React.FC<Props> = ({
overlay = false, overlay = false,
onPress onPress
}) => { }) => {
const { mode, theme } = useTheme() const { colors, theme } = useTheme()
const mounted = useRef(false) const mounted = useRef(false)
useEffect(() => { useEffect(() => {
@ -68,35 +68,35 @@ const Button: React.FC<Props> = ({
const loadingSpinkit = useMemo( const loadingSpinkit = useMemo(
() => ( () => (
<View style={{ position: 'absolute' }}> <View style={{ position: 'absolute' }}>
<Flow size={StyleConstants.Font.Size[size]} color={theme.secondary} /> <Flow size={StyleConstants.Font.Size[size]} color={colors.secondary} />
</View> </View>
), ),
[mode] [theme]
) )
const mainColor = useMemo(() => { const mainColor = useMemo(() => {
if (selected) { if (selected) {
return theme.blue return colors.blue
} else if (overlay) { } else if (overlay) {
return theme.primaryOverlay return colors.primaryOverlay
} else if (disabled || loading) { } else if (disabled || loading) {
return theme.disabled return colors.disabled
} else { } else {
if (destructive) { if (destructive) {
return theme.red return colors.red
} else { } else {
return theme.primaryDefault return colors.primaryDefault
} }
} }
}, [mode, disabled, loading, selected]) }, [theme, disabled, loading, selected])
const colorBackground = useMemo(() => { const colorBackground = useMemo(() => {
if (overlay) { if (overlay) {
return theme.backgroundOverlayInvert return colors.backgroundOverlayInvert
} else { } else {
return theme.backgroundDefault return colors.backgroundDefault
} }
}, [mode]) }, [theme])
const children = useMemo(() => { const children = useMemo(() => {
switch (type) { switch (type) {
@ -130,7 +130,7 @@ const Button: React.FC<Props> = ({
</> </>
) )
} }
}, [mode, content, loading, disabled]) }, [theme, content, loading, disabled])
const [layoutHeight, setLayoutHeight] = useState<number | undefined>() const [layoutHeight, setLayoutHeight] = useState<number | undefined>()

View File

@ -2,6 +2,7 @@ import EmojisButton from '@components/Emojis/Button'
import EmojisList from '@components/Emojis/List' import EmojisList from '@components/Emojis/List'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { useEmojisQuery } from '@utils/queryHooks/emojis' import { useEmojisQuery } from '@utils/queryHooks/emojis'
import { getInstanceFrequentEmojis } from '@utils/slices/instancesSlice'
import { chunk, forEach, groupBy, sortBy } from 'lodash' import { chunk, forEach, groupBy, sortBy } from 'lodash'
import React, { import React, {
Dispatch, Dispatch,
@ -11,11 +12,16 @@ import React, {
useEffect, useEffect,
useReducer useReducer
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next'
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image'
import { useSelector } from 'react-redux'
import EmojisContext, { emojisReducer } from './Emojis/helpers/EmojisContext' import EmojisContext, { emojisReducer } from './Emojis/helpers/EmojisContext'
const prefetchEmojis = ( const prefetchEmojis = (
sortedEmojis: { title: string; data: Mastodon.Emoji[][] }[], sortedEmojis: {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[],
reduceMotionEnabled: boolean reduceMotionEnabled: boolean
) => { ) => {
const prefetches: { uri: string }[] = [] const prefetches: { uri: string }[] = []
@ -101,14 +107,28 @@ const ComponentEmojis: React.FC<Props> = ({
[value, selectionRange.current?.start, selectionRange.current?.end] [value, selectionRange.current?.start, selectionRange.current?.end]
) )
const { t } = useTranslation()
const { data } = useEmojisQuery({ options: { enabled } }) const { data } = useEmojisQuery({ options: { enabled } })
const frequentEmojis = useSelector(getInstanceFrequentEmojis, () => true)
useEffect(() => { useEffect(() => {
if (data && data.length) { if (data && data.length) {
let sortedEmojis: { title: string; data: Mastodon.Emoji[][] }[] = [] let sortedEmojis: {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[] = []
forEach( forEach(
groupBy(sortBy(data, ['category', 'shortcode']), 'category'), groupBy(sortBy(data, ['category', 'shortcode']), 'category'),
(value, key) => sortedEmojis.push({ title: key, data: chunk(value, 5) }) (value, key) => sortedEmojis.push({ title: key, data: chunk(value, 5) })
) )
if (frequentEmojis.length) {
sortedEmojis.unshift({
title: t('componentEmojis:frequentUsed'),
data: chunk(
frequentEmojis.map(e => e.emoji),
5
)
})
}
emojisDispatch({ emojisDispatch({
type: 'load', type: 'load',
payload: sortedEmojis payload: sortedEmojis

View File

@ -7,7 +7,7 @@ import EmojisContext from './helpers/EmojisContext'
const EmojisButton = React.memo( const EmojisButton = React.memo(
() => { () => {
const { theme } = useTheme() const { colors } = useTheme()
const { emojisState, emojisDispatch } = useContext(EmojisContext) const { emojisState, emojisDispatch } = useContext(EmojisContext)
return emojisState.enabled ? ( return emojisState.enabled ? (
@ -30,8 +30,8 @@ const EmojisButton = React.memo(
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
color={ color={
emojisState.emojis && emojisState.emojis.length emojisState.emojis && emojisState.emojis.length
? theme.primaryDefault ? colors.primaryDefault
: theme.disabled : colors.disabled
} }
/> />
} }

View File

@ -1,4 +1,5 @@
import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { countInstanceEmoji } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation' import layoutAnimation from '@utils/styles/layoutAnimation'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
@ -14,20 +15,22 @@ import {
View View
} from 'react-native' } from 'react-native'
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image'
import { useDispatch } from 'react-redux'
import validUrl from 'valid-url' import validUrl from 'valid-url'
import EmojisContext from './helpers/EmojisContext' import EmojisContext from './helpers/EmojisContext'
const EmojisList = React.memo( const EmojisList = React.memo(
() => { () => {
const dispatch = useDispatch()
const { reduceMotionEnabled } = useAccessibility() const { reduceMotionEnabled } = useAccessibility()
const { t } = useTranslation() const { t } = useTranslation()
const { emojisState, emojisDispatch } = useContext(EmojisContext) const { emojisState, emojisDispatch } = useContext(EmojisContext)
const { theme } = useTheme() const { colors } = useTheme()
const listHeader = useCallback( const listHeader = useCallback(
({ section: { title } }) => ( ({ section: { title } }) => (
<Text style={[styles.group, { color: theme.secondary }]}>{title}</Text> <Text style={[styles.group, { color: colors.secondary }]}>{title}</Text>
), ),
[] []
) )
@ -42,12 +45,13 @@ const EmojisList = React.memo(
return ( return (
<Pressable <Pressable
key={emoji.shortcode} key={emoji.shortcode}
onPress={() => onPress={() => {
emojisDispatch({ emojisDispatch({
type: 'shortcode', type: 'shortcode',
payload: `:${emoji.shortcode}:` payload: `:${emoji.shortcode}:`
}) })
} dispatch(countInstanceEmoji(emoji))
}}
> >
<FastImage <FastImage
accessibilityLabel={t( accessibilityLabel={t(

View File

@ -3,7 +3,10 @@ import { createContext, Dispatch } from 'react'
export type EmojisState = { export type EmojisState = {
enabled: boolean enabled: boolean
active: boolean active: boolean
emojis: { title: string; data: Mastodon.Emoji[][] }[] emojis: {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[]
shortcode: Mastodon.Emoji['shortcode'] | null shortcode: Mastodon.Emoji['shortcode'] | null
} }

View File

@ -1,3 +1,4 @@
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useMemo, useState } from 'react' import React, { useCallback, useMemo, useState } from 'react'
import { import {
@ -23,7 +24,7 @@ export interface Props {
accessibilityHint?: AccessibilityProps['accessibilityHint'] accessibilityHint?: AccessibilityProps['accessibilityHint']
hidden?: boolean hidden?: boolean
uri: { preview?: string; original?: string; remote?: string } uri: { preview?: string; original?: string; remote?: string; static?: string }
blurhash?: string blurhash?: string
dimension?: { width: number; height: number } dimension?: { width: number; height: number }
onPress?: () => void onPress?: () => void
@ -51,7 +52,8 @@ const GracefullyImage = React.memo(
imageStyle, imageStyle,
setImageDimensions setImageDimensions
}: Props) => { }: Props) => {
const { theme } = useTheme() const { reduceMotionEnabled } = useAccessibility()
const { colors } = useTheme()
const [originalFailed, setOriginalFailed] = useState(false) const [originalFailed, setOriginalFailed] = useState(false)
const [imageLoaded, setImageLoaded] = useState(false) const [imageLoaded, setImageLoaded] = useState(false)
@ -59,7 +61,9 @@ const GracefullyImage = React.memo(
if (originalFailed) { if (originalFailed) {
return { uri: uri.remote || undefined } return { uri: uri.remote || undefined }
} else { } else {
return { uri: uri.original } return {
uri: reduceMotionEnabled && uri.static ? uri.static : uri.original
}
} }
}, [originalFailed]) }, [originalFailed])
@ -85,7 +89,7 @@ const GracefullyImage = React.memo(
source={{ uri: uri.preview }} source={{ uri: uri.preview }}
style={[ style={[
styles.placeholder, styles.placeholder,
{ backgroundColor: theme.shimmerDefault } { backgroundColor: colors.shimmerDefault }
]} ]}
/> />
) : null, ) : null,
@ -118,7 +122,7 @@ const GracefullyImage = React.memo(
<View <View
style={[ style={[
styles.placeholder, styles.placeholder,
{ backgroundColor: theme.shimmerDefault } { backgroundColor: colors.shimmerDefault }
]} ]}
/> />
) )
@ -135,7 +139,7 @@ const GracefullyImage = React.memo(
: { accessibilityRole: 'image' })} : { accessibilityRole: 'image' })}
accessibilityLabel={accessibilityLabel} accessibilityLabel={accessibilityLabel}
accessibilityHint={accessibilityHint} accessibilityHint={accessibilityHint}
style={[style, dimension, { backgroundColor: theme.shimmerDefault }]} style={[style, dimension, { backgroundColor: colors.shimmerDefault }]}
{...(onPress {...(onPress
? hidden ? hidden
? { disabled: true } ? { disabled: true }

View File

@ -17,10 +17,9 @@ const ComponentHashtag: React.FC<Props> = ({
onPress: customOnPress, onPress: customOnPress,
origin origin
}) => { }) => {
const { theme } = useTheme() const { colors } = useTheme()
const navigation = useNavigation< const navigation =
StackNavigationProp<Nav.TabLocalStackParamList> useNavigation<StackNavigationProp<Nav.TabLocalStackParamList>>()
>()
const onPress = useCallback(() => { const onPress = useCallback(() => {
analytics('search_account_press', { page: origin }) analytics('search_account_press', { page: origin })
@ -33,7 +32,7 @@ const ComponentHashtag: React.FC<Props> = ({
style={styles.itemDefault} style={styles.itemDefault}
onPress={customOnPress || onPress} onPress={customOnPress || onPress}
> >
<Text style={[styles.itemHashtag, { color: theme.primaryDefault }]}> <Text style={[styles.itemHashtag, { color: colors.primaryDefault }]}>
#{hashtag.name} #{hashtag.name}
</Text> </Text>
</Pressable> </Pressable>

View File

@ -11,13 +11,13 @@ export interface Props {
// Used for Android mostly // Used for Android mostly
const HeaderCenter = React.memo( const HeaderCenter = React.memo(
({ content, inverted = false }: Props) => { ({ content, inverted = false }: Props) => {
const { theme } = useTheme() const { colors } = useTheme()
return ( return (
<Text <Text
style={[ style={[
styles.text, styles.text,
{ color: inverted ? theme.primaryOverlay : theme.primaryDefault } { color: inverted ? colors.primaryOverlay : colors.primaryDefault }
]} ]}
children={content} children={content}
/> />

View File

@ -20,14 +20,14 @@ const HeaderLeft: React.FC<Props> = ({
background = false, background = false,
onPress onPress
}) => { }) => {
const { theme } = useTheme() const { colors, theme } = useTheme()
const children = useMemo(() => { const children = useMemo(() => {
switch (type) { switch (type) {
case 'icon': case 'icon':
return ( return (
<Icon <Icon
color={theme.primaryDefault} color={colors.primaryDefault}
name={content || 'ChevronLeft'} name={content || 'ChevronLeft'}
size={StyleConstants.Spacing.M * 1.25} size={StyleConstants.Spacing.M * 1.25}
/> />
@ -35,7 +35,7 @@ const HeaderLeft: React.FC<Props> = ({
case 'text': case 'text':
return ( return (
<Text <Text
style={[styles.text, { color: theme.primaryDefault }]} style={[styles.text, { color: colors.primaryDefault }]}
children={content} children={content}
/> />
) )
@ -50,7 +50,7 @@ const HeaderLeft: React.FC<Props> = ({
styles.base, styles.base,
{ {
backgroundColor: background backgroundColor: background
? theme.backgroundOverlayDefault ? colors.backgroundOverlayDefault
: undefined, : undefined,
minHeight: 44, minHeight: 44,
minWidth: 44, minWidth: 44,

View File

@ -41,14 +41,14 @@ const HeaderRight: React.FC<Props> = ({
disabled, disabled,
onPress onPress
}) => { }) => {
const { theme } = useTheme() const { colors, theme } = useTheme()
const loadingSpinkit = useMemo( const loadingSpinkit = useMemo(
() => ( () => (
<View style={{ position: 'absolute' }}> <View style={{ position: 'absolute' }}>
<Flow <Flow
size={StyleConstants.Font.Size.M * 1.25} size={StyleConstants.Font.Size.M * 1.25}
color={theme.secondary} color={colors.secondary}
/> />
</View> </View>
), ),
@ -64,7 +64,7 @@ const HeaderRight: React.FC<Props> = ({
name={content} name={content}
style={{ opacity: loading ? 0 : 1 }} style={{ opacity: loading ? 0 : 1 }}
size={StyleConstants.Spacing.M * 1.25} size={StyleConstants.Spacing.M * 1.25}
color={disabled ? theme.secondary : theme.primaryDefault} color={disabled ? colors.secondary : colors.primaryDefault}
/> />
{loading && loadingSpinkit} {loading && loadingSpinkit}
</> </>
@ -76,7 +76,7 @@ const HeaderRight: React.FC<Props> = ({
style={[ style={[
styles.text, styles.text,
{ {
color: disabled ? theme.secondary : theme.primaryDefault, color: disabled ? colors.secondary : colors.primaryDefault,
opacity: loading ? 0 : 1 opacity: loading ? 0 : 1
} }
]} ]}
@ -101,7 +101,7 @@ const HeaderRight: React.FC<Props> = ({
styles.base, styles.base,
{ {
backgroundColor: background backgroundColor: background
? theme.backgroundOverlayDefault ? colors.backgroundOverlayDefault
: undefined, : undefined,
minHeight: 44, minHeight: 44,
minWidth: 44, minWidth: 44,

View File

@ -57,7 +57,7 @@ const Input: React.FC<Props> = ({
setValue, setValue,
options options
}) => { }) => {
const { mode, theme } = useTheme() const { colors, mode } = useTheme()
const animateTitle = useAnimatedStyle(() => { const animateTitle = useAnimatedStyle(() => {
if (value) { if (value) {
@ -66,7 +66,7 @@ const Input: React.FC<Props> = ({
paddingHorizontal: withTiming(StyleConstants.Spacing.XS), paddingHorizontal: withTiming(StyleConstants.Spacing.XS),
left: withTiming(StyleConstants.Spacing.S), left: withTiming(StyleConstants.Spacing.S),
top: withTiming(-(StyleConstants.Font.Size.S / 2) - 2), top: withTiming(-(StyleConstants.Font.Size.S / 2) - 2),
backgroundColor: withTiming(theme.backgroundDefault) backgroundColor: withTiming(colors.backgroundDefault)
} }
} else { } else {
return { return {
@ -74,7 +74,7 @@ const Input: React.FC<Props> = ({
paddingHorizontal: withTiming(0), paddingHorizontal: withTiming(0),
left: withTiming(StyleConstants.Spacing.S), left: withTiming(StyleConstants.Spacing.S),
top: withTiming(StyleConstants.Spacing.S + 1), top: withTiming(StyleConstants.Spacing.S + 1),
backgroundColor: withTiming(theme.backgroundDefaultTransparent) backgroundColor: withTiming(colors.backgroundDefaultTransparent)
} }
} }
}, [mode, value]) }, [mode, value])
@ -109,7 +109,7 @@ const Input: React.FC<Props> = ({
style={[ style={[
styles.base, styles.base,
{ {
borderColor: theme.border, borderColor: colors.border,
flexDirection: multiline ? 'column' : 'row', flexDirection: multiline ? 'column' : 'row',
alignItems: 'stretch' alignItems: 'stretch'
} }
@ -127,7 +127,7 @@ const Input: React.FC<Props> = ({
style={[ style={[
styles.textInput, styles.textInput,
{ {
color: theme.primaryDefault, color: colors.primaryDefault,
minHeight: minHeight:
Platform.OS === 'ios' && multiline Platform.OS === 'ios' && multiline
? StyleConstants.Font.LineHeight.M * 5 ? StyleConstants.Font.LineHeight.M * 5
@ -149,14 +149,14 @@ const Input: React.FC<Props> = ({
</EmojisContext.Consumer> </EmojisContext.Consumer>
{title ? ( {title ? (
<Animated.Text <Animated.Text
style={[styles.title, animateTitle, { color: theme.secondary }]} style={[styles.title, animateTitle, { color: colors.secondary }]}
> >
{title} {title}
</Animated.Text> </Animated.Text>
) : null} ) : null}
<View style={{ flexDirection: 'row', alignSelf: 'flex-end' }}> <View style={{ flexDirection: 'row', alignSelf: 'flex-end' }}>
{options?.maxLength && value?.length ? ( {options?.maxLength && value?.length ? (
<Text style={[styles.maxLength, { color: theme.secondary }]}> <Text style={[styles.maxLength, { color: colors.secondary }]}>
{value?.length} / {options.maxLength} {value?.length} / {options.maxLength}
</Text> </Text>
) : null} ) : null}

View File

@ -39,7 +39,7 @@ const ComponentInstance: React.FC<Props> = ({
goBack = false goBack = false
}) => { }) => {
const { t } = useTranslation('componentInstance') const { t } = useTranslation('componentInstance')
const { mode, theme } = useTheme() const { colors, mode } = useTheme()
const { screenReaderEnabled } = useAccessibility() const { screenReaderEnabled } = useAccessibility()
const instances = useSelector(getInstances, () => true) const instances = useSelector(getInstances, () => true)
@ -149,10 +149,10 @@ const ComponentInstance: React.FC<Props> = ({
style={[ style={[
styles.prefix, styles.prefix,
{ {
color: theme.primaryDefault, color: colors.primaryDefault,
borderBottomColor: instanceQuery.isError borderBottomColor: instanceQuery.isError
? theme.red ? colors.red
: theme.border : colors.border
} }
]} ]}
editable={false} editable={false}
@ -162,10 +162,10 @@ const ComponentInstance: React.FC<Props> = ({
style={[ style={[
styles.textInput, styles.textInput,
{ {
color: theme.primaryDefault, color: colors.primaryDefault,
borderBottomColor: instanceQuery.isError borderBottomColor: instanceQuery.isError
? theme.red ? colors.red
: theme.border : colors.border
} }
]} ]}
onChangeText={onChangeText} onChangeText={onChangeText}
@ -175,7 +175,7 @@ const ComponentInstance: React.FC<Props> = ({
textContentType='URL' textContentType='URL'
onSubmitEditing={onSubmitEditing} onSubmitEditing={onSubmitEditing}
placeholder={' ' + t('server.textInput.placeholder')} placeholder={' ' + t('server.textInput.placeholder')}
placeholderTextColor={theme.secondary} placeholderTextColor={colors.secondary}
returnKeyType='go' returnKeyType='go'
keyboardAppearance={mode} keyboardAppearance={mode}
{...(scrollViewRef && { {...(scrollViewRef && {
@ -234,11 +234,11 @@ const ComponentInstance: React.FC<Props> = ({
<Icon <Icon
name='Lock' name='Lock'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={theme.secondary} color={colors.secondary}
style={styles.disclaimerIcon} style={styles.disclaimerIcon}
/> />
<Text <Text
style={[styles.disclaimerText, { color: theme.secondary }]} style={[styles.disclaimerText, { color: colors.secondary }]}
accessibilityRole='link' accessibilityRole='link'
onPress={() => { onPress={() => {
if (screenReaderEnabled) { if (screenReaderEnabled) {
@ -252,7 +252,7 @@ const ComponentInstance: React.FC<Props> = ({
{t('server.disclaimer.base')} {t('server.disclaimer.base')}
<Text <Text
accessible accessible
style={{ color: theme.blue }} style={{ color: colors.blue }}
onPress={() => { onPress={() => {
analytics('view_privacy') analytics('view_privacy')
WebBrowser.openBrowserAsync( WebBrowser.openBrowserAsync(

View File

@ -13,15 +13,15 @@ export interface Props {
const InstanceInfo = React.memo( const InstanceInfo = React.memo(
({ style, header, content, potentialWidth }: Props) => { ({ style, header, content, potentialWidth }: Props) => {
const { theme } = useTheme() const { colors } = useTheme()
return ( return (
<View style={[styles.base, style]} accessible> <View style={[styles.base, style]} accessible>
<Text style={[styles.header, { color: theme.primaryDefault }]}> <Text style={[styles.header, { color: colors.primaryDefault }]}>
{header} {header}
</Text> </Text>
{content ? ( {content ? (
<Text style={[styles.content, { color: theme.primaryDefault }]}> <Text style={[styles.content, { color: colors.primaryDefault }]}>
{content} {content}
</Text> </Text>
) : ( ) : (
@ -32,7 +32,7 @@ const InstanceInfo = React.memo(
: undefined : undefined
} }
height={StyleConstants.Font.LineHeight.M} height={StyleConstants.Font.LineHeight.M}
color={theme.shimmerDefault} color={colors.shimmerDefault}
noMargin noMargin
style={{ borderRadius: 0 }} style={{ borderRadius: 0 }}
/> />

View File

@ -8,11 +8,11 @@ export interface Props {
} }
const MenuHeader: React.FC<Props> = ({ heading }) => { const MenuHeader: React.FC<Props> = ({ heading }) => {
const { theme } = useTheme() const { colors } = useTheme()
return ( return (
<View style={styles.base}> <View style={styles.base}>
<Text style={[styles.text, { color: theme.secondary }]}>{heading}</Text> <Text style={[styles.text, { color: colors.secondary }]}>{heading}</Text>
</View> </View>
) )
} }

View File

@ -43,7 +43,7 @@ const MenuRow: React.FC<Props> = ({
loading = false, loading = false,
onPress onPress
}) => { }) => {
const { theme } = useTheme() const { colors, theme } = useTheme()
const { screenReaderEnabled } = useAccessibility() const { screenReaderEnabled } = useAccessibility()
const loadingSpinkit = useMemo( const loadingSpinkit = useMemo(
@ -51,7 +51,7 @@ const MenuRow: React.FC<Props> = ({
<View style={{ position: 'absolute' }}> <View style={{ position: 'absolute' }}>
<Flow <Flow
size={StyleConstants.Font.Size.M * 1.25} size={StyleConstants.Font.Size.M * 1.25}
color={theme.secondary} color={colors.secondary}
/> />
</View> </View>
), ),
@ -83,7 +83,7 @@ const MenuRow: React.FC<Props> = ({
<Icon <Icon
name={iconFront} name={iconFront}
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
color={theme[iconFrontColor]} color={colors[iconFrontColor]}
style={styles.iconFront} style={styles.iconFront}
/> />
)} )}
@ -92,7 +92,7 @@ const MenuRow: React.FC<Props> = ({
style={{ style={{
width: 8, width: 8,
height: 8, height: 8,
backgroundColor: theme.red, backgroundColor: colors.red,
borderRadius: 8, borderRadius: 8,
marginRight: StyleConstants.Spacing.S marginRight: StyleConstants.Spacing.S
}} }}
@ -100,7 +100,7 @@ const MenuRow: React.FC<Props> = ({
) : null} ) : null}
<View style={styles.main}> <View style={styles.main}>
<Text <Text
style={[styles.title, { color: theme.primaryDefault }]} style={[styles.title, { color: colors.primaryDefault }]}
numberOfLines={1} numberOfLines={1}
> >
{title} {title}
@ -116,7 +116,7 @@ const MenuRow: React.FC<Props> = ({
style={[ style={[
styles.content, styles.content,
{ {
color: theme.secondary, color: colors.secondary,
opacity: !iconBack && loading ? 0 : 1 opacity: !iconBack && loading ? 0 : 1
} }
]} ]}
@ -133,7 +133,7 @@ const MenuRow: React.FC<Props> = ({
value={switchValue} value={switchValue}
onValueChange={switchOnValueChange} onValueChange={switchOnValueChange}
disabled={switchDisabled} disabled={switchDisabled}
trackColor={{ true: theme.blue, false: theme.disabled }} trackColor={{ true: colors.blue, false: colors.disabled }}
style={{ opacity: loading ? 0 : 1 }} style={{ opacity: loading ? 0 : 1 }}
/> />
) : null} ) : null}
@ -141,7 +141,7 @@ const MenuRow: React.FC<Props> = ({
<Icon <Icon
name={iconBack} name={iconBack}
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
color={theme[iconBackColor]} color={colors[iconBackColor]}
style={[styles.iconBack, { opacity: loading ? 0 : 1 }]} style={[styles.iconBack, { opacity: loading ? 0 : 1 }]}
/> />
) : null} ) : null}
@ -150,7 +150,7 @@ const MenuRow: React.FC<Props> = ({
) : null} ) : null}
</View> </View>
{description ? ( {description ? (
<Text style={[styles.description, { color: theme.secondary }]}> <Text style={[styles.description, { color: colors.secondary }]}>
{description} {description}
</Text> </Text>
) : null} ) : null}

View File

@ -1,7 +1,7 @@
import Icon from '@components/Icon' import Icon from '@components/Icon'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import { getTheme } from '@utils/styles/themes' import { getColors, Theme } from '@utils/styles/themes'
import React, { RefObject } from 'react' import React, { RefObject } from 'react'
import { AccessibilityInfo } from 'react-native' import { AccessibilityInfo } from 'react-native'
import FlashMessage, { import FlashMessage, {
@ -17,7 +17,7 @@ const displayMessage = ({
message, message,
description, description,
onPress, onPress,
mode, theme,
type type
}: }:
| { | {
@ -27,7 +27,7 @@ const displayMessage = ({
message: string message: string
description?: string description?: string
onPress?: () => void onPress?: () => void
mode?: undefined theme?: undefined
type?: undefined type?: undefined
} }
| { | {
@ -37,7 +37,7 @@ const displayMessage = ({
message: string message: string
description?: string description?: string
onPress?: () => void onPress?: () => void
mode: 'light' | 'dark' theme: Theme
type: 'success' | 'error' | 'warning' type: 'success' | 'error' | 'warning'
}) => { }) => {
AccessibilityInfo.announceForAccessibility(message + '.' + description) AccessibilityInfo.announceForAccessibility(message + '.' + description)
@ -64,14 +64,14 @@ const displayMessage = ({
message, message,
description, description,
onPress, onPress,
...(mode && ...(theme &&
type && { type && {
renderFlashMessageIcon: () => { renderFlashMessageIcon: () => {
return ( return (
<Icon <Icon
name={iconMapping[type]} name={iconMapping[type]}
size={StyleConstants.Font.LineHeight.M} size={StyleConstants.Font.LineHeight.M}
color={getTheme(mode)[colorMapping[type]]} color={getColors(theme)[colorMapping[type]]}
style={{ marginRight: StyleConstants.Spacing.S }} style={{ marginRight: StyleConstants.Spacing.S }}
/> />
) )
@ -85,14 +85,14 @@ const displayMessage = ({
message, message,
description, description,
onPress, onPress,
...(mode && ...(theme &&
type && { type && {
renderFlashMessageIcon: () => { renderFlashMessageIcon: () => {
return ( return (
<Icon <Icon
name={iconMapping[type]} name={iconMapping[type]}
size={StyleConstants.Font.LineHeight.M} size={StyleConstants.Font.LineHeight.M}
color={getTheme(mode)[colorMapping[type]]} color={getColors(theme)[colorMapping[type]]}
style={{ marginRight: StyleConstants.Spacing.S }} style={{ marginRight: StyleConstants.Spacing.S }}
/> />
) )
@ -111,7 +111,7 @@ const removeMessage = () => {
} }
const Message = React.forwardRef<FlashMessage>((_, ref) => { const Message = React.forwardRef<FlashMessage>((_, ref) => {
const { mode, theme } = useTheme() const { colors, theme } = useTheme()
return ( return (
<FlashMessage <FlashMessage
@ -120,19 +120,19 @@ const Message = React.forwardRef<FlashMessage>((_, ref) => {
position='top' position='top'
floating floating
style={{ style={{
backgroundColor: theme.backgroundDefault, backgroundColor: colors.backgroundDefault,
shadowColor: theme.primaryDefault, shadowColor: colors.primaryDefault,
shadowOffset: { width: 0, height: 0 }, shadowOffset: { width: 0, height: 0 },
shadowOpacity: mode === 'light' ? 0.16 : 0.24, shadowOpacity: theme === 'light' ? 0.16 : 0.24,
shadowRadius: 4 shadowRadius: 4
}} }}
titleStyle={{ titleStyle={{
color: theme.primaryDefault, color: colors.primaryDefault,
...StyleConstants.FontStyle.M, ...StyleConstants.FontStyle.M,
fontWeight: StyleConstants.Font.Weight.Bold fontWeight: StyleConstants.Font.Weight.Bold
}} }}
textStyle={{ textStyle={{
color: theme.primaryDefault, color: colors.primaryDefault,
...StyleConstants.FontStyle.S ...StyleConstants.FontStyle.S
}} }}
// @ts-ignore // @ts-ignore

View File

@ -39,11 +39,11 @@ const ParseEmojis = React.memo(
adaptiveSize ? adaptiveFontsize : 0 adaptiveSize ? adaptiveFontsize : 0
) )
const { mode, theme } = useTheme() const { colors, theme } = useTheme()
const styles = useMemo(() => { const styles = useMemo(() => {
return StyleSheet.create({ return StyleSheet.create({
text: { text: {
color: theme.primaryDefault, color: colors.primaryDefault,
fontSize: adaptedFontsize, fontSize: adaptedFontsize,
lineHeight: adaptedLineheight, lineHeight: adaptedLineheight,
...(fontBold && { fontWeight: StyleConstants.Font.Weight.Bold }) ...(fontBold && { fontWeight: StyleConstants.Font.Weight.Bold })
@ -54,7 +54,7 @@ const ParseEmojis = React.memo(
transform: [{ translateY: -2 }] transform: [{ translateY: -2 }]
} }
}) })
}, [mode, adaptiveFontsize]) }, [theme, adaptiveFontsize])
return ( return (
<Text style={styles.text}> <Text style={styles.text}>

View File

@ -18,7 +18,7 @@ import { useSelector } from 'react-redux'
// Prevent going to the same hashtag multiple times // Prevent going to the same hashtag multiple times
const renderNode = ({ const renderNode = ({
routeParams, routeParams,
theme, colors,
node, node,
index, index,
adaptedFontsize, adaptedFontsize,
@ -30,7 +30,7 @@ const renderNode = ({
disableDetails disableDetails
}: { }: {
routeParams?: any routeParams?: any
theme: any colors: any
node: any node: any
index: number index: number
adaptedFontsize: number adaptedFontsize: number
@ -56,7 +56,7 @@ const renderNode = ({
accessible accessible
key={index} key={index}
style={{ style={{
color: theme.blue, color: colors.blue,
fontSize: adaptedFontsize, fontSize: adaptedFontsize,
lineHeight: adaptedLineheight lineHeight: adaptedLineheight
}} }}
@ -84,7 +84,8 @@ const renderNode = ({
<Text <Text
key={index} key={index}
style={{ style={{
color: accountIndex !== -1 ? theme.blue : theme.primaryDefault, color:
accountIndex !== -1 ? colors.blue : colors.primaryDefault,
fontSize: adaptedFontsize, fontSize: adaptedFontsize,
lineHeight: adaptedLineheight lineHeight: adaptedLineheight
}} }}
@ -114,7 +115,7 @@ const renderNode = ({
<Text <Text
key={index} key={index}
style={{ style={{
color: theme.blue, color: colors.blue,
alignItems: 'center', alignItems: 'center',
fontSize: adaptedFontsize, fontSize: adaptedFontsize,
lineHeight: adaptedLineheight lineHeight: adaptedLineheight
@ -132,7 +133,7 @@ const renderNode = ({
(showFullLink ? href : domain[1])} (showFullLink ? href : domain[1])}
{!shouldBeTag ? ( {!shouldBeTag ? (
<Icon <Icon
color={theme.blue} color={colors.blue}
name='ExternalLink' name='ExternalLink'
size={adaptedFontsize} size={adaptedFontsize}
style={{ style={{
@ -192,11 +193,10 @@ const ParseHTML = React.memo(
adaptiveSize ? adaptiveFontsize : 0 adaptiveSize ? adaptiveFontsize : 0
) )
const navigation = useNavigation< const navigation =
StackNavigationProp<Nav.TabLocalStackParamList> useNavigation<StackNavigationProp<Nav.TabLocalStackParamList>>()
>()
const route = useRoute() const route = useRoute()
const { mode, theme } = useTheme() const { colors, theme } = useTheme()
const { t, i18n } = useTranslation('componentParse') const { t, i18n } = useTranslation('componentParse')
if (!expandHint) { if (!expandHint) {
expandHint = t('HTML.defaultHint') expandHint = t('HTML.defaultHint')
@ -206,7 +206,7 @@ const ParseHTML = React.memo(
(node, index) => (node, index) =>
renderNode({ renderNode({
routeParams: route.params, routeParams: route.params,
theme, colors,
node, node,
index, index,
adaptedFontsize, adaptedFontsize,
@ -271,14 +271,14 @@ const ParseHTML = React.memo(
justifyContent: 'center', justifyContent: 'center',
marginTop: expanded ? 0 : -adaptedLineheight, marginTop: expanded ? 0 : -adaptedLineheight,
minHeight: 44, minHeight: 44,
backgroundColor: theme.backgroundDefault backgroundColor: colors.backgroundDefault
}} }}
> >
<Text <Text
style={{ style={{
textAlign: 'center', textAlign: 'center',
...StyleConstants.FontStyle.S, ...StyleConstants.FontStyle.S,
color: theme.primaryDefault color: colors.primaryDefault
}} }}
children={t(`HTML.expanded.${expanded.toString()}`, { children={t(`HTML.expanded.${expanded.toString()}`, {
hint: expandHint hint: expandHint
@ -289,7 +289,7 @@ const ParseHTML = React.memo(
</View> </View>
) )
}, },
[mode, i18n.language] [theme, i18n.language]
) )
return ( return (

View File

@ -19,7 +19,7 @@ export interface Props {
} }
const RelationshipIncoming: React.FC<Props> = ({ id }) => { const RelationshipIncoming: React.FC<Props> = ({ id }) => {
const { mode } = useTheme() const { theme } = useTheme()
const { t } = useTranslation() const { t } = useTranslation()
const queryKeyRelationship: QueryKeyRelationship = ['Relationship', { id }] const queryKeyRelationship: QueryKeyRelationship = ['Relationship', { id }]
@ -40,7 +40,7 @@ const RelationshipIncoming: React.FC<Props> = ({ id }) => {
haptics('Error') haptics('Error')
displayMessage({ displayMessage({
type: 'error', type: 'error',
mode, theme,
message: t('common:message.error.message', { message: t('common:message.error.message', {
function: t(`relationship:${type}.function`) function: t(`relationship:${type}.function`)
}), }),

View File

@ -19,7 +19,7 @@ export interface Props {
const RelationshipOutgoing = React.memo( const RelationshipOutgoing = React.memo(
({ id }: Props) => { ({ id }: Props) => {
const { mode } = useTheme() const { theme } = useTheme()
const { t } = useTranslation('componentRelationship') const { t } = useTranslation('componentRelationship')
const query = useRelationshipQuery({ id }) const query = useRelationshipQuery({ id })
@ -40,7 +40,7 @@ const RelationshipOutgoing = React.memo(
}, },
onError: (err: any, { payload: { action } }) => { onError: (err: any, { payload: { action } }) => {
displayMessage({ displayMessage({
mode, theme,
type: 'error', type: 'error',
message: t('common:message.error.message', { message: t('common:message.error.message', {
function: t(`${action}.function`) function: t(`${action}.function`)

View File

@ -10,13 +10,13 @@ export interface Props {
const ComponentSeparator = React.memo( const ComponentSeparator = React.memo(
({ extraMarginLeft = 0, extraMarginRight = 0 }: Props) => { ({ extraMarginLeft = 0, extraMarginRight = 0 }: Props) => {
const { theme } = useTheme() const { colors } = useTheme()
return ( return (
<View <View
style={{ style={{
backgroundColor: theme.backgroundDefault, backgroundColor: colors.backgroundDefault,
borderTopColor: theme.border, borderTopColor: colors.border,
borderTopWidth: StyleSheet.hairlineWidth, borderTopWidth: StyleSheet.hairlineWidth,
marginLeft: marginLeft:
StyleConstants.Spacing.Global.PagePadding + extraMarginLeft, StyleConstants.Spacing.Global.PagePadding + extraMarginLeft,

View File

@ -48,7 +48,7 @@ const Timeline: React.FC<Props> = ({
lookback, lookback,
customProps customProps
}) => { }) => {
const { theme } = useTheme() const { colors } = useTheme()
const { const {
data, data,
@ -118,8 +118,8 @@ const Timeline: React.FC<Props> = ({
refreshControl: ( refreshControl: (
<RefreshControl <RefreshControl
enabled enabled
colors={[theme.primaryDefault]} colors={[colors.primaryDefault]}
progressBackgroundColor={theme.backgroundDefault} progressBackgroundColor={colors.backgroundDefault}
refreshing={isFetching || isLoading} refreshing={isFetching || isLoading}
onRefresh={() => refetch()} onRefresh={() => refetch()}
/> />

View File

@ -32,7 +32,7 @@ const Avatars: React.FC<{ accounts: Mastodon.Account[] }> = ({ accounts }) => {
{accounts.slice(0, 4).map(account => ( {accounts.slice(0, 4).map(account => (
<GracefullyImage <GracefullyImage
key={account.id} key={account.id}
uri={{ original: account.avatar_static }} uri={{ original: account.avatar, static: account.avatar_static }}
dimension={{ dimension={{
width: StyleConstants.Avatar.M, width: StyleConstants.Avatar.M,
height: height:
@ -62,7 +62,7 @@ const TimelineConversation: React.FC<Props> = ({
getInstanceAccount, getInstanceAccount,
(prev, next) => prev?.id === next?.id (prev, next) => prev?.id === next?.id
) )
const { theme } = useTheme() const { colors } = useTheme()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const fireMutation = useCallback(() => { const fireMutation = useCallback(() => {
@ -77,9 +77,8 @@ const TimelineConversation: React.FC<Props> = ({
} }
}) })
const navigation = useNavigation< const navigation =
StackNavigationProp<Nav.TabLocalStackParamList> useNavigation<StackNavigationProp<Nav.TabLocalStackParamList>>()
>()
const onPress = useCallback(() => { const onPress = useCallback(() => {
analytics('timeline_conversation_press') analytics('timeline_conversation_press')
if (conversation.last_status) { if (conversation.last_status) {
@ -95,10 +94,10 @@ const TimelineConversation: React.FC<Props> = ({
<Pressable <Pressable
style={[ style={[
styles.base, styles.base,
{ backgroundColor: theme.backgroundDefault }, { backgroundColor: colors.backgroundDefault },
conversation.unread && { conversation.unread && {
borderLeftWidth: StyleConstants.Spacing.XS, borderLeftWidth: StyleConstants.Spacing.XS,
borderLeftColor: theme.blue, borderLeftColor: colors.blue,
paddingLeft: paddingLeft:
StyleConstants.Spacing.Global.PagePadding - StyleConstants.Spacing.Global.PagePadding -
StyleConstants.Spacing.XS StyleConstants.Spacing.XS

View File

@ -42,11 +42,10 @@ const TimelineDefault: React.FC<Props> = ({
disableDetails = false, disableDetails = false,
disableOnPress = false disableOnPress = false
}) => { }) => {
const { theme } = useTheme() const { colors } = useTheme()
const instanceAccount = useSelector(getInstanceAccount, () => true) const instanceAccount = useSelector(getInstanceAccount, () => true)
const navigation = useNavigation< const navigation =
StackNavigationProp<Nav.TabLocalStackParamList> useNavigation<StackNavigationProp<Nav.TabLocalStackParamList>>()
>()
const actualStatus = item.reblog ? item.reblog : item const actualStatus = item.reblog ? item.reblog : item
@ -78,7 +77,7 @@ const TimelineDefault: React.FC<Props> = ({
style={[ style={[
styles.statusView, styles.statusView,
{ {
backgroundColor: theme.backgroundDefault, backgroundColor: colors.backgroundDefault,
paddingBottom: paddingBottom:
disableDetails && disableOnPress disableDetails && disableOnPress
? StyleConstants.Spacing.Global.PagePadding ? StyleConstants.Spacing.Global.PagePadding

View File

@ -20,14 +20,17 @@ const TimelineEmpty = React.memo(
options: { notifyOnChangeProps: ['status'] } options: { notifyOnChangeProps: ['status'] }
}) })
const { mode, theme } = useTheme() const { colors, theme } = useTheme()
const { t, i18n } = useTranslation('componentTimeline') const { t, i18n } = useTranslation('componentTimeline')
const children = useMemo(() => { const children = useMemo(() => {
switch (status) { switch (status) {
case 'loading': case 'loading':
return ( return (
<Circle size={StyleConstants.Font.Size.L} color={theme.secondary} /> <Circle
size={StyleConstants.Font.Size.L}
color={colors.secondary}
/>
) )
case 'error': case 'error':
return ( return (
@ -35,9 +38,9 @@ const TimelineEmpty = React.memo(
<Icon <Icon
name='Frown' name='Frown'
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
color={theme.primaryDefault} color={colors.primaryDefault}
/> />
<Text style={[styles.error, { color: theme.primaryDefault }]}> <Text style={[styles.error, { color: colors.primaryDefault }]}>
{t('empty.error.message')} {t('empty.error.message')}
</Text> </Text>
<Button <Button
@ -56,18 +59,18 @@ const TimelineEmpty = React.memo(
<Icon <Icon
name='Smartphone' name='Smartphone'
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
color={theme.primaryDefault} color={colors.primaryDefault}
/> />
<Text style={[styles.error, { color: theme.primaryDefault }]}> <Text style={[styles.error, { color: colors.primaryDefault }]}>
{t('empty.success.message')} {t('empty.success.message')}
</Text> </Text>
</> </>
) )
} }
}, [mode, i18n.language, status]) }, [theme, i18n.language, status])
return ( return (
<View <View
style={[styles.base, { backgroundColor: theme.backgroundDefault }]} style={[styles.base, { backgroundColor: colors.backgroundDefault }]}
children={children} children={children}
/> />
) )

View File

@ -24,21 +24,21 @@ const TimelineFooter = React.memo(
} }
}) })
const { theme } = useTheme() const { colors } = useTheme()
return ( return (
<View style={styles.base}> <View style={styles.base}>
{!disableInfinity && hasNextPage ? ( {!disableInfinity && hasNextPage ? (
<Circle size={StyleConstants.Font.Size.L} color={theme.secondary} /> <Circle size={StyleConstants.Font.Size.L} color={colors.secondary} />
) : ( ) : (
<Text style={[styles.text, { color: theme.secondary }]}> <Text style={[styles.text, { color: colors.secondary }]}>
<Trans <Trans
i18nKey='componentTimeline:end.message' i18nKey='componentTimeline:end.message'
components={[ components={[
<Icon <Icon
name='Coffee' name='Coffee'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={theme.secondary} color={colors.secondary}
/> />
]} ]}
/> />

View File

@ -7,12 +7,14 @@ import { StyleSheet, Text, View } from 'react-native'
const TimelineLookback = React.memo( const TimelineLookback = React.memo(
() => { () => {
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const { theme } = useTheme() const { colors } = useTheme()
return ( return (
<View style={[styles.base, { backgroundColor: theme.backgroundDefault }]}> <View
style={[styles.base, { backgroundColor: colors.backgroundDefault }]}
>
<Text <Text
style={[StyleConstants.FontStyle.S, { color: theme.primaryDefault }]} style={[StyleConstants.FontStyle.S, { color: colors.primaryDefault }]}
> >
{t('lookback.message')} {t('lookback.message')}
</Text> </Text>

View File

@ -38,14 +38,13 @@ const TimelineNotifications: React.FC<Props> = ({
return <TimelineFiltered /> return <TimelineFiltered />
} }
const { theme } = useTheme() const { colors } = useTheme()
const instanceAccount = useSelector( const instanceAccount = useSelector(
getInstanceAccount, getInstanceAccount,
(prev, next) => prev?.id === next?.id (prev, next) => prev?.id === next?.id
) )
const navigation = useNavigation< const navigation =
StackNavigationProp<Nav.TabLocalStackParamList> useNavigation<StackNavigationProp<Nav.TabLocalStackParamList>>()
>()
const actualAccount = notification.status const actualAccount = notification.status
? notification.status.account ? notification.status.account
@ -65,7 +64,7 @@ const TimelineNotifications: React.FC<Props> = ({
style={[ style={[
styles.notificationView, styles.notificationView,
{ {
backgroundColor: theme.backgroundDefault, backgroundColor: colors.backgroundDefault,
paddingBottom: notification.status paddingBottom: notification.status
? 0 ? 0
: StyleConstants.Spacing.Global.PagePadding : StyleConstants.Spacing.Global.PagePadding
@ -148,8 +147,10 @@ const TimelineNotifications: React.FC<Props> = ({
status={notification.status} status={notification.status}
highlighted={highlighted} highlighted={highlighted}
accts={uniqBy( accts={uniqBy(
([notification.status.account] as Mastodon.Account[] & (
Mastodon.Mention[]) [notification.status.account] as Mastodon.Account[] &
Mastodon.Mention[]
)
.concat(notification.status.mentions) .concat(notification.status.mentions)
.filter(d => d?.id !== instanceAccount?.id), .filter(d => d?.id !== instanceAccount?.id),
d => d?.id d => d?.id

View File

@ -101,7 +101,7 @@ const TimelineRefresh: React.FC<Props> = ({
}) })
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const { theme } = useTheme() const { colors } = useTheme()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const clearFirstPage = () => { const clearFirstPage = () => {
@ -254,13 +254,16 @@ const TimelineRefresh: React.FC<Props> = ({
<View style={styles.base}> <View style={styles.base}>
{isFetching ? ( {isFetching ? (
<View style={styles.container2}> <View style={styles.container2}>
<Circle size={StyleConstants.Font.Size.L} color={theme.secondary} /> <Circle
size={StyleConstants.Font.Size.L}
color={colors.secondary}
/>
</View> </View>
) : ( ) : (
<> <>
<View style={styles.container1}> <View style={styles.container1}>
<Text <Text
style={[styles.explanation, { color: theme.primaryDefault }]} style={[styles.explanation, { color: colors.primaryDefault }]}
onLayout={onLayout} onLayout={onLayout}
children={t('refresh.fetchPreviousPage')} children={t('refresh.fetchPreviousPage')}
/> />
@ -277,14 +280,14 @@ const TimelineRefresh: React.FC<Props> = ({
<Icon <Icon
name='ArrowLeft' name='ArrowLeft'
size={StyleConstants.Font.Size.M} size={StyleConstants.Font.Size.M}
color={theme.primaryDefault} color={colors.primaryDefault}
/> />
} }
/> />
</View> </View>
<View style={styles.container2}> <View style={styles.container2}>
<Text <Text
style={[styles.explanation, { color: theme.primaryDefault }]} style={[styles.explanation, { color: colors.primaryDefault }]}
onLayout={onLayout} onLayout={onLayout}
children={t('refresh.refetch')} children={t('refresh.refetch')}
/> />

View File

@ -18,12 +18,11 @@ export interface Props {
const TimelineActioned = React.memo( const TimelineActioned = React.memo(
({ account, action, notification = false }: Props) => { ({ account, action, notification = false }: Props) => {
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const { theme } = useTheme() const { colors } = useTheme()
const navigation = useNavigation< const navigation =
StackNavigationProp<Nav.TabLocalStackParamList> useNavigation<StackNavigationProp<Nav.TabLocalStackParamList>>()
>()
const name = account.display_name || account.username const name = account.display_name || account.username
const iconColor = theme.primaryDefault const iconColor = colors.primaryDefault
const content = (content: string) => ( const content = (content: string) => (
<ParseEmojis content={content} emojis={account.emojis} size='S' /> <ParseEmojis content={content} emojis={account.emojis} size='S' />

View File

@ -33,8 +33,8 @@ const TimelineActions: React.FC<Props> = ({
}) => { }) => {
const navigation = useNavigation() const navigation = useNavigation()
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const { mode, theme } = useTheme() const { colors, theme } = useTheme()
const iconColor = theme.secondary const iconColor = colors.secondary
const queryClient = useQueryClient() const queryClient = useQueryClient()
const mutation = useTimelineMutation({ const mutation = useTimelineMutation({
@ -83,7 +83,7 @@ const TimelineActions: React.FC<Props> = ({
onError: (err: any, params, oldData) => { onError: (err: any, params, oldData) => {
const correctParam = params as MutationVarsTimelineUpdateStatusProperty const correctParam = params as MutationVarsTimelineUpdateStatusProperty
displayMessage({ displayMessage({
mode, theme,
type: 'error', type: 'error',
message: t('common:message.error.message', { message: t('common:message.error.message', {
function: t( function: t(
@ -185,7 +185,7 @@ const TimelineActions: React.FC<Props> = ({
{status.replies_count > 0 ? ( {status.replies_count > 0 ? (
<Text <Text
style={{ style={{
color: theme.secondary, color: colors.secondary,
fontSize: StyleConstants.Font.Size.M, fontSize: StyleConstants.Font.Size.M,
marginLeft: StyleConstants.Spacing.XS marginLeft: StyleConstants.Spacing.XS
}} }}
@ -198,14 +198,14 @@ const TimelineActions: React.FC<Props> = ({
[status.replies_count] [status.replies_count]
) )
const childrenReblog = useMemo(() => { const childrenReblog = useMemo(() => {
const color = (state: boolean) => (state ? theme.green : theme.secondary) const color = (state: boolean) => (state ? colors.green : colors.secondary)
return ( return (
<> <>
<Icon <Icon
name='Repeat' name='Repeat'
color={ color={
status.visibility === 'private' || status.visibility === 'direct' status.visibility === 'private' || status.visibility === 'direct'
? theme.disabled ? colors.disabled
: color(status.reblogged) : color(status.reblogged)
} }
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
@ -225,7 +225,7 @@ const TimelineActions: React.FC<Props> = ({
) )
}, [status.reblogged, status.reblogs_count]) }, [status.reblogged, status.reblogs_count])
const childrenFavourite = useMemo(() => { const childrenFavourite = useMemo(() => {
const color = (state: boolean) => (state ? theme.red : theme.secondary) const color = (state: boolean) => (state ? colors.red : colors.secondary)
return ( return (
<> <>
<Icon <Icon
@ -249,7 +249,7 @@ const TimelineActions: React.FC<Props> = ({
) )
}, [status.favourited, status.favourites_count]) }, [status.favourited, status.favourites_count])
const childrenBookmark = useMemo(() => { const childrenBookmark = useMemo(() => {
const color = (state: boolean) => (state ? theme.yellow : theme.secondary) const color = (state: boolean) => (state ? colors.yellow : colors.secondary)
return ( return (
<Icon <Icon
name='Bookmark' name='Bookmark'

View File

@ -19,10 +19,9 @@ const TimelineActionsUsers = React.memo(
} }
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const { theme } = useTheme() const { colors } = useTheme()
const navigation = useNavigation< const navigation =
StackNavigationProp<Nav.TabLocalStackParamList> useNavigation<StackNavigationProp<Nav.TabLocalStackParamList>>()
>()
return ( return (
<View style={styles.base}> <View style={styles.base}>
@ -38,7 +37,7 @@ const TimelineActionsUsers = React.memo(
'shared.actionsUsers.reblogged_by.accessibilityHint' 'shared.actionsUsers.reblogged_by.accessibilityHint'
)} )}
accessibilityRole='button' accessibilityRole='button'
style={[styles.text, { color: theme.blue }]} style={[styles.text, { color: colors.blue }]}
onPress={() => { onPress={() => {
analytics('timeline_shared_actionsusers_press_boosted', { analytics('timeline_shared_actionsusers_press_boosted', {
count: status.reblogs_count count: status.reblogs_count
@ -68,7 +67,7 @@ const TimelineActionsUsers = React.memo(
'shared.actionsUsers.favourited_by.accessibilityHint' 'shared.actionsUsers.favourited_by.accessibilityHint'
)} )}
accessibilityRole='button' accessibilityRole='button'
style={[styles.text, { color: theme.blue }]} style={[styles.text, { color: colors.blue }]}
onPress={() => { onPress={() => {
analytics('timeline_shared_actionsusers_press_boosted', { analytics('timeline_shared_actionsusers_press_boosted', {
count: status.favourites_count count: status.favourites_count

View File

@ -23,7 +23,7 @@ const AttachmentAudio: React.FC<Props> = ({
sensitiveShown, sensitiveShown,
audio audio
}) => { }) => {
const { theme } = useTheme() const { colors } = useTheme()
const [audioPlayer, setAudioPlayer] = useState<Audio.Sound>() const [audioPlayer, setAudioPlayer] = useState<Audio.Sound>()
const [audioPlaying, setAudioPlaying] = useState(false) const [audioPlaying, setAudioPlaying] = useState(false)
@ -56,7 +56,7 @@ const AttachmentAudio: React.FC<Props> = ({
style={[ style={[
styles.base, styles.base,
{ {
backgroundColor: theme.disabled, backgroundColor: colors.disabled,
aspectRatio: attachmentAspectRatio({ total, index }) aspectRatio: attachmentAspectRatio({ total, index })
} }
]} ]}
@ -102,7 +102,7 @@ const AttachmentAudio: React.FC<Props> = ({
alignSelf: 'flex-end', alignSelf: 'flex-end',
width: '100%', width: '100%',
height: StyleConstants.Spacing.M + StyleConstants.Spacing.S * 2, height: StyleConstants.Spacing.M + StyleConstants.Spacing.S * 2,
backgroundColor: theme.backgroundOverlayInvert, backgroundColor: colors.backgroundOverlayInvert,
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding, paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
borderRadius: 100, borderRadius: 100,
opacity: sensitiveShown ? 0.35 : undefined opacity: sensitiveShown ? 0.35 : undefined
@ -112,8 +112,8 @@ const AttachmentAudio: React.FC<Props> = ({
minimumValue={0} minimumValue={0}
maximumValue={audio.meta.original.duration * 1000} maximumValue={audio.meta.original.duration * 1000}
value={audioPosition} value={audioPosition}
minimumTrackTintColor={theme.secondary} minimumTrackTintColor={colors.secondary}
maximumTrackTintColor={theme.disabled} maximumTrackTintColor={colors.disabled}
// onSlidingStart={() => { // onSlidingStart={() => {
// audioPlayer?.pauseAsync() // audioPlayer?.pauseAsync()
// setAudioPlaying(false) // setAudioPlaying(false)
@ -123,7 +123,7 @@ const AttachmentAudio: React.FC<Props> = ({
// }} // }}
enabled={false} // Bug in above sliding actions enabled={false} // Bug in above sliding actions
thumbSize={StyleConstants.Spacing.M} thumbSize={StyleConstants.Spacing.M}
thumbTintColor={theme.primaryOverlay} thumbTintColor={colors.primaryOverlay}
/> />
</View> </View>
) : null} ) : null}

View File

@ -28,7 +28,16 @@ const AttachmentImage = React.memo(
uri={{ original: image.preview_url, remote: image.remote_url }} uri={{ original: image.preview_url, remote: image.remote_url }}
blurhash={image.blurhash} blurhash={image.blurhash}
onPress={onPress} onPress={onPress}
style={[{ aspectRatio: attachmentAspectRatio({ total, index }) }]} style={{
aspectRatio:
total > 1 ||
!image.meta?.original?.width ||
!image.meta?.original?.height
? attachmentAspectRatio({ total, index })
: image.meta.original.height / image.meta.original.width > 2
? 0.5
: image.meta.original.width / image.meta.original.height
}}
/> />
</View> </View>
) )

View File

@ -23,7 +23,7 @@ const AttachmentUnsupported: React.FC<Props> = ({
attachment attachment
}) => { }) => {
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const { theme } = useTheme() const { colors } = useTheme()
return ( return (
<View <View
@ -49,8 +49,8 @@ const AttachmentUnsupported: React.FC<Props> = ({
styles.text, styles.text,
{ {
color: attachment.blurhash color: attachment.blurhash
? theme.backgroundDefault ? colors.backgroundDefault
: theme.primaryDefault : colors.primaryDefault
} }
]} ]}
> >

View File

@ -16,9 +16,8 @@ export interface Props {
const TimelineAvatar = React.memo( const TimelineAvatar = React.memo(
({ queryKey, account, highlighted }: Props) => { ({ queryKey, account, highlighted }: Props) => {
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const navigation = useNavigation< const navigation =
StackNavigationProp<Nav.TabLocalStackParamList> useNavigation<StackNavigationProp<Nav.TabLocalStackParamList>>()
>()
// Need to fix go back root // Need to fix go back root
const onPress = useCallback(() => { const onPress = useCallback(() => {
analytics('timeline_shared_avatar_press', { analytics('timeline_shared_avatar_press', {
@ -38,7 +37,7 @@ const TimelineAvatar = React.memo(
}) })
})} })}
onPress={onPress} onPress={onPress}
uri={{ original: account.avatar_static }} uri={{ original: account.avatar, static: account.avatar_static }}
dimension={{ dimension={{
width: StyleConstants.Avatar.M, width: StyleConstants.Avatar.M,
height: StyleConstants.Avatar.M height: StyleConstants.Avatar.M

View File

@ -13,14 +13,14 @@ export interface Props {
const TimelineCard = React.memo( const TimelineCard = React.memo(
({ card }: Props) => { ({ card }: Props) => {
const { theme } = useTheme() const { colors } = useTheme()
const navigation = useNavigation() const navigation = useNavigation()
return ( return (
<Pressable <Pressable
accessible accessible
accessibilityRole='link' accessibilityRole='link'
style={[styles.card, { borderColor: theme.border }]} style={[styles.card, { borderColor: colors.border }]}
onPress={async () => { onPress={async () => {
analytics('timeline_shared_card_press') analytics('timeline_shared_card_press')
await openLink(card.url, navigation) await openLink(card.url, navigation)
@ -38,7 +38,7 @@ const TimelineCard = React.memo(
<View style={styles.right}> <View style={styles.right}>
<Text <Text
numberOfLines={2} numberOfLines={2}
style={[styles.rightTitle, { color: theme.primaryDefault }]} style={[styles.rightTitle, { color: colors.primaryDefault }]}
testID='title' testID='title'
> >
{card.title} {card.title}
@ -46,7 +46,10 @@ const TimelineCard = React.memo(
{card.description ? ( {card.description ? (
<Text <Text
numberOfLines={1} numberOfLines={1}
style={[styles.rightDescription, { color: theme.primaryDefault }]} style={[
styles.rightDescription,
{ color: colors.primaryDefault }
]}
testID='description' testID='description'
> >
{card.description} {card.description}
@ -54,7 +57,7 @@ const TimelineCard = React.memo(
) : null} ) : null}
<Text <Text
numberOfLines={1} numberOfLines={1}
style={[styles.rightLink, { color: theme.secondary }]} style={[styles.rightLink, { color: colors.secondary }]}
> >
{card.url} {card.url}
</Text> </Text>

View File

@ -10,15 +10,15 @@ import { Text, View } from 'react-native'
const TimelineFiltered = React.memo( const TimelineFiltered = React.memo(
() => { () => {
const { theme } = useTheme() const { colors } = useTheme()
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
return ( return (
<View style={{ backgroundColor: theme.backgroundDefault }}> <View style={{ backgroundColor: colors.backgroundDefault }}>
<Text <Text
style={{ style={{
...StyleConstants.FontStyle.S, ...StyleConstants.FontStyle.S,
color: theme.secondary, color: colors.secondary,
textAlign: 'center', textAlign: 'center',
paddingVertical: StyleConstants.Spacing.S, paddingVertical: StyleConstants.Spacing.S,
paddingLeft: StyleConstants.Avatar.M + StyleConstants.Spacing.S paddingLeft: StyleConstants.Avatar.M + StyleConstants.Spacing.S
@ -46,7 +46,7 @@ export const shouldFilter = ({
let shouldFilter = false let shouldFilter = false
if (!ownAccount) { if (!ownAccount) {
const parser = new htmlparser2.Parser({ const parser = new htmlparser2.Parser({
ontext (text: string) { ontext: (text: string) => {
const checkFilter = (filter: Mastodon.Filter) => { const checkFilter = (filter: Mastodon.Filter) => {
const escapedPhrase = filter.phrase.replace( const escapedPhrase = filter.phrase.replace(
/[.*+?^${}()|[\]\\]/g, /[.*+?^${}()|[\]\\]/g,
@ -54,7 +54,7 @@ export const shouldFilter = ({
) // $& means the whole matched string ) // $& means the whole matched string
switch (filter.whole_word) { switch (filter.whole_word) {
case true: case true:
if (new RegExp('\\b' + escapedPhrase + '\\b').test(text)) { if (new RegExp('\\B' + escapedPhrase + '\\B').test(text)) {
shouldFilter = true shouldFilter = true
} }
break break

View File

@ -13,7 +13,7 @@ export interface Props {
const TimelineFullConversation = React.memo( const TimelineFullConversation = React.memo(
({ queryKey, status }: Props) => { ({ queryKey, status }: Props) => {
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const { theme } = useTheme() const { colors } = useTheme()
return queryKey && return queryKey &&
queryKey[1].page !== 'Toot' && queryKey[1].page !== 'Toot' &&
@ -25,7 +25,7 @@ const TimelineFullConversation = React.memo(
<Text <Text
style={{ style={{
...StyleConstants.FontStyle.S, ...StyleConstants.FontStyle.S,
color: theme.blue, color: colors.blue,
marginTop: StyleConstants.Spacing.S marginTop: StyleConstants.Spacing.S
}} }}
> >

View File

@ -18,12 +18,12 @@ import HeaderSharedMuted from './HeaderShared/Muted'
const Names = React.memo( const Names = React.memo(
({ accounts }: { accounts: Mastodon.Account[] }) => { ({ accounts }: { accounts: Mastodon.Account[] }) => {
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const { theme } = useTheme() const { colors } = useTheme()
return ( return (
<Text <Text
numberOfLines={1} numberOfLines={1}
style={[styles.namesLeading, { color: theme.secondary }]} style={[styles.namesLeading, { color: colors.secondary }]}
> >
<Text>{t('shared.header.conversation.withAccounts')}</Text> <Text>{t('shared.header.conversation.withAccounts')}</Text>
{accounts.map((account, index) => ( {accounts.map((account, index) => (
@ -49,7 +49,7 @@ export interface Props {
const HeaderConversation = React.memo( const HeaderConversation = React.memo(
({ queryKey, conversation }: Props) => { ({ queryKey, conversation }: Props) => {
const { mode } = useTheme() const { colors, theme } = useTheme()
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const queryClient = useQueryClient() const queryClient = useQueryClient()
@ -57,7 +57,7 @@ const HeaderConversation = React.memo(
onMutate: true, onMutate: true,
onError: (err: any, _, oldData) => { onError: (err: any, _, oldData) => {
displayMessage({ displayMessage({
mode, theme,
type: 'error', type: 'error',
message: t('common:message.error.message', { message: t('common:message.error.message', {
function: t(`shared.header.conversation.delete.function`) function: t(`shared.header.conversation.delete.function`)
@ -74,8 +74,6 @@ const HeaderConversation = React.memo(
} }
}) })
const { theme } = useTheme()
const actionOnPress = useCallback(() => { const actionOnPress = useCallback(() => {
analytics('timeline_conversation_delete_press') analytics('timeline_conversation_delete_press')
mutation.mutate({ mutation.mutate({
@ -90,7 +88,7 @@ const HeaderConversation = React.memo(
() => ( () => (
<Icon <Icon
name='Trash' name='Trash'
color={theme.secondary} color={colors.secondary}
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
/> />
), ),

View File

@ -22,7 +22,7 @@ const TimelineHeaderDefault = React.memo(
({ queryKey, rootQueryKey, status }: Props) => { ({ queryKey, rootQueryKey, status }: Props) => {
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const navigation = useNavigation() const navigation = useNavigation()
const { theme } = useTheme() const { colors } = useTheme()
return ( return (
<View style={styles.base}> <View style={styles.base}>
@ -52,7 +52,7 @@ const TimelineHeaderDefault = React.memo(
children={ children={
<Icon <Icon
name='MoreHorizontal' name='MoreHorizontal'
color={theme.secondary} color={colors.secondary}
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
/> />
} }

View File

@ -23,7 +23,7 @@ export interface Props {
const TimelineHeaderNotification = React.memo( const TimelineHeaderNotification = React.memo(
({ queryKey, notification }: Props) => { ({ queryKey, notification }: Props) => {
const navigation = useNavigation() const navigation = useNavigation()
const { theme } = useTheme() const { colors } = useTheme()
const actions = useMemo(() => { const actions = useMemo(() => {
switch (notification.type) { switch (notification.type) {
@ -52,7 +52,7 @@ const TimelineHeaderNotification = React.memo(
children={ children={
<Icon <Icon
name='MoreHorizontal' name='MoreHorizontal'
color={theme.secondary} color={colors.secondary}
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
/> />
} }

View File

@ -13,7 +13,7 @@ export interface Props {
const HeaderSharedAccount = React.memo( const HeaderSharedAccount = React.memo(
({ account, withoutName = false }: Props) => { ({ account, withoutName = false }: Props) => {
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const { theme } = useTheme() const { colors } = useTheme()
return ( return (
<View style={styles.base}> <View style={styles.base}>
@ -36,7 +36,7 @@ const HeaderSharedAccount = React.memo(
accessibilityHint={t( accessibilityHint={t(
'shared.header.shared.account.account.accessibilityHint' 'shared.header.shared.account.account.accessibilityHint'
)} )}
style={[styles.acct, { color: theme.secondary }]} style={[styles.acct, { color: colors.secondary }]}
numberOfLines={1} numberOfLines={1}
> >
@{account.acct} @{account.acct}

View File

@ -12,7 +12,7 @@ export interface Props {
const HeaderSharedApplication = React.memo( const HeaderSharedApplication = React.memo(
({ application }: Props) => { ({ application }: Props) => {
const { theme } = useTheme() const { colors } = useTheme()
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
return application && application.name !== 'Web' ? ( return application && application.name !== 'Web' ? (
@ -24,7 +24,7 @@ const HeaderSharedApplication = React.memo(
}) })
application.website && (await openLink(application.website)) application.website && (await openLink(application.website))
}} }}
style={[styles.application, { color: theme.secondary }]} style={[styles.application, { color: colors.secondary }]}
numberOfLines={1} numberOfLines={1}
> >
{t('shared.header.shared.application', { {t('shared.header.shared.application', {

View File

@ -10,10 +10,10 @@ export interface Props {
const HeaderSharedCreated = React.memo( const HeaderSharedCreated = React.memo(
({ created_at }: Props) => { ({ created_at }: Props) => {
const { theme } = useTheme() const { colors } = useTheme()
return ( return (
<Text style={[styles.created_at, { color: theme.secondary }]}> <Text style={[styles.created_at, { color: colors.secondary }]}>
<RelativeTime date={created_at} /> <RelativeTime date={created_at} />
</Text> </Text>
) )

View File

@ -12,14 +12,14 @@ export interface Props {
const HeaderSharedMuted = React.memo( const HeaderSharedMuted = React.memo(
({ muted }: Props) => { ({ muted }: Props) => {
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const { theme } = useTheme() const { colors } = useTheme()
return muted ? ( return muted ? (
<Icon <Icon
accessibilityLabel={t('shared.header.shared.muted.accessibilityLabel')} accessibilityLabel={t('shared.header.shared.muted.accessibilityLabel')}
name='VolumeX' name='VolumeX'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={theme.secondary} color={colors.secondary}
style={styles.visibility} style={styles.visibility}
/> />
) : null ) : null

View File

@ -12,7 +12,7 @@ export interface Props {
const HeaderSharedVisibility = React.memo( const HeaderSharedVisibility = React.memo(
({ visibility }: Props) => { ({ visibility }: Props) => {
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const { theme } = useTheme() const { colors } = useTheme()
switch (visibility) { switch (visibility) {
case 'private': case 'private':
@ -23,7 +23,7 @@ const HeaderSharedVisibility = React.memo(
)} )}
name='Lock' name='Lock'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={theme.secondary} color={colors.secondary}
style={styles.visibility} style={styles.visibility}
/> />
) )
@ -35,7 +35,7 @@ const HeaderSharedVisibility = React.memo(
)} )}
name='Mail' name='Mail'
size={StyleConstants.Font.Size.S} size={StyleConstants.Font.Size.S}
color={theme.secondary} color={colors.secondary}
style={styles.visibility} style={styles.visibility}
/> />
) )

View File

@ -36,7 +36,7 @@ const TimelinePoll: React.FC<Props> = ({
reblog, reblog,
sameAccount sameAccount
}) => { }) => {
const { mode, theme } = useTheme() const { colors, theme } = useTheme()
const { t, i18n } = useTranslation('componentTimeline') const { t, i18n } = useTranslation('componentTimeline')
const [allOptions, setAllOptions] = useState( const [allOptions, setAllOptions] = useState(
@ -53,7 +53,7 @@ const TimelinePoll: React.FC<Props> = ({
haptics('Success') haptics('Success')
switch (theParams.payload.property) { switch (theParams.payload.property) {
case 'poll': case 'poll':
theParams.payload.data = (body as unknown) as Mastodon.Poll theParams.payload.data = body as unknown as Mastodon.Poll
updateStatusProperty(theParams) updateStatusProperty(theParams)
break break
} }
@ -61,7 +61,7 @@ const TimelinePoll: React.FC<Props> = ({
onError: (err: any, params) => { onError: (err: any, params) => {
const theParams = params as MutationVarsTimelineUpdateStatusProperty const theParams = params as MutationVarsTimelineUpdateStatusProperty
displayMessage({ displayMessage({
mode, theme,
type: 'error', type: 'error',
message: t('common:message.error.message', { message: t('common:message.error.message', {
// @ts-ignore // @ts-ignore
@ -136,7 +136,7 @@ const TimelinePoll: React.FC<Props> = ({
} }
} }
}, [ }, [
mode, theme,
i18n.language, i18n.language,
poll.expired, poll.expired,
poll.voted, poll.voted,
@ -147,14 +147,14 @@ const TimelinePoll: React.FC<Props> = ({
const pollExpiration = useMemo(() => { const pollExpiration = useMemo(() => {
if (poll.expired) { if (poll.expired) {
return ( return (
<Text style={[styles.expiration, { color: theme.secondary }]}> <Text style={[styles.expiration, { color: colors.secondary }]}>
{t('shared.poll.meta.expiration.expired')} {t('shared.poll.meta.expiration.expired')}
</Text> </Text>
) )
} else { } else {
if (poll.expires_at) { if (poll.expires_at) {
return ( return (
<Text style={[styles.expiration, { color: theme.secondary }]}> <Text style={[styles.expiration, { color: colors.secondary }]}>
<Trans <Trans
i18nKey='componentTimeline:shared.poll.meta.expiration.until' i18nKey='componentTimeline:shared.poll.meta.expiration.until'
components={[<RelativeTime date={poll.expires_at} />]} components={[<RelativeTime date={poll.expires_at} />]}
@ -163,7 +163,7 @@ const TimelinePoll: React.FC<Props> = ({
) )
} }
} }
}, [mode, i18n.language, poll.expired, poll.expires_at]) }, [theme, i18n.language, poll.expired, poll.expires_at])
const isSelected = useCallback( const isSelected = useCallback(
(index: number): string => (index: number): string =>
@ -174,8 +174,10 @@ const TimelinePoll: React.FC<Props> = ({
) )
const pollBodyDisallow = useMemo(() => { const pollBodyDisallow = useMemo(() => {
const maxValue = maxBy(poll.options, option => option.votes_count) const maxValue = maxBy(
?.votes_count poll.options,
option => option.votes_count
)?.votes_count
return poll.options.map((option, index) => ( return poll.options.map((option, index) => (
<View key={index} style={styles.optionContainer}> <View key={index} style={styles.optionContainer}>
<View style={styles.optionContent}> <View style={styles.optionContent}>
@ -188,14 +190,14 @@ const TimelinePoll: React.FC<Props> = ({
} }
size={StyleConstants.Font.Size.M} size={StyleConstants.Font.Size.M}
color={ color={
poll.own_votes?.includes(index) ? theme.blue : theme.disabled poll.own_votes?.includes(index) ? colors.blue : colors.disabled
} }
/> />
<Text style={styles.optionText}> <Text style={styles.optionText}>
<ParseEmojis content={option.title} emojis={poll.emojis} /> <ParseEmojis content={option.title} emojis={poll.emojis} />
</Text> </Text>
<Text <Text
style={[styles.optionPercentage, { color: theme.primaryDefault }]} style={[styles.optionPercentage, { color: colors.primaryDefault }]}
> >
{poll.votes_count {poll.votes_count
? Math.round( ? Math.round(
@ -217,13 +219,13 @@ const TimelinePoll: React.FC<Props> = ({
100 100
)}%`, )}%`,
backgroundColor: backgroundColor:
option.votes_count === maxValue ? theme.blue : theme.disabled option.votes_count === maxValue ? colors.blue : colors.disabled
} }
]} ]}
/> />
</View> </View>
)) ))
}, [mode, poll.options]) }, [theme, poll.options])
const pollBodyAllow = useMemo(() => { const pollBodyAllow = useMemo(() => {
return poll.options.map((option, index) => ( return poll.options.map((option, index) => (
<Pressable <Pressable
@ -256,7 +258,7 @@ const TimelinePoll: React.FC<Props> = ({
style={styles.optionSelection} style={styles.optionSelection}
name={isSelected(index)} name={isSelected(index)}
size={StyleConstants.Font.Size.M} size={StyleConstants.Font.Size.M}
color={theme.primaryDefault} color={colors.primaryDefault}
/> />
<Text style={styles.optionText}> <Text style={styles.optionText}>
<ParseEmojis content={option.title} emojis={poll.emojis} /> <ParseEmojis content={option.title} emojis={poll.emojis} />
@ -264,19 +266,19 @@ const TimelinePoll: React.FC<Props> = ({
</View> </View>
</Pressable> </Pressable>
)) ))
}, [mode, allOptions]) }, [theme, allOptions])
const pollVoteCounts = useMemo(() => { const pollVoteCounts = useMemo(() => {
if (poll.voters_count !== null) { if (poll.voters_count !== null) {
return ( return (
<Text style={[styles.votes, { color: theme.secondary }]}> <Text style={[styles.votes, { color: colors.secondary }]}>
{t('shared.poll.meta.count.voters', { count: poll.voters_count })} {t('shared.poll.meta.count.voters', { count: poll.voters_count })}
{' • '} {' • '}
</Text> </Text>
) )
} else if (poll.votes_count !== null) { } else if (poll.votes_count !== null) {
return ( return (
<Text style={[styles.votes, { color: theme.secondary }]}> <Text style={[styles.votes, { color: colors.secondary }]}>
{t('shared.poll.meta.count.votes', { count: poll.votes_count })} {t('shared.poll.meta.count.votes', { count: poll.votes_count })}
{' • '} {' • '}
</Text> </Text>

View File

@ -26,7 +26,7 @@ const TimelineTranslate = React.memo(
} }
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const { theme } = useTheme() const { colors } = useTheme()
const tootLanguage = status.language.slice(0, 2) const tootLanguage = status.language.slice(0, 2)
@ -82,10 +82,10 @@ const TimelineTranslate = React.memo(
...StyleConstants.FontStyle.M, ...StyleConstants.FontStyle.M,
color: color:
isLoading || isSuccess isLoading || isSuccess
? theme.secondary ? colors.secondary
: isError : isError
? theme.red ? colors.red
: theme.blue : colors.blue
}} }}
> >
{isError {isError
@ -109,7 +109,7 @@ const TimelineTranslate = React.memo(
{isLoading ? ( {isLoading ? (
<Circle <Circle
size={StyleConstants.Font.Size.M} size={StyleConstants.Font.Size.M}
color={theme.disabled} color={colors.disabled}
style={{ marginLeft: StyleConstants.Spacing.S }} style={{ marginLeft: StyleConstants.Spacing.S }}
/> />
) : null} ) : null}

View File

@ -8,6 +8,7 @@ export default {
screenImageViewer: require('./screens/imageViewer'), screenImageViewer: require('./screens/imageViewer'),
screenTabs: require('./screens/tabs'), screenTabs: require('./screens/tabs'),
componentEmojis: require('./components/emojis'),
componentInstance: require('./components/instance'), componentInstance: require('./components/instance'),
componentMediaSelector: require('./components/mediaSelector'), componentMediaSelector: require('./components/mediaSelector'),
componentParse: require('./components/parse'), componentParse: require('./components/parse'),

View File

@ -0,0 +1,3 @@
{
"frequentUsed": "Frequent used"
}

View File

@ -232,6 +232,14 @@
"cancel": "$t(common:buttons.cancel)" "cancel": "$t(common:buttons.cancel)"
} }
}, },
"darkTheme": {
"heading": "Dark theme",
"options": {
"lighter": "Lighter",
"darker": "Darker",
"cancel": "$t(common:buttons.cancel)"
}
},
"browser": { "browser": {
"heading": "Opening Link", "heading": "Opening Link",
"options": { "options": {
@ -240,6 +248,9 @@
"cancel": "$t(common:buttons.cancel)" "cancel": "$t(common:buttons.cancel)"
} }
}, },
"feedback": {
"heading": "Feature Requests"
},
"support": { "support": {
"heading": "Support tooot" "heading": "Support tooot"
}, },

View File

@ -8,6 +8,7 @@ export default {
screenImageViewer: require('./screens/imageViewer'), screenImageViewer: require('./screens/imageViewer'),
screenTabs: require('./screens/tabs'), screenTabs: require('./screens/tabs'),
componentEmojis: require('./components/emojis'),
componentInstance: require('./components/instance'), componentInstance: require('./components/instance'),
componentMediaSelector: require('./components/mediaSelector'), componentMediaSelector: require('./components/mediaSelector'),
componentParse: require('./components/parse'), componentParse: require('./components/parse'),

View File

@ -0,0 +1 @@
{}

View File

@ -8,6 +8,7 @@ export default {
screenImageViewer: require('./screens/imageViewer'), screenImageViewer: require('./screens/imageViewer'),
screenTabs: require('./screens/tabs'), screenTabs: require('./screens/tabs'),
componentEmojis: require('./components/emojis'),
componentInstance: require('./components/instance'), componentInstance: require('./components/instance'),
componentMediaSelector: require('./components/mediaSelector'), componentMediaSelector: require('./components/mediaSelector'),
componentParse: require('./components/parse'), componentParse: require('./components/parse'),

View File

@ -0,0 +1 @@
{}

View File

@ -1,8 +1,8 @@
{ {
"HTML": { "HTML": {
"expanded": { "expanded": {
"true": "Thu gọn {{hint}}", "true": "Cuộn {{hint}}",
"false": "Mở rộng {{hint}}" "false": "Mở {{hint}}"
}, },
"defaultHint": "Tút" "defaultHint": "Tút"
} }

View File

@ -2,7 +2,7 @@
"strings": { "strings": {
"prefixAgo": "", "prefixAgo": "",
"prefixFromNow": "", "prefixFromNow": "",
"suffixAgo": "trước", "suffixAgo": " trước",
"suffixFromNow": "", "suffixFromNow": "",
"seconds": "%d giây", "seconds": "%d giây",
"minute": "một phút", "minute": "một phút",

View File

@ -27,7 +27,7 @@
"follow_request": "{{name}} yêu cầu theo dõi bạn", "follow_request": "{{name}} yêu cầu theo dõi bạn",
"poll": "Cuộc bình chọn đã kết thúc", "poll": "Cuộc bình chọn đã kết thúc",
"reblog": { "reblog": {
"default": "{{name}} đã chia sẻ", "default": "{{name}} chia sẻ",
"notification": "{{name}} chia sẻ tút của bạn" "notification": "{{name}} chia sẻ tút của bạn"
} }
}, },
@ -62,7 +62,7 @@
}, },
"attachment": { "attachment": {
"sensitive": { "sensitive": {
"button": "Hiện nội dung nhạy cảm" "button": "NSFW"
}, },
"unsupported": { "unsupported": {
"text": "Không hỗ trợ định dạng", "text": "Không hỗ trợ định dạng",
@ -74,14 +74,16 @@
"accessibilityHint": "Đến trang cá nhân {{name}}" "accessibilityHint": "Đến trang cá nhân {{name}}"
}, },
"content": { "content": {
"expandHint": "nội dung bị ẩn" "expandHint": "nội dung ẩn"
}, },
"filtered": "Đã lọc", "filtered": "Đã lọc",
"fullConversation": "Xem thêm", "fullConversation": "Xem thêm",
"translate": { "translate": {
"default": "Dịch", "default": "Dịch",
"succeed": "Dịch bằng {{provider}} từ {{source}}", "succeed": "Dịch bằng {{provider}} từ {{source}}",
"failed": "Không thể dịch!" "failed": "Không thể dịch!",
"source_not_supported": "không hỗ trợ ngôn ngữ tút",
"target_not_supported": "Ngôn ngữ đích không hỗ trợ"
}, },
"header": { "header": {
"shared": { "shared": {
@ -93,7 +95,7 @@
"accessibilityHint": "Tài khoản" "accessibilityHint": "Tài khoản"
} }
}, },
"application": "Đăng bằng {{application}}", "application": "via {{application}}",
"muted": { "muted": {
"accessibilityLabel": "Đã ẩn tút" "accessibilityLabel": "Đã ẩn tút"
}, },
@ -107,7 +109,7 @@
} }
}, },
"conversation": { "conversation": {
"withAccounts": "Với", "withAccounts": "Với ",
"delete": { "delete": {
"function": "Xóa nhắn riêng" "function": "Xóa nhắn riêng"
} }
@ -146,16 +148,16 @@
}, },
"share": { "share": {
"status": { "status": {
"heading": "Tút", "heading": "Chia sẻ",
"button": "Chia sẻ URL tút" "button": "Chia sẻ URL tút"
}, },
"account": { "account": {
"heading": "Chia sẻ người dùng", "heading": "Chia sẻ",
"button": "Chia sẻ URL người dùng này" "button": "Chia sẻ URL người dùng này"
} }
}, },
"status": { "status": {
"heading": "Về tút", "heading": "Đối với tút",
"delete": { "delete": {
"function": "Xóa tút", "function": "Xóa tút",
"button": "Xóa tút này" "button": "Xóa tút này"

View File

@ -5,7 +5,7 @@
"cancel": "$t(common:buttons.cancel)" "cancel": "$t(common:buttons.cancel)"
}, },
"notificationsFilter": { "notificationsFilter": {
"heading": "Hiện những dạng thông báo", "heading": "Những kiểu thông báo cho phép",
"content": { "content": {
"follow": "$t(screenTabs:me.push.follow.heading)", "follow": "$t(screenTabs:me.push.follow.heading)",
"favourite": "$t(screenTabs:me.push.favourite.heading)", "favourite": "$t(screenTabs:me.push.favourite.heading)",

View File

@ -13,10 +13,10 @@
}, },
"right": { "right": {
"button": { "button": {
"default": "Tút", "default": "Đăng",
"conversation": "Tin nhắn", "conversation": "Tin nhắn",
"reply": "Lượt trả lời", "reply": "Trả lời",
"edit": "Tút" "edit": "Sửa"
}, },
"alert": { "alert": {
"default": { "default": {
@ -63,7 +63,7 @@
"option": { "option": {
"placeholder": { "placeholder": {
"accessibilityLabel": "Tùy chọn bình chọn {{index}}", "accessibilityLabel": "Tùy chọn bình chọn {{index}}",
"single": "Chỉ được chọn một", "single": "Lựa chọn",
"multiple": "Nhiều lựa chọn" "multiple": "Nhiều lựa chọn"
} }
}, },
@ -117,7 +117,7 @@
}, },
"visibility": { "visibility": {
"accessibilityLabel": "Tút thuộc dạng {{visibility}}", "accessibilityLabel": "Tút thuộc dạng {{visibility}}",
"title": "Dạng tút", "title": "Kiểu tút",
"options": { "options": {
"public": "Công khai", "public": "Công khai",
"unlisted": "Hạn chế", "unlisted": "Hạn chế",

View File

@ -53,7 +53,7 @@
"name": "Thông báo đẩy" "name": "Thông báo đẩy"
}, },
"profile": { "profile": {
"name": "Trang cá nhân" "name": "Cài đặt cá nhân"
}, },
"profileName": { "profileName": {
"name": "Tên hiển thị mới" "name": "Tên hiển thị mới"
@ -65,7 +65,7 @@
"name": "Metadata" "name": "Metadata"
}, },
"settings": { "settings": {
"name": "Cài đặt App" "name": "Thiết lập"
}, },
"switch": { "switch": {
"name": "Chuyển đổi tài khoản" "name": "Chuyển đổi tài khoản"
@ -102,22 +102,22 @@
}, },
"avatar": { "avatar": {
"title": "Ảnh đại diện", "title": "Ảnh đại diện",
"description": "Sẽ tự động chuyển còn 400x400px" "description": "Tự động nén xuống 400x400px"
}, },
"header": { "header": {
"title": "Ảnh bìa", "title": "Ảnh bìa",
"description": "Sẽ tự động chuyển còn 1500x1500px" "description": "Tự động nén xuống 1500x500px"
}, },
"note": { "note": {
"title": "Mô tả" "title": "Mô tả"
}, },
"fields": { "fields": {
"title": "Metadata", "title": "Metadata",
"total": "{{count}} trường", "total": "{{count}} mục",
"total_plural": "{{count}} trường" "total_plural": "{{count}} mục"
}, },
"visibility": { "visibility": {
"title": "Dạng tút", "title": "Kiểu tút mặc định",
"options": { "options": {
"public": "Công khai", "public": "Công khai",
"unlisted": "Hạn chế", "unlisted": "Hạn chế",
@ -126,19 +126,19 @@
} }
}, },
"sensitive": { "sensitive": {
"title": ăng nội dung nhạy cảm" "title": ây là tài khoản NSFW"
}, },
"lock": { "lock": {
"title": "Đây là tài khoản riêng tư", "title": "Đây là tài khoản riêng tư",
"description": "Tự bạn sẽ phê duyệt người theo dõi" "description": "Tự bạn sẽ phê duyệt người theo dõi"
}, },
"bot": { "bot": {
"title": "Tài khoản bot", "title": "Đây là tài khoản Bot",
"description": "Tài khoản này tự động thực hiện các hành động và không được quản lý bởi người thật" "description": "Tài khoản này tự động thực hiện các hành động và không được quản lý bởi người thật"
} }
}, },
"fields": { "fields": {
"group": "Nhóm {{index}}", "group": "Mục {{index}}",
"label": "Nhãn", "label": "Nhãn",
"content": "Nội dung" "content": "Nội dung"
} }
@ -150,11 +150,11 @@
"settings": "Bật trong cài đặt" "settings": "Bật trong cài đặt"
}, },
"global": { "global": {
"heading": "Bật khi {{acct}}", "heading": "Bật cho {{acct}}",
"description": "Thông báo được truyền qua máy chủ tooot" "description": "Thông báo được truyền qua máy chủ tooot"
}, },
"decode": { "decode": {
"heading": "Chi tiết thông báo", "heading": "Hiện chi tiết thông báo",
"description": "Theo mặc định, thông báo truyền qua máy chủ tooot sẽ được mã hóa, nhưng bạn cũng có thể chọn không mã hóa. Máy chủ của chúng tôi luôn công khai mã nguồn và không lưu lại bất cứ gì." "description": "Theo mặc định, thông báo truyền qua máy chủ tooot sẽ được mã hóa, nhưng bạn cũng có thể chọn không mã hóa. Máy chủ của chúng tôi luôn công khai mã nguồn và không lưu lại bất cứ gì."
}, },
"default": { "default": {
@ -232,6 +232,14 @@
"cancel": "$t(common:buttons.cancel)" "cancel": "$t(common:buttons.cancel)"
} }
}, },
"darkTheme": {
"heading": "Độ tối",
"options": {
"lighter": "Thấp",
"darker": "Vừa",
"cancel": "$t(common:buttons.cancel)"
}
},
"browser": { "browser": {
"heading": "Mở liên kết", "heading": "Mở liên kết",
"options": { "options": {
@ -240,6 +248,9 @@
"cancel": "$t(common:buttons.cancel)" "cancel": "$t(common:buttons.cancel)"
} }
}, },
"feedback": {
"heading": "Yêu cầu tính năng"
},
"support": { "support": {
"heading": "Ủng hộ tooot" "heading": "Ủng hộ tooot"
}, },
@ -250,13 +261,13 @@
"heading": "Liên hệ tooot" "heading": "Liên hệ tooot"
}, },
"analytics": { "analytics": {
"heading": "Đóng góp ý kiến", "heading": "Thu thập dữ liệu",
"description": "Chỉ thu thập thông tin không liên quan người dùng" "description": "Giúp cải thiện chất lượng app"
}, },
"version": "Phiên bản {{version}}" "version": "Phiên bản {{version}}"
}, },
"switch": { "switch": {
"existing": "Chọn từ màn hình đăng nhập", "existing": "Đã đăng nhập trước đó",
"new": "Đăng nhập máy chủ" "new": "Đăng nhập máy chủ"
} }
}, },
@ -279,15 +290,15 @@
} }
}, },
"attachments": { "attachments": {
"name": "<0 /><1>'s media</1>" "name": "Bộ sưu tập của <1></1>"
}, },
"search": { "search": {
"header": { "header": {
"prefix": "Đang tìm...", "prefix": "Tìm kiếm",
"placeholder": "về..." "placeholder": "tất cả mọi thứ"
}, },
"empty": { "empty": {
"general": "Nhập từ khóa <bold>$t(screenTabs:shared.search.sections.accounts)</bold>、<bold>$t(screenTabs:shared.search.sections.hashtags)</bold> hoặc<bold>$t(screenTabs:shared.search.sections.statuses)</bold>", "general": "Tìm <bold>$t(screenTabs:shared.search.sections.accounts)</bold>, <bold>$t(screenTabs:shared.search.sections.hashtags)</bold> hoặc <bold>$t(screenTabs:shared.search.sections.statuses)</bold><bold>",
"advanced": { "advanced": {
"header": "Tìm kiếm nâng cao", "header": "Tìm kiếm nâng cao",
"example": { "example": {
@ -310,12 +321,12 @@
}, },
"users": { "users": {
"accounts": { "accounts": {
"following": "Đang theo dõi {{count}}", "following": "{{count}} theo dõi",
"followers": "{{count}} người theo dõi" "followers": "{{count}} người theo dõi"
}, },
"statuses": { "statuses": {
"reblogged_by": "{{count}} người chia sẻ", "reblogged_by": "{{count}} chia sẻ",
"favourited_by": "{{count}} người thích" "favourited_by": "{{count}} thích"
} }
} }
} }

View File

@ -8,6 +8,7 @@ export default {
screenImageViewer: require('./screens/imageViewer'), screenImageViewer: require('./screens/imageViewer'),
screenTabs: require('./screens/tabs'), screenTabs: require('./screens/tabs'),
componentEmojis: require('./components/emojis'),
componentInstance: require('./components/instance'), componentInstance: require('./components/instance'),
componentMediaSelector: require('./components/mediaSelector'), componentMediaSelector: require('./components/mediaSelector'),
componentParse: require('./components/parse'), componentParse: require('./components/parse'),

View File

@ -0,0 +1,3 @@
{
"frequentUsed": "常用"
}

View File

@ -232,6 +232,14 @@
"cancel": "$t(common:buttons.cancel)" "cancel": "$t(common:buttons.cancel)"
} }
}, },
"darkTheme": {
"heading": "深色主题",
"options": {
"lighter": "暗淡",
"darker": "暗黑",
"cancel": "$t(common:buttons.cancel)"
}
},
"browser": { "browser": {
"heading": "外部链接", "heading": "外部链接",
"options": { "options": {
@ -240,6 +248,9 @@
"cancel": "$t(common:buttons.cancel)" "cancel": "$t(common:buttons.cancel)"
} }
}, },
"feedback": {
"heading": "功能建议"
},
"support": { "support": {
"heading": "赞助 tooot 开发" "heading": "赞助 tooot 开发"
}, },

View File

@ -68,7 +68,7 @@ const ScreenActions = React.memo(
break break
} }
const { theme } = useTheme() const { colors } = useTheme()
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const DEFAULT_VALUE = 350 const DEFAULT_VALUE = 350
@ -189,7 +189,7 @@ const ScreenActions = React.memo(
<Animated.View <Animated.View
style={[ style={[
styles.overlay, styles.overlay,
{ backgroundColor: theme.backgroundOverlayInvert } { backgroundColor: colors.backgroundOverlayInvert }
]} ]}
> >
<PanGestureHandler onGestureEvent={onGestureEvent}> <PanGestureHandler onGestureEvent={onGestureEvent}>
@ -198,7 +198,7 @@ const ScreenActions = React.memo(
styles.container, styles.container,
styleTop, styleTop,
{ {
backgroundColor: theme.backgroundDefault, backgroundColor: colors.backgroundDefault,
paddingBottom: insets.bottom || StyleConstants.Spacing.L paddingBottom: insets.bottom || StyleConstants.Spacing.L
} }
]} ]}
@ -206,7 +206,7 @@ const ScreenActions = React.memo(
<View <View
style={[ style={[
styles.handle, styles.handle,
{ backgroundColor: theme.primaryOverlay } { backgroundColor: colors.primaryOverlay }
]} ]}
/> />
{actions} {actions}

View File

@ -24,7 +24,7 @@ const ActionsAccount: React.FC<Props> = ({
account, account,
dismiss dismiss
}) => { }) => {
const { mode } = useTheme() const { theme } = useTheme()
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const queryClient = useQueryClient() const queryClient = useQueryClient()
@ -32,7 +32,7 @@ const ActionsAccount: React.FC<Props> = ({
onSuccess: (_, params) => { onSuccess: (_, params) => {
const theParams = params as MutationVarsTimelineUpdateAccountProperty const theParams = params as MutationVarsTimelineUpdateAccountProperty
displayMessage({ displayMessage({
mode, theme,
type: 'success', type: 'success',
message: t('common:message.success.message', { message: t('common:message.success.message', {
function: t( function: t(
@ -47,7 +47,7 @@ const ActionsAccount: React.FC<Props> = ({
onError: (err: any, params) => { onError: (err: any, params) => {
const theParams = params as MutationVarsTimelineUpdateAccountProperty const theParams = params as MutationVarsTimelineUpdateAccountProperty
displayMessage({ displayMessage({
mode, theme,
type: 'error', type: 'error',
message: t('common:message.error.message', { message: t('common:message.error.message', {
function: t( function: t(

View File

@ -26,13 +26,13 @@ const ActionsDomain: React.FC<Props> = ({
domain, domain,
dismiss dismiss
}) => { }) => {
const { mode } = useTheme() const { theme } = useTheme()
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const queryClient = useQueryClient() const queryClient = useQueryClient()
const mutation = useTimelineMutation({ const mutation = useTimelineMutation({
onSettled: () => { onSettled: () => {
displayMessage({ displayMessage({
mode, theme,
type: 'success', type: 'success',
message: t('common:message.success.message', { message: t('common:message.success.message', {
function: t(`shared.header.actions.domain.block.function`) function: t(`shared.header.actions.domain.block.function`)

View File

@ -30,7 +30,7 @@ const ActionsStatus: React.FC<Props> = ({
status, status,
dismiss dismiss
}) => { }) => {
const { mode } = useTheme() const { theme } = useTheme()
const { t } = useTranslation('componentTimeline') const { t } = useTranslation('componentTimeline')
const queryClient = useQueryClient() const queryClient = useQueryClient()
@ -42,7 +42,7 @@ const ActionsStatus: React.FC<Props> = ({
? (params as MutationVarsTimelineUpdateStatusProperty).payload.property ? (params as MutationVarsTimelineUpdateStatusProperty).payload.property
: 'delete' : 'delete'
displayMessage({ displayMessage({
mode, theme,
type: 'error', type: 'error',
message: t('common:message.error.message', { message: t('common:message.error.message', {
function: t(`shared.header.actions.status.${theFunction}.function`) function: t(`shared.header.actions.status.${theFunction}.function`)

View File

@ -36,7 +36,7 @@ const ScreenAnnouncements: React.FC<
navigation navigation
}) => { }) => {
const { reduceMotionEnabled } = useAccessibility() const { reduceMotionEnabled } = useAccessibility()
const { mode, theme } = useTheme() const { colors, mode } = useTheme()
const [index, setIndex] = useState(0) const [index, setIndex] = useState(0)
const { t } = useTranslation('screenAnnouncements') const { t } = useTranslation('screenAnnouncements')
@ -73,12 +73,12 @@ const ScreenAnnouncements: React.FC<
style={[ style={[
styles.announcement, styles.announcement,
{ {
borderColor: theme.primaryDefault, borderColor: colors.primaryDefault,
backgroundColor: theme.backgroundDefault backgroundColor: colors.backgroundDefault
} }
]} ]}
> >
<Text style={[styles.published, { color: theme.secondary }]}> <Text style={[styles.published, { color: colors.secondary }]}>
<Trans <Trans
i18nKey='screenAnnouncements:content.published' i18nKey='screenAnnouncements:content.published'
components={[<RelativeTime date={item.published_at} />]} components={[<RelativeTime date={item.published_at} />]}
@ -103,11 +103,11 @@ const ScreenAnnouncements: React.FC<
styles.reaction, styles.reaction,
{ {
borderColor: reaction.me borderColor: reaction.me
? theme.disabled ? colors.disabled
: theme.primaryDefault, : colors.primaryDefault,
backgroundColor: reaction.me backgroundColor: reaction.me
? theme.disabled ? colors.disabled
: theme.backgroundDefault : colors.backgroundDefault
} }
]} ]}
onPress={() => { onPress={() => {
@ -138,7 +138,7 @@ const ScreenAnnouncements: React.FC<
<Text <Text
style={[ style={[
styles.reactionCount, styles.reactionCount,
{ color: theme.primaryDefault } { color: colors.primaryDefault }
]} ]}
> >
{reaction.count} {reaction.count}
@ -147,13 +147,13 @@ const ScreenAnnouncements: React.FC<
</Pressable> </Pressable>
))} ))}
{/* <Pressable {/* <Pressable
style={[styles.reaction, { borderColor: theme.primaryDefault }]} style={[styles.reaction, { borderColor: colors.primaryDefault }]}
onPress={() => invisibleTextInputRef.current?.focus()} onPress={() => invisibleTextInputRef.current?.focus()}
> >
<Icon <Icon
name='Plus' name='Plus'
size={StyleConstants.Font.Size.M} size={StyleConstants.Font.Size.M}
color={theme.primaryDefault} color={colors.primaryDefault}
/> />
</Pressable> */} </Pressable> */}
</View> </View>
@ -177,7 +177,7 @@ const ScreenAnnouncements: React.FC<
</View> </View>
</View> </View>
), ),
[theme] [mode]
) )
const onMomentumScrollEnd = useCallback( const onMomentumScrollEnd = useCallback(
@ -201,7 +201,7 @@ const ScreenAnnouncements: React.FC<
alignItems: 'center' alignItems: 'center'
}} }}
> >
<Circle size={StyleConstants.Font.Size.L} color={theme.secondary} /> <Circle size={StyleConstants.Font.Size.L} color={colors.secondary} />
</View> </View>
) )
}, []) }, [])
@ -211,7 +211,7 @@ const ScreenAnnouncements: React.FC<
blurType={mode} blurType={mode}
blurAmount={20} blurAmount={20}
style={styles.base} style={styles.base}
reducedTransparencyFallbackColor={theme.backgroundDefault} reducedTransparencyFallbackColor={colors.backgroundDefault}
> >
<SafeAreaView style={styles.base}> <SafeAreaView style={styles.base}>
<FlatList <FlatList
@ -232,9 +232,9 @@ const ScreenAnnouncements: React.FC<
style={[ style={[
styles.indicator, styles.indicator,
{ {
borderColor: theme.primaryDefault, borderColor: colors.primaryDefault,
backgroundColor: backgroundColor:
i === index ? theme.primaryDefault : undefined, i === index ? colors.primaryDefault : undefined,
marginLeft: marginLeft:
i === query.data.length ? 0 : StyleConstants.Spacing.S i === query.data.length ? 0 : StyleConstants.Spacing.S
} }
@ -248,7 +248,7 @@ const ScreenAnnouncements: React.FC<
</BlurView> </BlurView>
) : ( ) : (
<SafeAreaView <SafeAreaView
style={[styles.base, { backgroundColor: theme.backgroundDefault }]} style={[styles.base, { backgroundColor: colors.backgroundDefault }]}
> >
<FlatList <FlatList
horizontal horizontal
@ -268,9 +268,9 @@ const ScreenAnnouncements: React.FC<
style={[ style={[
styles.indicator, styles.indicator,
{ {
borderColor: theme.primaryDefault, borderColor: colors.primaryDefault,
backgroundColor: backgroundColor:
i === index ? theme.primaryDefault : undefined, i === index ? colors.primaryDefault : undefined,
marginLeft: marginLeft:
i === query.data.length ? 0 : StyleConstants.Spacing.S i === query.data.length ? 0 : StyleConstants.Spacing.S
} }

View File

@ -50,7 +50,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
navigation navigation
}) => { }) => {
const { t } = useTranslation('screenCompose') const { t } = useTranslation('screenCompose')
const { theme } = useTheme() const { colors } = useTheme()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [hasKeyboard, setHasKeyboard] = useState(false) const [hasKeyboard, setHasKeyboard] = useState(false)
@ -373,7 +373,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
component={ComposeRoot} component={ComposeRoot}
options={{ options={{
title: headerContent, title: headerContent,
titleStyle: { headerTitleStyle: {
fontWeight: fontWeight:
totalTextCount > maxTootChars totalTextCount > maxTootChars
? StyleConstants.Font.Weight.Bold ? StyleConstants.Font.Weight.Bold
@ -381,7 +381,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
fontSize: StyleConstants.Font.Size.M fontSize: StyleConstants.Font.Size.M
}, },
headerTintColor: headerTintColor:
totalTextCount > maxTootChars ? theme.red : theme.secondary, totalTextCount > maxTootChars ? colors.red : colors.secondary,
headerLeft, headerLeft,
headerRight headerRight
}} }}

View File

@ -37,7 +37,7 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
const { t } = useTranslation('screenCompose') const { t } = useTranslation('screenCompose')
const navigation = useNavigation() const navigation = useNavigation()
const dispatch = useDispatch() const dispatch = useDispatch()
const { mode, theme } = useTheme() const { colors, theme } = useTheme()
const instanceDrafts = useSelector(getInstanceDrafts)?.filter( const instanceDrafts = useSelector(getInstanceDrafts)?.filter(
draft => draft.timestamp !== timestamp draft => draft.timestamp !== timestamp
) )
@ -56,7 +56,7 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
return ( return (
<Pressable <Pressable
accessibilityHint={t('content.draftsList.content.accessibilityHint')} accessibilityHint={t('content.draftsList.content.accessibilityHint')}
style={[styles.draft, { backgroundColor: theme.backgroundDefault }]} style={[styles.draft, { backgroundColor: colors.backgroundDefault }]}
onPress={async () => { onPress={async () => {
setCheckingAttachments(true) setCheckingAttachments(true)
let tempDraft = item let tempDraft = item
@ -104,7 +104,7 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
<HeaderSharedCreated created_at={item.timestamp} /> <HeaderSharedCreated created_at={item.timestamp} />
<Text <Text
numberOfLines={2} numberOfLines={2}
style={[styles.text, { color: theme.primaryDefault }]} style={[styles.text, { color: colors.primaryDefault }]}
> >
{item.text || {item.text ||
item.spoiler || item.spoiler ||
@ -132,12 +132,12 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
</Pressable> </Pressable>
) )
}, },
[mode] [theme]
) )
const renderHiddenItem = useCallback( const renderHiddenItem = useCallback(
({ item }) => ( ({ item }) => (
<View <View
style={[styles.hiddenBase, { backgroundColor: theme.red }]} style={[styles.hiddenBase, { backgroundColor: colors.red }]}
children={ children={
<Pressable <Pressable
style={styles.action} style={styles.action}
@ -146,14 +146,14 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
<Icon <Icon
name='Trash' name='Trash'
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
color={theme.primaryOverlay} color={colors.primaryOverlay}
/> />
} }
/> />
} }
/> />
), ),
[mode] [theme]
) )
return ( return (
@ -184,14 +184,14 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
<View <View
style={[ style={[
styles.modal, styles.modal,
{ backgroundColor: theme.backgroundOverlayInvert } { backgroundColor: colors.backgroundOverlayInvert }
]} ]}
children={ children={
<Text <Text
children='检查附件在服务器的状态…' children='检查附件在服务器的状态…'
style={{ style={{
...StyleConstants.FontStyle.M, ...StyleConstants.FontStyle.M,
color: theme.primaryOverlay color: colors.primaryOverlay
}} }}
/> />
} }

View File

@ -22,7 +22,7 @@ export interface Props {
const ComposeEditAttachmentImage: React.FC<Props> = ({ index }) => { const ComposeEditAttachmentImage: React.FC<Props> = ({ index }) => {
const { t } = useTranslation('screenCompose') const { t } = useTranslation('screenCompose')
const { theme } = useTheme() const { colors } = useTheme()
const { screenReaderEnabled } = useAccessibility() const { screenReaderEnabled } = useAccessibility()
const { composeState, composeDispatch } = useContext(ComposeContext) const { composeState, composeDispatch } = useContext(ComposeContext)
@ -146,16 +146,21 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index }) => {
<G> <G>
<Path <Path
d='M1000,0 L1000,1000 L0,1000 L0,0 L1000,0 Z M500,475 C486.192881,475 475,486.192881 475,500 C475,513.807119 486.192881,525 500,525 C513.807119,525 525,513.807119 525,500 C525,486.192881 513.807119,475 500,475 Z' d='M1000,0 L1000,1000 L0,1000 L0,0 L1000,0 Z M500,475 C486.192881,475 475,486.192881 475,500 C475,513.807119 486.192881,525 500,525 C513.807119,525 525,513.807119 525,500 C525,486.192881 513.807119,475 500,475 Z'
fill={theme.backgroundOverlayInvert} fill={colors.backgroundOverlayInvert}
/> />
<Circle <Circle
stroke={theme.primaryOverlay} stroke={colors.primaryOverlay}
stroke-width='2' stroke-width='2'
cx='500' cx='500'
cy='500' cy='500'
r='24' r='24'
/> />
<Circle fill={theme.primaryOverlay} cx='500' cy='500' r='2' /> <Circle
fill={colors.primaryOverlay}
cx='500'
cy='500'
r='2'
/>
</G> </G>
</G> </G>
</Svg> </Svg>
@ -163,7 +168,7 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index }) => {
</PanGestureHandler> </PanGestureHandler>
</View> </View>
{screenReaderEnabled ? null : ( {screenReaderEnabled ? null : (
<Text style={[styles.imageFocusText, { color: theme.primaryDefault }]}> <Text style={[styles.imageFocusText, { color: colors.primaryDefault }]}>
{t('content.editAttachment.content.imageFocus')} {t('content.editAttachment.content.imageFocus')}
</Text> </Text>
)} )}

View File

@ -13,7 +13,7 @@ export interface Props {
const ComposeEditAttachmentRoot: React.FC<Props> = ({ index }) => { const ComposeEditAttachmentRoot: React.FC<Props> = ({ index }) => {
const { t } = useTranslation('screenCompose') const { t } = useTranslation('screenCompose')
const { mode, theme } = useTheme() const { colors, mode } = useTheme()
const { composeState, composeDispatch } = useContext(ComposeContext) const { composeState, composeDispatch } = useContext(ComposeContext)
const theAttachment = composeState.attachments.uploads[index].remote! const theAttachment = composeState.attachments.uploads[index].remote!
@ -61,13 +61,15 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({ index }) => {
<ScrollView ref={scrollViewRef}> <ScrollView ref={scrollViewRef}>
{mediaDisplay} {mediaDisplay}
<View style={styles.altTextContainer}> <View style={styles.altTextContainer}>
<Text style={[styles.altTextInputHeading, { color: theme.primaryDefault }]}> <Text
style={[styles.altTextInputHeading, { color: colors.primaryDefault }]}
>
{t('content.editAttachment.content.altText.heading')} {t('content.editAttachment.content.altText.heading')}
</Text> </Text>
<TextInput <TextInput
style={[ style={[
styles.altTextInput, styles.altTextInput,
{ borderColor: theme.border, color: theme.primaryDefault } { borderColor: colors.border, color: colors.primaryDefault }
]} ]}
onFocus={() => scrollViewRef.current?.scrollToEnd()} onFocus={() => scrollViewRef.current?.scrollToEnd()}
autoCapitalize='none' autoCapitalize='none'
@ -76,12 +78,12 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({ index }) => {
multiline multiline
onChangeText={onChangeText} onChangeText={onChangeText}
placeholder={t('content.editAttachment.content.altText.placeholder')} placeholder={t('content.editAttachment.content.altText.placeholder')}
placeholderTextColor={theme.secondary} placeholderTextColor={colors.secondary}
scrollEnabled scrollEnabled
value={theAttachment.description} value={theAttachment.description}
keyboardAppearance={mode} keyboardAppearance={mode}
/> />
<Text style={[styles.altTextLength, { color: theme.secondary }]}> <Text style={[styles.altTextLength, { color: colors.secondary }]}>
{theAttachment.description?.length || 0} / 1500 {theAttachment.description?.length || 0} / 1500
</Text> </Text>
</View> </View>

View File

@ -6,7 +6,7 @@ import ComposeContext from './utils/createContext'
const ComposePosting = React.memo( const ComposePosting = React.memo(
() => { () => {
const { composeState } = useContext(ComposeContext) const { composeState } = useContext(ComposeContext)
const { theme } = useTheme() const { colors } = useTheme()
return ( return (
<Modal <Modal
@ -14,7 +14,9 @@ const ComposePosting = React.memo(
animationType='fade' animationType='fade'
visible={composeState.posting} visible={composeState.posting}
children={ children={
<View style={{ flex: 1, backgroundColor: theme.backgroundOverlayInvert }} /> <View
style={{ flex: 1, backgroundColor: colors.backgroundOverlayInvert }}
/>
} }
/> />
) )

View File

@ -30,7 +30,11 @@ import FastImage from 'react-native-fast-image'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { ComposeState } from './utils/types' import { ComposeState } from './utils/types'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { getInstanceConfigurationStatusCharsURL } from '@utils/slices/instancesSlice' import {
getInstanceConfigurationStatusCharsURL,
getInstanceFrequentEmojis
} from '@utils/slices/instancesSlice'
import { useTranslation } from 'react-i18next'
const prefetchEmojis = ( const prefetchEmojis = (
sortedEmojis: NonNullable<ComposeState['emoji']['emojis']>, sortedEmojis: NonNullable<ComposeState['emoji']['emojis']>,
@ -61,7 +65,7 @@ export let instanceConfigurationStatusCharsURL = 23
const ComposeRoot = React.memo( const ComposeRoot = React.memo(
() => { () => {
const { reduceMotionEnabled } = useAccessibility() const { reduceMotionEnabled } = useAccessibility()
const { theme } = useTheme() const { colors } = useTheme()
instanceConfigurationStatusCharsURL = useSelector( instanceConfigurationStatusCharsURL = useSelector(
getInstanceConfigurationStatusCharsURL, getInstanceConfigurationStatusCharsURL,
@ -99,15 +103,29 @@ const ComposeRoot = React.memo(
} }
}, [composeState.tag]) }, [composeState.tag])
const { t } = useTranslation()
const { data: emojisData } = useEmojisQuery({}) const { data: emojisData } = useEmojisQuery({})
const frequentEmojis = useSelector(getInstanceFrequentEmojis, () => true)
useEffect(() => { useEffect(() => {
if (emojisData && emojisData.length) { if (emojisData && emojisData.length) {
let sortedEmojis: { title: string; data: Mastodon.Emoji[][] }[] = [] const sortedEmojis: {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[] = []
forEach( forEach(
groupBy(sortBy(emojisData, ['category', 'shortcode']), 'category'), groupBy(sortBy(emojisData, ['category', 'shortcode']), 'category'),
(value, key) => (value, key) =>
sortedEmojis.push({ title: key, data: chunk(value, 5) }) sortedEmojis.push({ title: key, data: chunk(value, 5) })
) )
if (frequentEmojis.length) {
sortedEmojis.unshift({
title: t('componentEmojis:frequentUsed'),
data: chunk(
frequentEmojis.map(e => e.emoji),
5
)
})
}
composeDispatch({ composeDispatch({
type: 'emoji', type: 'emoji',
payload: { ...composeState.emoji, emojis: sortedEmojis } payload: { ...composeState.emoji, emojis: sortedEmojis }
@ -122,7 +140,7 @@ const ComposeRoot = React.memo(
<View key='listEmpty' style={styles.loading}> <View key='listEmpty' style={styles.loading}>
<Circle <Circle
size={StyleConstants.Font.Size.M * 1.25} size={StyleConstants.Font.Size.M * 1.25}
color={theme.secondary} color={colors.secondary}
/> />
</View> </View>
) )

View File

@ -16,19 +16,19 @@ const ComposeActions: React.FC = () => {
const { showActionSheetWithOptions } = useActionSheet() const { showActionSheetWithOptions } = useActionSheet()
const { composeState, composeDispatch } = useContext(ComposeContext) const { composeState, composeDispatch } = useContext(ComposeContext)
const { t } = useTranslation('screenCompose') const { t } = useTranslation('screenCompose')
const { theme } = useTheme() const { colors, mode } = useTheme()
const instanceConfigurationStatusMaxAttachments = useSelector( const instanceConfigurationStatusMaxAttachments = useSelector(
getInstanceConfigurationStatusMaxAttachments, getInstanceConfigurationStatusMaxAttachments,
() => true () => true
) )
const attachmentColor = useMemo(() => { const attachmentColor = useMemo(() => {
if (composeState.poll.active) return theme.disabled if (composeState.poll.active) return colors.disabled
if (composeState.attachments.uploads.length) { if (composeState.attachments.uploads.length) {
return theme.primaryDefault return colors.primaryDefault
} else { } else {
return theme.secondary return colors.secondary
} }
}, [composeState.poll.active, composeState.attachments.uploads]) }, [composeState.poll.active, composeState.attachments.uploads])
const attachmentOnPress = useCallback(async () => { const attachmentOnPress = useCallback(async () => {
@ -49,12 +49,12 @@ const ComposeActions: React.FC = () => {
}, [composeState.poll.active, composeState.attachments.uploads]) }, [composeState.poll.active, composeState.attachments.uploads])
const pollColor = useMemo(() => { const pollColor = useMemo(() => {
if (composeState.attachments.uploads.length) return theme.disabled if (composeState.attachments.uploads.length) return colors.disabled
if (composeState.poll.active) { if (composeState.poll.active) {
return theme.primaryDefault return colors.primaryDefault
} else { } else {
return theme.secondary return colors.secondary
} }
}, [composeState.poll.active, composeState.attachments.uploads]) }, [composeState.poll.active, composeState.attachments.uploads])
const pollOnPress = useCallback(() => { const pollOnPress = useCallback(() => {
@ -97,7 +97,8 @@ const ComposeActions: React.FC = () => {
t('content.root.actions.visibility.options.direct'), t('content.root.actions.visibility.options.direct'),
t('content.root.actions.visibility.options.cancel') t('content.root.actions.visibility.options.cancel')
], ],
cancelButtonIndex: 4 cancelButtonIndex: 4,
userInterfaceStyle: mode
}, },
buttonIndex => { buttonIndex => {
switch (buttonIndex) { switch (buttonIndex) {
@ -150,12 +151,12 @@ const ComposeActions: React.FC = () => {
}, [composeState.spoiler.active, composeState.textInputFocus]) }, [composeState.spoiler.active, composeState.textInputFocus])
const emojiColor = useMemo(() => { const emojiColor = useMemo(() => {
if (!composeState.emoji.emojis) return theme.disabled if (!composeState.emoji.emojis) return colors.disabled
if (composeState.emoji.active) { if (composeState.emoji.active) {
return theme.primaryDefault return colors.primaryDefault
} else { } else {
return theme.secondary return colors.secondary
} }
}, [composeState.emoji.active, composeState.emoji.emojis]) }, [composeState.emoji.active, composeState.emoji.emojis])
const emojiOnPress = useCallback(() => { const emojiOnPress = useCallback(() => {
@ -177,8 +178,8 @@ const ComposeActions: React.FC = () => {
style={[ style={[
styles.additions, styles.additions,
{ {
backgroundColor: theme.backgroundDefault, backgroundColor: colors.backgroundDefault,
borderTopColor: theme.border borderTopColor: colors.border
} }
]} ]}
> >
@ -223,7 +224,7 @@ const ComposeActions: React.FC = () => {
name={visibilityIcon} name={visibilityIcon}
size={24} size={24}
color={ color={
composeState.visibilityLock ? theme.disabled : theme.secondary composeState.visibilityLock ? colors.disabled : colors.secondary
} }
/> />
} }
@ -242,8 +243,8 @@ const ComposeActions: React.FC = () => {
size={24} size={24}
color={ color={
composeState.spoiler.active composeState.spoiler.active
? theme.primaryDefault ? colors.primaryDefault
: theme.secondary : colors.secondary
} }
/> />
} }

View File

@ -39,7 +39,7 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
const { showActionSheetWithOptions } = useActionSheet() const { showActionSheetWithOptions } = useActionSheet()
const { composeState, composeDispatch } = useContext(ComposeContext) const { composeState, composeDispatch } = useContext(ComposeContext)
const { t } = useTranslation('screenCompose') const { t } = useTranslation('screenCompose')
const { theme } = useTheme() const { colors, mode } = useTheme()
const navigation = useNavigation<any>() const navigation = useNavigation<any>()
const flatListRef = useRef<FlatList>(null) const flatListRef = useRef<FlatList>(null)
@ -135,8 +135,8 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
style={[ style={[
styles.duration, styles.duration,
{ {
color: theme.backgroundDefault, color: colors.backgroundDefault,
backgroundColor: theme.backgroundOverlayInvert backgroundColor: colors.backgroundOverlayInvert
} }
]} ]}
> >
@ -147,12 +147,12 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
<View <View
style={[ style={[
styles.uploading, styles.uploading,
{ backgroundColor: theme.backgroundOverlayInvert } { backgroundColor: colors.backgroundOverlayInvert }
]} ]}
> >
<Circle <Circle
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
color={theme.primaryOverlay} color={colors.primaryOverlay}
/> />
</View> </View>
) : ( ) : (
@ -213,7 +213,7 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
styles.container, styles.container,
{ {
width: DEFAULT_HEIGHT, width: DEFAULT_HEIGHT,
backgroundColor: theme.backgroundOverlayInvert backgroundColor: colors.backgroundOverlayInvert
} }
]} ]}
onPress={async () => { onPress={async () => {
@ -255,9 +255,9 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
<Icon <Icon
name={composeState.attachments.sensitive ? 'CheckCircle' : 'Circle'} name={composeState.attachments.sensitive ? 'CheckCircle' : 'Circle'}
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
color={theme.primaryDefault} color={colors.primaryDefault}
/> />
<Text style={[styles.sensitiveText, { color: theme.primaryDefault }]}> <Text style={[styles.sensitiveText, { color: colors.primaryDefault }]}>
{t('content.root.footer.attachments.sensitive')} {t('content.root.footer.attachments.sensitive')}
</Text> </Text>
</Pressable> </Pressable>

View File

@ -1,5 +1,6 @@
import haptics from '@components/haptics' import haptics from '@components/haptics'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { countInstanceEmoji } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { RefObject, useCallback, useContext, useEffect } from 'react' import React, { RefObject, useCallback, useContext, useEffect } from 'react'
@ -14,6 +15,7 @@ import {
View View
} from 'react-native' } from 'react-native'
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image'
import { useDispatch } from 'react-redux'
import validUrl from 'valid-url' import validUrl from 'valid-url'
import updateText from '../../updateText' import updateText from '../../updateText'
import ComposeContext from '../../utils/createContext' import ComposeContext from '../../utils/createContext'
@ -25,8 +27,9 @@ export interface Props {
const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => { const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
const { composeState, composeDispatch } = useContext(ComposeContext) const { composeState, composeDispatch } = useContext(ComposeContext)
const { reduceMotionEnabled } = useAccessibility() const { reduceMotionEnabled } = useAccessibility()
const { theme } = useTheme() const { colors } = useTheme()
const { t } = useTranslation() const { t } = useTranslation()
const dispatch = useDispatch()
useEffect(() => { useEffect(() => {
const tagEmojis = findNodeHandle(accessibleRefEmojis.current) const tagEmojis = findNodeHandle(accessibleRefEmojis.current)
@ -37,7 +40,7 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
const listHeader = useCallback( const listHeader = useCallback(
({ section: { title } }) => ( ({ section: { title } }) => (
<Text style={[styles.group, { color: theme.secondary }]}>{title}</Text> <Text style={[styles.group, { color: colors.secondary }]}>{title}</Text>
), ),
[] []
) )
@ -53,13 +56,14 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
<Pressable <Pressable
key={emoji.shortcode} key={emoji.shortcode}
onPress={() => { onPress={() => {
haptics('Light')
updateText({ updateText({
composeState, composeState,
composeDispatch, composeDispatch,
newText: `:${emoji.shortcode}:`, newText: `:${emoji.shortcode}:`,
type: 'emoji' type: 'emoji'
}) })
haptics('Light') dispatch(countInstanceEmoji(emoji))
}} }}
> >
<FastImage <FastImage

View File

@ -21,7 +21,7 @@ const ComposePoll: React.FC = () => {
composeDispatch composeDispatch
} = useContext(ComposeContext) } = useContext(ComposeContext)
const { t } = useTranslation('screenCompose') const { t } = useTranslation('screenCompose')
const { mode, theme } = useTheme() const { colors, mode } = useTheme()
const instanceConfigurationPoll = useSelector( const instanceConfigurationPoll = useSelector(
getInstanceConfigurationPoll, getInstanceConfigurationPoll,
@ -39,7 +39,7 @@ const ComposePoll: React.FC = () => {
}, []) }, [])
return ( return (
<View style={[styles.base, { borderColor: theme.border }]}> <View style={[styles.base, { borderColor: colors.border }]}>
<View style={styles.options}> <View style={styles.options}>
{[...Array(total)].map((e, i) => { {[...Array(total)].map((e, i) => {
const restOptions = Object.keys(options).filter( const restOptions = Object.keys(options).filter(
@ -57,7 +57,7 @@ const ComposePoll: React.FC = () => {
<Icon <Icon
name={multiple ? 'Square' : 'Circle'} name={multiple ? 'Square' : 'Circle'}
size={StyleConstants.Font.Size.L} size={StyleConstants.Font.Size.L}
color={theme.secondary} color={colors.secondary}
/> />
<TextInput <TextInput
accessibilityLabel={t( accessibilityLabel={t(
@ -69,8 +69,8 @@ const ComposePoll: React.FC = () => {
style={[ style={[
styles.textInput, styles.textInput,
{ {
borderColor: theme.border, borderColor: colors.border,
color: hasConflict ? theme.red : theme.primaryDefault color: hasConflict ? colors.red : colors.primaryDefault
} }
]} ]}
placeholder={ placeholder={
@ -78,7 +78,7 @@ const ComposePoll: React.FC = () => {
? t('content.root.footer.poll.option.placeholder.multiple') ? t('content.root.footer.poll.option.placeholder.multiple')
: t('content.root.footer.poll.option.placeholder.single') : t('content.root.footer.poll.option.placeholder.single')
} }
placeholderTextColor={theme.disabled} placeholderTextColor={colors.disabled}
maxLength={MAX_CHARS_PER_OPTION} maxLength={MAX_CHARS_PER_OPTION}
// @ts-ignore // @ts-ignore
value={options[i]} value={options[i]}
@ -168,7 +168,8 @@ const ComposePoll: React.FC = () => {
t('content.root.footer.poll.multiple.options.multiple'), t('content.root.footer.poll.multiple.options.multiple'),
t('content.root.footer.poll.multiple.options.cancel') t('content.root.footer.poll.multiple.options.cancel')
], ],
cancelButtonIndex: 2 cancelButtonIndex: 2,
userInterfaceStyle: mode
}, },
index => { index => {
if (index && index < 2) { if (index && index < 2) {
@ -211,7 +212,8 @@ const ComposePoll: React.FC = () => {
), ),
t('content.root.footer.poll.expiration.options.cancel') t('content.root.footer.poll.expiration.options.cancel')
], ],
cancelButtonIndex: expirations.length cancelButtonIndex: expirations.length,
userInterfaceStyle: mode
}, },
index => { index => {
if (index && index < expirations.length) { if (index && index < expirations.length) {

View File

@ -8,10 +8,10 @@ const ComposeReply: React.FC = () => {
const { const {
composeState: { replyToStatus } composeState: { replyToStatus }
} = useContext(ComposeContext) } = useContext(ComposeContext)
const { theme } = useTheme() const { colors } = useTheme()
return ( return (
<View style={[styles.base, { borderTopColor: theme.border }]}> <View style={[styles.base, { borderTopColor: colors.border }]}>
<TimelineDefault item={replyToStatus!} disableDetails disableOnPress /> <TimelineDefault item={replyToStatus!} disableDetails disableOnPress />
</View> </View>
) )

View File

@ -12,7 +12,7 @@ import { useSelector } from 'react-redux'
const ComposePostingAs = React.memo( const ComposePostingAs = React.memo(
() => { () => {
const { t } = useTranslation('screenCompose') const { t } = useTranslation('screenCompose')
const { theme } = useTheme() const { colors } = useTheme()
const instanceAccount = useSelector( const instanceAccount = useSelector(
getInstanceAccount, getInstanceAccount,
@ -21,7 +21,7 @@ const ComposePostingAs = React.memo(
const instanceUri = useSelector(getInstanceUri) const instanceUri = useSelector(getInstanceUri)
return ( return (
<Text style={[styles.text, { color: theme.secondary }]}> <Text style={[styles.text, { color: colors.secondary }]}>
{t('content.root.header.postingAs', { {t('content.root.header.postingAs', {
acct: instanceAccount?.acct, acct: instanceAccount?.acct,
domain: instanceUri domain: instanceUri

View File

@ -9,7 +9,7 @@ import ComposeContext from '../../utils/createContext'
const ComposeSpoilerInput: React.FC = () => { const ComposeSpoilerInput: React.FC = () => {
const { composeState, composeDispatch } = useContext(ComposeContext) const { composeState, composeDispatch } = useContext(ComposeContext)
const { t } = useTranslation('screenCompose') const { t } = useTranslation('screenCompose')
const { mode, theme } = useTheme() const { colors, mode } = useTheme()
return ( return (
<TextInput <TextInput
@ -17,8 +17,8 @@ const ComposeSpoilerInput: React.FC = () => {
style={[ style={[
styles.spoilerInput, styles.spoilerInput,
{ {
color: theme.primaryDefault, color: colors.primaryDefault,
borderBottomColor: theme.border borderBottomColor: colors.border
} }
]} ]}
autoCapitalize='none' autoCapitalize='none'
@ -27,7 +27,7 @@ const ComposeSpoilerInput: React.FC = () => {
enablesReturnKeyAutomatically enablesReturnKeyAutomatically
multiline multiline
placeholder={t('content.root.header.spoilerInput.placeholder')} placeholder={t('content.root.header.spoilerInput.placeholder')}
placeholderTextColor={theme.secondary} placeholderTextColor={colors.secondary}
onChangeText={content => onChangeText={content =>
formatText({ formatText({
textInput: 'spoiler', textInput: 'spoiler',

View File

@ -9,7 +9,7 @@ import ComposeContext from '../../utils/createContext'
const ComposeTextInput: React.FC = () => { const ComposeTextInput: React.FC = () => {
const { composeState, composeDispatch } = useContext(ComposeContext) const { composeState, composeDispatch } = useContext(ComposeContext)
const { t } = useTranslation('screenCompose') const { t } = useTranslation('screenCompose')
const { mode, theme } = useTheme() const { colors, mode } = useTheme()
return ( return (
<TextInput <TextInput
@ -17,15 +17,15 @@ const ComposeTextInput: React.FC = () => {
style={[ style={[
styles.textInput, styles.textInput,
{ {
color: theme.primaryDefault, color: colors.primaryDefault,
borderBottomColor: theme.border borderBottomColor: colors.border
} }
]} ]}
autoFocus autoFocus
enablesReturnKeyAutomatically enablesReturnKeyAutomatically
multiline multiline
placeholder={t('content.root.header.textInput.placeholder')} placeholder={t('content.root.header.textInput.placeholder')}
placeholderTextColor={theme.secondary} placeholderTextColor={colors.secondary}
onChangeText={content => onChangeText={content =>
formatText({ formatText({
textInput: 'text', textInput: 'text',

View File

@ -16,9 +16,9 @@ export interface Params {
} }
const TagText = ({ text }: { text: string }) => { const TagText = ({ text }: { text: string }) => {
const { theme } = useTheme() const { colors } = useTheme()
return <Text style={{ color: theme.blue }}>{text}</Text> return <Text style={{ color: colors.blue }}>{text}</Text>
} }
const debouncedSuggestions = debounce( const debouncedSuggestions = debounce(

View File

@ -40,7 +40,12 @@ export type ComposeState = {
} }
emoji: { emoji: {
active: boolean active: boolean
emojis: { title: string; data: Mastodon.Emoji[][] }[] | undefined emojis:
| {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[]
| undefined
} }
poll: { poll: {
active: boolean active: boolean

View File

@ -2,6 +2,7 @@ import haptics from '@components/haptics'
import { displayMessage } from '@components/Message' import { displayMessage } from '@components/Message'
import CameraRoll from '@react-native-community/cameraroll' import CameraRoll from '@react-native-community/cameraroll'
import { RootStackParamList } from '@utils/navigation/navigators' import { RootStackParamList } from '@utils/navigation/navigators'
import { Theme } from '@utils/styles/themes'
import * as FileSystem from 'expo-file-system' import * as FileSystem from 'expo-file-system'
import i18next from 'i18next' import i18next from 'i18next'
import { RefObject } from 'react' import { RefObject } from 'react'
@ -10,17 +11,17 @@ import FlashMessage from 'react-native-flash-message'
type CommonProps = { type CommonProps = {
messageRef: RefObject<FlashMessage> messageRef: RefObject<FlashMessage>
mode: 'light' | 'dark' theme: Theme
image: RootStackParamList['Screen-ImagesViewer']['imageUrls'][0] image: RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
} }
const saveIos = async ({ messageRef, mode, image }: CommonProps) => { const saveIos = async ({ messageRef, theme, image }: CommonProps) => {
CameraRoll.save(image.url) CameraRoll.save(image.url)
.then(() => { .then(() => {
haptics('Success') haptics('Success')
displayMessage({ displayMessage({
ref: messageRef, ref: messageRef,
mode, theme,
type: 'success', type: 'success',
message: i18next.t('screenImageViewer:content.save.succeed') message: i18next.t('screenImageViewer:content.save.succeed')
}) })
@ -32,7 +33,7 @@ const saveIos = async ({ messageRef, mode, image }: CommonProps) => {
haptics('Success') haptics('Success')
displayMessage({ displayMessage({
ref: messageRef, ref: messageRef,
mode, theme,
type: 'success', type: 'success',
message: i18next.t('screenImageViewer:content.save.succeed') message: i18next.t('screenImageViewer:content.save.succeed')
}) })
@ -41,7 +42,7 @@ const saveIos = async ({ messageRef, mode, image }: CommonProps) => {
haptics('Error') haptics('Error')
displayMessage({ displayMessage({
ref: messageRef, ref: messageRef,
mode, theme,
type: 'error', type: 'error',
message: i18next.t('screenImageViewer:content.save.failed') message: i18next.t('screenImageViewer:content.save.failed')
}) })
@ -50,7 +51,7 @@ const saveIos = async ({ messageRef, mode, image }: CommonProps) => {
haptics('Error') haptics('Error')
displayMessage({ displayMessage({
ref: messageRef, ref: messageRef,
mode, theme,
type: 'error', type: 'error',
message: i18next.t('screenImageViewer:content.save.failed') message: i18next.t('screenImageViewer:content.save.failed')
}) })
@ -58,7 +59,7 @@ const saveIos = async ({ messageRef, mode, image }: CommonProps) => {
}) })
} }
const saveAndroid = async ({ messageRef, mode, image }: CommonProps) => { const saveAndroid = async ({ messageRef, theme, image }: CommonProps) => {
const fileUri: string = `${FileSystem.documentDirectory}${image.id}.jpg` const fileUri: string = `${FileSystem.documentDirectory}${image.id}.jpg`
const downloadedFile: FileSystem.FileSystemDownloadResult = const downloadedFile: FileSystem.FileSystemDownloadResult =
await FileSystem.downloadAsync(image.url, fileUri) await FileSystem.downloadAsync(image.url, fileUri)
@ -67,7 +68,7 @@ const saveAndroid = async ({ messageRef, mode, image }: CommonProps) => {
haptics('Error') haptics('Error')
displayMessage({ displayMessage({
ref: messageRef, ref: messageRef,
mode, theme,
type: 'error', type: 'error',
message: i18next.t('screenImageViewer:content.save.failed') message: i18next.t('screenImageViewer:content.save.failed')
}) })
@ -83,7 +84,7 @@ const saveAndroid = async ({ messageRef, mode, image }: CommonProps) => {
haptics('Error') haptics('Error')
displayMessage({ displayMessage({
ref: messageRef, ref: messageRef,
mode, theme,
type: 'error', type: 'error',
message: i18next.t('screenImageViewer:content.save.failed') message: i18next.t('screenImageViewer:content.save.failed')
}) })
@ -96,7 +97,7 @@ const saveAndroid = async ({ messageRef, mode, image }: CommonProps) => {
haptics('Success') haptics('Success')
displayMessage({ displayMessage({
ref: messageRef, ref: messageRef,
mode, theme,
type: 'success', type: 'success',
message: i18next.t('screenImageViewer:content.save.succeed') message: i18next.t('screenImageViewer:content.save.succeed')
}) })
@ -105,7 +106,7 @@ const saveAndroid = async ({ messageRef, mode, image }: CommonProps) => {
haptics('Error') haptics('Error')
displayMessage({ displayMessage({
ref: messageRef, ref: messageRef,
mode, theme,
type: 'error', type: 'error',
message: i18next.t('screenImageViewer:content.save.failed') message: i18next.t('screenImageViewer:content.save.failed')
}) })

View File

@ -35,7 +35,7 @@ const HeaderComponent = React.memo(
imageUrls: RootStackParamList['Screen-ImagesViewer']['imageUrls'] imageUrls: RootStackParamList['Screen-ImagesViewer']['imageUrls']
}) => { }) => {
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const { mode } = useTheme() const { mode, theme } = useTheme()
const { t } = useTranslation('screenImageViewer') const { t } = useTranslation('screenImageViewer')
const { showActionSheetWithOptions } = useActionSheet() const { showActionSheetWithOptions } = useActionSheet()
@ -48,13 +48,14 @@ const HeaderComponent = React.memo(
t('content.options.share'), t('content.options.share'),
t('content.options.cancel') t('content.options.cancel')
], ],
cancelButtonIndex: 2 cancelButtonIndex: 2,
userInterfaceStyle: mode
}, },
async buttonIndex => { async buttonIndex => {
switch (buttonIndex) { switch (buttonIndex) {
case 0: case 0:
analytics('imageviewer_more_save_press') analytics('imageviewer_more_save_press')
saveImage({ messageRef, mode, image: imageUrls[currentIndex] }) saveImage({ messageRef, theme, image: imageUrls[currentIndex] })
break break
case 1: case 1:
analytics('imageviewer_more_share_press') analytics('imageviewer_more_share_press')
@ -117,7 +118,7 @@ const ScreenImagesViewer = ({
return null return null
} }
const { mode } = useTheme() const { theme } = useTheme()
const initialIndex = imageUrls.findIndex(image => image.id === id) const initialIndex = imageUrls.findIndex(image => image.id === id)
const [currentIndex, setCurrentIndex] = useState(initialIndex) const [currentIndex, setCurrentIndex] = useState(initialIndex)
@ -132,7 +133,7 @@ const ScreenImagesViewer = ({
imageIndex={initialIndex} imageIndex={initialIndex}
onImageIndexChange={index => setCurrentIndex(index)} onImageIndexChange={index => setCurrentIndex(index)}
onRequestClose={() => navigation.goBack()} onRequestClose={() => navigation.goBack()}
onLongPress={image => saveImage({ messageRef, mode, image })} onLongPress={image => saveImage({ messageRef, theme, image })}
HeaderComponent={() => ( HeaderComponent={() => (
<HeaderComponent <HeaderComponent
messageRef={messageRef} messageRef={messageRef}

View File

@ -31,7 +31,7 @@ const Tab = createBottomTabNavigator<ScreenTabsStackParamList>()
const ScreenTabs = React.memo( const ScreenTabs = React.memo(
({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => { ({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
const { theme } = useTheme() const { colors } = useTheme()
const instanceActive = useSelector(getInstanceActive) const instanceActive = useSelector(getInstanceActive)
const instanceAccount = useSelector( const instanceAccount = useSelector(
@ -42,8 +42,8 @@ const ScreenTabs = React.memo(
const screenOptions = useCallback( const screenOptions = useCallback(
({ route }): BottomTabNavigationOptions => ({ ({ route }): BottomTabNavigationOptions => ({
headerShown: false, headerShown: false,
tabBarActiveTintColor: theme.primaryDefault, tabBarActiveTintColor: colors.primaryDefault,
tabBarInactiveTintColor: theme.secondary, tabBarInactiveTintColor: colors.secondary,
tabBarShowLabel: false, tabBarShowLabel: false,
...(Platform.OS === 'android' && { tabBarHideOnKeyboard: true }), ...(Platform.OS === 'android' && { tabBarHideOnKeyboard: true }),
tabBarStyle: { display: instanceActive !== -1 ? 'flex' : 'none' }, tabBarStyle: { display: instanceActive !== -1 ? 'flex' : 'none' },
@ -78,7 +78,7 @@ const ScreenTabs = React.memo(
borderRadius: size, borderRadius: size,
overflow: 'hidden', overflow: 'hidden',
borderWidth: focused ? 2 : 0, borderWidth: focused ? 2 : 0,
borderColor: focused ? theme.secondary : color borderColor: focused ? colors.secondary : color
}} }}
/> />
) )

View File

@ -23,16 +23,18 @@ const prepareFields = (
}) })
} }
const TabMeProfileFields: React.FC<TabMeProfileStackScreenProps< const TabMeProfileFields: React.FC<
'Tab-Me-Profile-Fields' TabMeProfileStackScreenProps<'Tab-Me-Profile-Fields'> & {
> & { messageRef: RefObject<FlashMessage> }> = ({ messageRef: RefObject<FlashMessage>
}
> = ({
messageRef, messageRef,
route: { route: {
params: { fields } params: { fields }
}, },
navigation navigation
}) => { }) => {
const { mode, theme } = useTheme() const { colors, theme } = useTheme()
const { t, i18n } = useTranslation('screenTabs') const { t, i18n } = useTranslation('screenTabs')
const { mutateAsync, status } = useProfileMutation() const { mutateAsync, status } = useProfileMutation()
@ -77,7 +79,7 @@ const TabMeProfileFields: React.FC<TabMeProfileStackScreenProps<
content='Save' content='Save'
onPress={async () => { onPress={async () => {
mutateAsync({ mutateAsync({
mode, theme,
messageRef, messageRef,
message: { message: {
text: 'me.profile.root.note.title', text: 'me.profile.root.note.title',
@ -95,14 +97,14 @@ const TabMeProfileFields: React.FC<TabMeProfileStackScreenProps<
/> />
) )
}) })
}, [mode, i18n.language, dirty, status, newFields]) }, [theme, i18n.language, dirty, status, newFields])
return ( return (
<ScrollView style={styles.base} keyboardShouldPersistTaps='always'> <ScrollView style={styles.base} keyboardShouldPersistTaps='always'>
<View style={{ marginBottom: StyleConstants.Spacing.L * 2 }}> <View style={{ marginBottom: StyleConstants.Spacing.L * 2 }}>
{Array.from(Array(4).keys()).map(index => ( {Array.from(Array(4).keys()).map(index => (
<View key={index} style={styles.group}> <View key={index} style={styles.group}>
<Text style={[styles.headline, { color: theme.primaryDefault }]}> <Text style={[styles.headline, { color: colors.primaryDefault }]}>
{t('me.profile.fields.group', { index: index + 1 })} {t('me.profile.fields.group', { index: index + 1 })}
</Text> </Text>
<Input <Input

View File

@ -21,7 +21,7 @@ const TabMeProfileName: React.FC<
}, },
navigation navigation
}) => { }) => {
const { mode } = useTheme() const { theme } = useTheme()
const { t, i18n } = useTranslation('screenTabs') const { t, i18n } = useTranslation('screenTabs')
const { mutateAsync, status } = useProfileMutation() const { mutateAsync, status } = useProfileMutation()
@ -66,7 +66,7 @@ const TabMeProfileName: React.FC<
content='Save' content='Save'
onPress={async () => { onPress={async () => {
mutateAsync({ mutateAsync({
mode, theme,
messageRef, messageRef,
message: { message: {
text: 'me.profile.root.name.title', text: 'me.profile.root.name.title',
@ -82,7 +82,7 @@ const TabMeProfileName: React.FC<
/> />
) )
}) })
}, [mode, i18n.language, dirty, status, displayName]) }, [theme, i18n.language, dirty, status, displayName])
return ( return (
<ScrollView style={styles.base} keyboardShouldPersistTaps='always'> <ScrollView style={styles.base} keyboardShouldPersistTaps='always'>

View File

@ -10,16 +10,18 @@ import { Alert, StyleSheet, View } from 'react-native'
import FlashMessage from 'react-native-flash-message' import FlashMessage from 'react-native-flash-message'
import { ScrollView } from 'react-native-gesture-handler' import { ScrollView } from 'react-native-gesture-handler'
const TabMeProfileNote: React.FC<TabMeProfileStackScreenProps< const TabMeProfileNote: React.FC<
'Tab-Me-Profile-Note' TabMeProfileStackScreenProps<'Tab-Me-Profile-Note'> & {
> & { messageRef: RefObject<FlashMessage> }> = ({ messageRef: RefObject<FlashMessage>
}
> = ({
messageRef, messageRef,
route: { route: {
params: { note } params: { note }
}, },
navigation navigation
}) => { }) => {
const { mode } = useTheme() const { theme } = useTheme()
const { t, i18n } = useTranslation('screenTabs') const { t, i18n } = useTranslation('screenTabs')
const { mutateAsync, status } = useProfileMutation() const { mutateAsync, status } = useProfileMutation()
@ -64,7 +66,7 @@ const TabMeProfileNote: React.FC<TabMeProfileStackScreenProps<
content='Save' content='Save'
onPress={async () => { onPress={async () => {
mutateAsync({ mutateAsync({
mode, theme,
messageRef, messageRef,
message: { message: {
text: 'me.profile.root.note.title', text: 'me.profile.root.note.title',
@ -80,7 +82,7 @@ const TabMeProfileNote: React.FC<TabMeProfileStackScreenProps<
/> />
) )
}) })
}, [mode, i18n.language, dirty, status, newNote]) }, [theme, i18n.language, dirty, status, newNote])
return ( return (
<ScrollView style={styles.base} keyboardShouldPersistTaps='always'> <ScrollView style={styles.base} keyboardShouldPersistTaps='always'>

View File

@ -12,10 +12,12 @@ import { ScrollView } from 'react-native-gesture-handler'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import ProfileAvatarHeader from './Root/AvatarHeader' import ProfileAvatarHeader from './Root/AvatarHeader'
const TabMeProfileRoot: React.FC<TabMeProfileStackScreenProps< const TabMeProfileRoot: React.FC<
'Tab-Me-Profile-Root' TabMeProfileStackScreenProps<'Tab-Me-Profile-Root'> & {
> & { messageRef: RefObject<FlashMessage> }> = ({ messageRef, navigation }) => { messageRef: RefObject<FlashMessage>
const { mode } = useTheme() }
> = ({ messageRef, navigation }) => {
const { mode, theme } = useTheme()
const { t } = useTranslation('screenTabs') const { t } = useTranslation('screenTabs')
const { showActionSheetWithOptions } = useActionSheet() const { showActionSheetWithOptions } = useActionSheet()
@ -34,7 +36,8 @@ const TabMeProfileRoot: React.FC<TabMeProfileStackScreenProps<
t('me.profile.root.visibility.options.private'), t('me.profile.root.visibility.options.private'),
t('me.profile.root.visibility.options.cancel') t('me.profile.root.visibility.options.cancel')
], ],
cancelButtonIndex: 3 cancelButtonIndex: 3,
userInterfaceStyle: mode
}, },
async buttonIndex => { async buttonIndex => {
switch (buttonIndex) { switch (buttonIndex) {
@ -54,7 +57,7 @@ const TabMeProfileRoot: React.FC<TabMeProfileStackScreenProps<
new: indexVisibilityMapping[buttonIndex] new: indexVisibilityMapping[buttonIndex]
}) })
mutateAsync({ mutateAsync({
mode, theme,
messageRef, messageRef,
message: { message: {
text: 'me.profile.root.visibility.title', text: 'me.profile.root.visibility.title',
@ -69,7 +72,7 @@ const TabMeProfileRoot: React.FC<TabMeProfileStackScreenProps<
} }
} }
) )
}, [data?.source.privacy]) }, [theme, data?.source.privacy])
const onPressSensitive = useCallback(() => { const onPressSensitive = useCallback(() => {
analytics('me_profile_sensitive', { analytics('me_profile_sensitive', {
@ -77,7 +80,7 @@ const TabMeProfileRoot: React.FC<TabMeProfileStackScreenProps<
new: data?.source.sensitive === undefined ? true : !data.source.sensitive new: data?.source.sensitive === undefined ? true : !data.source.sensitive
}) })
mutateAsync({ mutateAsync({
mode, theme,
messageRef, messageRef,
message: { message: {
text: 'me.profile.root.sensitive.title', text: 'me.profile.root.sensitive.title',
@ -95,7 +98,7 @@ const TabMeProfileRoot: React.FC<TabMeProfileStackScreenProps<
new: data?.locked === undefined ? true : !data.locked new: data?.locked === undefined ? true : !data.locked
}) })
mutateAsync({ mutateAsync({
mode, theme,
messageRef, messageRef,
message: { message: {
text: 'me.profile.root.lock.title', text: 'me.profile.root.lock.title',
@ -105,7 +108,7 @@ const TabMeProfileRoot: React.FC<TabMeProfileStackScreenProps<
type: 'locked', type: 'locked',
data: data?.locked === undefined ? true : !data.locked data: data?.locked === undefined ? true : !data.locked
}) })
}, [data?.locked]) }, [theme, data?.locked])
const onPressBot = useCallback(() => { const onPressBot = useCallback(() => {
analytics('me_profile_bot', { analytics('me_profile_bot', {
@ -113,7 +116,7 @@ const TabMeProfileRoot: React.FC<TabMeProfileStackScreenProps<
new: data?.bot === undefined ? true : !data.bot new: data?.bot === undefined ? true : !data.bot
}) })
mutateAsync({ mutateAsync({
mode, theme,
messageRef, messageRef,
message: { message: {
text: 'me.profile.root.bot.title', text: 'me.profile.root.bot.title',
@ -123,7 +126,7 @@ const TabMeProfileRoot: React.FC<TabMeProfileStackScreenProps<
type: 'bot', type: 'bot',
data: data?.bot === undefined ? true : !data.bot data: data?.bot === undefined ? true : !data.bot
}) })
}, [data?.bot]) }, [theme, data?.bot])
return ( return (
<ScrollView> <ScrollView>

View File

@ -14,7 +14,7 @@ export interface Props {
} }
const ProfileAvatarHeader: React.FC<Props> = ({ type, messageRef }) => { const ProfileAvatarHeader: React.FC<Props> = ({ type, messageRef }) => {
const { mode } = useTheme() const { theme } = useTheme()
const { t } = useTranslation('screenTabs') const { t } = useTranslation('screenTabs')
const { showActionSheetWithOptions } = useActionSheet() const { showActionSheetWithOptions } = useActionSheet()
@ -35,7 +35,7 @@ const ProfileAvatarHeader: React.FC<Props> = ({ type, messageRef }) => {
resize: { width: 400, height: 400 } resize: { width: 400, height: 400 }
}) })
mutation.mutate({ mutation.mutate({
mode, theme,
messageRef, messageRef,
message: { message: {
text: `me.profile.root.${type}.title`, text: `me.profile.root.${type}.title`,

Some files were not shown because too many files have changed in this diff Show More