diff --git a/app/App.tsx b/app/App.tsx index fc6433e..1df94e8 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -1,6 +1,7 @@ import React from "react"; import { Platform } from "react-native"; -import { StatusBar } from "expo-status-bar"; +import { AppearanceProvider, useColorScheme } from "react-native-appearance"; +import { StatusBar, StatusBarStyle } from "expo-status-bar"; import App from "./src/App"; const editorSource = @@ -9,10 +10,14 @@ const editorSource = : { html: null }; export default function Root() { + const isDark = useColorScheme() === "dark"; + const style: StatusBarStyle = isDark ? "light" : "dark"; return ( - <> - - - + + <> + + + + ); } diff --git a/app/app.config.js b/app/app.config.js index 857336d..f7bf4b9 100644 --- a/app/app.config.js +++ b/app/app.config.js @@ -7,6 +7,7 @@ export default { version: "1.6.6", orientation: "portrait", icon: "./src/assets/icon.png", + userInterfaceStyle: "automatic", splash: { image: "./src/assets/splash.png", resizeMode: "contain", diff --git a/app/package.json b/app/package.json index f0efe2f..b3a0852 100644 --- a/app/package.json +++ b/app/package.json @@ -1,7 +1,7 @@ { "main": "node_modules/expo/AppEntry.js", "scripts": { - "start": "API_URL=http://localhost:4000/graphql expo start", + "start": "API_URL=https://api.serenity.re/graphql expo start", "start:prod": "expo start", "eject": "expo eject", "test": "jest", @@ -33,6 +33,7 @@ "react": "16.13.1", "react-dom": "16.13.1", "react-native": "https://github.com/expo/react-native/archive/sdk-41.0.0.tar.gz", + "react-native-appearance": "^0.3.4", "react-native-elements": "^3.3.2", "react-native-gesture-handler": "~1.10.2", "react-native-get-random-values": "~1.7.0", diff --git a/app/src/components/Navigation.tsx b/app/src/components/Navigation.tsx index 4302fc8..40ee443 100644 --- a/app/src/components/Navigation.tsx +++ b/app/src/components/Navigation.tsx @@ -1,15 +1,11 @@ import React from "react"; -import { NavigationContainer } from "@react-navigation/native"; +import { NavigationContainer, Theme } from "@react-navigation/native"; import { createStackNavigator } from "@react-navigation/stack"; import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; import { createDrawerNavigator } from "@react-navigation/drawer"; import { Icon, ThemeProvider } from "react-native-elements"; -import { - DefaultTheme, - Provider as PaperProvider, - configureFonts, -} from "react-native-paper"; +import { Provider as PaperProvider } from "react-native-paper"; import HomeScreen from "./screens/HomeScreen"; import NoteScreen from "./screens/NoteScreen"; import SettingsScreen from "./screens/SettingsScreen"; @@ -33,22 +29,30 @@ import useDevice from "../hooks/useDevice"; import useUser from "../hooks/useUser"; import fetchAllLicenseTokens from "../utils/server/fetchAllLicenseTokens"; import { useClient } from "urql"; -import colors from "../styles/colors"; +import { legacyColors } from "../styles/legacyColors"; import GoodbyeScreen from "./screens/GoodbyeScreen"; import DebugScreen from "./screens/DebugScreen"; import { sizes } from "../styles/fonts"; import { Platform } from "react-native"; +import useCurrentTheme, { DerivedTheme } from "../hooks/useCurrentTheme"; +import { NoteStackParamsList } from "./NavigationTypes"; const RootStack = createStackNavigator(); -const Stack = createStackNavigator(); +const Stack = createStackNavigator(); const Tab = createBottomTabNavigator(); const Drawer = createDrawerNavigator(); -const theme = { - colors: { - primary: colors.text, +const getHeaderOptions = (theme: DerivedTheme) => ({ + headerTintColor: theme.colors.primary, + headerTitleStyle: { + color: theme.colors.text, }, -}; + headerStyle: { + backgroundColor: theme.colors.background, + shadowColor: "transparent", + elevation: 0, + }, +}); const fontConfig = { macos: { @@ -71,30 +75,9 @@ const fontConfig = { }, }; -const nativePaperTheme = { - ...DefaultTheme, - fonts: configureFonts(fontConfig), - colors: { - ...DefaultTheme.colors, - primary: colors.text, - accent: colors.textBrightest, - }, -}; - -const headerOptions = { - headerTintColor: colors.primary, - headerTitleStyle: { - color: colors.text, - }, - headerStyle: { - backgroundColor: colors.background, - shadowColor: "transparent", - borderBottomWidth: 0, - elevation: 0, - }, -}; - function Notes() { + const theme = useCurrentTheme(); + const headerOptions = getHeaderOptions(theme); return ( - + - + = { + navigation: StackNavigationProp; + route: RouteProp; +}; diff --git a/app/src/components/screens/ContactsScreen.tsx b/app/src/components/screens/ContactsScreen.tsx index 3e02d45..7e2327f 100644 --- a/app/src/components/screens/ContactsScreen.tsx +++ b/app/src/components/screens/ContactsScreen.tsx @@ -6,14 +6,14 @@ import usePrivateInfo from "../../hooks/usePrivateInfo"; import useContactsAndContactInvitations from "../../hooks/useContactsAndContactInvitations"; import ListItemButton from "../ui/ListItemButton"; import ListItemLink from "../ui/ListItemLink"; -import colors from "../../styles/colors"; import ListItemDivider from "../ui/ListItemDivider"; +import useCurrentTheme from "../../hooks/useCurrentTheme"; const styles = StyleSheet.create({ container: { flex: 1, width: "100%", - backgroundColor: colors.background, + paddingTop: 16, }, sectionTitle: { fontSize: 14, @@ -22,6 +22,7 @@ const styles = StyleSheet.create({ }); export default function ContactsScreen({ navigation }) { + const theme = useCurrentTheme(); const deviceResult = useDevice(); const privateInfoResult = usePrivateInfo(); // also converts accepted contactInvitations to contacts @@ -69,7 +70,9 @@ export default function ContactsScreen({ navigation }) { } return ( - + { navigation.navigate("AcceptContactInvitationScreen"); @@ -113,14 +116,20 @@ export default function ContactsScreen({ navigation }) { renderSectionHeader={({ section: { title } }) => ( - + {title} @@ -135,13 +144,14 @@ export default function ContactsScreen({ navigation }) { borderRadius: 6, }} containerStyle={{ - borderColor: colors.divider, + borderColor: theme.colors.accent, borderWidth: StyleSheet.hairlineWidth, borderRadius: 6, + backgroundColor: theme.colors.background, }} > - + No confirmed contacts @@ -155,13 +165,14 @@ export default function ContactsScreen({ navigation }) { borderRadius: 6, }} containerStyle={{ - borderColor: colors.divider, + backgroundColor: theme.colors.background, + borderColor: theme.colors.accent, borderWidth: StyleSheet.hairlineWidth, borderRadius: 6, }} > - + No open invitations @@ -182,7 +193,7 @@ export default function ContactsScreen({ navigation }) { index === contactInvitations.length - 1 ? 6 : 0, }} containerStyle={{ - borderColor: colors.divider, + borderColor: theme.colors.accent, borderLeftWidth: StyleSheet.hairlineWidth, borderRightWidth: StyleSheet.hairlineWidth, borderTopLeftRadius: index === 0 ? 6 : 0, @@ -209,7 +220,7 @@ export default function ContactsScreen({ navigation }) { ) { const repositoryResult = useRepository(route.params.repositoryId); const userResult = useUser(); const deviceResult = useDevice(); diff --git a/app/src/components/screens/NoteScreen.tsx b/app/src/components/screens/NoteScreen.tsx index 6043083..3a58c3b 100644 --- a/app/src/components/screens/NoteScreen.tsx +++ b/app/src/components/screens/NoteScreen.tsx @@ -14,14 +14,17 @@ import UploadArrow from "../ui/UploadArrow"; import DownloadArrow from "../ui/DownloadArrow"; import ServerSyncInfo from "../ui/ServerSyncInfo"; import { Repository } from "../../types"; -import colors from "../../styles/colors"; +import useCurrentTheme from "../../hooks/useCurrentTheme"; + +import * as Colors from "../../styles/colors"; + import * as mutationQueue from "../../hooks/useSyncUtils/mutationQueue"; import { loadEditorSourceForAndroid } from "../../utils/editorSource/editorSource"; import { useEditorSource } from "../../context/EditorSourceContext"; +import { NoteStackProps } from "../NavigationTypes"; const styles = StyleSheet.create({ container: { - backgroundColor: "#fff", // changing it to flex: 1 leads to weird behaviour e.g. notes don't render at the top and it somehow flickers height: "100%", }, @@ -47,6 +50,8 @@ type HeaderRightProps = { }; const HeaderRight = ({ navigation, repository }: HeaderRightProps) => { + const theme = useCurrentTheme(); + const [ uploadSyncState, setUploadSyncState, @@ -111,14 +116,14 @@ const HeaderRight = ({ navigation, repository }: HeaderRightProps) => { uploadSyncState.state === "unknown" ? "#aaa" : uploadSyncState.state === "retry-in-progress" - ? colors.error - : colors.success + ? theme.colors.error + : Colors.SUCCESS } /> { { navigation.navigate("NoteSettings", { id: repository.id, @@ -140,7 +145,7 @@ const HeaderRight = ({ navigation, repository }: HeaderRightProps) => { /> { navigation.navigate("NoteSettings", { id: repository.id, @@ -153,13 +158,17 @@ const HeaderRight = ({ navigation, repository }: HeaderRightProps) => { let androidEditorSource = { html: null }; -export default function NoteScreen({ route, navigation }) { +export default function NoteScreen({ + route, + navigation, +}: NoteStackProps<"Note">) { const { id, isNew } = route.params; + const theme = useCurrentTheme(); const yDocRef = useRef(null); const contentRef = useRef(null); const initializedRef = useRef(false); const webViewRef = useRef(null); - const [, updateState] = React.useState(); + const [, updateState] = React.useState(); const [isDeleted, setIsDeleted] = React.useState(false); const forceUpdate = useCallback(() => updateState({}), []); const editorSource = useEditorSource(); @@ -230,7 +239,7 @@ export default function NoteScreen({ route, navigation }) { return ( - + ); } @@ -250,7 +259,7 @@ export default function NoteScreen({ route, navigation }) { renderLoading={() => ( - + )} onMessage={async (event) => { diff --git a/app/src/components/screens/NoteSettingsScreen.tsx b/app/src/components/screens/NoteSettingsScreen.tsx index c142994..96f8c66 100644 --- a/app/src/components/screens/NoteSettingsScreen.tsx +++ b/app/src/components/screens/NoteSettingsScreen.tsx @@ -1,36 +1,40 @@ import React from "react"; +import { Alert } from "react-native"; import { ListItem } from "react-native-elements"; import { useClient } from "urql"; -import Spacer from "../ui/Spacer"; -import ScrollScreenContainer from "../ui/ScrollScreenContainer"; -import ListHeader from "../ui/ListHeader"; -import ListItemInfo from "../ui/ListItemInfo"; +import { NoteStackProps } from "../NavigationTypes"; import useRepository from "../../hooks/useRepository"; import useUser from "../../hooks/useUser"; import useDevice from "../../hooks/useDevice"; -import { getIdentityKeys } from "../../utils/device"; -import deleteRepository from "../../utils/server/deleteRepository"; -import removeCollaboratorFromRepository from "../../utils/server/removeCollaboratorFromRepository"; -import { Alert } from "react-native"; -import * as repositoryStore from "../../utils/repositoryStore"; -import Text from "../ui/Text"; -import { RepositoryUpdate, RepositoryCollaborator } from "../../types"; import usePrivateInfo from "../../hooks/usePrivateInfo"; import useVerifiedDevicesForRepository from "../../hooks/useVerifiedDevicesForRepository"; -import ServerSyncInfo from "../ui/ServerSyncInfo"; import useHasActiveLicense from "../../hooks/useHasActiveLicense"; +import { RepositoryUpdate, RepositoryCollaborator } from "../../types"; +import Text from "../ui/Text"; +import Spacer from "../ui/Spacer"; +import ScrollScreenContainer from "../ui/ScrollScreenContainer"; +import ListHeader from "../ui/ListHeader"; +import ListItemInfo from "../ui/ListItemInfo"; +import ServerSyncInfo from "../ui/ServerSyncInfo"; import ListWrapper from "../ui/ListWrapper"; import ListItemDivider from "../ui/ListItemDivider"; import OutlineButton from "../ui/OutlineButton"; -import colors from "../../styles/colors"; import UploadArrow from "../ui/UploadArrow"; import DownloadArrow from "../ui/DownloadArrow"; +import removeCollaboratorFromRepository from "../../utils/server/removeCollaboratorFromRepository"; +import * as repositoryStore from "../../utils/repositoryStore"; +import { getIdentityKeys } from "../../utils/device"; +import deleteRepository from "../../utils/server/deleteRepository"; +import colors from "../../styles/colors"; type RepositoryCollaboratorWithMostRecentUpdate = RepositoryCollaborator & { mostRecentUpdate?: RepositoryUpdate; }; -export default function NoteSettingsScreen({ navigation, route }) { +export default function NoteSettingsScreen({ + navigation, + route, +}: NoteStackProps<"NoteSettings">) { const repositoryResult = useRepository(route.params.id); const userResult = useUser(); const deviceResult = useDevice(); diff --git a/app/src/components/screens/NotesScreen.tsx b/app/src/components/screens/NotesScreen.tsx index f2e26d5..34fdab2 100644 --- a/app/src/components/screens/NotesScreen.tsx +++ b/app/src/components/screens/NotesScreen.tsx @@ -13,30 +13,33 @@ import EmptyList from "../ui/EmptyList"; import ServerSyncInfo from "../ui/ServerSyncInfo"; import useHasActiveLicense from "../../hooks/useHasActiveLicense"; import useRepositoriesWithMutationRetries from "../../hooks/useRepositoriesWithMutationRetries"; -import colors from "../../styles/colors"; import Spacer from "../ui/Spacer"; import OutlineButton from "../ui/OutlineButton"; import ListItemDivider from "../ui/ListItemDivider"; import DownloadArrow from "../ui/DownloadArrow"; import UploadArrow from "../ui/UploadArrow"; import Text from "../ui/Text"; +import { NoteStackProps } from "../NavigationTypes"; +import useCurrentTheme from "../../hooks/useCurrentTheme"; +import { legacyColors } from "../../styles/legacyColors"; const styles = StyleSheet.create({ container: { flex: 1, width: "100%", - backgroundColor: colors.background, + paddingTop: 16, }, note: { padding: 10, fontSize: 16, height: 44, - borderBottomColor: colors.divider, + borderBottomColor: legacyColors.divider, borderBottomWidth: StyleSheet.hairlineWidth, }, }); -export default function Notes({ navigation }) { +export default function Notes({ navigation }: NoteStackProps<"Notes">) { + const theme = useCurrentTheme(); const repositoriesState = useRepositories(navigation); const privateInfoResult = usePrivateInfo(); const hasActiveLicenseResult = useHasActiveLicense(); @@ -78,7 +81,9 @@ export default function Notes({ navigation }) { : []; return ( - + - + Notes - + Sorted by last update {index === 0 ? null : } { navigation.navigate("Note", { id: item.id, @@ -197,7 +206,7 @@ export default function Notes({ navigation }) { index === notesList.length - 1 ? 6 : 0, }} containerStyle={{ - borderColor: colors.divider, + backgroundColor: theme.colors.surface, borderLeftWidth: StyleSheet.hairlineWidth, borderRightWidth: StyleSheet.hairlineWidth, borderTopLeftRadius: index === 0 ? 6 : 0, @@ -215,13 +224,16 @@ export default function Notes({ navigation }) { }} > - + {item.name} {names.length > 0 ? `Shared with ${names.join(", ")}` @@ -233,14 +245,14 @@ export default function Notes({ navigation }) { <> ) : null} {item.updatedAt ? formatDistanceToNow(new Date(item.updatedAt)) : "-"} - + ); diff --git a/app/src/components/screens/OnboardingScreen.tsx b/app/src/components/screens/OnboardingScreen.tsx index 30389d4..e3153f1 100644 --- a/app/src/components/screens/OnboardingScreen.tsx +++ b/app/src/components/screens/OnboardingScreen.tsx @@ -26,11 +26,11 @@ import { Y } from "../../vendor/index.js"; import { Icon } from "react-native-elements"; import colors from "../../styles/colors"; import ActivityIndicator from "../ui/ActivityIndicator"; +import useCurrentTheme from "../../hooks/useCurrentTheme"; const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: colors.background, justifyContent: "center", paddingHorizontal: 20, paddingVertical: 60, @@ -57,6 +57,7 @@ export default function OnboardingScreen({ navigation }) { const [processState, setProcessState] = React.useState< "default" | "createDeviceAndKeys" | "createUser" | "ready" | "failed" >("default"); + const theme = useCurrentTheme(); const [, createUser] = useMutation(createUserMutation); const client = useClient(); @@ -116,7 +117,9 @@ export default function OnboardingScreen({ navigation }) { }, []); return ( - + @@ -145,7 +148,7 @@ export default function OnboardingScreen({ navigation }) { {/* avoid the text to jump left then right due the icon loading, by applying a fixed with */} {processState === "default" ? ( - + ) : null} {processState === "createDeviceAndKeys" ? ( @@ -154,7 +157,7 @@ export default function OnboardingScreen({ navigation }) { ) : null} @@ -169,7 +172,12 @@ export default function OnboardingScreen({ navigation }) { ) : null} {processState === "createUser" ? : null} {processState === "ready" ? ( - + ) : null} {"\xa0\xa0"} Setting up User Account diff --git a/app/src/components/screens/WelcomeScreen.tsx b/app/src/components/screens/WelcomeScreen.tsx index 9f05f97..26e6b5f 100644 --- a/app/src/components/screens/WelcomeScreen.tsx +++ b/app/src/components/screens/WelcomeScreen.tsx @@ -11,11 +11,12 @@ import Text from "../ui/Text"; import OutlineButton from "../ui/OutlineButton"; import colors from "../../styles/colors"; import { privacyPolicyLink, termsOfServiceLink } from "../../utils/links"; +import useCurrentTheme from "../../hooks/useCurrentTheme"; +import { StackNavigationProp } from "@react-navigation/stack"; const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: colors.background, justifyContent: "flex-end", paddingHorizontal: 10, paddingVertical: 60, @@ -36,9 +37,17 @@ const styles = StyleSheet.create({ }, }); -export default function WelcomeScreen({ navigation }) { +interface WelcomeScreenProps { + navigation: StackNavigationProp; +} + +export default function WelcomeScreen({ navigation }: WelcomeScreenProps) { + const theme = useCurrentTheme(); + console.log(theme); return ( - + - + {props.children} diff --git a/app/src/components/ui/ListItemButton.tsx b/app/src/components/ui/ListItemButton.tsx index db5dfc7..6a371d6 100644 --- a/app/src/components/ui/ListItemButton.tsx +++ b/app/src/components/ui/ListItemButton.tsx @@ -1,7 +1,7 @@ import React from "react"; import { StyleSheet } from "react-native"; import { ListItem, Icon } from "react-native-elements"; -import colors from "../../styles/colors"; +import useCurrentTheme from "../../hooks/useCurrentTheme"; type Props = { children: string; @@ -10,10 +10,11 @@ type Props = { }; export default function ListItemButton(props: Props) { + const theme = useCurrentTheme(); const style = props.style || {}; return ( {props.children} - + ); } diff --git a/app/src/components/ui/ListItemExternalLink.tsx b/app/src/components/ui/ListItemExternalLink.tsx index 3b5d788..f5e8782 100644 --- a/app/src/components/ui/ListItemExternalLink.tsx +++ b/app/src/components/ui/ListItemExternalLink.tsx @@ -1,6 +1,7 @@ import React from "react"; import { StyleSheet } from "react-native"; import { ListItem, Icon } from "react-native-elements"; +import useCurrentTheme from "../../hooks/useCurrentTheme"; import colors from "../../styles/colors"; type Props = { @@ -10,10 +11,11 @@ type Props = { }; export default function ListItemExternalLink(props: Props) { + const theme = useCurrentTheme(); const style = props.style || {}; return ( - + {props.children} - + ); } diff --git a/app/src/components/ui/ListItemInfo.tsx b/app/src/components/ui/ListItemInfo.tsx index f872bb0..cfa89c9 100644 --- a/app/src/components/ui/ListItemInfo.tsx +++ b/app/src/components/ui/ListItemInfo.tsx @@ -3,6 +3,7 @@ import { Clipboard, Alert } from "react-native"; import { ListItem, Icon } from "react-native-elements"; import colors from "../../styles/colors"; import ListItemDivider from "../ui/ListItemDivider"; +import useCurrentTheme from "../../hooks/useCurrentTheme"; type Props = { children: string; @@ -11,22 +12,26 @@ type Props = { }; export default function ListItemInfo(props: Props) { + const theme = useCurrentTheme(); return ( <> {props.topDivider ? : null} { await Clipboard.setString(props.children); Alert.alert("Copied to Clipboard"); }} > - + {props.label} - {props.children} + + {props.children} + diff --git a/app/src/components/ui/ListItemLink.tsx b/app/src/components/ui/ListItemLink.tsx index 8cac151..2355e7d 100644 --- a/app/src/components/ui/ListItemLink.tsx +++ b/app/src/components/ui/ListItemLink.tsx @@ -2,6 +2,8 @@ import React from "react"; import { StyleProp, ViewStyle, View } from "react-native"; import { ListItem } from "react-native-elements"; import colors from "../../styles/colors"; +import useCurrentTheme from "../../hooks/useCurrentTheme"; + import ListItemDivider from "../ui/ListItemDivider"; type Props = { @@ -14,21 +16,25 @@ type Props = { }; export default function ListItemLink(props: Props) { + const theme = useCurrentTheme(); + + const color = props.disabled ? theme.colors.accent : theme.colors.text; + const { topDivider, ...otherProps } = props; return ( {topDivider ? : null} - + - - {props.children} - + {props.children} - + ); diff --git a/app/src/components/ui/ListWrapper.tsx b/app/src/components/ui/ListWrapper.tsx index 9c5960c..7562e71 100644 --- a/app/src/components/ui/ListWrapper.tsx +++ b/app/src/components/ui/ListWrapper.tsx @@ -1,6 +1,6 @@ import React from "react"; import { View, StyleSheet, StyleProp, ViewStyle } from "react-native"; -import colors from "../../styles/colors"; +import useCurrentTheme from "../../hooks/useCurrentTheme"; const styles = StyleSheet.create({ listWrapper: { @@ -8,7 +8,6 @@ const styles = StyleSheet.create({ borderRadius: 6, marginLeft: 10, marginRight: 10, - borderColor: colors.divider, borderWidth: StyleSheet.hairlineWidth, }, }); @@ -19,6 +18,18 @@ type Props = { }; export default function ListWrapper(props: Props) { + const theme = useCurrentTheme(); const style = props.style ? props.style : {}; - return ; + return ( + + ); } diff --git a/app/src/components/ui/OutlineButton.tsx b/app/src/components/ui/OutlineButton.tsx index 26f8051..dc957cf 100644 --- a/app/src/components/ui/OutlineButton.tsx +++ b/app/src/components/ui/OutlineButton.tsx @@ -2,7 +2,7 @@ import React from "react"; import { Button } from "react-native-paper"; import { StyleSheet } from "react-native"; import { Icon } from "react-native-elements"; -import colors from "../../styles/colors"; +import useCurrentTheme from "../../hooks/useCurrentTheme"; type ButtonProps = { iconType?: "plus" | "minus" | "share" | "verify"; @@ -17,8 +17,9 @@ type ButtonProps = { export default function MyButton(props: ButtonProps) { const style = props.style ? props.style : {}; + const theme = useCurrentTheme(); let icon = undefined; - let labelColor = props.secondary ? colors.textBright : colors.primary; + let labelColor = props.secondary ? theme.colors.accent : theme.colors.primary; if (props.iconType === "plus") { icon = ({ color }: { color: string; size: any }) => { return ( @@ -46,7 +47,7 @@ export default function MyButton(props: ButtonProps) { /> ); }; - labelColor = colors.error; + labelColor = theme.colors.error; } if (props.iconType === "share") { icon = ({ color }: { color: string; size: any }) => { @@ -82,7 +83,7 @@ export default function MyButton(props: ButtonProps) {