diff --git a/apps/expo/package.json b/apps/expo/package.json index 91bae2e..7b03c24 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -23,6 +23,7 @@ "@movie-web/provider-utils": "*", "@movie-web/tmdb": "*", "@react-native-anywhere/polyfill-base64": "0.0.1-alpha.0", + "@react-native-async-storage/async-storage": "1.21.0", "@react-navigation/native": "^6.1.9", "@tamagui/animations-moti": "^1.91.4", "@tamagui/babel-plugin": "^1.91.4", diff --git a/apps/expo/src/settings/index.ts b/apps/expo/src/settings/index.ts new file mode 100644 index 0000000..5e62240 --- /dev/null +++ b/apps/expo/src/settings/index.ts @@ -0,0 +1,33 @@ +import AsyncStorage from "@react-native-async-storage/async-storage"; + +import type { ThemeStoreOption } from "~/stores/theme"; + +interface ThemeSettings { + theme: ThemeStoreOption; +} + +interface Settings { + themes: ThemeSettings; +} + +const settingsKey = "settings"; + +const saveSettings = async (settings: Settings) => { + await AsyncStorage.setItem(settingsKey, JSON.stringify(settings)); +}; + +const loadSettings = async (): Promise => { + const json = await AsyncStorage.getItem(settingsKey); + return json ? (JSON.parse(json) as Settings) : null; +}; + +export const getTheme = async (): Promise => { + const settings = await loadSettings(); + return settings?.themes?.theme ?? "main"; +}; + +export const saveTheme = async (newTheme: ThemeStoreOption) => { + const settings = (await loadSettings()) ?? { themes: { theme: newTheme } }; + settings.themes.theme = newTheme; + await saveSettings(settings); +}; diff --git a/apps/expo/src/stores/theme/index.ts b/apps/expo/src/stores/theme/index.ts index 948409e..bd33f00 100644 --- a/apps/expo/src/stores/theme/index.ts +++ b/apps/expo/src/stores/theme/index.ts @@ -2,6 +2,8 @@ import { setAppIcon } from "expo-dynamic-app-icon"; import { create } from "zustand"; import { immer } from "zustand/middleware/immer"; +import { getTheme, saveTheme } from "~/settings"; + export type ThemeStoreOption = "main" | "blue" | "gray" | "red" | "teal"; export interface ThemeStore { @@ -10,13 +12,28 @@ export interface ThemeStore { } export const useThemeStore = create( - immer((set) => ({ - theme: "main", - setTheme(v) { + immer((set) => { + void getTheme().then((savedTheme) => { set((s) => { - s.theme = v; - setAppIcon(v); + s.theme = savedTheme; }); - }, - })), + }); + + return { + theme: "main", + setTheme: (newTheme) => { + setAppIcon(newTheme); + + saveTheme(newTheme) + .then(() => { + set((s) => { + s.theme = newTheme; + }); + }) + .catch((error) => { + console.error("Failed to save theme:", error); + }); + }, + }; + }), ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 01d1da6..0d8ce70 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: '@react-native-anywhere/polyfill-base64': specifier: 0.0.1-alpha.0 version: 0.0.1-alpha.0 + '@react-native-async-storage/async-storage': + specifier: 1.21.0 + version: 1.21.0(react-native@0.73.5) '@react-navigation/native': specifier: ^6.1.9 version: 6.1.9(react-native@0.73.5)(react@18.2.0) @@ -2874,6 +2877,15 @@ packages: base-64: 0.1.0 dev: false + /@react-native-async-storage/async-storage@1.21.0(react-native@0.73.5): + resolution: {integrity: sha512-JL0w36KuFHFCvnbOXRekqVAUplmOyT/OuCQkogo6X98MtpSaJOKEAeZnYO8JB0U/RIEixZaGI5px73YbRm/oag==} + peerDependencies: + react-native: ^0.0.0-0 || >=0.60 <1.0 + dependencies: + merge-options: 3.0.4 + react-native: 0.73.5(@babel/core@7.23.9)(@babel/preset-env@7.23.9)(react@18.2.0) + dev: false + /@react-native-community/cli-clean@12.3.6: resolution: {integrity: sha512-gUU29ep8xM0BbnZjwz9MyID74KKwutq9x5iv4BCr2im6nly4UMf1B1D+V225wR7VcDGzbgWjaezsJShLLhC5ig==} dependencies: @@ -8846,6 +8858,11 @@ packages: engines: {node: '>=0.10.0'} dev: false + /is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + dev: false + /is-plain-object@2.0.4: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} @@ -9669,6 +9686,13 @@ packages: resolution: {integrity: sha512-OcjA+jzjOYzKmKS6IQVALHLVz+rNTMPoJvCztFaZxwG14wtAW7VRZjwTQu06vKCYOxh4jVnik7ya0SXTB0W+xA==} dev: false + /merge-options@3.0.4: + resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} + engines: {node: '>=10'} + dependencies: + is-plain-obj: 2.1.0 + dev: false + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -13503,7 +13527,6 @@ packages: id: github.com/simonsturge/expo-dynamic-app-icon/acfd73827573e99a5e1bfbe47cf2fb1656e9ccf0 name: expo-dynamic-app-icon version: 1.2.0 - prepare: true requiresBuild: true peerDependencies: expo: '>=49'