Темная тема с React Navigation + Typescript + React Native Paper

В этой статье вы узнаете, как реализовать светлые и темные темы и использовать их во всем приложении с помощью React Native, React Navigation, React Native Paper и Typescript.

Вы также можете посмотреть этот видеоурок и репозиторий на Github.

Создание проекта React Native с помощью Typescript

Чтобы запустить проект React Native с помощью Typescript, просто выполните следующую команду.

npx react-native init NavigationTypescriptPaper --template react-native-template-typescript
Войдите в полноэкранный режим Выход из полноэкранного режима

Ссылка на документацию

Установка React Navigation

Для установки React Navigation нам необходимо установить следующие пакеты

yarn add @react-navigation/native react-native-screens react-native-safe-area-context
Войдите в полноэкранный режим Выход из полноэкранного режима

И в зависимости от типа навигации, который вы используете, вы устанавливаете только пакет для этого типа. Чтобы использовать стек, нам необходимо установить

yarn add @react-navigation/stack
Войдите в полноэкранный режим Выход из полноэкранного режима

Если вы используете Mac, выполните команду

npx pod-install ios
Войдите в полноэкранный режим Выход из полноэкранного режима

А для Android необходимо отредактировать файл MainActivity.java, который находится по адресу android/app/src/main/java/<имя проекта>/MainActivity.java.

import android.os.Bundle;
// ...

public class MainActivity extends ReactActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(null);
  }

  // ...
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Ссылка на документацию

Установка React Native Paper

Для установки React Native Paper нам необходимо установить следующие пакеты

yarn add react-native-paper react-native-vector-icons
Войдите в полноэкранный режим Выход из полноэкранного режима

Ссылка на документацию

Если вы используете Mac, отредактируйте файл PodFile и добавьте следующий код

pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
Войдите в полноэкранный режим Выход из полноэкранного режима

А для Android добавьте следующую строку в файл android/app/build.gradle.

apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
Войдите в полноэкранный режим Выход из полноэкранного режима

Ссылка на документацию

Создание ThemeContext и ThemeContextProvider

Сначала мы импортируем темы как из React Navigation, так и из React Native Paper

import {
  DarkTheme as NavigationDarkTheme,
  DefaultTheme as NavigationDefaultTheme,
  NavigationContainer,
} from '@react-navigation/native';
import {
  DarkTheme as PaperDarkTheme,
  DefaultTheme as PaperDefaultTheme,
  Provider as PaperProvider,
} from 'react-native-paper';
Войдите в полноэкранный режим Выход из полноэкранного режима

Поэтому мы объединим темы, создав тему light с объединением двух тем по умолчанию и тему dark с объединением двух темных тем.

const lightTheme = {
  ...NavigationDefaultTheme,
  ...PaperDefaultTheme,
  colors: {
    ...NavigationDefaultTheme.colors,
    ...PaperDefaultTheme.colors,
  },
};

const darkTheme = {
  ...NavigationDarkTheme,
  ...PaperDarkTheme,
  colors: {
    ...NavigationDarkTheme.colors,
    ...PaperDarkTheme.colors,
  },
};
Войдите в полноэкранный режим Выход из полноэкранного режима

Таким образом, у нас будет две темы, определенные со значениями по умолчанию для двух пакетов, и мы можем добавить свои собственные цвета, если захотим.

Далее мы можем определить 2 типа, которые мы будем использовать в нашей теме. Первое — это создание типа, который будет определять нашу тему, используя typeof из lightTheme, так что если мы добавим какие-либо дополнительные настройки в нашу тему, это будет отражено в типе.

export type Theme = typeof lightTheme;
Войдите в полноэкранный режим Выход из полноэкранного режима

Также мы определим типы тем, которые у нас будут, в данном случае это будут light и dark.

export type ThemeType = 'dark' | 'light';
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь мы можем определить, какие данные мы будем иметь в нашем контексте. Мы передадим текущую тему, а также ее тип, булево значение, указывающее, является ли тема темной, чтобы было легче сравнивать при ее использовании, функцию для переключения значения темы и функцию для прямого обновления темы, если это необходимо.

