forked from vergnet/application-amicale
Improved doc and typing and removed unused file
This commit is contained in:
parent
869a8e5ec0
commit
b66e50eaf8
15 changed files with 427 additions and 228 deletions
|
@ -4,6 +4,7 @@ import * as React from 'react';
|
|||
import {FlatList} from "react-native";
|
||||
import packageJson from '../../../package';
|
||||
import {List} from 'react-native-paper';
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
|
||||
type listItem = {
|
||||
name: string,
|
||||
|
@ -16,7 +17,7 @@ type listItem = {
|
|||
* @param object The raw json
|
||||
* @return {Array<listItem>}
|
||||
*/
|
||||
function generateListFromObject(object: { [string]: string }): Array<listItem> {
|
||||
function generateListFromObject(object: { [key: string]: string }): Array<listItem> {
|
||||
let list = [];
|
||||
let keys = Object.keys(object);
|
||||
let values = Object.values(object);
|
||||
|
@ -28,8 +29,7 @@ function generateListFromObject(object: { [string]: string }): Array<listItem> {
|
|||
}
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
route: Object
|
||||
navigation: StackNavigationProp,
|
||||
}
|
||||
|
||||
const LIST_ITEM_HEIGHT = 64;
|
||||
|
@ -39,23 +39,23 @@ const LIST_ITEM_HEIGHT = 64;
|
|||
*/
|
||||
export default class AboutDependenciesScreen extends React.Component<Props> {
|
||||
|
||||
data: Array<Object>;
|
||||
data: Array<listItem>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.data = generateListFromObject(packageJson.dependencies);
|
||||
}
|
||||
|
||||
keyExtractor = (item: Object) => item.name;
|
||||
keyExtractor = (item: listItem) => item.name;
|
||||
|
||||
renderItem = ({item}: Object) =>
|
||||
renderItem = ({item}: { item: listItem }) =>
|
||||
<List.Item
|
||||
title={item.name}
|
||||
description={item.version.replace('^', '').replace('~', '')}
|
||||
style={{height: LIST_ITEM_HEIGHT}}
|
||||
/>;
|
||||
|
||||
itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
|
||||
itemLayout = (data: any, index: number) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
|
|
@ -5,6 +5,14 @@ import {FlatList, Linking, Platform, View} from 'react-native';
|
|||
import i18n from "i18n-js";
|
||||
import {Avatar, Card, List, Title, withTheme} from 'react-native-paper';
|
||||
import packageJson from "../../../package.json";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
|
||||
type ListItem = {
|
||||
onPressCallback: () => void,
|
||||
icon: string,
|
||||
text: string,
|
||||
showChevron: boolean
|
||||
};
|
||||
|
||||
const links = {
|
||||
appstore: 'https://apps.apple.com/us/app/campus-amicale-insat/id1477722148',
|
||||
|
@ -29,7 +37,7 @@ const links = {
|
|||
};
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
navigation: StackNavigationProp,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -48,7 +56,7 @@ class AboutScreen extends React.Component<Props> {
|
|||
/**
|
||||
* Data to be displayed in the app card
|
||||
*/
|
||||
appData: Array<Object> = [
|
||||
appData = [
|
||||
{
|
||||
onPressCallback: () => openWebLink(Platform.OS === "ios" ? links.appstore : links.playstore),
|
||||
icon: Platform.OS === "ios" ? 'apple' : 'google-play',
|
||||
|
@ -83,7 +91,7 @@ class AboutScreen extends React.Component<Props> {
|
|||
/**
|
||||
* Data to be displayed in the author card
|
||||
*/
|
||||
authorData: Array<Object> = [
|
||||
authorData = [
|
||||
{
|
||||
onPressCallback: () => openWebLink(links.meme),
|
||||
icon: 'account-circle',
|
||||
|
@ -106,7 +114,7 @@ class AboutScreen extends React.Component<Props> {
|
|||
/**
|
||||
* Data to be displayed in the additional developer card
|
||||
*/
|
||||
additionalDevData: Array<Object> = [
|
||||
additionalDevData = [
|
||||
{
|
||||
onPressCallback: () => console.log('Meme this'),
|
||||
icon: 'account',
|
||||
|
@ -129,7 +137,7 @@ class AboutScreen extends React.Component<Props> {
|
|||
/**
|
||||
* Data to be displayed in the technologies card
|
||||
*/
|
||||
technoData: Array<Object> = [
|
||||
technoData = [
|
||||
{
|
||||
onPressCallback: () => openWebLink(links.react),
|
||||
icon: 'react',
|
||||
|
@ -146,7 +154,7 @@ class AboutScreen extends React.Component<Props> {
|
|||
/**
|
||||
* Order of information cards
|
||||
*/
|
||||
dataOrder: Array<Object> = [
|
||||
dataOrder = [
|
||||
{
|
||||
id: 'app',
|
||||
},
|
||||
|
@ -158,16 +166,9 @@ class AboutScreen extends React.Component<Props> {
|
|||
},
|
||||
];
|
||||
|
||||
|
||||
colors: Object;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the app icon
|
||||
*
|
||||
* @param props
|
||||
* @return {*}
|
||||
*/
|
||||
|
@ -187,7 +188,7 @@ class AboutScreen extends React.Component<Props> {
|
|||
* @param item The item to extract the key from
|
||||
* @return {string} The extracted key
|
||||
*/
|
||||
keyExtractor(item: Object): string {
|
||||
keyExtractor(item: ListItem): string {
|
||||
return item.icon;
|
||||
}
|
||||
|
||||
|
@ -271,7 +272,7 @@ class AboutScreen extends React.Component<Props> {
|
|||
* @param props
|
||||
* @return {*}
|
||||
*/
|
||||
getChevronIcon(props: Object) {
|
||||
getChevronIcon(props) {
|
||||
return (
|
||||
<List.Icon {...props} icon={'chevron-right'}/>
|
||||
);
|
||||
|
@ -284,18 +285,18 @@ class AboutScreen extends React.Component<Props> {
|
|||
* @param props
|
||||
* @return {*}
|
||||
*/
|
||||
getItemIcon(item: Object, props: Object) {
|
||||
getItemIcon(item: ListItem, props) {
|
||||
return (
|
||||
<List.Icon {...props} icon={item.icon}/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a clickable card item to be rendered inside a card.
|
||||
* Gets a clickable card item to be rendered inside a card.
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getCardItem = ({item}: Object) => {
|
||||
getCardItem = ({item}: { item: ListItem }) => {
|
||||
const getItemIcon = this.getItemIcon.bind(this, item);
|
||||
if (item.showChevron) {
|
||||
return (
|
||||
|
@ -323,7 +324,7 @@ class AboutScreen extends React.Component<Props> {
|
|||
* @param item The item to show
|
||||
* @return {*}
|
||||
*/
|
||||
getMainCard = ({item}: Object) => {
|
||||
getMainCard = ({item}: { item: { id: string } }) => {
|
||||
switch (item.id) {
|
||||
case 'app':
|
||||
return this.getAppCard();
|
||||
|
|
|
@ -5,14 +5,24 @@ import {FlatList, View} from "react-native";
|
|||
import AsyncStorageManager from "../../managers/AsyncStorageManager";
|
||||
import CustomModal from "../../components/Overrides/CustomModal";
|
||||
import {Button, List, Subheading, TextInput, Title, withTheme} from 'react-native-paper';
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
import {Modalize} from "react-native-modalize";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
|
||||
type PreferenceItem = {
|
||||
key: string,
|
||||
default: string,
|
||||
current: string,
|
||||
}
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomTheme
|
||||
};
|
||||
|
||||
type State = {
|
||||
modalCurrentDisplayItem: Object,
|
||||
currentPreferences: Array<Object>,
|
||||
modalCurrentDisplayItem: PreferenceItem,
|
||||
currentPreferences: Array<PreferenceItem>,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,20 +31,20 @@ type State = {
|
|||
*/
|
||||
class DebugScreen extends React.Component<Props, State> {
|
||||
|
||||
modalRef: Object;
|
||||
modalInputValue = '';
|
||||
|
||||
onModalRef: Function;
|
||||
|
||||
colors: Object;
|
||||
modalRef: Modalize;
|
||||
modalInputValue: string;
|
||||
|
||||
/**
|
||||
* Copies user preferences to state for easier manipulation
|
||||
*
|
||||
* @param props
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onModalRef = this.onModalRef.bind(this);
|
||||
this.colors = props.theme.colors;
|
||||
this.modalInputValue = "";
|
||||
let copy = {...AsyncStorageManager.getInstance().preferences};
|
||||
let currentPreferences = [];
|
||||
Object.values(copy).map((object) => {
|
||||
let currentPreferences : Array<PreferenceItem> = [];
|
||||
Object.values(copy).map((object: any) => {
|
||||
currentPreferences.push(object);
|
||||
});
|
||||
this.state = {
|
||||
|
@ -44,10 +54,11 @@ class DebugScreen extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Show the edit modal
|
||||
* Shows the edit modal
|
||||
*
|
||||
* @param item
|
||||
*/
|
||||
showEditModal(item: Object) {
|
||||
showEditModal(item: PreferenceItem) {
|
||||
this.setState({
|
||||
modalCurrentDisplayItem: item
|
||||
});
|
||||
|
@ -81,14 +92,14 @@ class DebugScreen extends React.Component<Props, State> {
|
|||
<Button
|
||||
mode="contained"
|
||||
dark={true}
|
||||
color={this.colors.success}
|
||||
color={this.props.theme.colors.success}
|
||||
onPress={() => this.saveNewPrefs(this.state.modalCurrentDisplayItem.key, this.modalInputValue)}>
|
||||
Save new value
|
||||
</Button>
|
||||
<Button
|
||||
mode="contained"
|
||||
dark={true}
|
||||
color={this.colors.danger}
|
||||
color={this.props.theme.colors.danger}
|
||||
onPress={() => this.saveNewPrefs(this.state.modalCurrentDisplayItem.key, this.state.modalCurrentDisplayItem.default)}>
|
||||
Reset to default
|
||||
</Button>
|
||||
|
@ -98,6 +109,12 @@ class DebugScreen extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of the given key in the preferences array
|
||||
*
|
||||
* @param key THe key to find the index of
|
||||
* @returns {number}
|
||||
*/
|
||||
findIndexOfKey(key: string) {
|
||||
let index = -1;
|
||||
for (let i = 0; i < this.state.currentPreferences.length; i++) {
|
||||
|
@ -130,11 +147,11 @@ class DebugScreen extends React.Component<Props, State> {
|
|||
*
|
||||
* @param ref
|
||||
*/
|
||||
onModalRef(ref: Object) {
|
||||
onModalRef = (ref: Modalize) => {
|
||||
this.modalRef = ref;
|
||||
}
|
||||
|
||||
renderItem = ({item}: Object) => {
|
||||
renderItem = ({item}: {item: PreferenceItem}) => {
|
||||
return (
|
||||
<List.Item
|
||||
title={item.key}
|
||||
|
|
|
@ -12,14 +12,18 @@ type Props = {
|
|||
collapsibleStack: Collapsible
|
||||
};
|
||||
|
||||
type State = {};
|
||||
type DatasetItem = {
|
||||
name: string,
|
||||
email: string,
|
||||
icon: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Class defining a planning event information page.
|
||||
*/
|
||||
class AmicaleContactScreen extends React.Component<Props, State> {
|
||||
|
||||
class AmicaleContactScreen extends React.Component<Props> {
|
||||
|
||||
// Dataset containing information about contacts
|
||||
CONTACT_DATASET = [
|
||||
{
|
||||
name: i18n.t("amicaleAbout.roles.interSchools"),
|
||||
|
@ -68,18 +72,11 @@ class AmicaleContactScreen extends React.Component<Props, State> {
|
|||
},
|
||||
];
|
||||
|
||||
colors: Object;
|
||||
keyExtractor = (item: DatasetItem) => item.email;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
}
|
||||
getChevronIcon = (props) => <List.Icon {...props} icon={'chevron-right'}/>;
|
||||
|
||||
keyExtractor = (item: Object) => item.email;
|
||||
|
||||
getChevronIcon = (props: Object) => <List.Icon {...props} icon={'chevron-right'}/>;
|
||||
|
||||
renderItem = ({item}: Object) => {
|
||||
renderItem = ({item}: { item: DatasetItem }) => {
|
||||
const onPress = () => Linking.openURL('mailto:' + item.email);
|
||||
return <List.Item
|
||||
title={item.name}
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {ScrollView, StyleSheet} from "react-native";
|
||||
import {Button, withTheme} from 'react-native-paper';
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
route: Object,
|
||||
}
|
||||
|
||||
type State = {}
|
||||
|
||||
class AmicaleHomeScreen extends React.Component<Props, State> {
|
||||
|
||||
state = {};
|
||||
|
||||
colors: Object;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.colors = props.theme.colors;
|
||||
}
|
||||
|
||||
render() {
|
||||
const nav = this.props.navigation;
|
||||
return (
|
||||
<ScrollView>
|
||||
<Button
|
||||
icon={"login"}
|
||||
onPress={() => nav.navigate("login")}
|
||||
>
|
||||
LOGIN
|
||||
</Button>
|
||||
<Button
|
||||
icon={"information"}
|
||||
onPress={() => nav.navigate("amicale-contact")}
|
||||
>
|
||||
INFO
|
||||
</Button>
|
||||
<Button
|
||||
icon={"information"}
|
||||
onPress={() => nav.navigate("club-list")}
|
||||
>
|
||||
CLUBS
|
||||
</Button>
|
||||
<Button
|
||||
icon={"information"}
|
||||
onPress={() => nav.navigate("profile")}
|
||||
>
|
||||
PROFILE
|
||||
</Button>
|
||||
<Button
|
||||
icon={"information"}
|
||||
onPress={() => nav.navigate("vote")}
|
||||
>
|
||||
VOTE
|
||||
</Button>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
card: {
|
||||
margin: 10,
|
||||
},
|
||||
header: {
|
||||
fontSize: 36,
|
||||
marginBottom: 48
|
||||
},
|
||||
textInput: {},
|
||||
btnContainer: {
|
||||
marginTop: 5,
|
||||
marginBottom: 10,
|
||||
}
|
||||
});
|
||||
|
||||
export default withTheme(AmicaleHomeScreen);
|
|
@ -6,25 +6,11 @@ import {Card, List, Text, withTheme} from 'react-native-paper';
|
|||
import i18n from 'i18n-js';
|
||||
import Autolink from "react-native-autolink";
|
||||
|
||||
type Props = {
|
||||
};
|
||||
|
||||
type State = {
|
||||
};
|
||||
type Props = {};
|
||||
|
||||
const CONTACT_LINK = 'clubs@amicale-insat.fr';
|
||||
|
||||
/**
|
||||
* Class defining a planning event information page.
|
||||
*/
|
||||
class ClubAboutScreen extends React.Component<Props, State> {
|
||||
|
||||
colors: Object;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
}
|
||||
class ClubAboutScreen extends React.Component<Props> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
|
|
@ -65,6 +65,12 @@ class ClubDisplayScreen extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the category with the given ID
|
||||
*
|
||||
* @param id The category's ID
|
||||
* @returns {string|*}
|
||||
*/
|
||||
getCategoryName(id: number) {
|
||||
if (this.categories !== null) {
|
||||
for (let i = 0; i < this.categories.length; i++) {
|
||||
|
@ -75,6 +81,12 @@ class ClubDisplayScreen extends React.Component<Props, State> {
|
|||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the view for rendering categories
|
||||
*
|
||||
* @param categories The categories to display (max 2)
|
||||
* @returns {null|*}
|
||||
*/
|
||||
getCategoriesRender(categories: [number, number]) {
|
||||
if (this.categories === null)
|
||||
return null;
|
||||
|
@ -95,12 +107,19 @@ class ClubDisplayScreen extends React.Component<Props, State> {
|
|||
return <View style={{flexDirection: 'row', marginTop: 5}}>{final}</View>;
|
||||
}
|
||||
|
||||
getManagersRender(resp: Array<string>, email: string | null) {
|
||||
let final = [];
|
||||
for (let i = 0; i < resp.length; i++) {
|
||||
final.push(<Paragraph key={i.toString()}>{resp[i]}</Paragraph>)
|
||||
/**
|
||||
* Gets the view for rendering club managers if any
|
||||
*
|
||||
* @param managers The list of manager names
|
||||
* @param email The club contact email
|
||||
* @returns {*}
|
||||
*/
|
||||
getManagersRender(managers: Array<string>, email: string | null) {
|
||||
let managersListView = [];
|
||||
for (let i = 0; i < managers.length; i++) {
|
||||
managersListView.push(<Paragraph key={i.toString()}>{managers[i]}</Paragraph>)
|
||||
}
|
||||
const hasManagers = resp.length > 0;
|
||||
const hasManagers = managers.length > 0;
|
||||
return (
|
||||
<Card style={{marginTop: 10, marginBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
|
||||
<Card.Title
|
||||
|
@ -113,13 +132,20 @@ class ClubDisplayScreen extends React.Component<Props, State> {
|
|||
icon="account-tie"/>}
|
||||
/>
|
||||
<Card.Content>
|
||||
{final}
|
||||
{managersListView}
|
||||
{this.getEmailButton(email, hasManagers)}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the email button to contact the club, or the amicale if the club does not have any managers
|
||||
*
|
||||
* @param email The club contact email
|
||||
* @param hasManagers True if the club has managers
|
||||
* @returns {*}
|
||||
*/
|
||||
getEmailButton(email: string | null, hasManagers: boolean) {
|
||||
const destinationEmail = email != null && hasManagers
|
||||
? email
|
||||
|
@ -141,13 +167,21 @@ class ClubDisplayScreen extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
updateHeaderTitle(data: Object) {
|
||||
/**
|
||||
* Updates the header title to match the given club
|
||||
*
|
||||
* @param data The club data
|
||||
*/
|
||||
updateHeaderTitle(data: club) {
|
||||
this.props.navigation.setOptions({title: data.name})
|
||||
}
|
||||
|
||||
getScreen = (response: Array<Object>) => {
|
||||
let data: club = response[0];
|
||||
getScreen = (response: Array<{ [key: string]: any } | null>) => {
|
||||
let data: club | null = null;
|
||||
if (response[0] != null) {
|
||||
data = response[0];
|
||||
this.updateHeaderTitle(data);
|
||||
}
|
||||
if (data != null) {
|
||||
return (
|
||||
<ScrollView style={{paddingLeft: 5, paddingRight: 5}}>
|
||||
|
@ -184,7 +218,6 @@ class ClubDisplayScreen extends React.Component<Props, State> {
|
|||
);
|
||||
} else
|
||||
return null;
|
||||
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
|
@ -131,6 +131,15 @@ class ClubListScreen extends React.Component<Props, State> {
|
|||
|
||||
onChipSelect = (id: number) => this.updateFilteredData(null, id);
|
||||
|
||||
/**
|
||||
* Updates the search string and category filter, saving them to the State.
|
||||
*
|
||||
* If the given category is already in the filter, it removes it.
|
||||
* Otherwise it adds it to the filter.
|
||||
*
|
||||
* @param filterStr The new filter string to use
|
||||
* @param categoryId The category to add/remove from the filter
|
||||
*/
|
||||
updateFilteredData(filterStr: string | null, categoryId: number | null) {
|
||||
let newCategoriesState = [...this.state.currentlySelectedCategories];
|
||||
let newStrState = this.state.currentSearchString;
|
||||
|
@ -150,6 +159,11 @@ class ClubListScreen extends React.Component<Props, State> {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list header, with controls to change the categories filter
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getListHeader() {
|
||||
return <ClubListHeader
|
||||
categories={this.categories}
|
||||
|
@ -158,6 +172,12 @@ class ClubListScreen extends React.Component<Props, State> {
|
|||
/>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the category object of the given ID
|
||||
*
|
||||
* @param id The ID of the category to find
|
||||
* @returns {*}
|
||||
*/
|
||||
getCategoryOfId = (id: number) => {
|
||||
for (let i = 0; i < this.categories.length; i++) {
|
||||
if (id === this.categories[i].id)
|
||||
|
@ -165,6 +185,12 @@ class ClubListScreen extends React.Component<Props, State> {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the given item should be rendered according to current name and category filters
|
||||
*
|
||||
* @param item The club to check
|
||||
* @returns {boolean}
|
||||
*/
|
||||
shouldRenderItem(item: club) {
|
||||
let shouldRender = this.state.currentlySelectedCategories.length === 0
|
||||
|| isItemInCategoryFilter(this.state.currentlySelectedCategories, item.category);
|
||||
|
|
|
@ -11,10 +11,11 @@ import {Collapsible} from "react-navigation-collapsible";
|
|||
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
import AsyncStorageManager from "../../managers/AsyncStorageManager";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
route: Object,
|
||||
navigation: StackNavigationProp,
|
||||
route: { params: { nextScreen: string } },
|
||||
collapsibleStack: Collapsible,
|
||||
theme: CustomTheme
|
||||
}
|
||||
|
@ -47,9 +48,9 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
dialogError: 0,
|
||||
};
|
||||
|
||||
onEmailChange: Function;
|
||||
onPasswordChange: Function;
|
||||
passwordInputRef: Object;
|
||||
onEmailChange: (value: string) => null;
|
||||
onPasswordChange: (value: string) => null;
|
||||
passwordInputRef: { current: null | TextInput };
|
||||
|
||||
nextScreen: string | null;
|
||||
|
||||
|
@ -64,6 +65,9 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
this.handleNavigationParams();
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves the screen to navigate to after a successful login if one was provided in navigation parameters
|
||||
*/
|
||||
handleNavigationParams() {
|
||||
if (this.props.route.params != null) {
|
||||
if (this.props.route.params.nextScreen != null)
|
||||
|
@ -73,6 +77,11 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an error dialog with the corresponding login error
|
||||
*
|
||||
* @param error The error given by the login request
|
||||
*/
|
||||
showErrorDialog = (error: number) =>
|
||||
this.setState({
|
||||
dialogVisible: true,
|
||||
|
@ -81,6 +90,10 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
|
||||
hideErrorDialog = () => this.setState({dialogVisible: false});
|
||||
|
||||
/**
|
||||
* Navigates to the screen specified in navigation parameters or simply go back tha stack.
|
||||
* Saves in user preferences to not show the login banner again.
|
||||
*/
|
||||
handleSuccess = () => {
|
||||
// Do not show the login banner again
|
||||
AsyncStorageManager.getInstance().savePref(
|
||||
|
@ -93,32 +106,75 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
this.props.navigation.replace(this.nextScreen);
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigates to the Amicale website screen with the reset password link as navigation parameters
|
||||
*/
|
||||
onResetPasswordClick = () => this.props.navigation.navigate('amicale-website', {path: RESET_PASSWORD_PATH});
|
||||
|
||||
/**
|
||||
* The user has unfocused the input, his email is ready to be validated
|
||||
*/
|
||||
validateEmail = () => this.setState({isEmailValidated: true});
|
||||
|
||||
/**
|
||||
* Checks if the entered email is valid (matches the regex)
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isEmailValid() {
|
||||
return emailRegex.test(this.state.email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we should tell the user his email is invalid.
|
||||
* We should only show this if his email is invalid and has been checked when un-focusing the input
|
||||
*
|
||||
* @returns {boolean|boolean}
|
||||
*/
|
||||
shouldShowEmailError() {
|
||||
return this.state.isEmailValidated && !this.isEmailValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* The user has unfocused the input, his password is ready to be validated
|
||||
*/
|
||||
validatePassword = () => this.setState({isPasswordValidated: true});
|
||||
|
||||
/**
|
||||
* Checks if the user has entered a password
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isPasswordValid() {
|
||||
return this.state.password !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we should tell the user his password is invalid.
|
||||
* We should only show this if his password is invalid and has been checked when un-focusing the input
|
||||
*
|
||||
* @returns {boolean|boolean}
|
||||
*/
|
||||
shouldShowPasswordError() {
|
||||
return this.state.isPasswordValidated && !this.isPasswordValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the email and password are valid, and we are not loading a request, then the login button can be enabled
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
shouldEnableLogin() {
|
||||
return this.isEmailValid() && this.isPasswordValid() && !this.state.loading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user input changes in the email or password field.
|
||||
* This saves the new value in the State and disabled input validation (to prevent errors to show while typing)
|
||||
*
|
||||
* @param isEmail True if the field is the email field
|
||||
* @param value The new field value
|
||||
*/
|
||||
onInputChange(isEmail: boolean, value: string) {
|
||||
if (isEmail) {
|
||||
this.setState({
|
||||
|
@ -133,8 +189,23 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
onEmailSubmit = () => this.passwordInputRef.focus();
|
||||
/**
|
||||
* Focuses the password field when the email field is done
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
onEmailSubmit = () => {
|
||||
if (this.passwordInputRef.current != null)
|
||||
this.passwordInputRef.current.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks on login or finishes to type his password.
|
||||
*
|
||||
* Checks if we should allow the user to login,
|
||||
* then makes the login request and enters a loading state until the request finishes
|
||||
*
|
||||
*/
|
||||
onSubmit = () => {
|
||||
if (this.shouldEnableLogin()) {
|
||||
this.setState({loading: true});
|
||||
|
@ -147,6 +218,11 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the form input
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getFormInput() {
|
||||
return (
|
||||
<View>
|
||||
|
@ -173,9 +249,7 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
{i18n.t("loginScreen.emailError")}
|
||||
</HelperText>
|
||||
<TextInput
|
||||
ref={(ref) => {
|
||||
this.passwordInputRef = ref;
|
||||
}}
|
||||
ref={this.passwordInputRef}
|
||||
label={i18n.t("loginScreen.password")}
|
||||
mode='outlined'
|
||||
value={this.state.password}
|
||||
|
@ -201,6 +275,10 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the card containing the input form
|
||||
* @returns {*}
|
||||
*/
|
||||
getMainCard() {
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
|
@ -239,6 +317,11 @@ class LoginScreen extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the card containing the information about the Amicale account
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getSecondaryCard() {
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
|
|
|
@ -12,10 +12,12 @@ import {Collapsible} from "react-navigation-collapsible";
|
|||
import {withCollapsible} from "../../utils/withCollapsible";
|
||||
import type {cardList} from "../../components/Lists/CardList/CardList";
|
||||
import CardList from "../../components/Lists/CardList/CardList";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
theme: Object,
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomTheme,
|
||||
collapsibleStack: Collapsible,
|
||||
}
|
||||
|
||||
|
@ -23,6 +25,23 @@ type State = {
|
|||
dialogVisible: boolean,
|
||||
}
|
||||
|
||||
type ProfileData = {
|
||||
first_name: string,
|
||||
last_name: string,
|
||||
email: string,
|
||||
birthday: string,
|
||||
phone: string,
|
||||
branch: string,
|
||||
link: string,
|
||||
validity: boolean,
|
||||
clubs: Array<Club>,
|
||||
}
|
||||
type Club = {
|
||||
id: number,
|
||||
name: string,
|
||||
is_manager: boolean,
|
||||
}
|
||||
|
||||
const CLUBS_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Clubs.png";
|
||||
const VOTE_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Vote.png";
|
||||
|
||||
|
@ -34,9 +53,9 @@ class ProfileScreen extends React.Component<Props, State> {
|
|||
dialogVisible: false,
|
||||
};
|
||||
|
||||
data: Object;
|
||||
data: ProfileData;
|
||||
|
||||
flatListData: Array<Object>;
|
||||
flatListData: Array<{ id: string }>;
|
||||
amicaleDataset: cardList;
|
||||
|
||||
constructor() {
|
||||
|
@ -79,12 +98,25 @@ class ProfileScreen extends React.Component<Props, State> {
|
|||
|
||||
hideDisconnectDialog = () => this.setState({dialogVisible: false});
|
||||
|
||||
/**
|
||||
* Gets the logout header button
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getHeaderButton = () => <MaterialHeaderButtons>
|
||||
<Item title="logout" iconName="logout" onPress={this.showDisconnectDialog}/>
|
||||
</MaterialHeaderButtons>;
|
||||
|
||||
getScreen = (data: Object) => {
|
||||
/**
|
||||
* Gets the main screen component with the fetched data
|
||||
*
|
||||
* @param data The data fetched from the server
|
||||
* @returns {*}
|
||||
*/
|
||||
getScreen = (data: Array<{ [key: string]: any } | null>) => {
|
||||
if (data[0] != null) {
|
||||
this.data = data[0];
|
||||
}
|
||||
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
|
@ -109,7 +141,7 @@ class ProfileScreen extends React.Component<Props, State> {
|
|||
)
|
||||
};
|
||||
|
||||
getRenderItem = ({item}: Object) => {
|
||||
getRenderItem = ({item}: { item: { id: string } }) => {
|
||||
switch (item.id) {
|
||||
case '0':
|
||||
return this.getWelcomeCard();
|
||||
|
@ -122,6 +154,11 @@ class ProfileScreen extends React.Component<Props, State> {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the list of services available with the Amicale account
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getServicesList() {
|
||||
return (
|
||||
<CardList
|
||||
|
@ -131,12 +168,17 @@ class ProfileScreen extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a card welcoming the user to his account
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getWelcomeCard() {
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Title
|
||||
title={i18n.t("profileScreen.welcomeTitle", {name: this.data.first_name})}
|
||||
left={(props) => <Avatar.Image
|
||||
left={() => <Avatar.Image
|
||||
size={64}
|
||||
source={ICON_AMICALE}
|
||||
style={{backgroundColor: 'transparent',}}
|
||||
|
@ -340,7 +382,7 @@ class ProfileScreen extends React.Component<Props, State> {
|
|||
* @param item The club to render
|
||||
* @return {*}
|
||||
*/
|
||||
clubListItem = ({item}: Object) => {
|
||||
clubListItem = ({item}: { item: Club }) => {
|
||||
const onPress = () => this.openClubDetailsScreen(item.id);
|
||||
let description = i18n.t("profileScreen.isMember");
|
||||
let icon = (props) => <List.Icon {...props} icon="chevron-right"/>;
|
||||
|
@ -356,9 +398,9 @@ class ProfileScreen extends React.Component<Props, State> {
|
|||
/>;
|
||||
};
|
||||
|
||||
clubKeyExtractor = (item: Object) => item.name;
|
||||
clubKeyExtractor = (item: Club) => item.name;
|
||||
|
||||
sortClubList = (a: Object, b: Object) => a.is_manager ? -1 : 1;
|
||||
sortClubList = (a: Club, b: Club) => a.is_manager ? -1 : 1;
|
||||
|
||||
/**
|
||||
* Renders the list of clubs the user is part of
|
||||
|
@ -366,7 +408,7 @@ class ProfileScreen extends React.Component<Props, State> {
|
|||
* @param list The club list
|
||||
* @return {*}
|
||||
*/
|
||||
getClubList(list: Array<Object>) {
|
||||
getClubList(list: Array<Club>) {
|
||||
list.sort(this.sortClubList);
|
||||
return (
|
||||
//$FlowFixMe
|
||||
|
|
|
@ -9,6 +9,7 @@ import VoteTease from "../../components/Amicale/Vote/VoteTease";
|
|||
import VoteSelect from "../../components/Amicale/Vote/VoteSelect";
|
||||
import VoteResults from "../../components/Amicale/Vote/VoteResults";
|
||||
import VoteWait from "../../components/Amicale/Vote/VoteWait";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
|
||||
export type team = {
|
||||
id: number,
|
||||
|
@ -86,13 +87,16 @@ type objectVoteDates = {
|
|||
const MIN_REFRESH_TIME = 5 * 1000;
|
||||
|
||||
type Props = {
|
||||
navigation: Object
|
||||
navigation: StackNavigationProp
|
||||
}
|
||||
|
||||
type State = {
|
||||
hasVoted: boolean,
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen displaying vote information and controls
|
||||
*/
|
||||
export default class VoteScreen extends React.Component<Props, State> {
|
||||
|
||||
state = {
|
||||
|
@ -107,7 +111,7 @@ export default class VoteScreen extends React.Component<Props, State> {
|
|||
today: Date;
|
||||
|
||||
mainFlatListData: Array<{ key: string }>;
|
||||
lastRefresh: Date;
|
||||
lastRefresh: Date | null;
|
||||
|
||||
authRef: { current: null | AuthenticatedScreen };
|
||||
|
||||
|
@ -116,22 +120,30 @@ export default class VoteScreen extends React.Component<Props, State> {
|
|||
this.hasVoted = false;
|
||||
this.today = new Date();
|
||||
this.authRef = React.createRef();
|
||||
this.lastRefresh = null;
|
||||
this.mainFlatListData = [
|
||||
{key: 'main'},
|
||||
{key: 'info'},
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads vote data if last refresh delta is smaller than the minimum refresh time
|
||||
*/
|
||||
reloadData = () => {
|
||||
let canRefresh;
|
||||
if (this.lastRefresh !== undefined)
|
||||
canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) > MIN_REFRESH_TIME;
|
||||
const lastRefresh = this.lastRefresh;
|
||||
if (lastRefresh != null)
|
||||
canRefresh = (new Date().getTime() - lastRefresh.getTime()) > MIN_REFRESH_TIME;
|
||||
else
|
||||
canRefresh = true;
|
||||
if (canRefresh && this.authRef.current != null)
|
||||
this.authRef.current.reload()
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates the objects containing string and Date representations of key vote dates
|
||||
*/
|
||||
generateDateObject() {
|
||||
const strings = this.datesString;
|
||||
if (strings != null) {
|
||||
|
@ -152,6 +164,16 @@ export default class VoteScreen extends React.Component<Props, State> {
|
|||
this.dates = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string representation of the given date.
|
||||
*
|
||||
* If the given date is the same day as today, only return the tile.
|
||||
* Otherwise, return the full date.
|
||||
*
|
||||
* @param date The Date object representation of the wanted date
|
||||
* @param dateString The string representation of the wanted date
|
||||
* @returns {string}
|
||||
*/
|
||||
getDateString(date: Date, dateString: string): string {
|
||||
if (this.today.getDate() === date.getDate()) {
|
||||
const str = getTimeOnlyString(dateString);
|
||||
|
@ -176,7 +198,7 @@ export default class VoteScreen extends React.Component<Props, State> {
|
|||
return this.dates != null && this.today > this.dates.date_result_begin;
|
||||
}
|
||||
|
||||
mainRenderItem = ({item}: Object) => {
|
||||
mainRenderItem = ({item}: { item: { key: string } }) => {
|
||||
if (item.key === 'info')
|
||||
return <VoteTitle/>;
|
||||
else if (item.key === 'main' && this.dates != null)
|
||||
|
@ -282,6 +304,13 @@ export default class VoteScreen extends React.Component<Props, State> {
|
|||
isVoteRunning={this.isVoteRunning()}/>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the authenticated screen.
|
||||
*
|
||||
* Teams and dates are not mandatory to allow showing the information box even if api requests fail
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<AuthenticatedScreen
|
||||
|
|
|
@ -7,29 +7,29 @@ import ImageModal from 'react-native-image-modal';
|
|||
import Autolink from "react-native-autolink";
|
||||
import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
|
||||
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
import type {feedItem} from "./HomeScreen";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
route: Object
|
||||
navigation: StackNavigationProp,
|
||||
route: { params: { data: feedItem, date: string } }
|
||||
};
|
||||
|
||||
const ICON_AMICALE = require('../../../assets/amicale.png');
|
||||
const NAME_AMICALE = 'Amicale INSA Toulouse';
|
||||
|
||||
/**
|
||||
* Class defining a planning event information page.
|
||||
* Class defining a feed item page.
|
||||
*/
|
||||
class FeedItemScreen extends React.Component<Props> {
|
||||
|
||||
displayData: Object;
|
||||
displayData: feedItem;
|
||||
date: string;
|
||||
|
||||
colors: Object;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
this.displayData = this.props.route.params.data;
|
||||
this.date = this.props.route.params.date;
|
||||
this.displayData = props.route.params.data;
|
||||
this.date = props.route.params.date;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -38,16 +38,29 @@ class FeedItemScreen extends React.Component<Props> {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the feed item out link in browser or compatible app
|
||||
*/
|
||||
onOutLinkPress = () => {
|
||||
Linking.openURL(this.displayData.permalink_url);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the out link header button
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getHeaderButton = () => {
|
||||
return <MaterialHeaderButtons>
|
||||
<Item title="main" iconName={'facebook'} color={"#2e88fe"} onPress={this.onOutLinkPress}/>
|
||||
</MaterialHeaderButtons>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the Amicale INSA avatar
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getAvatar() {
|
||||
return (
|
||||
<Avatar.Image size={48} source={ICON_AMICALE}
|
||||
|
@ -55,8 +68,8 @@ class FeedItemScreen extends React.Component<Props> {
|
|||
);
|
||||
}
|
||||
|
||||
getContent() {
|
||||
const hasImage = this.displayData.full_picture !== '' && this.displayData.full_picture !== undefined;
|
||||
render() {
|
||||
const hasImage = this.displayData.full_picture !== '' && this.displayData.full_picture != null;
|
||||
return (
|
||||
<ScrollView style={{margin: 5,}}>
|
||||
<Card.Title
|
||||
|
@ -89,10 +102,6 @@ class FeedItemScreen extends React.Component<Props> {
|
|||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.getContent();
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(FeedItemScreen);
|
||||
|
|
|
@ -111,8 +111,6 @@ type State = {
|
|||
*/
|
||||
class HomeScreen extends React.Component<Props, State> {
|
||||
|
||||
colors: Object;
|
||||
|
||||
isLoggedIn: boolean | null;
|
||||
|
||||
fabRef: { current: null | AnimatedFAB };
|
||||
|
@ -125,7 +123,6 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
this.fabRef = React.createRef();
|
||||
this.currentNewFeed = [];
|
||||
this.isLoggedIn = null;
|
||||
|
@ -155,6 +152,9 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates login state and navigation parameters on screen focus
|
||||
*/
|
||||
onScreenFocus = () => {
|
||||
if (ConnectionManager.getInstance().isLoggedIn() !== this.isLoggedIn) {
|
||||
this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
|
||||
|
@ -169,6 +169,9 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
this.handleNavigationParams();
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigates to the a new screen if navigation parameters specify one
|
||||
*/
|
||||
handleNavigationParams = () => {
|
||||
if (this.props.route.params != null) {
|
||||
if (this.props.route.params.nextScreen != null) {
|
||||
|
@ -179,6 +182,11 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets header buttons based on login state
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getHeaderButton = () => {
|
||||
let onPressLog = () => this.props.navigation.navigate("login", {nextScreen: "profile"});
|
||||
let logIcon = "login";
|
||||
|
@ -262,7 +270,7 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
id: 'washers',
|
||||
data: dashboardData == null ? 0 : dashboardData.available_machines.washers,
|
||||
icon: 'washing-machine',
|
||||
color: this.colors.proxiwashColor,
|
||||
color: this.props.theme.colors.proxiwashColor,
|
||||
onPress: this.onProxiwashClick,
|
||||
isAvailable: dashboardData == null ? false : dashboardData.available_machines.washers > 0
|
||||
},
|
||||
|
@ -270,7 +278,7 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
id: 'dryers',
|
||||
data: dashboardData == null ? 0 : dashboardData.available_machines.dryers,
|
||||
icon: 'tumble-dryer',
|
||||
color: this.colors.proxiwashColor,
|
||||
color: this.props.theme.colors.proxiwashColor,
|
||||
onPress: this.onProxiwashClick,
|
||||
isAvailable: dashboardData == null ? false : dashboardData.available_machines.dryers > 0
|
||||
},
|
||||
|
@ -278,7 +286,7 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
id: 'available_tutorials',
|
||||
data: dashboardData == null ? 0 : dashboardData.available_tutorials,
|
||||
icon: 'school',
|
||||
color: this.colors.tutorinsaColor,
|
||||
color: this.props.theme.colors.tutorinsaColor,
|
||||
onPress: this.onTutorInsaClick,
|
||||
isAvailable: dashboardData == null ? false : dashboardData.available_tutorials > 0
|
||||
},
|
||||
|
@ -286,7 +294,7 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
id: 'proximo_articles',
|
||||
data: dashboardData == null ? 0 : dashboardData.proximo_articles,
|
||||
icon: 'shopping',
|
||||
color: this.colors.proximoColor,
|
||||
color: this.props.theme.colors.proximoColor,
|
||||
onPress: this.onProximoClick,
|
||||
isAvailable: dashboardData == null ? false : dashboardData.proximo_articles > 0
|
||||
},
|
||||
|
@ -294,7 +302,7 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
id: 'today_menu',
|
||||
data: dashboardData == null ? [] : dashboardData.today_menu,
|
||||
icon: 'silverware-fork-knife',
|
||||
color: this.colors.menuColor,
|
||||
color: this.props.theme.colors.menuColor,
|
||||
onPress: this.onMenuClick,
|
||||
isAvailable: dashboardData == null ? false : dashboardData.today_menu.length > 0
|
||||
},
|
||||
|
@ -324,6 +332,11 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
return this.getDashboardActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a dashboard item with action buttons
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getDashboardActions() {
|
||||
return <ActionsDashBoardItem {...this.props} isLoggedIn={this.isLoggedIn}/>;
|
||||
}
|
||||
|
@ -446,7 +459,7 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
onEventContainerClick = () => this.props.navigation.navigate('planning');
|
||||
|
||||
/**
|
||||
* Gets the event render item.
|
||||
* Gets the event dashboard render item.
|
||||
* If a preview is available, it will be rendered inside
|
||||
*
|
||||
* @param content
|
||||
|
@ -473,6 +486,12 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a dashboard shortcut item
|
||||
*
|
||||
* @param item
|
||||
* @returns {*}
|
||||
*/
|
||||
dashboardRowRenderItem = ({item}: { item: dashboardSmallItem }) => {
|
||||
return (
|
||||
<SquareDashboardItem
|
||||
|
@ -486,7 +505,7 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
/**
|
||||
* Gets a classic dashboard item.
|
||||
* Gets a dashboard item with a row of shortcut buttons.
|
||||
*
|
||||
* @param content
|
||||
* @return {*}
|
||||
|
@ -553,7 +572,7 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
|
||||
/**
|
||||
* Callback used when closing the banner.
|
||||
* This hides the banner and saves to preferences to prevent it from reopening
|
||||
* This hides the banner and saves to preferences to prevent it from reopening.
|
||||
*/
|
||||
onHideBanner = () => {
|
||||
this.setState({bannerVisible: false});
|
||||
|
@ -563,6 +582,10 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback when pressing the login button on the banner.
|
||||
* This hides the banner and takes the user to the login page.
|
||||
*/
|
||||
onLoginBanner = () => {
|
||||
this.onHideBanner();
|
||||
this.props.navigation.navigate("login", {nextScreen: "profile"});
|
||||
|
|
|
@ -41,6 +41,9 @@ class ScannerScreen extends React.Component<Props, State> {
|
|||
this.requestPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests permission to use the camera
|
||||
*/
|
||||
requestPermissions = () => {
|
||||
if (Platform.OS === 'android')
|
||||
request(PERMISSIONS.ANDROID.CAMERA).then(this.updatePermissionStatus)
|
||||
|
@ -48,8 +51,19 @@ class ScannerScreen extends React.Component<Props, State> {
|
|||
request(PERMISSIONS.IOS.CAMERA).then(this.updatePermissionStatus)
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the state permission status
|
||||
*
|
||||
* @param result
|
||||
*/
|
||||
updatePermissionStatus = (result) => this.setState({hasPermission: result === RESULTS.GRANTED});
|
||||
|
||||
/**
|
||||
* Opens scanned link if it is a valid app link or shows and error dialog
|
||||
*
|
||||
* @param type The barcode type
|
||||
* @param data The scanned value
|
||||
*/
|
||||
handleCodeScanned = ({type, data}) => {
|
||||
if (!URLHandler.isUrlValid(data))
|
||||
this.showErrorDialog();
|
||||
|
@ -59,6 +73,11 @@ class ScannerScreen extends React.Component<Props, State> {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a view asking user for permission to use the camera
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getPermissionScreen() {
|
||||
return <View style={{marginLeft: 10, marginRight: 10}}>
|
||||
<Text>{i18n.t("scannerScreen.errorPermission")}</Text>
|
||||
|
@ -77,6 +96,9 @@ class ScannerScreen extends React.Component<Props, State> {
|
|||
</View>
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog indicating how to use the scanner
|
||||
*/
|
||||
showHelpDialog = () => {
|
||||
this.setState({
|
||||
dialogVisible: true,
|
||||
|
@ -86,6 +108,9 @@ class ScannerScreen extends React.Component<Props, State> {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows a loading dialog
|
||||
*/
|
||||
showOpeningDialog = () => {
|
||||
this.setState({
|
||||
loading: true,
|
||||
|
@ -93,6 +118,9 @@ class ScannerScreen extends React.Component<Props, State> {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows a dialog indicating the user the scanned code was invalid
|
||||
*/
|
||||
showErrorDialog() {
|
||||
this.setState({
|
||||
dialogVisible: true,
|
||||
|
@ -102,11 +130,21 @@ class ScannerScreen extends React.Component<Props, State> {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide any dialog
|
||||
*/
|
||||
onDialogDismiss = () => this.setState({
|
||||
dialogVisible: false,
|
||||
scanned: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets a view with the scanner.
|
||||
* This scanner uses the back camera, can only scan qr codes and has a square mask on the center.
|
||||
* The mask is only for design purposes as a code is scanned as soon as it enters the camera view
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getScanner() {
|
||||
return (
|
||||
<RNCamera
|
||||
|
|
|
@ -32,10 +32,10 @@ export function stringMatchQuery(str: string, query: string) {
|
|||
* Checks if the given arrays have an item in common
|
||||
*
|
||||
* @param filter The filter array
|
||||
* @param categories The item's categories array
|
||||
* @param categories The item's categories tuple
|
||||
* @returns {boolean} True if at least one entry is in both arrays
|
||||
*/
|
||||
export function isItemInCategoryFilter(filter: Array<string>, categories: Array<string>) {
|
||||
export function isItemInCategoryFilter(filter: Array<number>, categories: [number, number]) {
|
||||
for (const category of categories) {
|
||||
if (filter.indexOf(category) !== -1)
|
||||
return true;
|
||||
|
|
Loading…
Reference in a new issue