mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Merge branch 'main' into candidate
This commit is contained in:
		@@ -166,7 +166,7 @@ const styles = StyleSheet.create({
 | 
			
		||||
  core: {
 | 
			
		||||
    flex: 1,
 | 
			
		||||
    flexDirection: 'row',
 | 
			
		||||
    paddingVertical: StyleConstants.Spacing.S
 | 
			
		||||
    paddingTop: StyleConstants.Spacing.S
 | 
			
		||||
  },
 | 
			
		||||
  front: {
 | 
			
		||||
    flex: 2,
 | 
			
		||||
 
 | 
			
		||||
@@ -130,7 +130,9 @@ const Timeline: React.FC<Props> = ({
 | 
			
		||||
      }
 | 
			
		||||
      ListEmptyComponent={<TimelineEmpty queryKey={queryKey} />}
 | 
			
		||||
      ItemSeparatorComponent={ItemSeparatorComponent}
 | 
			
		||||
      maintainVisibleContentPosition={{ minIndexForVisible: 0 }}
 | 
			
		||||
      {...(isFetchingPreviousPage && {
 | 
			
		||||
        maintainVisibleContentPosition: { minIndexForVisible: 0 }
 | 
			
		||||
      })}
 | 
			
		||||
      refreshing={isFetchingPreviousPage}
 | 
			
		||||
      onRefresh={() => {
 | 
			
		||||
        if (!disableRefresh && !isFetchingPreviousPage) {
 | 
			
		||||
 
 | 
			
		||||
@@ -251,6 +251,10 @@
 | 
			
		||||
          "cancel": "$t(common:buttons.cancel)"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "staticEmoji": {
 | 
			
		||||
        "heading": "Use static emoji",
 | 
			
		||||
        "description": "If you encounter frequent app crash when viewing emoji list, you can try to use static emoji instead."
 | 
			
		||||
      },
 | 
			
		||||
      "feedback": {
 | 
			
		||||
        "heading": "Feature Requests"
 | 
			
		||||
      },
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import CustomText from '@components/Text'
 | 
			
		||||
import { useAppDispatch } from '@root/store'
 | 
			
		||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
 | 
			
		||||
import { countInstanceEmoji } from '@utils/slices/instancesSlice'
 | 
			
		||||
import { getSettingsStaticEmoji } from '@utils/slices/settingsSlice'
 | 
			
		||||
import { StyleConstants } from '@utils/styles/constants'
 | 
			
		||||
import { useTheme } from '@utils/styles/ThemeManager'
 | 
			
		||||
import React, { RefObject, useCallback, useContext, useEffect } from 'react'
 | 
			
		||||
@@ -10,11 +11,13 @@ import { useTranslation } from 'react-i18next'
 | 
			
		||||
import {
 | 
			
		||||
  AccessibilityInfo,
 | 
			
		||||
  findNodeHandle,
 | 
			
		||||
  Image,
 | 
			
		||||
  Pressable,
 | 
			
		||||
  SectionList,
 | 
			
		||||
  View
 | 
			
		||||
} from 'react-native'
 | 
			
		||||
import FastImage from 'react-native-fast-image'
 | 
			
		||||
import { useSelector } from 'react-redux'
 | 
			
		||||
import validUrl from 'valid-url'
 | 
			
		||||
import updateText from '../../updateText'
 | 
			
		||||
import ComposeContext from '../../utils/createContext'
 | 
			
		||||
@@ -30,6 +33,8 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
 | 
			
		||||
  const { t } = useTranslation()
 | 
			
		||||
  const dispatch = useAppDispatch()
 | 
			
		||||
 | 
			
		||||
  const staticEmoji = useSelector(getSettingsStaticEmoji)
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const tagEmojis = findNodeHandle(accessibleRefEmojis.current)
 | 
			
		||||
    if (composeState.emoji.active) {
 | 
			
		||||
@@ -82,24 +87,45 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
 | 
			
		||||
                    dispatch(countInstanceEmoji(emoji))
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  <FastImage
 | 
			
		||||
                    accessibilityLabel={t(
 | 
			
		||||
                      'common:customEmoji.accessibilityLabel',
 | 
			
		||||
                      {
 | 
			
		||||
                        emoji: emoji.shortcode
 | 
			
		||||
                      }
 | 
			
		||||
                    )}
 | 
			
		||||
                    accessibilityHint={t(
 | 
			
		||||
                      'screenCompose:content.root.footer.emojis.accessibilityHint'
 | 
			
		||||
                    )}
 | 
			
		||||
                    source={{ uri }}
 | 
			
		||||
                    style={{
 | 
			
		||||
                      width: 32,
 | 
			
		||||
                      height: 32,
 | 
			
		||||
                      padding: StyleConstants.Spacing.S,
 | 
			
		||||
                      margin: StyleConstants.Spacing.S
 | 
			
		||||
                    }}
 | 
			
		||||
                  />
 | 
			
		||||
                  {staticEmoji ? (
 | 
			
		||||
                    <Image
 | 
			
		||||
                      accessibilityLabel={t(
 | 
			
		||||
                        'common:customEmoji.accessibilityLabel',
 | 
			
		||||
                        {
 | 
			
		||||
                          emoji: emoji.shortcode
 | 
			
		||||
                        }
 | 
			
		||||
                      )}
 | 
			
		||||
                      accessibilityHint={t(
 | 
			
		||||
                        'screenCompose:content.root.footer.emojis.accessibilityHint'
 | 
			
		||||
                      )}
 | 
			
		||||
                      source={{ uri }}
 | 
			
		||||
                      style={{
 | 
			
		||||
                        width: 32,
 | 
			
		||||
                        height: 32,
 | 
			
		||||
                        padding: StyleConstants.Spacing.S,
 | 
			
		||||
                        margin: StyleConstants.Spacing.S
 | 
			
		||||
                      }}
 | 
			
		||||
                    />
 | 
			
		||||
                  ) : (
 | 
			
		||||
                    <FastImage
 | 
			
		||||
                      accessibilityLabel={t(
 | 
			
		||||
                        'common:customEmoji.accessibilityLabel',
 | 
			
		||||
                        {
 | 
			
		||||
                          emoji: emoji.shortcode
 | 
			
		||||
                        }
 | 
			
		||||
                      )}
 | 
			
		||||
                      accessibilityHint={t(
 | 
			
		||||
                        'screenCompose:content.root.footer.emojis.accessibilityHint'
 | 
			
		||||
                      )}
 | 
			
		||||
                      source={{ uri }}
 | 
			
		||||
                      style={{
 | 
			
		||||
                        width: 32,
 | 
			
		||||
                        height: 32,
 | 
			
		||||
                        padding: StyleConstants.Spacing.S,
 | 
			
		||||
                        margin: StyleConstants.Spacing.S
 | 
			
		||||
                      }}
 | 
			
		||||
                    />
 | 
			
		||||
                  )}
 | 
			
		||||
                </Pressable>
 | 
			
		||||
              )
 | 
			
		||||
            } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,35 +22,39 @@ const SettingsAnalytics: React.FC = () => {
 | 
			
		||||
  const instanceVersion = useSelector(getInstanceVersion, () => true)
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <MenuContainer>
 | 
			
		||||
      <MenuRow
 | 
			
		||||
        title={t('me.settings.analytics.heading')}
 | 
			
		||||
        description={t('me.settings.analytics.description')}
 | 
			
		||||
        switchValue={settingsAnalytics}
 | 
			
		||||
        switchOnValueChange={() =>
 | 
			
		||||
          dispatch(changeAnalytics(!settingsAnalytics))
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
      <CustomText
 | 
			
		||||
        fontStyle='S'
 | 
			
		||||
        style={{
 | 
			
		||||
          textAlign: 'center',
 | 
			
		||||
          marginTop: StyleConstants.Spacing.S,
 | 
			
		||||
          color: colors.secondary
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        {t('me.settings.version', { version: Constants.manifest?.version })}
 | 
			
		||||
      </CustomText>
 | 
			
		||||
      <CustomText
 | 
			
		||||
        fontStyle='S'
 | 
			
		||||
        style={{
 | 
			
		||||
          textAlign: 'center',
 | 
			
		||||
          color: colors.secondary
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        {t('me.settings.instanceVersion', { version: instanceVersion })}
 | 
			
		||||
      </CustomText>
 | 
			
		||||
    </MenuContainer>
 | 
			
		||||
    <>
 | 
			
		||||
      <MenuContainer>
 | 
			
		||||
        <MenuRow
 | 
			
		||||
          title={t('me.settings.analytics.heading')}
 | 
			
		||||
          description={t('me.settings.analytics.description')}
 | 
			
		||||
          switchValue={settingsAnalytics}
 | 
			
		||||
          switchOnValueChange={() =>
 | 
			
		||||
            dispatch(changeAnalytics(!settingsAnalytics))
 | 
			
		||||
          }
 | 
			
		||||
        />
 | 
			
		||||
      </MenuContainer>
 | 
			
		||||
      <MenuContainer>
 | 
			
		||||
        <CustomText
 | 
			
		||||
          fontStyle='S'
 | 
			
		||||
          style={{
 | 
			
		||||
            textAlign: 'center',
 | 
			
		||||
            marginTop: StyleConstants.Spacing.S,
 | 
			
		||||
            color: colors.secondary
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          {t('me.settings.version', { version: Constants.manifest?.version })}
 | 
			
		||||
        </CustomText>
 | 
			
		||||
        <CustomText
 | 
			
		||||
          fontStyle='S'
 | 
			
		||||
          style={{
 | 
			
		||||
            textAlign: 'center',
 | 
			
		||||
            color: colors.secondary
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          {t('me.settings.instanceVersion', { version: instanceVersion })}
 | 
			
		||||
        </CustomText>
 | 
			
		||||
      </MenuContainer>
 | 
			
		||||
    </>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,9 @@ import {
 | 
			
		||||
  getSettingsBrowser,
 | 
			
		||||
  getSettingsFontsize,
 | 
			
		||||
  getSettingsDarkTheme,
 | 
			
		||||
  changeDarkTheme
 | 
			
		||||
  changeDarkTheme,
 | 
			
		||||
  getSettingsStaticEmoji,
 | 
			
		||||
  changeStaticEmoji
 | 
			
		||||
} from '@utils/slices/settingsSlice'
 | 
			
		||||
import { useTheme } from '@utils/styles/ThemeManager'
 | 
			
		||||
import * as Notifications from 'expo-notifications'
 | 
			
		||||
@@ -37,6 +39,7 @@ const SettingsApp: React.FC = () => {
 | 
			
		||||
  const settingsTheme = useSelector(getSettingsTheme)
 | 
			
		||||
  const settingsDarkTheme = useSelector(getSettingsDarkTheme)
 | 
			
		||||
  const settingsBrowser = useSelector(getSettingsBrowser)
 | 
			
		||||
  const settingsStaticEmoji = useSelector(getSettingsStaticEmoji)
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <MenuContainer>
 | 
			
		||||
@@ -266,6 +269,18 @@ const SettingsApp: React.FC = () => {
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
      <MenuRow
 | 
			
		||||
        title={t('me.settings.staticEmoji.heading')}
 | 
			
		||||
        description={t('me.settings.staticEmoji.description')}
 | 
			
		||||
        switchValue={settingsStaticEmoji}
 | 
			
		||||
        switchOnValueChange={() => {
 | 
			
		||||
          analytics('settings_staticemoji_press', {
 | 
			
		||||
            current: settingsStaticEmoji.toString(),
 | 
			
		||||
            new: !settingsStaticEmoji.toString()
 | 
			
		||||
          })
 | 
			
		||||
          dispatch(changeStaticEmoji(!settingsStaticEmoji))
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
    </MenuContainer>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { SettingsV0 } from './v0'
 | 
			
		||||
import { SettingsV1 } from './v1'
 | 
			
		||||
import { SettingsV2 } from './v2'
 | 
			
		||||
 | 
			
		||||
const settingsMigration = {
 | 
			
		||||
  1: (state: SettingsV0): SettingsV1 => {
 | 
			
		||||
@@ -7,6 +8,13 @@ const settingsMigration = {
 | 
			
		||||
      ...state,
 | 
			
		||||
      darkTheme: 'lighter'
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  2: (state: SettingsV1): SettingsV2 => {
 | 
			
		||||
    return {
 | 
			
		||||
      ...state,
 | 
			
		||||
      darkTheme: 'lighter',
 | 
			
		||||
      staticEmoji: false
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								src/utils/migrations/settings/v2.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/utils/migrations/settings/v2.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
export type SettingsV2 = {
 | 
			
		||||
  fontsize: -1 | 0 | 1 | 2 | 3
 | 
			
		||||
  language: string
 | 
			
		||||
  theme: 'light' | 'dark' | 'auto'
 | 
			
		||||
  darkTheme: 'lighter' | 'darker'
 | 
			
		||||
  browser: 'internal' | 'external'
 | 
			
		||||
  staticEmoji: boolean
 | 
			
		||||
  analytics: boolean
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
 | 
			
		||||
import { LOCALES } from '@root/i18n/locales'
 | 
			
		||||
import { RootState } from '@root/store'
 | 
			
		||||
import { SettingsV2 } from '@utils/migrations/settings/v2'
 | 
			
		||||
import * as Analytics from 'expo-firebase-analytics'
 | 
			
		||||
import * as Localization from 'expo-localization'
 | 
			
		||||
import { pickBy } from 'lodash'
 | 
			
		||||
@@ -13,14 +14,7 @@ export const changeAnalytics = createAsyncThunk(
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
export type SettingsState = {
 | 
			
		||||
  fontsize: -1 | 0 | 1 | 2 | 3
 | 
			
		||||
  language: string
 | 
			
		||||
  theme: 'light' | 'dark' | 'auto'
 | 
			
		||||
  darkTheme: 'lighter' | 'darker'
 | 
			
		||||
  browser: 'internal' | 'external'
 | 
			
		||||
  analytics: boolean
 | 
			
		||||
}
 | 
			
		||||
export type SettingsState = SettingsV2
 | 
			
		||||
 | 
			
		||||
export const settingsInitialState = {
 | 
			
		||||
  fontsize: 0,
 | 
			
		||||
@@ -37,6 +31,7 @@ export const settingsInitialState = {
 | 
			
		||||
  theme: 'auto',
 | 
			
		||||
  darkTheme: 'lighter',
 | 
			
		||||
  browser: 'internal',
 | 
			
		||||
  staticEmoji: false,
 | 
			
		||||
  analytics: true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -73,6 +68,12 @@ const settingsSlice = createSlice({
 | 
			
		||||
      action: PayloadAction<NonNullable<SettingsState['browser']>>
 | 
			
		||||
    ) => {
 | 
			
		||||
      state.browser = action.payload
 | 
			
		||||
    },
 | 
			
		||||
    changeStaticEmoji: (
 | 
			
		||||
      state,
 | 
			
		||||
      action: PayloadAction<NonNullable<SettingsState['staticEmoji']>>
 | 
			
		||||
    ) => {
 | 
			
		||||
      state.staticEmoji = action.payload
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  extraReducers: builder => {
 | 
			
		||||
@@ -89,6 +90,8 @@ export const getSettingsTheme = (state: RootState) => state.settings.theme
 | 
			
		||||
export const getSettingsDarkTheme = (state: RootState) =>
 | 
			
		||||
  state.settings.darkTheme
 | 
			
		||||
export const getSettingsBrowser = (state: RootState) => state.settings.browser
 | 
			
		||||
export const getSettingsStaticEmoji = (state: RootState) =>
 | 
			
		||||
  state.settings.staticEmoji
 | 
			
		||||
export const getSettingsAnalytics = (state: RootState) =>
 | 
			
		||||
  state.settings.analytics
 | 
			
		||||
 | 
			
		||||
@@ -97,6 +100,7 @@ export const {
 | 
			
		||||
  changeLanguage,
 | 
			
		||||
  changeTheme,
 | 
			
		||||
  changeDarkTheme,
 | 
			
		||||
  changeBrowser
 | 
			
		||||
  changeBrowser,
 | 
			
		||||
  changeStaticEmoji
 | 
			
		||||
} = settingsSlice.actions
 | 
			
		||||
export default settingsSlice.reducer
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user