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 15KB

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