Application Android et IOS pour l'amicale des élèves https://play.google.com/store/apps/details?id=fr.amicaleinsat.application
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.tsx 15KB

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