diff --git a/packages/core/jest-setup.js b/packages/core/jest-setup.js index 80ba86baa..7d8190595 100644 --- a/packages/core/jest-setup.js +++ b/packages/core/jest-setup.js @@ -21,3 +21,7 @@ jest.mock("expo-asset", () => { } return { ...actual, Asset, getManifestBaseUrl: () => "" }; }); + +jest.mock("@react-native-async-storage/async-storage", () => + require("@react-native-async-storage/async-storage/jest/async-storage-mock") +); diff --git a/packages/maps/jest-setup.js b/packages/maps/jest-setup.js index 4fbbd8e26..0debaa332 100644 --- a/packages/maps/jest-setup.js +++ b/packages/maps/jest-setup.js @@ -18,3 +18,7 @@ jest.mock("expo-asset", () => { } return { ...actual, Asset, getManifestBaseUrl: () => "" }; }); + +jest.mock("@react-native-async-storage/async-storage", () => + require("@react-native-async-storage/async-storage/jest/async-storage-mock") +); diff --git a/packages/theme/package.json b/packages/theme/package.json index 68f05fd74..080240aa5 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -42,7 +42,8 @@ "color": "^4.2.3", "deepmerge": "^4.3.1", "lodash.isobject": "^3.0.2", - "react-native-typography": "^1.4.1" + "react-native-typography": "^1.4.1", + "@react-native-async-storage/async-storage": "1.21.0" }, "eslintIgnore": [ "node_modules/", diff --git a/packages/theme/src/Provider.tsx b/packages/theme/src/Provider.tsx index 7a8d26a21..3cdbd7820 100644 --- a/packages/theme/src/Provider.tsx +++ b/packages/theme/src/Provider.tsx @@ -1,8 +1,16 @@ import React from "react"; import { Dimensions, Platform } from "react-native"; +import AsyncStorage from "@react-native-async-storage/async-storage"; import createThemeValuesProxy from "./createThemeValuesProxy"; import DefaultTheme from "./DefaultTheme"; -import { Breakpoints, ReadTheme, ValidatedTheme } from "./types"; +import { + Breakpoints, + ChangeThemeOptions, + ReadTheme, + ValidatedTheme, +} from "./types"; + +const SAVED_SELECTED_THEME_KEY = "saved_selected_theme"; type ThemeContextType = { theme: ReadTheme; @@ -34,18 +42,9 @@ const Provider: React.FC> = ({ Dimensions.get("window").width ); - React.useEffect(() => { - const listener = Dimensions.addEventListener("change", ({ window }) => - setDeviceWidth(window.width) - ); - return () => { - listener.remove(); - }; - }); - const changeTheme = React.useCallback( - (themeName: string) => { - const theme = themes.find((theme) => theme.name === themeName); + (themeName: string, options?: ChangeThemeOptions) => { + const theme = themes.find((t) => t.name === themeName); if (!theme) { console.warn( "Theme with name", @@ -55,6 +54,12 @@ const Provider: React.FC> = ({ return; } setCurrentTheme(theme); + + if (options?.persistent === true) { + AsyncStorage.setItem(SAVED_SELECTED_THEME_KEY, themeName).catch((e) => { + console.warn("Failed to persist selected theme", e); + }); + } }, [themes, setCurrentTheme] ); @@ -98,6 +103,30 @@ const Provider: React.FC> = ({ [currentTheme, deviceWidth, breakpoints] ); + React.useEffect(() => { + const listener = Dimensions.addEventListener("change", ({ window }) => + setDeviceWidth(window.width) + ); + return () => { + listener.remove(); + }; + }); + + React.useEffect(() => { + const run = async () => { + const savedSelectedThemeName = await AsyncStorage.getItem( + SAVED_SELECTED_THEME_KEY + ); + if (savedSelectedThemeName) { + changeTheme(savedSelectedThemeName); + } + }; + run(); + + // This should only run once, ignore deps + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return ( {children} @@ -110,7 +139,10 @@ const useTheme = (): ReadTheme => { return theme; }; -const useChangeTheme = (): ((themeName: string) => void) => { +const useChangeTheme = (): (( + themeName: string, + options?: ChangeThemeOptions +) => void) => { const { changeTheme } = React.useContext(ThemeContext); return changeTheme; }; diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts index bb3121c35..121fc81a1 100644 --- a/packages/theme/src/types.ts +++ b/packages/theme/src/types.ts @@ -39,3 +39,5 @@ export type ColorPalettes = { export type Breakpoints = { [key: string]: number; }; + +export type ChangeThemeOptions = { persistent?: boolean }; diff --git a/yarn.lock b/yarn.lock index aed5b4754..d0ad8d47d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3371,6 +3371,13 @@ resolved "https://registry.yarnpkg.com/@react-google-maps/marker-clusterer/-/marker-clusterer-2.16.1.tgz#882878a7b49ce9ae44c7b02ff6fbc4c2aeb77c95" integrity sha512-jOuyqzWLeXvQcoAu6TCVWHAuko+sDt0JjawNHBGqUNLywMtTCvYP0L0PiqJZOUCUeRYGdUy0AKxQ+30vAkvwag== +"@react-native-async-storage/async-storage@1.21.0": + version "1.21.0" + resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.21.0.tgz#d7e370028e228ab84637016ceeb495878b7a44c8" + integrity sha512-JL0w36KuFHFCvnbOXRekqVAUplmOyT/OuCQkogo6X98MtpSaJOKEAeZnYO8JB0U/RIEixZaGI5px73YbRm/oag== + dependencies: + merge-options "^3.0.4" + "@react-native-community/cli-clean@12.3.6": version "12.3.6" resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-12.3.6.tgz#e8a7910bebc97266fd5068649013a03958021fc4" @@ -9117,7 +9124,7 @@ is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== -is-plain-obj@^2.0.0: +is-plain-obj@^2.0.0, is-plain-obj@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== @@ -10731,6 +10738,13 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merge-options@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7" + integrity sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ== + dependencies: + is-plain-obj "^2.1.0" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"