Compare commits
4 commits
53ec2bb578
...
de8820eada
Author | SHA1 | Date | |
---|---|---|---|
|
de8820eada | ||
|
dc944060e1 | ||
|
2c11addf40 | ||
|
8bacddc7b5 |
10 changed files with 442 additions and 325 deletions
4
App.tsx
4
App.tsx
|
@ -52,10 +52,6 @@ import { retrieveLoginToken } from './src/utils/loginToken';
|
|||
import { setupNotifications } from './src/utils/Notifications';
|
||||
import { TabRoutes } from './src/navigation/TabNavigator';
|
||||
|
||||
// Native optimizations https://reactnavigation.org/docs/react-native-screens
|
||||
// Crashes app when navigating away from webview on android 9+
|
||||
// enableScreens(true);
|
||||
|
||||
initLocales();
|
||||
setupNotifications();
|
||||
|
||||
|
|
502
package-lock.json
generated
502
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -48,7 +48,7 @@
|
|||
"react-native-permissions": "3.0.5",
|
||||
"react-native-push-notification": "8.1.0",
|
||||
"react-native-reanimated": "1.13.2",
|
||||
"react-native-render-html": "5.1.1",
|
||||
"react-native-render-html": "6.1.0",
|
||||
"react-native-safe-area-context": "3.3.2",
|
||||
"react-native-screens": "3.7.0",
|
||||
"react-native-splash-screen": "3.2.0",
|
||||
|
|
|
@ -18,9 +18,13 @@
|
|||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Text } from 'react-native-paper';
|
||||
import HTML from 'react-native-render-html';
|
||||
import { GestureResponderEvent, Linking } from 'react-native';
|
||||
import { Text, useTheme } from 'react-native-paper';
|
||||
import HTML, {
|
||||
CustomRendererProps,
|
||||
TBlock,
|
||||
TText,
|
||||
} from 'react-native-render-html';
|
||||
import { Dimensions, GestureResponderEvent, Linking } from 'react-native';
|
||||
|
||||
type PropsType = {
|
||||
html: string;
|
||||
|
@ -30,37 +34,54 @@ type PropsType = {
|
|||
* Abstraction layer for Agenda component, using custom configuration
|
||||
*/
|
||||
function CustomHTML(props: PropsType) {
|
||||
const theme = useTheme();
|
||||
const openWebLink = (_event: GestureResponderEvent, link: string) => {
|
||||
Linking.openURL(link);
|
||||
};
|
||||
|
||||
const getBasicText = (
|
||||
_htmlAttribs: any,
|
||||
children: any,
|
||||
_convertedCSSStyles: any,
|
||||
passProps: any
|
||||
) => {
|
||||
return <Text {...passProps}>{children}</Text>;
|
||||
// Why is this so complex?? I just want to replace the default Text element with the one
|
||||
// from react-native-paper
|
||||
// Might need to read the doc a bit more: https://meliorence.github.io/react-native-render-html/
|
||||
// For now this seems to work
|
||||
const getBasicText = (rendererProps: CustomRendererProps<TBlock>) => {
|
||||
let text: TText | undefined;
|
||||
if (rendererProps.tnode.children.length > 0) {
|
||||
const phrasing = rendererProps.tnode.children[0];
|
||||
if (phrasing.children.length > 0) {
|
||||
text = phrasing.children[0] as TText;
|
||||
}
|
||||
}
|
||||
if (text) {
|
||||
return <Text>{text.data}</Text>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getListBullet = () => {
|
||||
return <Text>- </Text>;
|
||||
};
|
||||
|
||||
// Surround description with p to allow text styling if the description is not html
|
||||
return (
|
||||
<HTML
|
||||
html={`<p>${props.html}</p>`}
|
||||
// Surround description with p to allow text styling if the description is not html
|
||||
source={{ html: `<p>${props.html}</p>` }}
|
||||
// Use Paper Text instead of React
|
||||
renderers={{
|
||||
p: getBasicText,
|
||||
li: getBasicText,
|
||||
}}
|
||||
listsPrefixesRenderers={{
|
||||
ul: getListBullet,
|
||||
// Sometimes we have images inside the text, just ignore them
|
||||
ignoredDomTags={['img']}
|
||||
// Ignore text color
|
||||
ignoredStyles={['color', 'backgroundColor']}
|
||||
contentWidth={Dimensions.get('window').width - 50}
|
||||
renderersProps={{
|
||||
a: {
|
||||
onPress: openWebLink,
|
||||
},
|
||||
ul: {
|
||||
markerTextStyle: {
|
||||
color: theme.colors.text,
|
||||
},
|
||||
},
|
||||
}}
|
||||
ignoredTags={['img']}
|
||||
ignoredStyles={['color', 'background-color']}
|
||||
onLinkPress={openWebLink}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ function ClubListScreen() {
|
|||
headerTitleContainerStyle:
|
||||
Platform.OS === 'ios'
|
||||
? { marginHorizontal: 0, width: '70%' }
|
||||
: { marginHorizontal: 0, right: 50, left: 50 },
|
||||
: { width: '100%' },
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [navigation]);
|
||||
|
|
|
@ -39,6 +39,7 @@ import { useFocusEffect, useNavigation } from '@react-navigation/native';
|
|||
import { TabRoutes } from '../../navigation/TabNavigator';
|
||||
import { useShouldShowMascot } from '../../context/preferencesContext';
|
||||
import { useLogin } from '../../context/loginContext';
|
||||
import { saveLoginToken } from '../../utils/loginToken';
|
||||
|
||||
type Props = StackScreenProps<MainStackParamsList, MainRoutes.Login>;
|
||||
|
||||
|
@ -100,6 +101,7 @@ function LoginScreen(props: Props) {
|
|||
if (homeMascot.shouldShow) {
|
||||
homeMascot.setShouldShow(false);
|
||||
}
|
||||
saveLoginToken(token);
|
||||
setLogin(token);
|
||||
if (!nextScreen) {
|
||||
navigation.goBack();
|
||||
|
|
|
@ -81,6 +81,15 @@ function GroupSelectionScreen() {
|
|||
const favoriteGroups = getFavoriteGroups();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const getSearchBar = () => {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Searchbar
|
||||
placeholder={i18n.t('screens.proximo.search')}
|
||||
onChangeText={setCurrentSearchString}
|
||||
/>
|
||||
);
|
||||
};
|
||||
navigation.setOptions({
|
||||
headerTitle: getSearchBar,
|
||||
headerBackTitleVisible: false,
|
||||
|
@ -91,16 +100,6 @@ function GroupSelectionScreen() {
|
|||
});
|
||||
}, [navigation]);
|
||||
|
||||
const getSearchBar = () => {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Searchbar
|
||||
placeholder={i18n.t('screens.proximo.search')}
|
||||
onChangeText={setCurrentSearchString}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a render item for the given article
|
||||
*
|
||||
|
|
|
@ -120,6 +120,7 @@ function ProximoListScreen(props: Props) {
|
|||
const theme = useTheme();
|
||||
const { articles, setArticles } = useCachedProximoArticles();
|
||||
const modalRef = useRef<Modalize>(null);
|
||||
const navParams = props.route.params;
|
||||
|
||||
const [currentSearchString, setCurrentSearchString] = useState('');
|
||||
const [currentSortMode, setCurrentSortMode] = useState(2);
|
||||
|
@ -130,6 +131,70 @@ function ProximoListScreen(props: Props) {
|
|||
const sortModes = [sortPrice, sortPriceReverse, sortName, sortNameReverse];
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const getSearchBar = () => {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Searchbar
|
||||
placeholder={i18n.t('screens.proximo.search')}
|
||||
onChangeText={setCurrentSearchString}
|
||||
autoFocus={navParams.shouldFocusSearchBar}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const getModalSortMenu = () => {
|
||||
return (
|
||||
<View style={styles.modalContainer}>
|
||||
<Title style={styles.sortTitle}>
|
||||
{i18n.t('screens.proximo.sortOrder')}
|
||||
</Title>
|
||||
<RadioButton.Group
|
||||
onValueChange={setSortMode}
|
||||
value={currentSortMode.toString()}
|
||||
>
|
||||
<RadioButton.Item
|
||||
label={i18n.t('screens.proximo.sortPrice')}
|
||||
value={'0'}
|
||||
/>
|
||||
<RadioButton.Item
|
||||
label={i18n.t('screens.proximo.sortPriceReverse')}
|
||||
value={'1'}
|
||||
/>
|
||||
<RadioButton.Item
|
||||
label={i18n.t('screens.proximo.sortName')}
|
||||
value={'2'}
|
||||
/>
|
||||
<RadioButton.Item
|
||||
label={i18n.t('screens.proximo.sortNameReverse')}
|
||||
value={'3'}
|
||||
/>
|
||||
</RadioButton.Group>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
const setSortMode = (mode: string) => {
|
||||
const currentMode = parseInt(mode, 10);
|
||||
setCurrentSortMode(currentMode);
|
||||
if (modalRef.current && currentMode !== currentSortMode) {
|
||||
modalRef.current.close();
|
||||
}
|
||||
};
|
||||
const getSortMenuButton = () => {
|
||||
return (
|
||||
<MaterialHeaderButtons>
|
||||
<Item
|
||||
title="main"
|
||||
iconName="sort"
|
||||
onPress={() => {
|
||||
setModalCurrentDisplayItem(getModalSortMenu());
|
||||
if (modalRef.current) {
|
||||
modalRef.current.open();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MaterialHeaderButtons>
|
||||
);
|
||||
};
|
||||
|
||||
navigation.setOptions({
|
||||
headerRight: getSortMenuButton,
|
||||
headerTitle: getSearchBar,
|
||||
|
@ -137,21 +202,9 @@ function ProximoListScreen(props: Props) {
|
|||
headerTitleContainerStyle:
|
||||
Platform.OS === 'ios'
|
||||
? { marginHorizontal: 0, width: '70%' }
|
||||
: { marginHorizontal: 0, right: 50, left: 50 },
|
||||
: { width: '100%' },
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [navigation, currentSortMode]);
|
||||
|
||||
/**
|
||||
* Callback used when clicking on the sort menu button.
|
||||
* It will open the modal to show a sort selection
|
||||
*/
|
||||
const onSortMenuPress = () => {
|
||||
setModalCurrentDisplayItem(getModalSortMenu());
|
||||
if (modalRef.current) {
|
||||
modalRef.current.open();
|
||||
}
|
||||
};
|
||||
}, [navigation, currentSortMode, navParams.shouldFocusSearchBar]);
|
||||
|
||||
/**
|
||||
* Callback used when clicking an article in the list.
|
||||
|
@ -166,19 +219,6 @@ function ProximoListScreen(props: Props) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the current sort mode.
|
||||
*
|
||||
* @param mode The number representing the mode
|
||||
*/
|
||||
const setSortMode = (mode: string) => {
|
||||
const currentMode = parseInt(mode, 10);
|
||||
setCurrentSortMode(currentMode);
|
||||
if (modalRef.current && currentMode !== currentSortMode) {
|
||||
modalRef.current.close();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a color depending on the quantity available
|
||||
*
|
||||
|
@ -197,35 +237,6 @@ function ProximoListScreen(props: Props) {
|
|||
return color;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the sort menu header button
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
const getSortMenuButton = () => {
|
||||
return (
|
||||
<MaterialHeaderButtons>
|
||||
<Item title="main" iconName="sort" onPress={onSortMenuPress} />
|
||||
</MaterialHeaderButtons>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the header search bar
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
const getSearchBar = () => {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Searchbar
|
||||
placeholder={i18n.t('screens.proximo.search')}
|
||||
onChangeText={setCurrentSearchString}
|
||||
autoFocus={props.route.params.shouldFocusSearchBar}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the modal content depending on the given article
|
||||
*
|
||||
|
@ -262,42 +273,6 @@ function ProximoListScreen(props: Props) {
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the modal content to display a sort menu
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
const getModalSortMenu = () => {
|
||||
return (
|
||||
<View style={styles.modalContainer}>
|
||||
<Title style={styles.sortTitle}>
|
||||
{i18n.t('screens.proximo.sortOrder')}
|
||||
</Title>
|
||||
<RadioButton.Group
|
||||
onValueChange={setSortMode}
|
||||
value={currentSortMode.toString()}
|
||||
>
|
||||
<RadioButton.Item
|
||||
label={i18n.t('screens.proximo.sortPrice')}
|
||||
value={'0'}
|
||||
/>
|
||||
<RadioButton.Item
|
||||
label={i18n.t('screens.proximo.sortPriceReverse')}
|
||||
value={'1'}
|
||||
/>
|
||||
<RadioButton.Item
|
||||
label={i18n.t('screens.proximo.sortName')}
|
||||
value={'2'}
|
||||
/>
|
||||
<RadioButton.Item
|
||||
label={i18n.t('screens.proximo.sortNameReverse')}
|
||||
value={'3'}
|
||||
/>
|
||||
</RadioButton.Group>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a render item for the given article
|
||||
*
|
||||
|
@ -341,8 +316,8 @@ function ProximoListScreen(props: Props) {
|
|||
data: data
|
||||
.filter(
|
||||
(d) =>
|
||||
props.route.params.category === -1 ||
|
||||
props.route.params.category === d.category_id
|
||||
navParams.category === -1 ||
|
||||
navParams.category === d.category_id
|
||||
)
|
||||
.sort(sortModes[currentSortMode]),
|
||||
keyExtractor: keyExtractor,
|
||||
|
|
|
@ -21,16 +21,12 @@ export async function retrieveLoginToken(): Promise<string | undefined> {
|
|||
/**
|
||||
* Saves the login token in the secure keychain
|
||||
*
|
||||
* @param email
|
||||
* @param token
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
export async function saveLoginToken(
|
||||
email: string,
|
||||
token: string
|
||||
): Promise<void> {
|
||||
export async function saveLoginToken(token: string): Promise<void> {
|
||||
return new Promise((resolve: () => void, reject: () => void) => {
|
||||
Keychain.setGenericPassword(email, token).then(resolve).catch(reject);
|
||||
Keychain.setGenericPassword('amicale', token).then(resolve).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useLogin } from '../context/loginContext';
|
||||
import { deleteLoginToken } from './loginToken';
|
||||
|
||||
export const useLogout = () => {
|
||||
const { setLogin } = useLogin();
|
||||
|
||||
const onLogout = useCallback(() => {
|
||||
deleteLoginToken();
|
||||
setLogin(undefined);
|
||||
}, [setLogin]);
|
||||
return onLogout;
|
||||
|
|
Loading…
Reference in a new issue