First implementation of planning

This commit is contained in:
keplyx 2019-09-15 15:12:26 +02:00
parent 575b900268
commit ddf58959c9
6 changed files with 429 additions and 59 deletions

View file

@ -10,7 +10,7 @@
"android",
"web"
],
"version": "1.0.2",
"version": "1.0.3",
"orientation": "portrait",
"primaryColor": "#be1522",
"icon": "./assets/android.icon.png",
@ -36,7 +36,7 @@
},
"android": {
"package": "fr.amicaleinsat.application",
"versionCode": 5,
"versionCode": 6,
"icon": "./assets/android.icon.png",
"adaptiveIcon": {
"foregroundImage": "./assets/android.adaptive-icon.png",

View file

@ -242,6 +242,7 @@ export default {
// Text
textColor: "#000",
textDisabledColor: "#d9e1e8",
inverseTextColor: "#fff",
noteFontSize: 14,
get defaultTextColor() {
@ -260,6 +261,10 @@ export default {
customMaterialIconColor: "#5d5d5d",
fetchedDataSectionListErrorText: "#898989",
// Calendar/Agenda
agendaBackgroundColor: '#f3f3f4',
agendaEmptyLine: '#dbdbdc',
// PROXIWASH
proxiwashFinishedColor: "rgba(54,165,22,0.31)",
proxiwashReadyColor: "transparent",

View file

@ -243,6 +243,7 @@ export default {
// Text
textColor: "#ebebeb",
textDisabledColor: "#3b3f42",
inverseTextColor: "#000",
noteFontSize: 14,
get defaultTextColor() {
@ -261,6 +262,10 @@ export default {
customMaterialIconColor: "#b3b3b3",
fetchedDataSectionListErrorText: "#acacac",
// Calendar/Agenda
agendaBackgroundColor: '#373737',
agendaEmptyLine: '#464646',
// PROXIWASH
proxiwashFinishedColor: "rgba(17,149,32,0.53)",
proxiwashReadyColor: "transparent",

124
package-lock.json generated
View file

@ -1866,6 +1866,16 @@
"node-int64": "^0.4.0"
}
},
"buffer": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4",
"isarray": "^1.0.0"
}
},
"buffer-alloc": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
@ -2504,11 +2514,54 @@
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"dom-serializer": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz",
"integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==",
"requires": {
"domelementtype": "^2.0.1",
"entities": "^2.0.0"
},
"dependencies": {
"domelementtype": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
},
"entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
}
}
},
"dom-walk": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz",
"integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg="
},
"domelementtype": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
},
"domhandler": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
"requires": {
"domelementtype": "1"
}
},
"domutils": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
"integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
"requires": {
"dom-serializer": "0",
"domelementtype": "1"
}
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -2540,6 +2593,11 @@
"once": "^1.4.0"
}
},
"entities": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
},
"envinfo": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-5.12.1.tgz",
@ -2597,6 +2655,11 @@
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
},
"events": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
},
"exec-sh": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz",
@ -4067,6 +4130,36 @@
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
"integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w=="
},
"html-entities": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz",
"integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8="
},
"htmlparser2": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
"requires": {
"domelementtype": "^1.3.1",
"domhandler": "^2.3.0",
"domutils": "^1.5.1",
"entities": "^1.1.1",
"inherits": "^2.0.1",
"readable-stream": "^3.1.1"
},
"dependencies": {
"readable-stream": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
"integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"http-errors": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
@ -4092,6 +4185,11 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"image-size": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz",
@ -6131,6 +6229,16 @@
"resolved": "https://registry.npmjs.org/react-native-branch/-/react-native-branch-2.2.5.tgz",
"integrity": "sha1-QHTdY7SXPmOX2c5Q6XtXx3pRjp0="
},
"react-native-calendars": {
"version": "1.212.0",
"resolved": "https://registry.npmjs.org/react-native-calendars/-/react-native-calendars-1.212.0.tgz",
"integrity": "sha512-FrWabRJlUeTjCF8qx8cYAId4SbCMmVmO5eE791ykQaTG2/jvQJz2O3MF5QRjJHyaS/QdeEilKf9w/Mk43bM42Q==",
"requires": {
"lodash": "^4.0.0",
"prop-types": "^15.5.10",
"xdate": "^0.8.0"
}
},
"react-native-drawer": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/react-native-drawer/-/react-native-drawer-2.5.1.tgz",
@ -6242,6 +6350,17 @@
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-1.0.1.tgz",
"integrity": "sha512-RENoo6/sJc3FApP7vJ1Js7WyDuTVh97bbr5aMjJyw3kqpR2/JDHyL/dQFfOvSSAc+VjitpR9/CfPPad7tLRiIA=="
},
"react-native-render-html": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/react-native-render-html/-/react-native-render-html-4.1.2.tgz",
"integrity": "sha512-lIW7GfNCsqOzdv0hxiZms24uC9Hu8hqufMyD4FBjfs8HSc4pUKnvBgOljiqEXmjGhDEJM6oY7QGglAV0hL69bQ==",
"requires": {
"buffer": "^4.5.1",
"events": "^1.1.0",
"html-entities": "^1.2.0",
"htmlparser2": "^3.10.1"
}
},
"react-native-safe-area-view": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/react-native-safe-area-view/-/react-native-safe-area-view-0.12.0.tgz",
@ -8208,6 +8327,11 @@
"uuid": "^3.3.2"
}
},
"xdate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/xdate/-/xdate-0.8.2.tgz",
"integrity": "sha1-17AzwASF0CaVuvAET06s2j/JYaM="
},
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",

