Application Android et IOS pour l'amicale des élèves
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ProfileScreen.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. // @flow
  2. import * as React from 'react';
  3. import {Animated, FlatList, StyleSheet, View} from "react-native";
  4. import {Avatar, Button, Card, Divider, List, Paragraph, withTheme} from 'react-native-paper';
  5. import AuthenticatedScreen from "../../components/Amicale/AuthenticatedScreen";
  6. import i18n from 'i18n-js';
  7. import LogoutDialog from "../../components/Amicale/LogoutDialog";
  8. import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
  9. import CustomTabBar from "../../components/Tabbar/CustomTabBar";
  10. import {Collapsible} from "react-navigation-collapsible";
  11. import {withCollapsible} from "../../utils/withCollapsible";
  12. import type {cardList} from "../../components/Lists/CardList/CardList";
  13. import CardList from "../../components/Lists/CardList/CardList";
  14. import {StackNavigationProp} from "@react-navigation/stack";
  15. import type {CustomTheme} from "../../managers/ThemeManager";
  16. type Props = {
  17. navigation: StackNavigationProp,
  18. theme: CustomTheme,
  19. collapsibleStack: Collapsible,
  20. }
  21. type State = {
  22. dialogVisible: boolean,
  23. }
  24. type ProfileData = {
  25. first_name: string,
  26. last_name: string,
  27. email: string,
  28. birthday: string,
  29. phone: string,
  30. branch: string,
  31. link: string,
  32. validity: boolean,
  33. clubs: Array<Club>,
  34. }
  35. type Club = {
  36. id: number,
  37. name: string,
  38. is_manager: boolean,
  39. }
  40. const CLUBS_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Clubs.png";
  41. const VOTE_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Vote.png";
  42. const ICON_AMICALE = require('../../../assets/amicale.png');
  43. class ProfileScreen extends React.Component<Props, State> {
  44. state = {
  45. dialogVisible: false,
  46. };
  47. data: ProfileData;
  48. flatListData: Array<{ id: string }>;
  49. amicaleDataset: cardList;
  50. constructor() {
  51. super();
  52. this.flatListData = [
  53. {id: '0'},
  54. {id: '1'},
  55. {id: '2'},
  56. {id: '3'},
  57. ]
  58. this.amicaleDataset = [
  59. {
  60. title: i18n.t('screens.clubsAbout'),
  61. subtitle: i18n.t('servicesScreen.descriptions.clubs'),
  62. image: CLUBS_IMAGE,
  63. onPress: () => this.props.navigation.navigate("club-list"),
  64. },
  65. {
  66. title: i18n.t('screens.vote'),
  67. subtitle: i18n.t('servicesScreen.descriptions.vote'),
  68. image: VOTE_IMAGE,
  69. onPress: () => this.props.navigation.navigate("vote"),
  70. },
  71. {
  72. title: i18n.t('screens.amicaleWebsite'),
  73. subtitle: i18n.t('servicesScreen.descriptions.amicaleWebsite'),
  74. image: ICON_AMICALE,
  75. onPress: () => this.props.navigation.navigate("amicale-website"),
  76. },
  77. ];
  78. }
  79. componentDidMount() {
  80. this.props.navigation.setOptions({
  81. headerRight: this.getHeaderButton,
  82. });
  83. }
  84. showDisconnectDialog = () => this.setState({dialogVisible: true});
  85. hideDisconnectDialog = () => this.setState({dialogVisible: false});
  86. /**
  87. * Gets the logout header button
  88. *
  89. * @returns {*}
  90. */
  91. getHeaderButton = () => <MaterialHeaderButtons>
  92. <Item title="logout" iconName="logout" onPress={this.showDisconnectDialog}/>
  93. </MaterialHeaderButtons>;
  94. /**
  95. * Gets the main screen component with the fetched data
  96. *
  97. * @param data The data fetched from the server
  98. * @returns {*}
  99. */
  100. getScreen = (data: Array<{ [key: string]: any } | null>) => {
  101. if (data[0] != null) {
  102. this.data = data[0];
  103. }
  104. const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
  105. return (
  106. <View style={{flex: 1}}>
  107. <Animated.FlatList
  108. renderItem={this.getRenderItem}
  109. data={this.flatListData}
  110. // Animations
  111. onScroll={onScroll}
  112. contentContainerStyle={{
  113. paddingTop: containerPaddingTop,
  114. paddingBottom: CustomTabBar.TAB_BAR_HEIGHT,
  115. minHeight: '100%'
  116. }}
  117. scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
  118. />
  119. <LogoutDialog
  120. {...this.props}
  121. visible={this.state.dialogVisible}
  122. onDismiss={this.hideDisconnectDialog}
  123. />
  124. </View>
  125. )
  126. };
  127. getRenderItem = ({item}: { item: { id: string } }) => {
  128. switch (item.id) {
  129. case '0':
  130. return this.getWelcomeCard();
  131. case '1':
  132. return this.getPersonalCard();
  133. case '2':
  134. return this.getClubCard();
  135. default:
  136. return this.getMembershipCar();
  137. }
  138. };
  139. /**
  140. * Gets the list of services available with the Amicale account
  141. *
  142. * @returns {*}
  143. */
  144. getServicesList() {
  145. return (
  146. <CardList
  147. dataset={this.amicaleDataset}
  148. isHorizontal={true}
  149. />
  150. );
  151. }
  152. /**
  153. * Gets a card welcoming the user to his account
  154. *
  155. * @returns {*}
  156. */
  157. getWelcomeCard() {
  158. return (
  159. <Card style={styles.card}>
  160. <Card.Title
  161. title={i18n.t("profileScreen.welcomeTitle", {name: this.data.first_name})}
  162. left={() => <Avatar.Image
  163. size={64}
  164. source={ICON_AMICALE}
  165. style={{backgroundColor: 'transparent',}}
  166. />}
  167. titleStyle={{marginLeft: 10}}
  168. />
  169. <Card.Content>
  170. <Divider/>
  171. <Paragraph>
  172. {i18n.t("profileScreen.welcomeDescription")}
  173. </Paragraph>
  174. {this.getServicesList()}
  175. <Paragraph>
  176. {i18n.t("profileScreen.welcomeFeedback")}
  177. </Paragraph>
  178. <Divider/>
  179. <Card.Actions>
  180. <Button
  181. icon="bug"
  182. mode="contained"
  183. onPress={() => this.props.navigation.navigate('feedback')}
  184. style={styles.editButton}>
  185. {i18n.t("feedbackScreen.homeButtonTitle")}
  186. </Button>
  187. </Card.Actions>
  188. </Card.Content>
  189. </Card>
  190. );
  191. }
  192. /**
  193. * Checks if the given field is available
  194. *
  195. * @param field The field to check
  196. * @return {boolean}
  197. */
  198. isFieldAvailable(field: ?string) {
  199. return field !== null;
  200. }
  201. /**
  202. * Gets the given field value.
  203. * If the field does not have a value, returns a placeholder text
  204. *
  205. * @param field The field to get the value from
  206. * @return {*}
  207. */
  208. getFieldValue(field: ?string) {
  209. return this.isFieldAvailable(field)
  210. ? field
  211. : i18n.t("profileScreen.noData");
  212. }
  213. /**
  214. * Gets a list item showing personal information
  215. *
  216. * @param field The field to display
  217. * @param icon The icon to use
  218. * @return {*}
  219. */
  220. getPersonalListItem(field: ?string, icon: string) {
  221. let title = this.isFieldAvailable(field) ? this.getFieldValue(field) : ':(';
  222. let subtitle = this.isFieldAvailable(field) ? '' : this.getFieldValue(field);
  223. return (
  224. <List.Item
  225. title={title}
  226. description={subtitle}
  227. left={props => <List.Icon
  228. {...props}
  229. icon={icon}
  230. color={this.isFieldAvailable(field) ? undefined : this.props.theme.colors.textDisabled}
  231. />}
  232. />
  233. );
  234. }
  235. /**
  236. * Gets a card containing user personal information
  237. *
  238. * @return {*}
  239. */
  240. getPersonalCard() {
  241. return (
  242. <Card style={styles.card}>
  243. <Card.Title
  244. title={this.data.first_name + ' ' + this.data.last_name}
  245. subtitle={this.data.email}
  246. left={(props) => <Avatar.Icon
  247. {...props}
  248. icon="account"
  249. color={this.props.theme.colors.primary}
  250. style={styles.icon}
  251. />}
  252. />
  253. <Card.Content>
  254. <Divider/>
  255. <List.Section>
  256. <List.Subheader>{i18n.t("profileScreen.personalInformation")}</List.Subheader>
  257. {this.getPersonalListItem(this.data.birthday, "cake-variant")}
  258. {this.getPersonalListItem(this.data.phone, "phone")}
  259. {this.getPersonalListItem(this.data.email, "email")}
  260. {this.getPersonalListItem(this.data.branch, "school")}
  261. </List.Section>
  262. <Divider/>
  263. <Card.Actions>
  264. <Button
  265. icon="account-edit"
  266. mode="contained"
  267. onPress={() => this.props.navigation.navigate('amicale-website', {path: this.data.link})}
  268. style={styles.editButton}>
  269. {i18n.t("profileScreen.editInformation")}
  270. </Button>
  271. </Card.Actions>
  272. </Card.Content>
  273. </Card>
  274. );
  275. }
  276. /**
  277. * Gets a cars containing clubs the user is part of
  278. *
  279. * @return {*}
  280. */
  281. getClubCard() {
  282. return (
  283. <Card style={styles.card}>
  284. <Card.Title
  285. title={i18n.t("profileScreen.clubs")}
  286. subtitle={i18n.t("profileScreen.clubsSubtitle")}
  287. left={(props) => <Avatar.Icon
  288. {...props}
  289. icon="account-group"
  290. color={this.props.theme.colors.primary}
  291. style={styles.icon}
  292. />}
  293. />
  294. <Card.Content>
  295. <Divider/>
  296. {this.getClubList(this.data.clubs)}
  297. </Card.Content>
  298. </Card>
  299. );
  300. }
  301. /**
  302. * Gets a card showing if the user has payed his membership
  303. *
  304. * @return {*}
  305. */
  306. getMembershipCar() {
  307. return (
  308. <Card style={styles.card}>
  309. <Card.Title
  310. title={i18n.t("profileScreen.membership")}
  311. subtitle={i18n.t("profileScreen.membershipSubtitle")}
  312. left={(props) => <Avatar.Icon
  313. {...props}
  314. icon="credit-card"
  315. color={this.props.theme.colors.primary}
  316. style={styles.icon}
  317. />}
  318. />
  319. <Card.Content>
  320. <List.Section>
  321. {this.getMembershipItem(this.data.validity)}
  322. </List.Section>
  323. </Card.Content>
  324. </Card>
  325. );
  326. }
  327. /**
  328. * Gets the item showing if the user has payed his membership
  329. *
  330. * @return {*}
  331. */
  332. getMembershipItem(state: boolean) {
  333. return (
  334. <List.Item
  335. title={state ? i18n.t("profileScreen.membershipPayed") : i18n.t("profileScreen.membershipNotPayed")}
  336. left={props => <List.Icon
  337. {...props}
  338. color={state ? this.props.theme.colors.success : this.props.theme.colors.danger}
  339. icon={state ? 'check' : 'close'}
  340. />}
  341. />
  342. );
  343. }
  344. /**
  345. * Opens the club details screen for the club of given ID
  346. * @param id The club's id to open
  347. */
  348. openClubDetailsScreen(id: number) {
  349. this.props.navigation.navigate("club-information", {clubId: id});
  350. }
  351. /**
  352. * Gets a list item for the club list
  353. *
  354. * @param item The club to render
  355. * @return {*}
  356. */
  357. clubListItem = ({item}: { item: Club }) => {
  358. const onPress = () => this.openClubDetailsScreen(item.id);
  359. let description = i18n.t("profileScreen.isMember");
  360. let icon = (props) => <List.Icon {...props} icon="chevron-right"/>;
  361. if (item.is_manager) {
  362. description = i18n.t("profileScreen.isManager");
  363. icon = (props) => <List.Icon {...props} icon="star" color={this.props.theme.colors.primary}/>;
  364. }
  365. return <List.Item
  366. title={item.name}
  367. description={description}
  368. left={icon}
  369. onPress={onPress}
  370. />;
  371. };
  372. clubKeyExtractor = (item: Club) => item.name;
  373. sortClubList = (a: Club, b: Club) => a.is_manager ? -1 : 1;
  374. /**
  375. * Renders the list of clubs the user is part of
  376. *
  377. * @param list The club list
  378. * @return {*}
  379. */
  380. getClubList(list: Array<Club>) {
  381. list.sort(this.sortClubList);
  382. return (
  383. //$FlowFixMe
  384. <FlatList
  385. renderItem={this.clubListItem}
  386. keyExtractor={this.clubKeyExtractor}
  387. data={list}
  388. />
  389. );
  390. }
  391. render() {
  392. return (
  393. <AuthenticatedScreen
  394. {...this.props}
  395. requests={[
  396. {
  397. link: 'user/profile',
  398. params: {},
  399. mandatory: true,
  400. }
  401. ]}
  402. renderFunction={this.getScreen}
  403. />
  404. );
  405. }
  406. }
  407. const styles = StyleSheet.create({
  408. card: {
  409. margin: 10,
  410. },
  411. icon: {
  412. backgroundColor: 'transparent'
  413. },
  414. editButton: {
  415. marginLeft: 'auto'
  416. }
  417. });
  418. export default withCollapsible(withTheme(ProfileScreen));