diff --git a/App.js b/App.js index 4733a2a..9973224 100644 --- a/App.js +++ b/App.js @@ -1,5 +1,6 @@ import * as eva from "@eva-design/eva"; +import { Appearance, AppearanceProvider } from "react-native-appearance"; import { ApplicationProvider, IconRegistry } from "@ui-kitten/components"; import { AppLoading } from "expo"; @@ -20,14 +21,24 @@ class App extends React.Component { this.state = { authToken: null, - isReady: false + isReady: false, + systemColorScheme: Appearance.getColorScheme() }; } async componentDidMount() { const authToken = await AsyncStorage.getItem("authToken"); - this.setState({ authToken, isReady: true }); + + this.appearanceListener = Appearance.addChangeListener(({ colorScheme }) => { + this.setState({ systemColorScheme: colorScheme }); + }); + } + + componentWillUnmount() { + if (this.appearanceListener) { + this.appearanceListener.remove(); + } } handleAuthTokenUpdate(authToken) { @@ -42,32 +53,34 @@ class App extends React.Component { } return ( - - - - - {this.state.authToken ? ( - <> - + + + + + + {this.state.authToken ? ( + <> + + ({ title: route.params.subject })} + /> + + ) : ( ({ title: route.params.subject })} + name="Sign In" + component={SignIn} + initialParams={{ onAuthTokenUpdate: this.handleAuthTokenUpdate.bind(this) }} /> - - ) : ( - - )} - - + )} + + + ); } diff --git a/app.json b/app.json index d41c424..c125d58 100644 --- a/app.json +++ b/app.json @@ -23,6 +23,7 @@ ], "ios": { "supportsTablet": true - } + }, + "userInterfaceStyle": "automatic" } } diff --git a/components/account/SignIn.js b/components/account/SignIn.js index 348f312..004df45 100644 --- a/components/account/SignIn.js +++ b/components/account/SignIn.js @@ -1,10 +1,12 @@ -import { Button, Input, Layout, Text } from "@ui-kitten/components"; +import { Button, Input, Layout, Text, useStyleSheet } from "@ui-kitten/components"; import React, { useState } from "react"; import { createZimbraClient } from "../../utils"; -import styles from "../../styles"; +import themedStyles from "../../styles"; function SignIn({ route }) { + const styles = useStyleSheet(themedStyles); + const [username, setUsername] = useState(null); const [password, setPassword] = useState(null); const [errorMessage, setErrorMessage] = useState(null); diff --git a/components/conversation/Detail.js b/components/conversation/Detail.js index a0338e8..14c2f90 100644 --- a/components/conversation/Detail.js +++ b/components/conversation/Detail.js @@ -1,12 +1,28 @@ -import { Divider, Icon, ListItem, TopNavigation, TopNavigationAction } from "@ui-kitten/components"; -import { SafeAreaView, Text } from "react-native"; +import { Divider, Icon, TopNavigation, TopNavigationAction, useStyleSheet } from "@ui-kitten/components"; +import LoadingScreen from "../shared/LoadingScreen"; import MessageList from "../message/List"; import React from "react"; +import { SafeAreaView } from "react-native"; +import Subject from "./DetailSubject"; import { createZimbraClient } from "../../utils"; +import themedStyles from "../../styles"; import useSWR from "swr"; +const BackIcon = (props) => ; +const TrashIcon = (props) => ; +const ArchiveIcon = (props) => ; + +const RightActions = () => ( + <> + + + +); + function ConversationDetail({ navigation, route }) { + const styles = useStyleSheet(themedStyles); + async function fetcher(_key, id) { return (await createZimbraClient()).getConversation({ id, @@ -15,29 +31,28 @@ function ConversationDetail({ navigation, route }) { }); } - const { data, error } = useSWR(["getConversation", route.params.id], fetcher); - function handleBackActionPress() { navigation.goBack(); } - function BackIcon(props) { - return ; - } + const BackButtonWrapper = () => ; + const SubjectWrapper = () => ( + + ); - function renderBackButton() { - return ; - } + const { data, error } = useSWR(["getConversation", route.params.id], fetcher); return ( - - + + - - { data ? ( - + {data ? ( + ) : ( - Loading... + <> + + + )} ); diff --git a/components/conversation/DetailSubject.js b/components/conversation/DetailSubject.js new file mode 100644 index 0000000..0e34020 --- /dev/null +++ b/components/conversation/DetailSubject.js @@ -0,0 +1,24 @@ +import { Icon, Text, TopNavigation, TopNavigationAction, useStyleSheet } from "@ui-kitten/components"; + +import React from "react"; +import themedStyles from "../../styles"; + +const StarIcon = (props) => ; + +function ConversationDetailSubject({ subject }) { + const styles = useStyleSheet(themedStyles); + + const SubjectHeading = () => ( + {subject} + ); + + const StarAction = () => ( + + ); + + return ( + + ); +} + +export default ConversationDetailSubject; \ No newline at end of file diff --git a/components/conversation/List.js b/components/conversation/List.js index f750ff6..a1c918e 100644 --- a/components/conversation/List.js +++ b/components/conversation/List.js @@ -1,14 +1,20 @@ -import { Divider, List, Text, TopNavigation } from "@ui-kitten/components"; +import { Divider, Icon, List, TopNavigation, TopNavigationAction, useStyleSheet } from "@ui-kitten/components"; +import { Platform, SafeAreaView } from "react-native"; import ConversationListItem from "./ListItem"; +import LoadingScreen from "../shared/LoadingScreen"; import React from "react"; -import { SafeAreaView } from "react-native"; import { createZimbraClient } from "../../utils"; +import themedStyles from "../../styles"; import useSWR from "swr"; const REFRESH_INTERVAL_SECONDS = 60 * 1000; +const MenuIcon = (props) => ; + function ConversationList({ navigation }) { + const styles = useStyleSheet(themedStyles); + async function fetcher(_key, query) { return (await createZimbraClient()).search({ query }); } @@ -19,25 +25,23 @@ function ConversationList({ navigation }) { return ; } - if (error) { - console.error(error); - - return Error; - } + const MenuAction = () => ( + + ); return ( - - + + {data ? ( ) : ( - Loading... + )} ); diff --git a/components/conversation/ListItem.js b/components/conversation/ListItem.js index a110c1d..aa7e963 100644 --- a/components/conversation/ListItem.js +++ b/components/conversation/ListItem.js @@ -7,15 +7,13 @@ function ConversationListItem({ navigation, id, subject, emailAddresses }) { navigation.navigate("conversationDetail", { id: id, subject: subject }); } - function renderIcon(props) { - return ; - } + const PersonIcon = (props) => ; return ( ); diff --git a/components/message/HtmlViewer.js b/components/message/HtmlViewer.js index c284eb8..3b503bf 100644 --- a/components/message/HtmlViewer.js +++ b/components/message/HtmlViewer.js @@ -1,7 +1,8 @@ -import { Dimensions, Text } from "react-native"; import React, { useState } from "react"; +import { Dimensions } from "react-native"; import { WebView } from "react-native-webview"; +import { useTheme } from "@ui-kitten/components"; const injectedJavascript = ` setTimeout(function() { @@ -12,19 +13,28 @@ const injectedJavascript = ` }, 10); true;`; -const injectedCss = ` +function HtmlViewer({ html }) { + const theme = useTheme(); + + // Higher starting height seems to lead to better height/width calculations + const [height, setHeight] = useState(1000); + + const injectedCss = ` `; -function HtmlViewer({ html }) { - // Higher starting height seems to lead to better height/width calculations - const [height, setHeight] = useState(1000); - function handleWebViewMessage(event) { const { height, width } = JSON.parse(event.nativeEvent.data); @@ -62,7 +72,7 @@ function HtmlViewer({ html }) { onMessage={handleWebViewMessage} injectedJavaScript={injectedJavascript} scrollEnabled={false} - style={{ flex: 0, height: height }} + style={{ backgroundColor: theme["background-basic-color-1"], flex: 0, height: height }} /> ); } diff --git a/components/message/List.js b/components/message/List.js index b6afd48..afa1c9d 100644 --- a/components/message/List.js +++ b/components/message/List.js @@ -1,13 +1,24 @@ -import { List } from "@ui-kitten/components"; +import { List, useStyleSheet } from "@ui-kitten/components"; + import MessageListItem from "./ListItem"; import React from "react"; +import themedStyles from "../../styles"; + +function MessageList({ navigation, messages, ListHeaderComponent }) { + const styles = useStyleSheet(themedStyles); -function MessageList({ navigation, messages }) { function renderItem({ item }) { return ; } - - return ; + + return ( + + ); } -export default MessageList; \ No newline at end of file +export default MessageList; diff --git a/components/message/ListItem.js b/components/message/ListItem.js index 4865a1f..dbc4d3c 100644 --- a/components/message/ListItem.js +++ b/components/message/ListItem.js @@ -1,10 +1,12 @@ -import { Icon, Layout, ListItem, Text } from "@ui-kitten/components"; +import { Icon, Layout, ListItem, useStyleSheet } from "@ui-kitten/components"; import HtmlViewer from "./HtmlViewer"; import React from "react"; -import styles from "../../styles"; +import themedStyles from "../../styles"; function MessageListItem({ from, html, to }) { + const styles = useStyleSheet(themedStyles); + function getToNames() { return to.map((e) => e.displayName).join(", "); } @@ -17,10 +19,10 @@ function MessageListItem({ from, html, to }) { <> - + diff --git a/components/shared/LoadingScreen.js b/components/shared/LoadingScreen.js new file mode 100644 index 0000000..719ca2c --- /dev/null +++ b/components/shared/LoadingScreen.js @@ -0,0 +1,16 @@ +import { Layout, Spinner, Text, useStyleSheet } from "@ui-kitten/components"; + +import React from "react"; +import themedStyles from "../../styles"; + +function LoadingScreen({ level, error }) { + const styles = useStyleSheet(themedStyles); + + return ( + + {error ? Sorry, failed to load. : } + + ); +} + +export default LoadingScreen; diff --git a/package.json b/package.json index 5e98d31..e76bad5 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "react": "~16.9.0", "react-dom": "~16.9.0", "react-native": "https://github.com/expo/react-native/archive/sdk-37.0.1.tar.gz", + "react-native-appearance": "~0.3.3", "react-native-gesture-handler": "~1.6.0", "react-native-reanimated": "~1.7.0", "react-native-safe-area-context": "0.7.3", diff --git a/styles.js b/styles.js index efde615..098f6ab 100644 --- a/styles.js +++ b/styles.js @@ -1,24 +1,39 @@ -import { StyleSheet } from "react-native"; +import { Platform, StatusBar } from "react-native"; -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: "#fff", - alignItems: "center", - justifyContent: "center" - }, +import { StyleService } from "@ui-kitten/components"; +const styles = StyleService.create({ paddedLayout: { flex: 1, - paddingVertical: 20, - paddingHorizontal: 20 + paddingVertical: 10, // This should match the design system + paddingHorizontal: 20 // This should match the design system }, centeredLayout: { alignItems: "center", flex: 1, justifyContent: "center" + }, + + safeAreaView: { + backgroundColor: "background-basic-color-1", + flex: 1, + ...Platform.select({ + android: { + paddingTop: StatusBar.currentHeight + } + }) + }, + + topNavigation: {}, + + list: { + backgroundColor: "background-basic-color-1" + }, + + subjectHeading: { + paddingLeft: 11 } }); -export default styles; +export default styles; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3cfcf01..1992d8c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4649,6 +4649,15 @@ react-is@^16.12.0, react-is@^16.13.0, react-is@^16.7.0, react-is@^16.8.1, react- resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-native-appearance@~0.3.3: + version "0.3.4" + resolved "https://registry.yarnpkg.com/react-native-appearance/-/react-native-appearance-0.3.4.tgz#2cbcbc5142cdc1898c116684f519b16c879cbec2" + integrity sha512-Vz3zdJbAEiMDwuw6wH98TT1WVfBvWjvANutYtkIbl16KGRCigtSgt6IIiLsF3/TSS3y3FtHhWDelFeGw/rtuig== + dependencies: + fbemitter "^2.1.1" + invariant "^2.2.4" + use-subscription "^1.0.0" + react-native-eva-icons@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/react-native-eva-icons/-/react-native-eva-icons-1.3.1.tgz#1e6e019b0fd3cb1669db50bd6bbdaa6d89327593" @@ -5661,7 +5670,7 @@ url-parse@^1.4.4: querystringify "^2.1.1" requires-port "^1.0.0" -use-subscription@^1.4.0: +use-subscription@^1.0.0, use-subscription@^1.4.0: version "1.4.1" resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.4.1.tgz#edcbcc220f1adb2dd4fa0b2f61b6cc308e620069" integrity sha512-7+IIwDG/4JICrWHL/Q/ZPK5yozEnvRm6vHImu0LKwQlmWGKeiF7mbAenLlK/cTNXrTtXHU/SFASQHzB6+oSJMQ==