Skip to content

Commit

Permalink
Merge pull request #102 from ineshbose/feature/100-user_settings
Browse files Browse the repository at this point in the history
User settings
  • Loading branch information
ineshbose committed Jan 28, 2022
2 parents ad82b67 + 5db8bdd commit 62eeb2a
Show file tree
Hide file tree
Showing 13 changed files with 515 additions and 62 deletions.
22 changes: 20 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,27 @@ import { MaterialIconsPack } from './app/components/AppIcons';
import { ColorScheme } from './app/types';
import { ThemeContext } from './app/contexts/ThemeContext';
import { ImageProps } from 'react-native';
import { getData, storeData } from './app/api/store';

export default function App() {
const isLoadingComplete = useCachedResources();
const [theme, setTheme] = React.useState<ColorScheme>('light');

React.useEffect(() => {
const getSetTheme = async () => {
const themeData = (await getData('theme', 'light')) as ColorScheme;
setTheme(themeData);
};

getSetTheme();
}, [setTheme]);

const switchTheme = async () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
await storeData('theme', newTheme);
};

const toggleIcon = (props: Partial<ImageProps> | undefined) => (
<Icon
key="themeToggle"
Expand All @@ -34,15 +50,17 @@ export default function App() {
appearance="ghost"
status="basic"
accessoryLeft={toggleIcon}
onPress={() => setTheme(theme === 'light' ? 'dark' : 'light')}
onPress={switchTheme}
{...props}
/>
);

return isLoadingComplete ? (
<>
<IconRegistry icons={MaterialIconsPack} />
<ThemeContext.Provider value={{ theme, ThemeToggle }}>
<ThemeContext.Provider
value={{ theme, setTheme, switchTheme, ThemeToggle }}
>
<ApplicationProvider
{...eva}
customMapping={mapping}
Expand Down
12 changes: 5 additions & 7 deletions src/app/api/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage';

export const storeData = async (key: string, value: any) => {
try {
await AsyncStorage.setItem(`@${key}`, value);
return await AsyncStorage.setItem(`@${key}`, value);
} catch (e) {
// saving error
}
Expand All @@ -11,18 +11,16 @@ export const storeData = async (key: string, value: any) => {
export const storeObject = async (key: string, value: object) => {
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(`@${key}`, jsonValue);
return await AsyncStorage.setItem(`@${key}`, jsonValue);
} catch (e) {
// saving error
}
};

export const getData = async (key: string) => {
export const getData = async (key: string, _default?: string) => {
try {
const value = await AsyncStorage.getItem(`@${key}`);
if (value !== null) {
// value previous stored
}
return value !== null ? value : _default;
} catch (e) {
// error reading value
}
Expand All @@ -39,7 +37,7 @@ export const getObject = async (key: string) => {

export const removeItem = async (key: string) => {
try {
await AsyncStorage.removeItem(`@${key}`);
return await AsyncStorage.removeItem(`@${key}`);
} catch (e) {
// error removing value
}
Expand Down
6 changes: 4 additions & 2 deletions src/app/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export const createUser = async (
}
};

export const updateUser = async (props: UpdateData<User>) => {
export const updateUser = async (
props: UpdateData<User> & { password?: string; old_password?: string }
) => {
try {
const { id } = props;
const response = await axiosInstance.patch<User>(
Expand All @@ -34,7 +36,7 @@ export const updateUser = async (props: UpdateData<User>) => {
);
return response.data;
} catch (e) {
// handle error
throw axios.isAxiosError(e) ? (e.response?.data as FormError) : e;
}
};

Expand Down
9 changes: 8 additions & 1 deletion src/app/contexts/AppContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,14 @@ export const ContextProvider = ({ children }: ChildComponents) => {
items,
headerAction,
loading,
helpers: { signIn, signUp, signOut, setItems, setHeaderAction },
helpers: {
signIn,
signUp,
signOut,
setUser,
setItems,
setHeaderAction,
},
}}
>
{children}
Expand Down
4 changes: 4 additions & 0 deletions src/app/contexts/ThemeContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import { ColorScheme } from '../types';

type ThemeContextType = {
theme: ColorScheme;
setTheme: React.Dispatch<React.SetStateAction<ColorScheme>>;
switchTheme: Function;
ThemeToggle: (props: ButtonProps | undefined) => JSX.Element;
};

export const ThemeContext = React.createContext<ThemeContextType>({
theme: 'light',
setTheme: () => {},
switchTheme: () => {},
ThemeToggle: () => <Button />,
});

Expand Down
112 changes: 71 additions & 41 deletions src/app/navigation/BottomTabNavigator.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {
BottomTabBarProps,
BottomTabHeaderProps,
BottomTabScreenProps,
createBottomTabNavigator,
} from '@react-navigation/bottom-tabs';
import * as React from 'react';
import { Image, ImageProps, View } from 'react-native';
import { Image, ImageProps, Pressable } from 'react-native';
import {
RootTabParamList,
RouteActionIcon,
Expand All @@ -16,9 +17,9 @@ import {
BottomNavigationTab,
Button,
ButtonGroup,
Card,
Icon,
Modal,
MenuItem,
OverflowMenu,
Text,
TopNavigation,
} from '@ui-kitten/components';
Expand All @@ -27,12 +28,13 @@ import StatsPage from '../screens/StatsPage';
import JournalPage from '../screens/JournalPage';
import ResourcesPage from '../screens/ResourcesPage';
import { useAppContext } from '../contexts/AppContext';
import { useThemeContext } from '../contexts/ThemeContext';
import { ParamListBase, RouteProp } from '@react-navigation/native';
import SettingsPage from '../screens/SettingsPage';
import { IconOptions } from '../types';

const BottomTab = createBottomTabNavigator<RootTabParamList>();

const headerButtonIcons: RouteActionIcon<RootTabParamList> = {
const headerButtonIcons: RouteActionIcon<Partial<RootTabParamList>> = {
Home: 'edit',
Journal: 'calendar-today',
Stats: 'calendar-today',
Expand Down Expand Up @@ -62,20 +64,31 @@ const tabs: RootTab[] = [
component: ResourcesPage,
icon: 'menu-book',
},
{
name: 'Settings',
component: SettingsPage,
icon: 'settings',
hideTab: true,
},
];

export default function BottomTabNavigator() {
export default function BottomTabNavigator({
navigation,
}: BottomTabScreenProps<RootTabParamList>) {
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
user,
headerAction,
helpers: { signOut, setHeaderAction },
} = useAppContext();
const { ThemeToggle } = useThemeContext();
const [modalVisible, setModalVisible] = React.useState<boolean>(false);

const navigationLeftAccessory = (props: {} | undefined) => (
<View {...props} style={{ flexDirection: 'row', alignItems: 'center' }}>
<Pressable
{...props}
style={{ flexDirection: 'row', alignItems: 'center' }}
onPress={() => navigation.navigate('Home')}
>
<Image
style={{
height: 30,
Expand All @@ -87,7 +100,7 @@ export default function BottomTabNavigator() {
}}
/>
<Text category="s2">Portion Mate</Text>
</View>
</Pressable>
);

const navRightAccessoryActionIcon = (
Expand All @@ -102,37 +115,57 @@ export default function BottomTabNavigator() {
/>
);

const cardIcons = (
props: Partial<ImageProps> | undefined,
name: IconOptions
) => <Icon name={name} {...props} />;

const userAvatar = (props: Partial<ImageProps> | undefined) => (
<Icon key="user" name="person" {...props} />
);

const userOptionsToggle = (props: {} | undefined) => (
<Button
accessoryLeft={userAvatar}
onPress={() => setModalVisible(true)}
appearance="ghost"
{...props}
/>
);

const navigationRightAccessory = (
props: {} | undefined,
{ route }: BottomTabHeaderProps
) => (
<ButtonGroup appearance="ghost" {...props}>
<Button
accessoryLeft={(p) => navRightAccessoryActionIcon(p, route)}
onPress={() =>
setHeaderAction(headerAction === route.name ? '' : route.name)
}
/>
<Button
accessoryLeft={userAvatar}
onPress={() => setModalVisible(true)}
/>
<Modal
visible={modalVisible}
{headerButtonIcons[route.name as RouteNames<RootTabParamList>] ? (
<Button
accessoryLeft={(p) => navRightAccessoryActionIcon(p, route)}
onPress={() =>
setHeaderAction(headerAction === route.name ? '' : route.name)
}
/>
) : (
<></>
)}
<OverflowMenu
anchor={userOptionsToggle}
backdropStyle={{ backgroundColor: 'rgba(0, 0, 0, 0.5)' }}
onBackdropPress={() => setModalVisible(false)}
visible={modalVisible}
onSelect={() => setModalVisible(false)}
>
<Card>
{/* {user?.forename && <Text>Hello, {user.forename}</Text>} */}
<Button disabled>Settings</Button>
<ThemeToggle appearance="filled" />
<Button onPress={() => signOut()}>Sign Out</Button>
</Card>
</Modal>
<MenuItem
title="Settings"
accessoryLeft={(p) => cardIcons(p, 'settings')}
onPress={() => navigation.navigate('Settings')}
/>
<MenuItem
title="Sign Out"
accessoryLeft={(p) => cardIcons(p, 'logout')}
onPress={() => signOut()}
/>
</OverflowMenu>
</ButtonGroup>
);

Expand All @@ -152,12 +185,7 @@ export default function BottomTabNavigator() {
size: number;
}
| Partial<ImageProps>
) => {
if (props) {
// props.styles.tintColor = '#fff';
}
return <Icon name={tab.icon} {...props} />;
};
) => <Icon name={tab.icon} {...props} />;

const TabBar = (props: BottomTabBarProps) => (
<BottomNavigation
Expand All @@ -168,13 +196,15 @@ export default function BottomTabNavigator() {
appearance="noIndicator"
{...props}
>
{tabs.map((tab) => (
<BottomNavigationTab
key={tab.name}
icon={(p) => navTabIcon(tab, p)}
title={tab.name}
/>
))}
{tabs
.filter((tab) => !tab.hideTab)
.map((tab) => (
<BottomNavigationTab
key={tab.name}
icon={(p) => navTabIcon(tab, p)}
title={tab.name}
/>
))}
</BottomNavigation>
);

Expand Down
1 change: 1 addition & 0 deletions src/app/navigation/LinkingConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const linking: LinkingOptions<RootStackParamList> = {
Journal: 'journal',
Stats: 'stats',
Resources: 'resources',
Settings: 'settings',
},
},
Auth: {
Expand Down
6 changes: 4 additions & 2 deletions src/app/screens/ResourcesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,20 @@ import { useAppContext } from '../contexts/AppContext';
export default function ResourcesPage() {
const { headerAction } = useAppContext();
const isAction = headerAction === 'Resources';
const [fetched, setFetched] = React.useState<boolean>(false);
const [resources, setResources] = React.useState<Resources>([]);
const [selectedResource, setSelectedResource] = React.useState<Resource>();

React.useEffect(() => {
const getItems = async () => {
if (!(resources && resources.length > 0)) {
if (!fetched) {
setResources((await getResources()) as Resources);
setFetched(true);
}
};

getItems();
}, [resources, setResources]);
}, [fetched, setResources, setFetched]);

const bookmarkAction = async (resource: Resource) => {
const resourceIdx = resources.indexOf(resource);
Expand Down
Loading

0 comments on commit 62eeb2a

Please sign in to comment.