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

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