export interface ThemeContextValue {
  theme: Theme;
  themeType: ThemeType;
  isDarkTheme: boolean;
  toggleThemeType: () => void;
  setThemeType: React.Dispatch<React.SetStateAction<ThemeType>>;
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Поэтому мы будем использовать React.createContext для создания контекста и передачи значений по умолчанию в свойства лица.

export const ThemeContext = React.createContext<ThemeContextValue>({
  theme: lightTheme,
  themeType: 'light',
  isDarkTheme: false,
  setThemeType: () => {},
  toggleThemeType: () => {},
});
Войдите в полноэкранный режим Выход из полноэкранного режима

Поскольку мы будем использовать хуки, мы уже можем создать свой хук, который назовем useTheme, просто для того, чтобы было проще использовать этот контекст.

export const useTheme = () => useContext(ThemeContext);
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь мы перейдем к реализации контекста, где создадим компонент ThemeContextProvider и интерфейс для его реквизитов.

export interface ThemeContextProviderProps {
  children: React.ReactNode;
}

export const ThemeContextProvider = ({children}: ThemeContextProviderProps) => {
    // ...
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Внутри него мы будем использовать useColorScheme, чтобы узнать, находится ли мобильник в нормальном режиме или темном, и передадим это значение в useState, где мы будем хранить тип темы.

export const ThemeContextProvider = ({children}: ThemeContextProviderProps) => {
    const colorScheme = useColorScheme();
    const [themeType, setThemeType] = useState<ThemeType>(colorScheme || 'light');

    // ...
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Мы создадим простую функцию для переключения типа темы.

export const ThemeContextProvider = ({children}: ThemeContextProviderProps) => {
    // ...

    const toggleThemeType = useCallback(() => {
      setThemeType(prev => (prev === 'dark' ? 'light' : 'dark'));
    }, []);

    // ...
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Также мы определим isDarkTheme и саму тему, которая будет использоваться

export const ThemeContextProvider = ({children}: ThemeContextProviderProps) => {
    // ...

    const isDarkTheme = useMemo(() => themeType === 'dark', [themeType]);
  const theme = useMemo(
    () => (isDarkTheme ? darkTheme : lightTheme),
    [isDarkTheme],
  );

    // ...
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь, когда мы установили все значения контекста, мы можем создать NavigationContainer и PaperProvider, чтобы передать тему, а также наш провайдер со значениями контекста.

Полный компонент будет выглядеть следующим образом

export const ThemeContextProvider = ({children}: ThemeContextProviderProps) => {
  const colorScheme = useColorScheme();
  const [themeType, setThemeType] = useState<ThemeType>(colorScheme || 'light');

  const toggleThemeType = useCallback(() => {
    setThemeType(prev => (prev === 'dark' ? 'light' : 'dark'));
  }, []);

  const isDarkTheme = useMemo(() => themeType === 'dark', [themeType]);
  const theme = useMemo(
    () => (isDarkTheme ? darkTheme : lightTheme),
    [isDarkTheme],
  );

  return (
    <NavigationContainer theme={theme}>
      <PaperProvider theme={theme}>
        <ThemeContext.Provider
          value={{
            theme,
            themeType,
            isDarkTheme,
            setThemeType,
            toggleThemeType,
          }}>
          {children}
        </ThemeContext.Provider>
      </PaperProvider>
    </NavigationContainer>
  );
};
Войдите в полноэкранный режим Выход из полноэкранного режима

Использование контекста и изменение темы

В нашем App.tsx мы создадим ThemeContextProvider и внутри него будем использовать стек для навигации с помощью createStackNavigator. Внутри этого стека у нас будет экран, просто чтобы продемонстрировать, что тема работает.

const TestScreen = () => {
    // ...
};

const Stack = createStackNavigator();

const App = () => {
  return (
    <ThemeContextProvider>
      <Stack.Navigator>
        <Stack.Screen name="Test" component={TestScreen} />
      </Stack.Navigator>
    </ThemeContextProvider>
  );
};
Войдите в полноэкранный режим Выход из полноэкранного режима

Внутри нашего тестового экрана мы можем использовать созданный нами пользовательский хук useTheme для захвата значений контекста и использования их по своему усмотрению.

const TestScreen = () => {
  const {toggleThemeType, themeType, isDarkTheme, theme} = useTheme();

  return (
    <View>
      <Button mode="contained" onPress={toggleThemeType}>
        Toggle Theme
      </Button>
      <Headline>{themeType}</Headline>
      <Headline>isDarkTheme: {`${isDarkTheme}`}</Headline>
      <Headline>Primary: {theme.colors.primary}</Headline>
    </View>
  );
};
Войдите в полноэкранный режим Выход из полноэкранного режима

Нажав на кнопку, мы видим, что тема меняется.

Мы также можем проанализировать, что если перевести телефон в темный режим, приложение уже запускает тему как dark.

Оцените статью
devanswers.ru
Добавить комментарий