View file

@ -23,10 +23,12 @@
"react-native": "^0.59.9",
"react-native-app-intro-slider": "^3.0.0",
"react-native-autolink": "^1.8.1",
"react-native-calendars": "^1.212.0",
"react-native-modal": "^11.3.1",
"react-native-modalize": "^1.2.1",
"react-native-paper": "^2.16.0",
"react-native-platform-touchable": "^1.1.1",
"react-native-render-html": "^4.1.2",
"react-native-side-menu": "^1.1.3",
"react-native-status-bar-height": "^2.3.1",
"react-native-webview": "^5.8.1",

View file

@ -1,18 +1,33 @@
// @flow
import * as React from 'react';
import {Button, H3, Text} from 'native-base';
import {Content, H1, H2, H3, Text} from 'native-base';
import i18n from "i18n-js";
import {View, Platform} from "react-native";
import CustomMaterialIcon from "../components/CustomMaterialIcon";
import {View, Image} from "react-native";
import ThemeManager from "../utils/ThemeManager";
import {Linking} from "expo";
import BaseContainer from "../components/BaseContainer";
import {Agenda} from 'react-native-calendars';
import HTML from 'react-native-render-html';
import Touchable from 'react-native-platform-touchable';
import Modalize from 'react-native-modalize';
import WebDataManager from "../utils/WebDataManager";
type Props = {
navigation: Object,
}
type State = {
modalCurrentDisplayItem: Object,
refreshing: boolean,
agendaItems: Object,
};
const FETCH_URL = "https://amicale-insat.fr/event/json/list";
const AGENDA_MONTH_SPAN = 6;
/**
* Opens a link in the device's browser
* @param link The link to open
@ -24,67 +39,286 @@ function openWebLink(link) {
/**
* Class defining the app's planning screen
*/
export default class PlanningScreen extends React.Component<Props> {
export default class PlanningScreen extends React.Component<Props, State> {
modalRef: { current: null | Modalize };
webDataManager: WebDataManager;
constructor(props: any) {
super(props);
this.modalRef = React.createRef();
this.webDataManager = new WebDataManager(FETCH_URL);
}
componentDidMount() {
this._onRefresh();
}
state = {
modalCurrentDisplayItem: {},
refreshing: false,
agendaItems: {}
};
getCurrentDate() {
let today = new Date();
return this.getFormattedDate(today);
}
getFormattedDate(date: Date) {
let dd = String(date.getDate()).padStart(2, '0');
let mm = String(date.getMonth() + 1).padStart(2, '0'); //January is 0!
let yyyy = date.getFullYear();
return yyyy + '-' + mm + '-' + dd;
}
generateEmptyCalendar() {
let end = new Date(new Date().setMonth(new Date().getMonth() + AGENDA_MONTH_SPAN + 1));
let daysOfYear = {};
for (let d = new Date(2019, 8, 1); d <= end; d.setDate(d.getDate() + 1)) {
daysOfYear[this.getFormattedDate(new Date(d))] = []
}
return daysOfYear;
}
getModalContent() {
return (
<View style={{
flex: 1,
padding: 20
}}>
<H1 style={{
marginTop: 20,
}}>
{this.state.modalCurrentDisplayItem.title}
</H1>
<H3 style={{
marginTop: 10,
color: ThemeManager.getCurrentThemeVariables().listNoteColor
}}>
{this.getFormattedTime(this.state.modalCurrentDisplayItem)}
</H3>
<Content>
{this.state.modalCurrentDisplayItem.logo !== null ?
<View style={{width: '100%', height: 200, marginTop: 20, marginBottom: 20}}>
<Image style={{flex: 1, resizeMode: "contain"}}
source={{uri: this.state.modalCurrentDisplayItem.logo}}/>
</View>
: <View/>}
{this.state.modalCurrentDisplayItem.description !== null ?
// Surround description with div to allow text styling if the description is not html
<HTML html={"<div>" + this.state.modalCurrentDisplayItem.description + "</div>"}
tagsStyles={{
p: {color: ThemeManager.getCurrentThemeVariables().textColor},
div: {color: ThemeManager.getCurrentThemeVariables().textColor}
}}/>
: <View/>}
</Content>
</View>
);
}
showItemDetails(item: Object) {
this.setState({
modalCurrentDisplayItem: item
});
if (this.modalRef.current) {
this.modalRef.current.open();
}
}
getRenderItem(item: Object) {
return (
<Touchable
style={{
backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor,
borderRadius: 10,
marginRight: 10,
marginTop: 17,
}}
onPress={() => this.showItemDetails(item)}>
<View style={{
padding: 10,
flex: 1,
flexDirection: 'row'
}}>
<View style={{width: '70%'}}>
<Text style={{
color: ThemeManager.getCurrentThemeVariables().listNoteColor,
marginTop: 5,
marginBottom: 10
}}>
{this.getFormattedTime(item)}
</Text>
<H3 style={{marginBottom: 10}}>{item.title}</H3>
</View>
<View style={{
width: '30%',
height: 80
}}>
{item.logo !== null ?
<Image source={{uri: item.logo}}
style={{
flex: 1,
resizeMode: "contain"
}}/>
: <View/>}
</View>
</View>
</Touchable>
);
}
getRenderEmptyDate() {
return (
<View style={{
padding: 10,
flex: 1,
}}>
<View style={{
width: '100%',
height: 1,
backgroundColor: ThemeManager.getCurrentThemeVariables().agendaEmptyLine,
marginTop: 'auto',
marginBottom: 'auto',
}}/>
</View>
);
}
rowHasChanged(r1: Object, r2: Object) {
if (r1 !== undefined && r2 !== undefined)
return r1.title !== r2.title;
else return !(r1 === undefined && r2 === undefined);
}
/**
* Refresh data and show a toast if any error occurred
* @private
*/
_onRefresh = () => {
this.setState({refreshing: true});
this.webDataManager.readData()
.then((fetchedData) => {
this.setState({
refreshing: false,
});
this.generateEventAgenda(fetchedData);
})
.catch((err) => {
this.setState({
refreshing: false,
});
console.log(err);
});
};
generateEventAgenda(eventList: Array<Object>) {
let agendaItems = this.generateEmptyCalendar();
for (let i = 0; i < eventList.length; i++) {
if (agendaItems[this.getEventStartDate(eventList[i])] !== undefined)
agendaItems[this.getEventStartDate(eventList[i])].push(eventList[i]);
}
this.setState({agendaItems: agendaItems})
}
getEventStartDate(event: Object) {
return event.date_begin.split(" ")[0];
}
getEventStartTime(event: Object) {
if (event !== undefined && Object.keys(event).length > 0 && event.date_begin !== null)
return this.formatTime(event.date_begin.split(" ")[1]);
else
return "";
}
getEventEndTime(event: Object) {
if (event !== undefined && Object.keys(event).length > 0 && event.date_end !== null)
return this.formatTime(event.date_end.split(" ")[1]);
else
return "";
}
getFormattedTime(event: Object) {
if (this.getEventEndTime(event) !== "")
return this.getEventStartTime(event) + " - " + this.getEventEndTime(event)
else
return this.getEventStartTime(event);
}
formatTime(time: string) {
let array = time.split(':');
return array[0] + ':' + array[1];
}
render() {
const nav = this.props.navigation;
return (
<BaseContainer navigation={nav} headerTitle={i18n.t('screens.planning')}>
<View style={{
flexGrow: 1,
justifyContent: 'center',
alignItems: 'center',
}}>
{ Platform.OS === 'android' ?
<View
style={{
justifyContent: 'center',
alignItems: 'center',
}}>
<View style={{
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: 100,
marginBottom: 20
}}>
<CustomMaterialIcon
icon={'forklift'}
fontSize={100}
width={100}
color={ThemeManager.getCurrentThemeVariables().fetchedDataSectionListErrorText}/>
</View>
<H3 style={{
textAlign: 'center',
marginRight: 20,
marginLeft: 20,
color: ThemeManager.getCurrentThemeVariables().fetchedDataSectionListErrorText
}}>
{i18n.t('planningScreen.wipTitle')}
</H3>
<Text style={{
textAlign: 'center',
color: ThemeManager.getCurrentThemeVariables().fetchedDataSectionListErrorText
}}>
{i18n.t('planningScreen.wipSubtitle')}
</Text>
</View>
:
<View/>
}
<Button block style={{marginTop: 20, marginRight: 10, marginLeft: 10}}
onPress={() => openWebLink('https://www.facebook.com/groups/2054302624595234/')}>
<Text>Clubs et Evenements</Text>
</Button>
</View>
<Modalize ref={this.modalRef}
modalStyle={{
backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor,
}}>
{this.getModalContent()}
</Modalize>
<Agenda
// the list of items that have to be displayed in agenda. If you want to render item as empty date
// the value of date key kas to be an empty array []. If there exists no value for date key it is
// considered that the date in question is not yet loaded
items={this.state.agendaItems}
// initially selected day
selected={this.getCurrentDate()}
// Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
minDate={"2019-09-01"}
// Max amount of months allowed to scroll to the past. Default = 50
pastScrollRange={1}
// Max amount of months allowed to scroll to the future. Default = 50
futureScrollRange={AGENDA_MONTH_SPAN}
// If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the refreshing prop correctly.
onRefresh={() => this._onRefresh()}
// Set this true while waiting for new data from a refresh
refreshing={this.state.refreshing}
renderItem={(item) => this.getRenderItem(item)}
renderEmptyDate={() => this.getRenderEmptyDate()}
rowHasChanged={() => this.rowHasChanged()}
// agenda theme
theme={{
backgroundColor: ThemeManager.getCurrentThemeVariables().agendaBackgroundColor,
calendarBackground: ThemeManager.getCurrentThemeVariables().containerBgColor,
textSectionTitleColor: ThemeManager.getCurrentThemeVariables().listNoteColor,
selectedDayBackgroundColor: ThemeManager.getCurrentThemeVariables().brandPrimary,
selectedDayTextColor: '#ffffff',
todayTextColor: ThemeManager.getCurrentThemeVariables().brandPrimary,
dayTextColor: ThemeManager.getCurrentThemeVariables().textColor,
textDisabledColor: ThemeManager.getCurrentThemeVariables().textDisabledColor,
dotColor: ThemeManager.getCurrentThemeVariables().brandPrimary,
selectedDotColor: '#ffffff',
arrowColor: 'orange',
monthTextColor: ThemeManager.getCurrentThemeVariables().brandPrimary,
indicatorColor: ThemeManager.getCurrentThemeVariables().brandPrimary,
textDayFontWeight: '300',
textMonthFontWeight: 'bold',
textDayHeaderFontWeight: '300',
textDayFontSize: 16,
textMonthFontSize: 16,
textDayHeaderFontSize: 16,
agendaDayTextColor: ThemeManager.getCurrentThemeVariables().listNoteColor,
agendaDayNumColor: ThemeManager.getCurrentThemeVariables().listNoteColor,
agendaTodayColor: ThemeManager.getCurrentThemeVariables().brandPrimary,
agendaKnobColor: ThemeManager.getCurrentThemeVariables().brandPrimary,
// Fix for days hiding behind knob
'stylesheet.calendar.header': {
week: {
marginTop: 0,
flexDirection: 'row',
justifyContent: 'space-between'
}
}
}}
/>
</BaseContainer>
);
}
}