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.

AboutScreen.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. // @flow
  2. import * as React from 'react';
  3. import {FlatList, Linking, Platform, Image, View} from 'react-native';
  4. import i18n from 'i18n-js';
  5. import {Avatar, Card, List, withTheme} from 'react-native-paper';
  6. import {StackNavigationProp} from '@react-navigation/stack';
  7. import {Modalize} from 'react-native-modalize';
  8. import packageJson from '../../../package.json';
  9. import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
  10. import APP_LOGO from '../../../assets/android.icon.round.png';
  11. import type {
  12. CardTitleIconPropsType,
  13. ListIconPropsType,
  14. } from '../../constants/PaperStyles';
  15. import OptionsDialog from '../../components/Dialogs/OptionsDialog';
  16. type ListItemType = {
  17. onPressCallback: () => void,
  18. icon: string,
  19. text: string,
  20. showChevron: boolean,
  21. };
  22. type AthorsItemType = {
  23. name: string,
  24. message: string,
  25. btnTrool: OptionsDialogButtonType,
  26. btnLinkedin: OptionsDialogButtonType,
  27. btnMail: OptionsDialogButtonType,
  28. };
  29. const links = {
  30. appstore: 'https://apps.apple.com/us/app/campus-amicale-insat/id1477722148',
  31. playstore:
  32. 'https://play.google.com/store/apps/details?id=fr.amicaleinsat.application',
  33. git:
  34. 'https://git.etud.insa-toulouse.fr/vergnet/application-amicale/src/branch/master/README.md',
  35. changelog:
  36. 'https://git.etud.insa-toulouse.fr/vergnet/application-amicale/src/branch/master/Changelog.md',
  37. license:
  38. 'https://git.etud.insa-toulouse.fr/vergnet/application-amicale/src/branch/master/LICENSE',
  39. arnaudMail:
  40. 'mailto:vergnet@etud.insa-toulouse.fr?' +
  41. 'subject=' +
  42. 'Application Amicale INSA Toulouse' +
  43. '&body=' +
  44. 'Coucou !\n\n',
  45. arnaudLinkedin: 'https://www.linkedin.com/in/arnaud-vergnet-434ba5179/',
  46. yohanMail:
  47. 'mailto:ysimard@etud.insa-toulouse.fr?' +
  48. 'subject=' +
  49. 'Application Amicale INSA Toulouse' +
  50. '&body=' +
  51. 'Coucou !\n\n',
  52. yohanLinkedin: 'https://www.linkedin.com/in/yohan-simard',
  53. react: 'https://facebook.github.io/react-native/',
  54. meme: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
  55. };
  56. type PropsType = {
  57. navigation: StackNavigationProp,
  58. };
  59. /**
  60. * Opens a link in the device's browser
  61. * @param link The link to open
  62. */
  63. function openWebLink(link: string) {
  64. Linking.openURL(link);
  65. }
  66. /**
  67. * Class defining an about screen. This screen shows the user information about the app and it's author.
  68. */
  69. class AboutScreen extends React.Component<PropsType> {
  70. modalRef: Modalize | null;
  71. /**
  72. * Data team
  73. */
  74. teamUsers = {
  75. arnaud: {
  76. name: 'Arnaud Vergnrt',
  77. message: i18n.t('screens.about.user.arnaud'),
  78. icon: 'crown',
  79. btnTrool: {
  80. title: 'SWAG',
  81. onPress: () => {
  82. openWebLink(links.meme);
  83. },
  84. },
  85. btnLinkedin: {
  86. title: '',
  87. icon: 'linkedin',
  88. onPress: () => {
  89. openWebLink(links.arnaudMail);
  90. },
  91. },
  92. btnMail: {
  93. title: '',
  94. icon: 'email-edit',
  95. onPress: () => {
  96. openWebLink(links.arnaudLinkedin);
  97. },
  98. },
  99. },
  100. yohan: {
  101. name: 'Yohan Simard',
  102. message: i18n.t('screens.about.user.yohan'),
  103. icon: 'xml',
  104. btnTrool: null,
  105. btnLinkedin: {
  106. title: '',
  107. icon: 'linkedin',
  108. onPress: () => {
  109. openWebLink(links.yohanLinkedin);
  110. },
  111. },
  112. btnMail: {
  113. title: '',
  114. icon: 'email-edit',
  115. onPress: () => {
  116. openWebLink(links.yohanMail);
  117. },
  118. },
  119. },
  120. };
  121. /**
  122. * Data thanks
  123. */
  124. thanksUsers = {
  125. beranger: {
  126. name: 'Béranger Quintana Y Arciosana',
  127. message: i18n.t('screens.about.user.beranger'),
  128. icon: 'account-heart',
  129. btnTrool: null,
  130. btnLinkedin: null,
  131. btnMail: null,
  132. },
  133. celine: {
  134. name: 'Céline Tassin',
  135. message: i18n.t('screens.about.user.celine'),
  136. icon: 'brush',
  137. btnTrool: null,
  138. btnLinkedin: null,
  139. btnMail: null,
  140. },
  141. damien: {
  142. name: 'Damien Molina',
  143. message: i18n.t('screens.about.user.damien'),
  144. icon: 'web',
  145. btnTrool: null,
  146. btnLinkedin: null,
  147. btnMail: null,
  148. },
  149. titouan: {
  150. name: 'Titouan Labourdette',
  151. message: i18n.t('screens.about.user.titouan'),
  152. icon: 'shield-bug',
  153. btnTrool: null,
  154. btnLinkedin: null,
  155. btnMail: null,
  156. },
  157. theo: {
  158. name: 'Théo Tami',
  159. message: i18n.t('screens.about.user.theo'),
  160. icon: 'food-apple',
  161. btnTrool: null,
  162. btnLinkedin: null,
  163. btnMail: null,
  164. },
  165. };
  166. /**
  167. * Data to be displayed in the app card
  168. */
  169. appData = [
  170. {
  171. onPressCallback: () => {
  172. openWebLink(Platform.OS === 'ios' ? links.appstore : links.playstore);
  173. },
  174. icon: Platform.OS === 'ios' ? 'apple' : 'google-play',
  175. text:
  176. Platform.OS === 'ios'
  177. ? i18n.t('screens.about.appstore')
  178. : i18n.t('screens.about.playstore'),
  179. showChevron: true,
  180. },
  181. {
  182. onPressCallback: () => {
  183. const {navigation} = this.props;
  184. navigation.navigate('feedback');
  185. },
  186. icon: 'bug',
  187. text: i18n.t('screens.feedback.homeButtonTitle'),
  188. showChevron: true,
  189. },
  190. {
  191. onPressCallback: () => {
  192. openWebLink(links.git);
  193. },
  194. icon: 'git',
  195. text: 'Git',
  196. showChevron: true,
  197. },
  198. {
  199. onPressCallback: () => {
  200. openWebLink(links.changelog);
  201. },
  202. icon: 'refresh',
  203. text: i18n.t('screens.about.changelog'),
  204. showChevron: true,
  205. },
  206. {
  207. onPressCallback: () => {
  208. openWebLink(links.license);
  209. },
  210. icon: 'file-document',
  211. text: i18n.t('screens.about.license'),
  212. showChevron: true,
  213. },
  214. ];
  215. /**
  216. * Data to be displayed in the additional developer card
  217. */
  218. teamData = [
  219. {
  220. onPressCallback: () => {
  221. this.onListItemPress(this.teamUsers.arnaud);
  222. },
  223. icon: this.teamUsers.arnaud.icon,
  224. text: this.teamUsers.arnaud.name,
  225. showChevron: false,
  226. },
  227. {
  228. onPressCallback: () => {
  229. this.onListItemPress(this.teamUsers.yohan);
  230. },
  231. icon: this.teamUsers.yohan.icon,
  232. text: this.teamUsers.yohan.name,
  233. showChevron: false,
  234. },
  235. {
  236. onPressCallback: () => {
  237. const {navigation} = this.props;
  238. navigation.navigate('feedback');
  239. },
  240. icon: 'hand-pointing-right',
  241. text: i18n.t('screens.about.user.you'),
  242. showChevron: true,
  243. },
  244. ];
  245. /**
  246. * Data to be displayed in the thanks card
  247. */
  248. thanksData = [
  249. {
  250. onPressCallback: () => {
  251. this.onListItemPress(this.thanksUsers.beranger);
  252. },
  253. icon: this.thanksUsers.beranger.icon,
  254. text: this.thanksUsers.beranger.name,
  255. showChevron: false,
  256. },
  257. {
  258. onPressCallback: () => {
  259. this.onListItemPress(this.thanksUsers.celine);
  260. },
  261. icon: this.thanksUsers.celine.icon,
  262. text: this.thanksUsers.celine.name,
  263. showChevron: false,
  264. },
  265. {
  266. onPressCallback: () => {
  267. this.onListItemPress(this.thanksUsers.damien);
  268. },
  269. icon: this.thanksUsers.damien.icon,
  270. text: this.thanksUsers.damien.name,
  271. showChevron: false,
  272. },
  273. {
  274. onPressCallback: () => {
  275. this.onListItemPress(this.thanksUsers.titouan);
  276. },
  277. icon: this.thanksUsers.titouan.icon,
  278. text: this.thanksUsers.titouan.name,
  279. showChevron: false,
  280. },
  281. {
  282. onPressCallback: () => {
  283. this.onListItemPress(this.thanksUsers.theo);
  284. },
  285. icon: this.thanksUsers.theo.icon,
  286. text: this.thanksUsers.theo.name,
  287. showChevron: false,
  288. },
  289. ];
  290. /**
  291. * Data to be displayed in the technologies card
  292. */
  293. technoData = [
  294. {
  295. onPressCallback: () => {
  296. openWebLink(links.react);
  297. },
  298. icon: 'react',
  299. text: i18n.t('screens.about.reactNative'),
  300. showChevron: true,
  301. },
  302. {
  303. onPressCallback: () => {
  304. const {navigation} = this.props;
  305. navigation.navigate('dependencies');
  306. },
  307. icon: 'developer-board',
  308. text: i18n.t('screens.about.libs'),
  309. showChevron: true,
  310. },
  311. ];
  312. /**
  313. * Order of information cards
  314. */
  315. dataOrder = [
  316. {
  317. id: 'app',
  318. },
  319. {
  320. id: 'team',
  321. },
  322. {
  323. id: 'thanks',
  324. },
  325. {
  326. id: 'techno',
  327. },
  328. ];
  329. constructor(props: PropsType) {
  330. super(props);
  331. this.state = {
  332. dialogVisible: false,
  333. dialogTitle: '',
  334. dialogMessage: '',
  335. dialogButtons: [],
  336. onDialogDismiss: () => {
  337. this.setState({dialogVisible: false});
  338. },
  339. };
  340. }
  341. /**
  342. * Callback used when clicking an article in the list.
  343. * It opens the modal to show detailed information about the article
  344. *
  345. * @param user A user key
  346. */
  347. onListItemPress(user: AthorsItemType) {
  348. const dialogBtn: Array<IconOptionsDialogButtonType> = [
  349. {
  350. title: 'OK',
  351. onPress: () => {
  352. this.setState({dialogVisible: false});
  353. },
  354. },
  355. ];
  356. if (user.btnMail != null) {
  357. dialogBtn.push(user.btnMail);
  358. }
  359. if (user.btnLinkedin != null) {
  360. dialogBtn.push(user.btnLinkedin);
  361. }
  362. if (user.btnTrool != null) {
  363. dialogBtn.push(user.btnTrool);
  364. }
  365. this.setState({
  366. dialogVisible: true,
  367. dialogTitle: user.name,
  368. dialogMessage: user.message,
  369. dialogButtons: dialogBtn,
  370. });
  371. }
  372. /**
  373. * Gets the app card showing information and links about the app.
  374. *
  375. * @return {*}
  376. */
  377. getAppCard(): React.Node {
  378. return (
  379. <Card style={{marginBottom: 10}}>
  380. <Card.Title
  381. title="Campus"
  382. subtitle={packageJson.version}
  383. left={(iconProps: CardTitleIconPropsType): React.Node => (
  384. <Image
  385. size={iconProps.size}
  386. source={APP_LOGO}
  387. style={{width: iconProps.size, height: iconProps.size}}
  388. />
  389. )}
  390. />
  391. <Card.Content>
  392. <FlatList
  393. data={this.appData}
  394. keyExtractor={this.keyExtractor}
  395. renderItem={this.getCardItem}
  396. />
  397. </Card.Content>
  398. </Card>
  399. );
  400. }
  401. /**
  402. * Gets the team card showing information and links about the team
  403. *
  404. * @return {*}
  405. */
  406. getTeamCard(): React.Node {
  407. return (
  408. <Card style={{marginBottom: 10}}>
  409. <Card.Title
  410. title={i18n.t('screens.about.team')}
  411. left={(iconProps: CardTitleIconPropsType): React.Node => (
  412. <Avatar.Icon size={iconProps.size} icon="account-multiple" />
  413. )}
  414. />
  415. <Card.Content>
  416. <FlatList
  417. data={this.teamData}
  418. keyExtractor={this.keyExtractor}
  419. renderItem={this.getCardItem}
  420. />
  421. </Card.Content>
  422. </Card>
  423. );
  424. }
  425. /**
  426. * Get the thank you card showing support information and links
  427. *
  428. * @return {*}
  429. */
  430. getThanksCard(): React.Node {
  431. return (
  432. <Card style={{marginBottom: 10}}>
  433. <Card.Title
  434. title={i18n.t('screens.about.thanks')}
  435. left={(iconProps: CardTitleIconPropsType): React.Node => (
  436. <Avatar.Icon size={iconProps.size} icon="hand-heart" />
  437. )}
  438. />
  439. <Card.Content>
  440. <FlatList
  441. data={this.thanksData}
  442. keyExtractor={this.keyExtractor}
  443. renderItem={this.getCardItem}
  444. />
  445. </Card.Content>
  446. </Card>
  447. );
  448. }
  449. /**
  450. * Gets the techno card showing information and links about the technologies used in the app
  451. *
  452. * @return {*}
  453. */
  454. getTechnoCard(): React.Node {
  455. return (
  456. <Card style={{marginBottom: 10}}>
  457. <Card.Title
  458. title={i18n.t('screens.about.technologies')}
  459. left={(iconProps: CardTitleIconPropsType): React.Node => (
  460. <Avatar.Icon size={iconProps.size} icon="wrench" />
  461. )}
  462. />
  463. <Card.Content>
  464. <FlatList
  465. data={this.technoData}
  466. keyExtractor={this.keyExtractor}
  467. renderItem={this.getCardItem}
  468. />
  469. </Card.Content>
  470. </Card>
  471. );
  472. }
  473. /**
  474. * Gets a chevron icon
  475. *
  476. * @param props
  477. * @return {*}
  478. */
  479. static getChevronIcon(props: ListIconPropsType): React.Node {
  480. return (
  481. <List.Icon color={props.color} style={props.style} icon="chevron-right" />
  482. );
  483. }
  484. /**
  485. * Gets a custom list item icon
  486. *
  487. * @param item The item to show the icon for
  488. * @param props
  489. * @return {*}
  490. */
  491. static getItemIcon(item: ListItemType, props: ListIconPropsType): React.Node {
  492. return (
  493. <List.Icon color={props.color} style={props.style} icon={item.icon} />
  494. );
  495. }
  496. /**
  497. * Gets a clickable card item to be rendered inside a card.
  498. *
  499. * @returns {*}
  500. */
  501. getCardItem = ({item}: {item: ListItemType}): React.Node => {
  502. const getItemIcon = (props: ListIconPropsType): React.Node =>
  503. AboutScreen.getItemIcon(item, props);
  504. if (item.showChevron) {
  505. return (
  506. <List.Item
  507. title={item.text}
  508. left={getItemIcon}
  509. right={AboutScreen.getChevronIcon}
  510. onPress={item.onPressCallback}
  511. />
  512. );
  513. }
  514. return (
  515. <List.Item
  516. title={item.text}
  517. left={getItemIcon}
  518. onPress={item.onPressCallback}
  519. />
  520. );
  521. };
  522. /**
  523. * Gets a card, depending on the given item's id
  524. *
  525. * @param item The item to show
  526. * @return {*}
  527. */
  528. getMainCard = ({item}: {item: {id: string}}): React.Node => {
  529. switch (item.id) {
  530. case 'app':
  531. return this.getAppCard();
  532. case 'team':
  533. return this.getTeamCard();
  534. case 'thanks':
  535. return this.getThanksCard();
  536. case 'techno':
  537. return this.getTechnoCard();
  538. default:
  539. return null;
  540. }
  541. };
  542. /**
  543. * Extracts a key from the given item
  544. *
  545. * @param item The item to extract the key from
  546. * @return {string} The extracted key
  547. */
  548. keyExtractor = (item: ListItemType): string => item.icon;
  549. render(): React.Node {
  550. const {state} = this;
  551. return (
  552. <View
  553. style={{
  554. height: '100%',
  555. }}>
  556. <CollapsibleFlatList
  557. style={{padding: 5}}
  558. data={this.dataOrder}
  559. renderItem={this.getMainCard}
  560. />
  561. <OptionsDialog
  562. visible={state.dialogVisible}
  563. title={state.dialogTitle}
  564. message={state.dialogMessage}
  565. buttons={state.dialogButtons}
  566. onDismiss={state.onDialogDismiss}
  567. />
  568. </View>
  569. );
  570. }
  571. }
  572. export default withTheme(AboutScreen);