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

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