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

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