Compare commits

...

10 Commits

Author SHA1 Message Date
17049cdf04 version bump 2026-06-11 17:13:52 +02:00
c564108c5d Kalender ics link und Bugfix 2024-10-14 20:50:52 +02:00
faece890f8 Reordered Tests 2024-10-14 20:04:51 +02:00
dafefb83f2 DEMOUSER Toast 2024-10-14 19:57:36 +02:00
09fd5f3032 Tests untereinander 2024-10-14 19:24:53 +02:00
22f24947ae Fix DayTypes 2024-10-14 18:52:17 +02:00
0b13955ffe build 2024-09-19 20:44:55 +02:00
0ece80359f initial load shows no error 2024-09-19 17:54:43 +02:00
0d5c46001c build android 2024-09-19 16:46:42 +02:00
5805bc6bf3 fixed reload on settings change 2024-09-19 16:43:27 +02:00
6 changed files with 874 additions and 273 deletions

View File

@@ -1,22 +1,24 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import axios from 'axios';
import { SettingsContext } from './SettingsProvider';
import { encode as btoa } from 'base-64';
import { format } from 'date-fns';
import Settings from './screens/Settings';
import { ThemeButton, ThemeText } from './common/ThemeTypes';
import React, { createContext, useContext, useEffect, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { SettingsContext } from './SettingsProvider';
import { ThemeButton, ThemeText } from './common/ThemeTypes';
import { useColors } from './common/colors';
import { decode as atob, encode as btoa } from 'base-64'
import Login from './screens/Login';
import { ToastType, useToast } from './common/ToastProvider';
export const InterfaceContext = createContext<any>(null);
const Interface = ({ children }) => {
const colors = useColors();
const { showToast, hideToast } = useToast();
const { settings } = useContext(SettingsContext);
const [userInformation, setUserInformation] = useState(null);
const [baseData, setBaseData] = useState(null);
const [loading, setLoading] = useState(true);
const [initialLoading, setInitialLoading] = useState(true);
const [error, setError] = useState(false);
const [errorObject, setErrorObject] = useState(null);
@@ -31,6 +33,7 @@ const Interface = ({ children }) => {
setBaseData(basedata)
if (!userinformation || !basedata) throw new InterfaceError("Data returned is Null");
setError(false)
setInitialLoading(false)
} catch (error) {
console.error(error);
setErrorObject(error);
@@ -73,11 +76,20 @@ const Interface = ({ children }) => {
};
const fetchUserInformation = async (username) => {
const userinformation = await requestGet(`userinformation/?username=${username}`, false);
if (!userinformation) {
return await requestGet(`demoinformation/`);
try {
return await requestGet(`userinformation/?username=${username}`, false);
} catch (error) {
if (error.response.status === 404) {
const demoinformation = await requestGet(`demoinformation/`);
if (demoinformation) {
console.log("Using Demoinformation");
showToast(ToastType.Info, "You are using a DEMOUSER", 5000);
}
return demoinformation;
}
console.error("Error fetching user information:", error);
return Null;
}
return userinformation;
};
const fetchTimes = async () => {
@@ -122,7 +134,7 @@ const Interface = ({ children }) => {
}
} catch (error) {
if (!handleError) {
return null
throw(error);
} else if (error.response) {
if (error.response.data.message) {
throw new NetworkError(error.response.status + ": " + error.response.data.message, url);
@@ -175,7 +187,7 @@ const Interface = ({ children }) => {
<Login />
</View>
)
if (!loading) return (children)
if (!initialLoading) return (children)
}
return (
@@ -201,4 +213,4 @@ export class NetworkError extends Error {
this.name = "NetworkError";
this.url = url;
}
}
}

View File

@@ -2,7 +2,7 @@
"expo": {
"name": "KlaTab",
"slug": "klatab",
"version": "1.4.0",
"version": "1.4.2",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
@@ -15,12 +15,12 @@
"**/*"
],
"ios": {
"buildNumber": "24",
"buildNumber": "30",
"supportsTablet": true,
"bundleIdentifier": "de.edvschule-plattling.klatab.klatab"
},
"android": {
"versionCode": 27,
"versionCode": 30,
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"

1018
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,7 @@
"base-64": "^1.0.0",
"crypto-browserify": "^3.12.0",
"date-fns": "^3.6.0",
"expo": "^51.0.32",
"expo": "~51.0.37",
"expo-application": "~5.9.1",
"expo-build-properties": "~0.12.5",
"expo-status-bar": "~1.12.1",

View File

@@ -1,6 +1,6 @@
import { addMonths, addDays, format, isSameDay, isWithinInterval, parseISO, getDay, parse, differenceInDays } from 'date-fns';
import React, { useContext, useEffect, useState, useMemo, useCallback } from 'react';
import { FlatList, ScrollView, StyleSheet, View, Text, TouchableOpacity, Modal } from 'react-native';
import { FlatList, ScrollView, StyleSheet, View, Text, TouchableOpacity, Modal, Linking } from 'react-native';
import ErrorBoundary from 'react-native-error-boundary';
import { RefreshControl } from 'react-native-web-refresh-control';
import { InterfaceContext } from '../Interface';
@@ -107,26 +107,39 @@ const Calendar = () => {
);
} else if (selectedView === 'tests') {
return (
<FlatList
refreshControl={<RefreshControl refreshing={loading} onRefresh={refresh} colors={[colors.primary]} />}
data={calendarData.tests}
keyExtractor={(item) => item.date + item.hourFrom + item.hourUntil + item.subject}
renderItem={({ item }) => (
<View style={[styles.dayBox, styles.rowContainer, { backgroundColor: colors.backPrimary }]}>
<ThemeText style={{ width: 82, fontWeight: 'bold' }}>{format(item.date, 'dd.MM.yyyy')}</ThemeText>
<ThemeText style={{ width: 60 }}>{item.subject}</ThemeText>
<ThemeText style={{ width: 25 }}>{item.type}</ThemeText>
<ThemeText style={{ width: 40 }}>{item.room}</ThemeText>
<ThemeText style={{ width: 40 }}>{item.hourFrom}-{item.hourUntil}</ThemeText>
<ThemeText style={{ width: 82 }}>{item.timeFrom}-{item.timeUntil}</ThemeText>
{userInformation.usertype == 'teacher' && <ThemeText style={{ width: 30 }}>{item.class}</ThemeText>}
{userInformation.usertype == 'pupil' && <ThemeText style={{ width: 30 }}>{item.teacher}</ThemeText>}
</View>
)}
getItemLayout={(data, index) => (
{ length: 70, offset: 70 * index, index }
)}
/>
<View style={{ marginLeft: 5 }}>
<TouchableOpacity onPress={() => Linking.openURL('https://ux4.edvschule-plattling.de/~hthaler/tests-calendar/')}>
<Text style={{ color: colors.primary }}>Prüfungen in einen Kalender importieren (.ics)</Text>
</TouchableOpacity>
<FlatList
refreshControl={<RefreshControl refreshing={loading} onRefresh={refresh} colors={[colors.primary]} />}
data={calendarData.tests}
keyExtractor={(item) => item.date + item.hourFrom + item.hourUntil + item.subject}
renderItem={({ item }) => (
<View style={{ marginTop: 5, borderBottomWidth: 1, borderColor: '#ccc', flexDirection: 'row', flexWrap: 'wrap' }}>
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 5, marginRight: 20 }}>
<ThemeText style={{ fontWeight: 'bold', marginRight: 5 }}>{format(item.date, 'dd.MM.yyyy')}</ThemeText>
<ThemeText>{`${item.timeFrom}-${item.timeUntil}`}</ThemeText>
</View>
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 5 }}>
<ThemeText style={{ marginRight: 5 }}>{item.subject}</ThemeText>
<ThemeText style={{ marginRight: 5 }}>{item.type}</ThemeText>
{userInformation.usertype === 'teacher' && (
<ThemeText style={{ marginRight: 5 }}>{item.class}</ThemeText>
)}
{userInformation.usertype === 'pupil' && (
<ThemeText style={{ marginRight: 5 }}>{item.teacher}</ThemeText>
)}
<ThemeText style={{ marginRight: 5 }}>{item.room}</ThemeText>
<ThemeText style={{ marginRight: 5 }}>{`${item.hourFrom||'n'}-${item.hourUntil||'n'}`}</ThemeText>
</View>
</View>
)}
getItemLayout={(data, index) => (
{ length: 70, offset: 70 * index, index }
)}
/>
</View>
);
} else if (selectedView === 'appointments') {
// Group appointments by day
@@ -154,7 +167,7 @@ const Calendar = () => {
data={groupedAppointmentsArray}
keyExtractor={(item) => format(item.date, 'yyyy-MM-dd')}
renderItem={({ item }) => (
<View style={{ marginTop: 5, borderBottomWidth: 1, borderColor: '#ccc', }}>
<View style={{ marginTop: 5, borderBottomWidth: 1, borderColor: '#ccc' }}>
<ThemeText style={{ fontSize: 18, marginBottom: 10, marginLeft: 20 }}>{format(item.date, 'dd.MM.yyyy')}</ThemeText>
{item.appointments.map((appointment, index) => (
<View key={index} style={{ flexDirection: 'row', alignItems: 'center', marginLeft: 5, marginBottom: 10 }}>
@@ -377,11 +390,22 @@ const DayModal = React.memo(({ date, calendarData, onClose, dayColors, colors, u
);
});
const getDayType = (date, calendarData) => {
const dayType = calendarData.dayTypes.find((d) =>
new Date(d.from) <= date && date <= new Date(d.until)
);
// Set the date to midnight to avoid timezone issues
const targetDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
const dayType = calendarData.dayTypes.find((d) => {
// Create midnight dates for comparison
const fromDate = new Date(d.from);
const untilDate = new Date(d.until);
// Set both dates to midnight
fromDate.setHours(0, 0, 0, 0);
untilDate.setHours(0, 0, 0, 0);
return fromDate <= targetDate && targetDate <= untilDate;
});
return dayType ? dayType.type : "schultag";
};
@@ -441,4 +465,4 @@ function endsNextDayMidnight(start, end) {
const endTimeIsMidnight = endD.getHours() === 0 && endD.getMinutes() === 0 && endD.getSeconds() === 0;
return (daysDifference == 1 || daysDifference == -1) && endTimeIsMidnight
}
}

View File

@@ -1,7 +1,6 @@
import FontAwesome5 from '@expo/vector-icons/FontAwesome5';
import * as Application from 'expo-application';
import { useContext, useEffect, useRef, useState } from 'react';
import { ActivityIndicator, Alert, Animated, Button, Image, Linking, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { ActivityIndicator, Alert, Animated, Button, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import ErrorBoundary from 'react-native-error-boundary';
import { SettingsContext } from '../SettingsProvider';
import Credits from '../common/Credits';
@@ -55,10 +54,6 @@ const Settings = () => {
}
};
const handleLinkPress = (url) => {
Linking.openURL(url);
};
const handleChange = (setter) => (value) => {
setter(value);
showToast(ToastType.Warn, 'Ungespeicherte Änderungen');