From c2ca202e09a84033188ee250d9a29428ab15616b Mon Sep 17 00:00:00 2001 From: BigBug Date: Tue, 12 Jan 2021 13:37:49 +0800 Subject: [PATCH] Issue 31 (#32) * feat * fix: errors --- App.ts | 4 + package.json | 111 ++++++++--- src/app-loading.tsx | 63 ------ src/app.tsx | 104 ++++++---- src/common/announcement.tsx | 36 ++-- src/common/bar-chart-styled.tsx | 3 +- src/common/common-line.tsx | 3 +- src/common/line-chart-styled.tsx | 3 +- src/common/list-header.tsx | 3 +- src/common/navigation-bar.tsx | 6 +- .../navigation/app-navigator-container.tsx | 31 --- src/common/navigation/app-navigator.ts | 41 ---- src/common/navigation/app-navigator.tsx | 41 ++++ src/common/navigation/main-tab-navigator.tsx | 184 ++++++++---------- src/common/progress-bar.tsx | 4 +- src/common/providers.tsx | 22 ++- src/common/store.ts | 4 +- src/common/tab-bar-icon.tsx | 5 +- src/common/text-styled.tsx | 10 +- src/common/theme.ts | 46 +++-- src/common/themed-bottom-tab-bar.tsx | 17 +- src/common/with-theme.tsx | 29 --- src/screens/account-picker-screen.tsx | 28 ++- .../add-transaction-next-screen.tsx | 44 +++-- .../add-transaction-screen.tsx | 26 +-- .../components/list-item-styled.tsx | 9 +- .../narration-input-screen.tsx | 31 +-- .../payee-input-screen.tsx | 30 +-- .../quick-add-accounts-selector.tsx | 16 +- .../components/accounts-styled.tsx | 15 +- .../components/net-assets-styled.tsx | 11 +- src/screens/home-screen/email-icon.tsx | 3 +- src/screens/home-screen/home-screen.tsx | 15 +- src/screens/ledger-screen.tsx | 10 +- src/screens/mine-screen/about.tsx | 51 +++-- src/screens/mine-screen/account-header.tsx | 23 ++- src/screens/mine-screen/mine-screen.tsx | 17 +- src/screens/mine-screen/pre-auth-view.tsx | 8 +- .../components/contact-row.tsx | 15 +- .../referral-screen/components/gift-icon.tsx | 3 +- .../components/invite-section.tsx | 22 ++- .../components/referral-gift-icon.tsx | 3 +- src/screens/referral-screen/invite-screen.tsx | 41 ++-- .../referral-screen/referral-screen.tsx | 35 ++-- src/translations/index.ts | 5 +- src/types/navigation-param.ts | 28 +++ src/types/theme-props.ts | 26 +-- 47 files changed, 684 insertions(+), 601 deletions(-) delete mode 100644 src/app-loading.tsx delete mode 100644 src/common/navigation/app-navigator-container.tsx delete mode 100644 src/common/navigation/app-navigator.ts create mode 100644 src/common/navigation/app-navigator.tsx delete mode 100644 src/common/with-theme.tsx create mode 100644 src/types/navigation-param.ts diff --git a/App.ts b/App.ts index 4c25d1d..db0525e 100644 --- a/App.ts +++ b/App.ts @@ -3,4 +3,8 @@ // GLOBAL.XMLHttpRequest = GLOBAL.originalXMLHttpRequest || GLOBAL.XMLHttpRequest; global.Buffer = global.Buffer || require("buffer").Buffer; import { App } from "./src/app"; +import { LogBox } from "react-native"; +LogBox.ignoreLogs([ + "Non-serializable values were found in the navigation state", +]); export default App; diff --git a/package.json b/package.json index 41e835b..40be81c 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,55 @@ } } }, + "eslintConfig": { + "extends": [ + "airbnb-base", + "plugin:react/recommended", + "plugin:@typescript-eslint/recommended", + "prettier" + ], + "plugins": [ + "@typescript-eslint", + "react" + ], + "env": { + "browser": true + }, + "rules": { + "curly": [ + "error", + "all" + ], + "max-classes-per-file": 0, + "no-param-reassign": 0, + "no-restricted-syntax": [ + "error", + "ForInStatement", + "LabelStatement", + "WithStatement" + ], + "no-underscore-dangle": 0, + "no-unused-expressions": 0, + "import/extensions": 0, + "import/no-unresolved": 0, + "import/prefer-default-export": 0, + "class-methods-use-this": 0, + "global-require": 0, + "@typescript-eslint/no-var-requires": 0 + }, + "overrides": [ + { + "files": "*.js", + "rules": { + "@typescript-eslint/explicit-function-return-type": 0 + } + }, + { + "files": "*.ts", + "parser": "@typescript-eslint/parser" + } + ] + }, "husky": { "hooks": { "pre-commit": "pretty-quick --staged" @@ -52,13 +101,18 @@ }, "dependencies": { "@ant-design/react-native": "^4.0.4", - "@expo/vector-icons": "^10.0.0", + "@callstack/react-theme-provider": "^2.1.0", + "@expo/vector-icons": "^12.0.0", "@react-native-community/async-storage": "~1.12.0", "@react-native-community/cameraroll": "^4.0.0", + "@react-native-community/masked-view": "0.1.10", "@react-native-community/picker": "1.6.6", - "@react-native-community/segmented-control": "2.1.1", + "@react-native-community/segmented-control": "2.2.1", "@react-native-community/slider": "3.0.3", - "@react-native-community/viewpager": "4.1.6", + "@react-native-community/viewpager": "4.2.0", + "@react-navigation/bottom-tabs": "^5.11.1", + "@react-navigation/native": "^5.8.9", + "@react-navigation/stack": "^5.12.6", "@sentry/react": "^5.21.1", "@sentry/react-native": "^1.7.1", "@types/deep-extend": "^0.4.31", @@ -77,22 +131,23 @@ "buffer": "^5.4.3", "currency-icons": "^1.0.13", "deep-extend": "^0.6.0", - "expo": "^39.0.3", + "expo": "^40.0.0", "expo-analytics": "^1.0.13", - "expo-asset": "~8.2.0", - "expo-constants": "~9.2.0", - "expo-contacts": "~8.5.0", - "expo-font": "~8.3.0", - "expo-haptics": "~8.3.0", - "expo-localization": "~9.0.0", - "expo-mail-composer": "~8.4.0", - "expo-notifications": "~0.7.2", - "expo-permissions": "~9.3.0", - "expo-sms": "~8.3.1", - "expo-splash-screen": "~0.6.1", - "expo-status-bar": "~1.0.2", - "expo-store-review": "~2.2.0", - "expo-web-browser": "~8.5.0", + "expo-asset": "~8.2.1", + "expo-constants": "~9.3.3", + "expo-contacts": "~8.6.0", + "expo-font": "~8.4.0", + "expo-haptics": "~8.4.0", + "expo-localization": "~9.1.0", + "expo-mail-composer": "~9.0.0", + "expo-mixpanel-analytics": "0.0.7", + "expo-notifications": "~0.8.2", + "expo-permissions": "~10.0.0", + "expo-sms": "~8.4.0", + "expo-splash-screen": "~0.8.1", + "expo-status-bar": "~1.0.3", + "expo-store-review": "~2.3.0", + "expo-web-browser": "~8.6.0", "fbemitter": "^2.1.1", "graphql": "14.5.8", "graphql-tag": "2.10.1", @@ -103,17 +158,17 @@ "react": "16.13.1", "react-apollo": "3.1.3", "react-dom": "16.13.1", - "react-native": "https://github.com/expo/react-native/archive/sdk-39.0.3.tar.gz", + "react-native": "https://github.com/expo/react-native/archive/sdk-40.0.1.tar.gz", "react-native-animatable": "^1.3.3", "react-native-appearance": "~0.3.3", - "react-native-gesture-handler": "~1.7.0", - "react-native-safe-area-context": "3.1.4", + "react-native-gesture-handler": "~1.8.0", + "react-native-reanimated": "~1.13.0", + "react-native-safe-area-context": "3.1.9", "react-native-safe-area-view": "^1.1.1", - "react-native-screens": "~2.10.1", + "react-native-screens": "~2.15.0", "react-native-svg": "12.1.0", - "react-native-web": "~0.13.7", - "react-native-webview": "10.7.0", - "react-navigation": "^3.12.0", + "react-native-web": "~0.13.12", + "react-native-webview": "11.0.0", "react-redux": "6.0.0", "redux": "4.0.1", "redux-devtools-extension": "2.13.8", @@ -134,7 +189,7 @@ "apollo": "^2.30.2", "babel-plugin-import": "1.11.0", "babel-plugin-module-resolver": "^3.0.0", - "babel-preset-expo": "^8.3.0", + "babel-preset-expo": "8.3.0", "eslint": "7.2.0", "eslint-config-airbnb-base": "14.2.0", "eslint-config-prettier": "6.11.0", @@ -142,7 +197,7 @@ "eslint-plugin-react": "7.20.0", "husky": "3.0.0", "jest": "^26.2.2", - "jest-expo": "^39.0.0", + "jest-expo": "^40.0.0", "prettier": "^2.0.5", "pretty-quick": "1.11.1", "react-test-renderer": "16.8.3", @@ -151,7 +206,7 @@ "tslint-config-prettier": "1.18.0", "tslint-microsoft-contrib": "6.0.0", "tslint-require-connnect-typing": "1.0.1", - "typescript": "~3.9.2" + "typescript": "~4.0.0" }, "private": true } diff --git a/src/app-loading.tsx b/src/app-loading.tsx deleted file mode 100644 index b3edcd3..0000000 --- a/src/app-loading.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { AppLoading } from "expo"; -import * as Font from "expo-font"; -import * as React from "react"; -import { connect } from "react-redux"; -import { Ionicons } from "@expo/vector-icons"; -import { AppState } from "@/common/store"; -import { setTheme, theme } from "@/common/theme"; -import { i18n } from "@/translations"; - -export function AppLoaderRoot({ onFinish }: { onFinish(): void }): JSX.Element { - return ; -} - -interface Props { - onFinish(): void; - - mixpanelId?: string; - userId?: string; - locale?: string; - currentTheme?: "dark" | "light"; -} - -const AppLoadingContainer = connect((state: AppState) => ({ - userId: state.base.userId, - mixpanelId: state.base.mixpanelId, - locale: state.base.locale, - currentTheme: state.base.currentTheme, -}))(function AppLoadingInner(props: Props): JSX.Element { - const { locale, onFinish, currentTheme } = props; - - if (locale && i18n) { - i18n.locale = locale; - } - - if (currentTheme !== theme.name) { - setTheme(currentTheme); - } - - const loadResourcesAsync = async () => { - try { - await Font.loadAsync({ - ...Ionicons.font, - "space-mono": require("./assets/fonts/SpaceMono-Regular.ttf"), - antoutline: require("../node_modules/@ant-design/icons-react-native/fonts/antoutline.ttf"), - }); - } catch (error) { - console.error(`failed to loadResourcesAsync: ${error}`); - } - }; - - const handleLoadingError = (error: Error) => { - console.warn(error); - }; - - return ( - - ); -}); diff --git a/src/app.tsx b/src/app.tsx index c1f5719..bbeb343 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -3,19 +3,19 @@ import React, { useEffect } from "react"; import { StyleSheet, View } from "react-native"; import { StatusBar } from "expo-status-bar"; import { connect } from "react-redux"; -import { AppNavigatorContainer } from "@/common/navigation/app-navigator-container"; +import { AppNavigator } from "@/common/navigation/app-navigator"; import { Providers } from "@/common/providers"; import "@/common/sentry"; -import { theme, setTheme } from "@/common/theme"; -import { withTheme } from "@/common/with-theme"; +import { actionUpdateReduxState } from "@/common/root-reducer"; +import { useTheme } from "@/common/theme"; +import { Scope, TranslateOptions } from "i18n-js"; import { PreAuthView } from "@/screens/mine-screen/pre-auth-view"; import { useCachedResources } from "@/common/hooks/use-cached-resource"; -import { i18n } from "@/translations"; +import { i18n, LocalizationContext } from "@/translations"; const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: theme.white, }, }); @@ -36,51 +36,75 @@ export function App() { }; }, []); + const [locale, setLocale] = React.useState(i18n.locale); + const localizationContext = React.useMemo( + () => ({ + t: (scope: Scope, options: TranslateOptions) => + i18n.t(scope, { locale, ...options }), + locale, + setLocale, + }), + [locale] + ); + if (!isLoadingComplete) { return null; } return ( - - - + + + + + ); } -const AppContent = withTheme( - connect( - (state: { - base: { authToken: string; currentTheme: string; locale: string }; - }) => ({ - authToken: state.base.authToken, - currentTheme: state.base.currentTheme, - locale: state.base.locale, - }) - )( - (props: { - authToken: string; - currentTheme: "dark" | "light"; - locale: string; - }) => { - const { locale, currentTheme } = props; +const AppContent = connect( + (state: { + base: { authToken: string; currentTheme: string; locale: string }; + }) => ({ + authToken: state.base.authToken, + currentTheme: state.base.currentTheme, + locale: state.base.locale, + }), + (dispatch) => ({ + updateReduxState(payload: { base: { currentTheme: string } }): void { + dispatch(actionUpdateReduxState(payload)); + }, + }) +)( + (props: { + authToken: string; + currentTheme: "dark" | "light"; + locale: string; + updateReduxState: (state: { base: { currentTheme: string } }) => void; + }) => { + const { locale, currentTheme, updateReduxState } = props; + const { setLocale } = React.useContext(LocalizationContext); + const theme = useTheme(); + if (locale && i18n) { + i18n.locale = locale; + } - if (locale && i18n) { - i18n.locale = locale; - } + if (currentTheme !== theme.name) { + updateReduxState({ + base: { currentTheme }, + }); + } - if (currentTheme !== theme.name) { - setTheme(currentTheme); - } + React.useEffect(() => { + setLocale(locale); + }, []); - if (!props.authToken) { - return ; - } - return ( - - - - - ); + if (!props.authToken) { + return ; } - ) + return ( + + + + + ); + } ); diff --git a/src/common/announcement.tsx b/src/common/announcement.tsx index b22b8df..81f5a10 100644 --- a/src/common/announcement.tsx +++ b/src/common/announcement.tsx @@ -6,18 +6,18 @@ import { TouchableOpacity, AsyncStorage, } from "react-native"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; +import { ColorTheme } from "@/types/theme-props"; import { contentPadding, ScreenWidth } from "@/common/screen-util"; -import { NavigationScreenProp } from "react-navigation"; type Props = { - navigation: NavigationScreenProp; + navigation: any; title: string; subtitle: string; icon: JSX.Element; }; -const styles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ container: { height: 120, @@ -85,7 +85,11 @@ export function Announcement(props: Props): JSX.Element { init(); }, []); - const { title, subtitle, icon } = props; + const { title, subtitle, icon, navigation } = props; + + const theme = useTheme(); + + const styles = getStyles(theme.colorTheme); if (hide) { return ; @@ -93,22 +97,26 @@ export function Announcement(props: Props): JSX.Element { return ( { - props.navigation.navigate("Mine", { fromAnnouncement: true }); + style={styles.container} + onPress={async () => { + try { + await AsyncStorage.setItem("@SubscriptionFlash:key", "true"); + } catch (error) { + console.error(`failed to set subscription flash value: ${error}`); + } + navigation.navigate("Mine"); }} > - - {subtitle} - - + + {subtitle} + {title} {icon} { setHide(true); @@ -119,7 +127,7 @@ export function Announcement(props: Props): JSX.Element { } }} > - + ); diff --git a/src/common/bar-chart-styled.tsx b/src/common/bar-chart-styled.tsx index b405325..223a539 100644 --- a/src/common/bar-chart-styled.tsx +++ b/src/common/bar-chart-styled.tsx @@ -1,7 +1,7 @@ import React from "react"; import { BarChart } from "@/common/bar-chart"; import { contentPadding, ScreenWidth } from "@/common/screen-util"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; import { i18n } from "@/translations"; export function BarChartStyled({ @@ -13,6 +13,7 @@ export function BarChartStyled({ numbers: Array; currencySymbol: string; }): JSX.Element { + const theme = useTheme().colorTheme; return ( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/src/common/common-line.tsx b/src/common/common-line.tsx index ac2444b..72f3d60 100644 --- a/src/common/common-line.tsx +++ b/src/common/common-line.tsx @@ -1,7 +1,8 @@ import { View } from "react-native"; import React from "react"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; export function CommonLine(): JSX.Element { + const theme = useTheme().colorTheme; return ; } diff --git a/src/common/line-chart-styled.tsx b/src/common/line-chart-styled.tsx index 1e6a697..b6ef2eb 100644 --- a/src/common/line-chart-styled.tsx +++ b/src/common/line-chart-styled.tsx @@ -2,7 +2,7 @@ import React from "react"; import { LineChart } from "@yuyongmao/react-native-chart-kit"; import { contentPadding, ScreenWidth } from "@/common/screen-util"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; import { i18n } from "@/translations"; export function LineChartStyled({ @@ -14,6 +14,7 @@ export function LineChartStyled({ numbers: Array; currencySymbol: string; }): JSX.Element { + const theme = useTheme().colorTheme; return ( ; + navigation?: any; rightText?: string; onRightClick?: () => void; }; @@ -24,6 +23,7 @@ export const NavigationBar = connect((state: AppState) => ({ currentTheme: state.base.currentTheme, }))(function NavigationBarInner(props: Props): JSX.Element { const { title, showBack, navigation, rightText, onRightClick } = props; + const theme = useTheme().colorTheme; return ( { - return { locale: state.base.locale, currentTheme: state.base.currentTheme }; -})(function AppNavigatorContainerInner(props: Props): JSX.Element { - const [locale, setLocale] = useState(props.locale); - const t = (scope: Scope, options: TranslateOptions) => { - return i18n.t(scope, { locale, ...options }); - }; - const { currentTheme } = props; - return ( - - ); -}); diff --git a/src/common/navigation/app-navigator.ts b/src/common/navigation/app-navigator.ts deleted file mode 100644 index d662808..0000000 --- a/src/common/navigation/app-navigator.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { - createAppContainer, - createStackNavigator, - createSwitchNavigator, -} from "react-navigation"; - -import { MainTabNavigator } from "@/common/navigation/main-tab-navigator"; -import { AddTransactionScreen } from "@/screens/add-transaction-screen"; -import { AddTransactionNextScreen } from "@/screens/add-transaction-screen/add-transaction-next-screen"; -import { PayeeInputScreen } from "@/screens/add-transaction-screen/payee-input-screen"; -import { NarrationInputScreen } from "@/screens/add-transaction-screen/narration-input-screen"; -import { AccountPickerScreen } from "@/screens/account-picker-screen"; -import { ReferralScreen } from "@/screens/referral-screen/referral-screen"; -import { InviteScreen } from "@/screens/referral-screen/invite-screen"; - -const RootScreen = createSwitchNavigator({ - Main: MainTabNavigator, -}); - -const AppRoot = createStackNavigator( - { - Root: { - screen: RootScreen, - navigationOptions: { - header: null, - }, - }, - AddTransaction: AddTransactionScreen, - AddTransactionNext: AddTransactionNextScreen, - AccountPicker: AccountPickerScreen, - PayeeInput: PayeeInputScreen, - NarrationInput: NarrationInputScreen, - Referral: ReferralScreen, - Invite: InviteScreen, - }, - { - headerMode: "none", - } -); - -export const AppNavigator = createAppContainer(AppRoot); diff --git a/src/common/navigation/app-navigator.tsx b/src/common/navigation/app-navigator.tsx new file mode 100644 index 0000000..8a437e1 --- /dev/null +++ b/src/common/navigation/app-navigator.tsx @@ -0,0 +1,41 @@ +import * as React from "react"; +import { NavigationContainer } from "@react-navigation/native"; +import { createStackNavigator } from "@react-navigation/stack"; +import { RootStackParamList } from "@/types/navigation-param"; + +import { MainTabNavigator } from "@/common/navigation/main-tab-navigator"; +import { AddTransactionScreen } from "@/screens/add-transaction-screen"; +import { AddTransactionNextScreen } from "@/screens/add-transaction-screen/add-transaction-next-screen"; +import { PayeeInputScreen } from "@/screens/add-transaction-screen/payee-input-screen"; +import { NarrationInputScreen } from "@/screens/add-transaction-screen/narration-input-screen"; +import { AccountPickerScreen } from "@/screens/account-picker-screen"; +import { ReferralScreen } from "@/screens/referral-screen/referral-screen"; +import { InviteScreen } from "@/screens/referral-screen/invite-screen"; + +const Stack = createStackNavigator(); + +function RootNavigator() { + return ( + + + + + + + + + + + ); +} + +export function AppNavigator() { + return ( + + + + ); +} diff --git a/src/common/navigation/main-tab-navigator.tsx b/src/common/navigation/main-tab-navigator.tsx index d6cd135..f28dd88 100644 --- a/src/common/navigation/main-tab-navigator.tsx +++ b/src/common/navigation/main-tab-navigator.tsx @@ -1,121 +1,97 @@ -import * as Haptics from "expo-haptics"; import * as React from "react"; import { Platform } from "react-native"; import { - createBottomTabNavigator, - createStackNavigator, - NavigationScreenProp, -} from "react-navigation"; -import { ThemedBottomTabBar } from "@/common/themed-bottom-tab-bar"; + HomeParamList, + LedgerParamList, + MineParamList, + MainTabParamList, +} from "@/types/navigation-param"; -import { TFuncType } from "@/types/screen-props"; +import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; +import { createStackNavigator } from "@react-navigation/stack"; +import { ThemedBottomTabBar } from "@/common/themed-bottom-tab-bar"; import { HomeScreen } from "@/screens/home-screen"; import { LedgerScreen } from "@/screens/ledger-screen"; import { MineScreen } from "@/screens/mine-screen/mine-screen"; import { TabBarIcon } from "@/common/tab-bar-icon"; +import { LocalizationContext } from "@/translations"; -const HomeStack = createStackNavigator( - { - Home: HomeScreen, - }, - { - headerMode: "none", - } -); +const HomeStack = createStackNavigator(); -HomeStack.navigationOptions = ({ - screenProps: { t }, -}: { - screenProps: { t: TFuncType }; -}) => ({ - tabBarLabel: t("home"), - tabBarIcon: function TabBarIconWrapper({ focused }: { focused: boolean }) { - const name = Platform.OS === "ios" ? "ios-home" : "md-home"; - return ; - }, - tabBarOnPress: async ({ - navigation, - }: { - navigation: NavigationScreenProp; - }) => { - await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - navigation.navigate("Home"); - }, -}); +function HomeNavigator() { + return ( + + + + ); +} -const LEDGER_ROUTE = "ledger"; +const LedgerStack = createStackNavigator(); -const LedgerStack = createStackNavigator( - { - [LEDGER_ROUTE]: LedgerScreen, - }, - { - headerMode: "none", - } -); +function LinkNavigator() { + return ( + + + + ); +} -LedgerStack.navigationOptions = ({ - screenProps: { t }, -}: { - screenProps: { t: TFuncType }; -}) => ({ - tabBarLabel: t(LEDGER_ROUTE), - tabBarIcon: function TabBarIconWrapper({ focused }: { focused: boolean }) { - const name = Platform.OS === "ios" ? "ios-journal" : "md-journal"; - return ; - }, - tabBarOnPress: async ({ - navigation, - }: { - navigation: NavigationScreenProp; - }) => { - await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - navigation.navigate(LEDGER_ROUTE); - }, -}); +const MineStack = createStackNavigator(); -const MineStack = createStackNavigator( - { - Mine: MineScreen, - }, - { - headerMode: "none", - } -); +function MineNavigator() { + return ( + + + + ); +} -MineStack.navigationOptions = ({ - screenProps: { t }, -}: { - screenProps: { t: TFuncType }; -}) => ({ - tabBarLabel: t("mine"), - tabBarIcon: function TabBarIconWrapper({ focused }: { focused: boolean }) { - const name = Platform.OS === "ios" ? "ios-contact" : "md-contact"; - return ; - }, - tabBarOnPress: async ({ - navigation, - }: { - navigation: NavigationScreenProp; - }) => { - await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - navigation.navigate("Mine", { fromAnnouncement: false }); - }, -}); +const MainTab = createBottomTabNavigator(); -export const MainTabNavigator = createBottomTabNavigator( - { - HomeStack, - LedgerStack, - MineStack, - }, - { - navigationOptions: { - header: null, - }, - tabBarComponent: function tabBarComponent(props) { - return ; - }, - } -); +export function MainTabNavigator() { + const { t } = React.useContext(LocalizationContext); + return ( + } + initialRouteName="Home" + > + { + const name = Platform.OS === "ios" ? "ios-home" : "md-home"; + const tabIcon = ; + return tabIcon; + }, + }} + /> + { + const name = Platform.OS === "ios" ? "ios-journal" : "md-journal"; + const tabIcon = ; + return tabIcon; + }, + }} + /> + { + const name = Platform.OS === "ios" ? "ios-apps" : "md-apps"; + const tabIcon = ; + return tabIcon; + }, + }} + /> + + ); +} diff --git a/src/common/progress-bar.tsx b/src/common/progress-bar.tsx index b6b086d..db4550e 100644 --- a/src/common/progress-bar.tsx +++ b/src/common/progress-bar.tsx @@ -5,7 +5,7 @@ import { View, } from "react-native"; import * as React from "react"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; type Props = { progress: number; @@ -13,7 +13,7 @@ type Props = { export function ProgressBar(props: Props): JSX.Element { const { progress } = props; - + const theme = useTheme().colorTheme; if (progress === 1) { return ; } diff --git a/src/common/providers.tsx b/src/common/providers.tsx index e2ea672..38d014c 100644 --- a/src/common/providers.tsx +++ b/src/common/providers.tsx @@ -5,9 +5,9 @@ import { connect, Provider } from "react-redux"; import { PersistGate } from "redux-persist/integration/react"; import { apolloClient } from "@/common/apollo-client"; import { persistor, store } from "@/common/store"; -import { antdDarkTheme, antdLightTheme } from "@/common/theme"; import enUS from "@ant-design/react-native/lib/locale-provider/en_US"; import zhCN from "@ant-design/react-native/lib/locale-provider/zh_CN"; +import { themes, ThemeProvider } from "@/common/theme"; export function Providers({ children, @@ -16,16 +16,16 @@ export function Providers({ }): JSX.Element { return ( - + {children} - + ); } -const AntdProviderContainer = connect( +const AntdThemeProviderContainer = connect( (state: { base: { currentTheme: string; locale: string } }) => { return { currentTheme: state.base.currentTheme, @@ -42,11 +42,13 @@ const AntdProviderContainer = connect( children: JSX.Element | Array; }): JSX.Element { return ( - - {children} - + + + {children} + + ); }); diff --git a/src/common/store.ts b/src/common/store.ts index 92650ac..9c07b7f 100644 --- a/src/common/store.ts +++ b/src/common/store.ts @@ -4,7 +4,7 @@ import { composeWithDevTools } from "redux-devtools-extension"; import { persistStore } from "redux-persist"; import thunk from "redux-thunk"; import { persistedReducer } from "@/common/root-reducer"; -import { theme } from "@/common/theme"; +import { colorMode } from "@/common/theme"; export interface AppState { base: { @@ -21,7 +21,7 @@ type Mode = "dark" | "light"; const preloadedState: DeepPartial = { base: { locale: Locatization.locale, - currentTheme: theme.name as Mode, + currentTheme: colorMode as Mode, }, }; diff --git a/src/common/tab-bar-icon.tsx b/src/common/tab-bar-icon.tsx index 84c09fa..1282477 100644 --- a/src/common/tab-bar-icon.tsx +++ b/src/common/tab-bar-icon.tsx @@ -1,6 +1,6 @@ import { Ionicons } from "@expo/vector-icons"; import * as React from "react"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; interface Props { name: string; @@ -9,9 +9,12 @@ interface Props { export function TabBarIcon(props: Props): JSX.Element { const { focused, name } = props; + const theme = useTheme().colorTheme; const color = focused ? theme.tabIconSelected : theme.tabIconDefault; return ( { +const getStyles = (theme: ColorTheme) => { return { fontSize: 20, color: theme.text01 }; }; @@ -12,8 +13,9 @@ export function TextStyled({ }: { children: React.ReactNode; }): JSX.Element { + const theme = useTheme().colorTheme; return ( - + {children} ); @@ -25,6 +27,7 @@ export function HeaderText({ }: { children: React.ReactNode; }): JSX.Element { + const theme = useTheme().colorTheme; return ( = createTheming(themes[colorMode]); + +export { ThemeProvider, withTheme, useTheme, colorMode }; diff --git a/src/common/themed-bottom-tab-bar.tsx b/src/common/themed-bottom-tab-bar.tsx index c45bbd2..d2ad825 100644 --- a/src/common/themed-bottom-tab-bar.tsx +++ b/src/common/themed-bottom-tab-bar.tsx @@ -1,13 +1,16 @@ import * as React from "react"; -import { BottomTabBar } from "react-navigation"; -import { ThemeProps } from "@/types/theme-props"; -import { theme } from "@/common/theme"; +import { + BottomTabBar, + BottomTabBarOptions, + BottomTabBarProps, +} from "@react-navigation/bottom-tabs"; -type Props = { - currentTheme: ThemeProps; -}; +import { useTheme } from "@/common/theme"; -export function ThemedBottomTabBar(props: Props): JSX.Element { +export function ThemedBottomTabBar( + props: BottomTabBarProps +): JSX.Element { + const theme = useTheme().colorTheme; return ( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/src/common/with-theme.tsx b/src/common/with-theme.tsx deleted file mode 100644 index e19665e..0000000 --- a/src/common/with-theme.tsx +++ /dev/null @@ -1,29 +0,0 @@ -// @flow -import * as React from "react"; -import { connect } from "react-redux"; -import { ThemeProps } from "@/types/theme-props"; - -type Props = { - forwardedRef: React.Ref; - theme: ThemeProps; -}; - -// tslint:disable-next-line:no-any -export const withTheme = (InnerComponent: any): any => { - return connect((state: { base: { currentTheme: ThemeProps } }) => ({ - theme: state.base.currentTheme, - }))(function HOC(innerProps: Props): JSX.Element { - const displayName = `WithTheme(${ - InnerComponent.displayName || InnerComponent.name || "Component" - })`; - const { forwardedRef, theme, ...props } = innerProps; - return ( - - ); - }); -}; diff --git a/src/screens/account-picker-screen.tsx b/src/screens/account-picker-screen.tsx index eb3f53e..2c7c9d0 100644 --- a/src/screens/account-picker-screen.tsx +++ b/src/screens/account-picker-screen.tsx @@ -1,18 +1,19 @@ import React, { useEffect } from "react"; import { View, Text, StyleSheet, ScrollView, SafeAreaView } from "react-native"; -import { NavigationScreenProp } from "react-navigation"; import { List, Tabs } from "@ant-design/react-native"; import { NavigationBar } from "@/common/navigation-bar"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; import { i18n } from "@/translations"; import { analytics } from "@/common/analytics"; import { OptionTab } from "@/screens/add-transaction-screen/hooks/use-ledger-meta"; +import { ColorTheme } from "@/types/theme-props"; type Props = { - navigation: NavigationScreenProp; + navigation: any; + route: any; }; -const styles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ container: { flex: 1, @@ -28,12 +29,20 @@ export function AccountPickerScreen(pickerProps: Props): JSX.Element { init(); }, []); - const { navigation } = pickerProps; - const onSelected: (item: string) => void = navigation.getParam("onSelected"); - const optionTabs: Array = navigation.getParam("optionTabs"); + const { navigation, route } = pickerProps; + + const { + onSelected, + optionTabs, + }: { + onSelected: (item: string) => void; + optionTabs: Array; + } = route.params; + const tabs = optionTabs.map((opt) => { return { title: opt.title }; }); + const theme = useTheme().colorTheme; const renderOptionTabs = optionTabs.map((val, index) => { return ( @@ -73,14 +82,15 @@ export function AccountPickerScreen(pickerProps: Props): JSX.Element { ); }); + const styles = getStyles(theme); return ( - + - + ; + navigation: any; userId: string; + route: any; }; -const styles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ container: { flex: 1, @@ -70,22 +71,23 @@ export const AddTransactionNextScreen = connect( } init(); }, []); - + const theme = useTheme().colorTheme; + const styles = getStyles(theme); const { assetsOptionTabs, expensesOptionTabs } = useLedgerMeta(props.userId); - const [assets, setAssets] = useState( - props.navigation.getParam("currentAsset") - ); - const [expenses, setExpenses] = useState( - props.navigation.getParam("currentExpense") - ); + const { + currentAsset, + currentExpense, + currentMoney, + currentCurrency, + onRefresh, + } = props.route.params; + const [assets, setAssets] = useState(currentAsset); + const [expenses, setExpenses] = useState(currentExpense); const [payee, setPayee] = useState(""); const [date, setDate] = useState(getFormatDate(new Date())); const [narration, setNarration] = useState(""); const { mutate, error } = useAddEntriesToRemote(); - const currentMoney = props.navigation.getParam("currentMoney"); - const currentCurrency = props.navigation.getParam("currentCurrency"); - const onRefresh = props.navigation.getParam("onRefresh"); const currencySymbol = getCurrencySymbol(currentCurrency); const addEntries = async () => { @@ -136,7 +138,7 @@ export const AddTransactionNextScreen = connect( }; return ( - + - - - + + + {`${currencySymbol}${currentMoney.split(".")[0]}`} - + {`${currentMoney.split(".")[1]}`} - {payee} - {date} + {payee} + {date} ; + navigation: any; + route: any; }; -const styles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ container: { flex: 1, @@ -71,7 +72,8 @@ export function AddTransactionScreen(props: Props): JSX.Element { } init(); }, []); - + const theme = useTheme().colorTheme; + const styles = getStyles(theme); const [currentMoney, setCurrentMoney] = React.useState("0.00"); const [keyValues, setKeyValues] = React.useState>([]); @@ -109,20 +111,20 @@ export function AddTransactionScreen(props: Props): JSX.Element { } return money; }; - const onRefresh = props.navigation.getParam("onRefresh"); + const { onRefresh } = props.route.params; const currencySymbol = getCurrencySymbol(currentCurrency); return ( - + - - + + {`${currencySymbol}${currentMoney}`} +const getStyles = (theme: ColorTheme) => StyleSheet.create({ container: { backgroundColor: theme.white, @@ -18,9 +19,11 @@ export function ListItemStyled({ children: React.ReactNode; onPress?: () => void; }): JSX.Element { + const theme = useTheme().colorTheme; + const styles = getStyles(theme); return ( ; + navigation: any; + route: any; }; -const styles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ container: { backgroundColor: theme.white, @@ -36,21 +37,23 @@ export function NarrationInputScreen(props: Props): JSX.Element { init(); }, []); - const [narration, setNarration] = useState( - props.navigation.getParam("narration") || "" - ); + const theme = useTheme().colorTheme; + const styles = getStyles(theme); + const { narration, onSaved } = props.route.params; + const [newNarration, setNarration] = useState(narration || ""); const onRightClick = async () => { - const onSaved = props.navigation.getParam("onSaved"); if (onSaved) { - onSaved(narration); + onSaved(newNarration); } - await analytics.track("tap_narration_input_save", { narration }); + await analytics.track("tap_narration_input_save", { + narration: newNarration, + }); props.navigation.pop(); }; return ( - + - + ; + navigation: any; + route: any; }; -const styles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ container: { backgroundColor: theme.white, @@ -37,22 +38,21 @@ export function PayeeInputScreen(props: Props): JSX.Element { } init(); }, []); - - const [payee, setPayee] = useState( - props.navigation.getParam("payee") || "" - ); + const theme = useTheme().colorTheme; + const styles = getStyles(theme); + const { payee, onSaved } = props.route.params; + const [newPayee, setPayee] = useState(payee || ""); const onRightClick = async () => { - const onSaved = props.navigation.getParam("onSaved"); if (onSaved) { - onSaved(payee); + onSaved(newPayee); } - await analytics.track("tap_payee_input_save", { payee }); + await analytics.track("tap_payee_input_save", { payee: newPayee }); props.navigation.pop(); }; return ( - + - + void; - navigation: NavigationScreenProp; + navigation: any; }; -const styles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ container: { minHeight: 80, @@ -68,6 +68,8 @@ export const QuickAddAccountsSelector = connect( userId: state.base.userId, }) )(function AssetsExpensesSelectorInner(props: Props): JSX.Element { + const theme = useTheme().colorTheme; + const styles = getStyles(theme); const { userId, onChange, navigation } = props; const [refreshing, setRefreshing] = useState(false); const { @@ -97,7 +99,7 @@ export const QuickAddAccountsSelector = connect( if (loading) { return ( + { await refetch(); @@ -132,7 +134,7 @@ export const QuickAddAccountsSelector = connect( return ( +const getStyles = (theme: ColorTheme) => StyleSheet.create({ text: { fontSize: 20, @@ -34,12 +35,14 @@ export function AccountsRow({ value: string; circleColor: string; }): JSX.Element { + const theme = useTheme().colorTheme; + const styles = getStyles(theme); return ( - - - {title} + + + {title} - {value} + {value} ); } diff --git a/src/screens/home-screen/components/net-assets-styled.tsx b/src/screens/home-screen/components/net-assets-styled.tsx index cbd838c..8c70dce 100644 --- a/src/screens/home-screen/components/net-assets-styled.tsx +++ b/src/screens/home-screen/components/net-assets-styled.tsx @@ -1,8 +1,9 @@ import * as React from "react"; import { Text, View, StyleSheet } from "react-native"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; +import { ColorTheme } from "@/types/theme-props"; -const getStyles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ text: { fontSize: 28, @@ -20,9 +21,11 @@ export function NetAssetsStyled({ }: { netAssets: string; }): JSX.Element { + const theme = useTheme().colorTheme; + const styles = getStyles(theme); return ( - - {netAssets} + + {netAssets} ); } diff --git a/src/screens/home-screen/email-icon.tsx b/src/screens/home-screen/email-icon.tsx index a51a600..b6bce17 100644 --- a/src/screens/home-screen/email-icon.tsx +++ b/src/screens/home-screen/email-icon.tsx @@ -1,8 +1,9 @@ import { G, Path, Svg } from "react-native-svg"; import * as React from "react"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; export function EmailIcon(): JSX.Element { + const theme = useTheme().colorTheme; return ( +const getStyles = (theme: ColorTheme) => StyleSheet.create({ container: { flex: 1, @@ -49,7 +49,7 @@ const styles = () => }); type Props = { - navigation: NavigationScreenProp; + navigation: any; userId: string; theme: string; }; @@ -66,7 +66,8 @@ export const HomeScreen = connect( } init(); }, []); - + const theme = useTheme().colorTheme; + const styles = getStyles(theme); const { currencies, refetch: ledgerMetaRefetch } = useLedgerMeta( props.userId ); @@ -102,7 +103,7 @@ export const HomeScreen = connect( const { spendingReportSubscription } = useFeatureFlags(props.userId); return ( <> - + - {i18n.t("quickAdd")} + {i18n.t("quickAdd")} {i18n.t("monthlyNetIncome")} diff --git a/src/screens/ledger-screen.tsx b/src/screens/ledger-screen.tsx index 9bdc163..698b2f8 100644 --- a/src/screens/ledger-screen.tsx +++ b/src/screens/ledger-screen.tsx @@ -10,10 +10,11 @@ import { headers } from "@/common/headers"; import { getEndpoint } from "@/common/request"; import { statusBarHeight } from "@/common/screen-util"; import { AppState } from "@/common/store"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; import { ProgressBar } from "@/common/progress-bar"; +import { ColorTheme } from "@/types/theme-props"; -const getStyles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ container: { flex: 1, @@ -49,7 +50,8 @@ export const LedgerScreen = connect((state: AppState) => { return { authToken: state.base.authToken }; })(function BbsScreenInner(props: Props): JSX.Element { let webViewRef: WebView | null; - + const theme = useTheme().colorTheme; + const styles = getStyles(theme); const [progress, setProgress] = useState(0); useEffect(() => { @@ -65,7 +67,7 @@ export const LedgerScreen = connect((state: AppState) => { webViewRef.reload(); } }; - const styles = getStyles(); + const { authToken } = props; const [uri, setUri] = useState(getEndpoint("ledger/editor/")); return ( diff --git a/src/screens/mine-screen/about.tsx b/src/screens/mine-screen/about.tsx index 4133785..8533582 100644 --- a/src/screens/mine-screen/about.tsx +++ b/src/screens/mine-screen/about.tsx @@ -3,24 +3,30 @@ import { List, Picker, Toast, Portal } from "@ant-design/react-native"; import Constants from "expo-constants"; import * as WebBrowser from "expo-web-browser"; import React, { useEffect, useState } from "react"; -import { Alert, Platform, ScrollView, Switch, View } from "react-native"; +import { + Alert, + Platform, + ScrollView, + Switch, + View, + AsyncStorage, +} from "react-native"; import { connect } from "react-redux"; import { analytics } from "@/common/analytics"; import { ListHeader } from "@/common/list-header"; import { registerForPushNotificationAsync } from "@/common/register-push-token"; import { actionUpdateReduxState } from "@/common/root-reducer"; import { AppState } from "@/common/store"; -import { setTheme, theme } from "@/common/theme"; -import { i18n } from "@/translations"; -import { ScreenProps } from "@/types/screen-props"; +import { useTheme } from "@/common/theme"; +import { i18n, LocalizationContext } from "@/translations"; import { actionLogout } from "@/screens/mine-screen/account-reducer"; import { useUserProfile } from "@/screens/mine-screen/hooks/use-user-profile"; import { useUpdateReportSubscribeToRemote } from "@/screens/mine-screen/hooks/use-update-report-subscribe"; import { useFeatureFlags } from "@/common/feature-flags/use-feature-flags"; import { AccountHeader } from "@/screens/mine-screen/account-header"; import { InviteSection } from "@/screens/referral-screen/components/invite-section"; -import { NavigationScreenProp } from "react-navigation"; import { ReportStatus } from "../../../__generated__/globalTypes"; +import { useIsFocused } from "@react-navigation/native"; const { Item } = List; const { Brief } = Item; @@ -32,11 +38,9 @@ type Props = { updateReduxState: (state: { base: { locale?: string; currentTheme?: string }; }) => void; - screenProps: ScreenProps; currentTheme: "dark" | "light"; userId: string; - fromAnnouncement: boolean; - navigation: NavigationScreenProp; + navigation: any; }; export const About = connect( @@ -60,12 +64,12 @@ export const About = connect( locale, logout, updateReduxState, - screenProps, currentTheme, userId, - fromAnnouncement, navigation, }: Props) => { + const theme = useTheme().colorTheme; + const { setLocale } = React.useContext(LocalizationContext); const pickerSource = [ { value: ReportStatus.WEEKLY, label: i18n.t("weekly") }, { value: ReportStatus.MONTHLY, label: i18n.t("monthly") }, @@ -80,8 +84,28 @@ export const About = connect( }, []); const [reportAnimateCount, setReportAnimateCount] = useState(0); + const [subscriptionFlash, setSubscriptionFlash] = useState(false); + const isFocused = useIsFocused(); + + React.useEffect(() => { + async function init() { + try { + const value = await AsyncStorage.getItem("@SubscriptionFlash:key"); + if (value !== null) { + setSubscriptionFlash(value === "true"); + } else { + setSubscriptionFlash(false); + } + await AsyncStorage.setItem("@SubscriptionFlash:key", "false"); + } catch (error) { + console.error(`failed to get subscription flash value: ${error}`); + } + } + init(); + }, [isFocused]); + useEffect(() => { - if (fromAnnouncement) { + if (subscriptionFlash) { const interval = setInterval(() => { if (reportAnimateCount < 5) { setReportAnimateCount(reportAnimateCount + 1); @@ -91,7 +115,7 @@ export const About = connect( } setReportAnimateCount(0); return undefined; - }, [fromAnnouncement, reportAnimateCount]); + }, [subscriptionFlash, reportAnimateCount]); const { emailReportStatus } = useUserProfile(userId); const [reportStatus, setReportStatue] = useState( @@ -216,7 +240,7 @@ export const About = connect( base: { locale: changeTo }, }); i18n.locale = changeTo; - screenProps.setLocale(changeTo); + setLocale(changeTo); await analytics.track("tap_switch_language", { changeTo }); }} /> @@ -238,7 +262,6 @@ export const About = connect( value={currentTheme === "dark"} onValueChange={async (value) => { const mode = value ? "dark" : "light"; - setTheme(mode); updateReduxState({ base: { currentTheme: mode }, }); diff --git a/src/screens/mine-screen/account-header.tsx b/src/screens/mine-screen/account-header.tsx index 857fc3d..bb9e4e8 100644 --- a/src/screens/mine-screen/account-header.tsx +++ b/src/screens/mine-screen/account-header.tsx @@ -3,7 +3,7 @@ import * as React from "react"; import { StyleSheet, Text, View } from "react-native"; import { connect } from "react-redux"; import { AppState } from "@/common/store"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; import { i18n } from "@/translations"; import { ScreenWidth } from "@/common/screen-util"; import { LoginWebView } from "@/screens/mine-screen/login-web-view"; @@ -11,8 +11,9 @@ import { useStateIfMounted } from "@/common/hooks/use-state-if-mounted"; import { useUserProfile } from "@/screens/mine-screen/hooks/use-user-profile"; import { LoadingTile } from "@/common/loading-tile"; import { analytics } from "@/common/analytics"; +import { ColorTheme } from "@/types/theme-props"; -const getStyles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ titleContainer: { paddingHorizontal: 14, @@ -48,6 +49,8 @@ const getStyles = () => export const EmailHeader = ({ userId }: { userId: string }) => { const { email, loading, error } = useUserProfile(userId); + const theme = useTheme().colorTheme; + const styles = getStyles(theme); if (loading || error || !email) { if (error) { Toast.fail(`failed to fetch user: ${error}`, 5); @@ -56,7 +59,7 @@ export const EmailHeader = ({ userId }: { userId: string }) => { } return ( - + {email} @@ -67,22 +70,22 @@ export const AccountHeader = connect((state: AppState) => ({ userId: state.base.userId, authToken: state.base.authToken, }))(({ userId, authToken }: { userId: string; authToken: string }) => { + const theme = useTheme().colorTheme; + const styles = getStyles(theme); return ( - + {userId && authToken ? ( ) : ( - {i18n.t("login")} + {i18n.t("login")} )} ); }); -const getLoginOrSignUpStyles = () => +const getLoginOrSignUpStyles = (theme: ColorTheme) => StyleSheet.create({ closeButton: { width: 60, @@ -110,8 +113,8 @@ export function LoginOrSignUp(props: LoginOrSignUpProps): JSX.Element { const onCloseModal = () => { setShouldDisplayModal(false); }; - - const styles = getLoginOrSignUpStyles(); + const theme = useTheme().colorTheme; + const styles = getLoginOrSignUpStyles(theme); return ( { diff --git a/src/screens/mine-screen/mine-screen.tsx b/src/screens/mine-screen/mine-screen.tsx index 410a595..1d5b27a 100644 --- a/src/screens/mine-screen/mine-screen.tsx +++ b/src/screens/mine-screen/mine-screen.tsx @@ -1,25 +1,24 @@ import * as React from "react"; import { View } from "react-native"; import { statusBarHeight } from "@/common/screen-util"; -import { theme } from "@/common/theme"; -import { ScreenProps } from "@/types/screen-props"; +import { useTheme } from "@/common/theme"; import { About } from "@/screens/mine-screen/about"; -import { NavigationScreenProp } from "react-navigation"; import { analytics } from "@/common/analytics"; type Props = { - navigation: NavigationScreenProp; - screenProps: ScreenProps; + navigation: any; }; export function MineScreen(props: Props): JSX.Element { + const theme = useTheme().colorTheme; + const { navigation } = props; React.useEffect(() => { async function init() { await analytics.track("page_view_mine", {}); } init(); }, []); - const fromAnnouncement = props.navigation.getParam("fromAnnouncement"); + return ( - + ); } diff --git a/src/screens/mine-screen/pre-auth-view.tsx b/src/screens/mine-screen/pre-auth-view.tsx index 0a5e1b7..3f7096d 100644 --- a/src/screens/mine-screen/pre-auth-view.tsx +++ b/src/screens/mine-screen/pre-auth-view.tsx @@ -1,15 +1,16 @@ import * as React from "react"; import { Dimensions, View, StyleSheet, Image, Text } from "react-native"; import { Button } from "@ant-design/react-native"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; import { i18n } from "@/translations"; import { LoginOrSignUp } from "@/screens/mine-screen/account-header"; import { analytics } from "@/common/analytics"; +import { ColorTheme } from "@/types/theme-props"; const { width, height } = Dimensions.get("window"); const buttonWidth = (width - 20 * 3) / 2; -const getStyles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ container: { height, @@ -53,7 +54,8 @@ export function PreAuthView(): JSX.Element { } init(); }, []); - const styles = getStyles(); + const theme = useTheme().colorTheme; + const styles = getStyles(theme); return ( diff --git a/src/screens/referral-screen/components/contact-row.tsx b/src/screens/referral-screen/components/contact-row.tsx index fbdf95f..8a47ba0 100644 --- a/src/screens/referral-screen/components/contact-row.tsx +++ b/src/screens/referral-screen/components/contact-row.tsx @@ -1,9 +1,10 @@ import * as React from "react"; import { Text, View, StyleSheet, TouchableOpacity } from "react-native"; import { contentPadding } from "@/common/screen-util"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; import { MaterialIcons } from "@expo/vector-icons"; import { CommonMargin } from "@/common/common-margin"; +import { ColorTheme } from "@/types/theme-props"; type ContactRowProps = { name: string; @@ -12,7 +13,7 @@ type ContactRowProps = { selected: boolean; }; -const styles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ rowContainer: { paddingHorizontal: contentPadding, @@ -30,19 +31,23 @@ export function ContactRow({ emailOrNumber, selected, }: ContactRowProps): JSX.Element { + const theme = useTheme().colorTheme; + const styles = getStyles(theme); return ( - + - {name || emailOrNumber} + {name || emailOrNumber} {name.length > 0 && ( - {emailOrNumber} + {emailOrNumber} )} diff --git a/src/screens/referral-screen/components/gift-icon.tsx b/src/screens/referral-screen/components/gift-icon.tsx index 3c3fe56..35d6e8d 100644 --- a/src/screens/referral-screen/components/gift-icon.tsx +++ b/src/screens/referral-screen/components/gift-icon.tsx @@ -1,8 +1,9 @@ import { G, Path, Svg, Polygon } from "react-native-svg"; import * as React from "react"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; export function GiftIcon(): JSX.Element { + const theme = useTheme().colorTheme; return ( diff --git a/src/screens/referral-screen/components/invite-section.tsx b/src/screens/referral-screen/components/invite-section.tsx index f705321..c894bbe 100644 --- a/src/screens/referral-screen/components/invite-section.tsx +++ b/src/screens/referral-screen/components/invite-section.tsx @@ -1,17 +1,17 @@ import * as React from "react"; import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; import { contentPadding, ScreenWidth, onePx } from "@/common/screen-util"; -import { NavigationScreenProp } from "react-navigation"; import { i18n } from "@/translations"; import { GiftIcon } from "@/screens/referral-screen/components/gift-icon"; import { analytics } from "@/common/analytics"; +import { ColorTheme } from "@/types/theme-props"; type Props = { - navigation: NavigationScreenProp; + navigation: any; }; -const styles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ container: { paddingHorizontal: contentPadding, @@ -50,21 +50,23 @@ const styles = () => }); export function InviteSection(props: Props): JSX.Element { + const theme = useTheme().colorTheme; + const styles = getStyles(theme); return ( - - {i18n.t("inviteFriends")} + + {i18n.t("inviteFriends")} { await analytics.track("tap_navigate_to_referral", {}); props.navigation.navigate("Referral"); }} > - - {i18n.t("inviteSummary")} + + {i18n.t("inviteSummary")} - + diff --git a/src/screens/referral-screen/components/referral-gift-icon.tsx b/src/screens/referral-screen/components/referral-gift-icon.tsx index 4734c22..181f29b 100644 --- a/src/screens/referral-screen/components/referral-gift-icon.tsx +++ b/src/screens/referral-screen/components/referral-gift-icon.tsx @@ -1,8 +1,9 @@ import { G, Path, Svg, Polygon } from "react-native-svg"; import * as React from "react"; -import { theme } from "@/common/theme"; +import { useTheme } from "@/common/theme"; export function ReferralGiftIcon(): JSX.Element { + const theme = useTheme().colorTheme; return ( ; + navigation: any; + route: any; }; -const styles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ container: { flex: 1, backgroundColor: theme.white }, bodyContainer: { flex: 1, alignItems: "center", justifyContent: "center" }, @@ -81,6 +83,9 @@ export function InviteScreen(props: Props) { init(); }, []); + const theme = useTheme().colorTheme; + const styles = getStyles(theme); + const contacts = useContacts(); const [selectedContacts, setSelectedContacts] = React.useState([]); @@ -149,7 +154,7 @@ export function InviteScreen(props: Props) { }, [sections, keyword]); const onInvitePress = async () => { - const shareLink = String(props.navigation.getParam("shareLink") || ""); + const { shareLink } = props.route.params; let didShare = false; const message = `${i18n.t("recommend")} ${shareLink}`; const emails = selectedContacts @@ -189,10 +194,10 @@ export function InviteScreen(props: Props) { const renderBody = () => { if (contacts.loading) { return ( - + - {i18n.t("loading")} + {i18n.t("loading")} ); } @@ -202,23 +207,23 @@ export function InviteScreen(props: Props) { ? i18n.t("noContactPermission") : String(contacts.error.message); return ( - + - {errMsg} + {errMsg} ); } return ( - + { @@ -233,8 +238,8 @@ export function InviteScreen(props: Props) { bounces={false} sections={filteredSection} renderSectionHeader={({ section }) => ( - - + + {section.key!.toUpperCase()} @@ -262,15 +267,15 @@ export function InviteScreen(props: Props) { ); }} extraData={selectedContacts} - contentContainerStyle={styles().contentContainerStyle} + contentContainerStyle={styles.contentContainerStyle} /> - + @@ -280,7 +285,7 @@ export function InviteScreen(props: Props) { }; return ( - + ; + navigation: any; userId: string; }; -const styles = () => +const getStyles = (theme: ColorTheme) => StyleSheet.create({ container: { backgroundColor: theme.white, @@ -110,52 +110,53 @@ export const ReferralScreen = connect( const { userId } = props; const shareLink = `beancount.io/sign-up/?src=${Platform.OS}&by=${userId}`; - + const theme = useTheme().colorTheme; + const styles = getStyles(theme); return ( - + - + - {i18n.t("rewardSummary")} + {i18n.t("rewardSummary")} - {i18n.t("rewardDetail")} + {i18n.t("rewardDetail")} - - + + {shareLink} { Clipboard.setString(shareLink); Toast.show(i18n.t("copied"), 1); await analytics.track("tap_share_link_copy", { shareLink }); }} > - {i18n.t("copy")} + {i18n.t("copy")} diff --git a/src/translations/index.ts b/src/translations/index.ts index d75f19b..ce3f39f 100644 --- a/src/translations/index.ts +++ b/src/translations/index.ts @@ -1,4 +1,5 @@ import * as Locatization from "expo-localization"; +import * as React from "react"; import i18n from "i18n-js"; import { en } from "@/translations/en"; import { zh } from "@/translations/zh"; @@ -9,4 +10,6 @@ if (i18n) { i18n.locale = Locatization.locale; } -export { i18n }; +const defaultValue: any = {}; +const LocalizationContext = React.createContext(defaultValue); +export { i18n, LocalizationContext }; diff --git a/src/types/navigation-param.ts b/src/types/navigation-param.ts new file mode 100644 index 0000000..2661e2a --- /dev/null +++ b/src/types/navigation-param.ts @@ -0,0 +1,28 @@ +export type RootStackParamList = { + Root: undefined; + AddTransaction: undefined; + AddTransactionNext: undefined; + AccountPicker: undefined; + PayeeInput: undefined; + NarrationInput: undefined; + Referral: undefined; + Invite: undefined; +}; + +export type MainTabParamList = { + Home: undefined; + Ledger: undefined; + Mine: undefined; +}; + +export type HomeParamList = { + HomeScreen: undefined; +}; + +export type LedgerParamList = { + LedgerScreen: undefined; +}; + +export type MineParamList = { + MineScreen: undefined; +}; diff --git a/src/types/theme-props.ts b/src/types/theme-props.ts index 08bfeae..e7cbc33 100644 --- a/src/types/theme-props.ts +++ b/src/types/theme-props.ts @@ -2,10 +2,12 @@ export interface ThemeProps { name: string; antdTheme: AntdTheme; - theme: ColorTheme; + colorTheme: ColorTheme; + sizing: Array; } export interface AntdTheme { + color_text_base: string; brand_primary: string; color_link: string; primary_button_fill: string; @@ -13,25 +15,25 @@ export interface AntdTheme { } export interface ColorTheme { + primary: string; + secondary: string; + white: string; black: string; - black10: string; - black20: string; - black40: string; - black60: string; - black80: string; black90: string; + black80: string; + black60: string; + black40: string; + black20: string; + black10: string; + text01: string; error: string; + success: string; + warning: string; information: string; nav01: string; nav02: string; - primary: string; - secondary: string; - success: string; tabIconDefault: string; tabIconSelected: string; - text01: string; - warning: string; - white: string; activeTintColor: string; inactiveTintColor: string; activeBackgroundColor: string;