Skip to content

Commit

Permalink
Improve timetable UX, unify loading states (neuland-ingolstadt#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
BuildmodeOne authored Nov 15, 2023
1 parent b377201 commit c7b5a03
Show file tree
Hide file tree
Showing 20 changed files with 214 additions and 132 deletions.
7 changes: 1 addition & 6 deletions src/app/(pages)/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { type Calendar } from '@/types/data'
import { type Exam } from '@/types/utils'
import { calendar, loadExamList } from '@/utils/calendar-utils'
import { MODAL_BOTTOM_MARGIN, PAGE_PADDING } from '@/utils/style-utils'
import { LoadingState } from '@/utils/ui-utils'
import { useTheme } from '@react-navigation/native'
import { useRouter } from 'expo-router'
import React, { useEffect, useState } from 'react'
Expand All @@ -34,12 +35,6 @@ export default function CalendarPage(): JSX.Element {

const [selectedData, setSelectedData] = useState<string>('Events')

enum LoadingState {
LOADING,
LOADED,
ERROR,
REFRESHING,
}
const [error, setError] = useState<Error | null>(null)
const [loadingState, setLoadingState] = useState(LoadingState.LOADING)
const primussUrl = 'https://www3.primuss.de/cgi-bin/login/index.pl?FH=fhin'
Expand Down
7 changes: 1 addition & 6 deletions src/app/(pages)/events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Divider from '@/components/Elements/Universal/Divider'
import { type Colors } from '@/components/colors'
import { type CLEvents } from '@/types/neuland-api'
import { MODAL_BOTTOM_MARGIN, PAGE_PADDING } from '@/utils/style-utils'
import { LoadingState } from '@/utils/ui-utils'
import { useTheme } from '@react-navigation/native'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
Expand All @@ -19,12 +20,6 @@ import { RefreshControl } from 'react-native-gesture-handler'
export default function Events(): JSX.Element {
const [events, setEvents] = useState<CLEvents[]>([])
const colors = useTheme().colors as Colors
enum LoadingState {
LOADING,
LOADED,
ERROR,
REFRESHING,
}
const [error, setError] = useState<Error | null>(null)
const { t } = useTranslation('common')

Expand Down
8 changes: 1 addition & 7 deletions src/app/(pages)/lecturers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { type Colors } from '@/components/colors'
import { type NormalizedLecturer } from '@/types/utils'
import { normalizeLecturers } from '@/utils/lecturers-utils'
import { MODAL_BOTTOM_MARGIN, PAGE_PADDING } from '@/utils/style-utils'
import { LoadingState } from '@/utils/ui-utils'
import { useTheme } from '@react-navigation/native'
import { useGlobalSearchParams, useRouter } from 'expo-router'
import React, { useEffect, useState } from 'react'
Expand All @@ -25,13 +26,6 @@ import {
export default function LecturersCard(): JSX.Element {
const router = useRouter()

enum LoadingState {
LOADING,
LOADED,
ERROR,
REFRESHING,
}

const [personalLecturers, setPersonalLecturers] = useState<
NormalizedLecturer[]
>([])
Expand Down
7 changes: 1 addition & 6 deletions src/app/(pages)/mobility.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { type Colors } from '@/components/colors'
import { MobilityContext } from '@/components/provider'
import { type CLEvents } from '@/types/neuland-api'
import { MODAL_BOTTOM_MARGIN, PAGE_PADDING } from '@/utils/style-utils'
import { LoadingState } from '@/utils/ui-utils'
import { useTheme } from '@react-navigation/native'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
Expand All @@ -20,12 +21,6 @@ import { RefreshControl } from 'react-native-gesture-handler'
export default function Mobility(): JSX.Element {
const [events, setEvents] = useState<CLEvents[]>([])
const colors = useTheme().colors as Colors
enum LoadingState {
LOADING,
LOADED,
ERROR,
REFRESHING,
}
const [error, setError] = useState<Error | null>(null)
const [loadingState, setLoadingState] = useState(LoadingState.LOADING)
const { mobilityKind, mobilityStation } = React.useContext(MobilityContext)
Expand Down
8 changes: 1 addition & 7 deletions src/app/(pages)/news.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
MODAL_BOTTOM_MARGIN,
PAGE_PADDING,
} from '@/utils/style-utils'
import { LoadingState } from '@/utils/ui-utils'
import { Ionicons } from '@expo/vector-icons'
import { useTheme } from '@react-navigation/native'
import { router } from 'expo-router'
Expand All @@ -37,13 +38,6 @@ export default function NewsScreen(): JSX.Element {
const [news, setNews] = useState<ThiNews[] | null>(null)
const [errorMsg, setErrorMsg] = useState('')

enum LoadingState {
LOADING,
LOADED,
ERROR,
REFRESHING,
}

const [loadingState, setLoadingState] = useState<LoadingState>(
LoadingState.LOADING
)
Expand Down
8 changes: 1 addition & 7 deletions src/app/(tabs)/food.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FoodFilterContext } from '@/components/provider'
import { type Food } from '@/types/neuland-api'
import { loadFoodEntries } from '@/utils/food-utils'
import { PAGE_BOTTOM_SAFE_AREA, PAGE_PADDING } from '@/utils/style-utils'
import { LoadingState } from '@/utils/ui-utils'
import { Ionicons } from '@expo/vector-icons'
import { useTheme } from '@react-navigation/native'
import * as Haptics from 'expo-haptics'
Expand All @@ -24,13 +25,6 @@ import {
View,
} from 'react-native'

enum LoadingState {
LOADING,
LOADED,
ERROR,
REFRESHING,
}

function FoodScreen(): JSX.Element {
const [days, setDays] = useState<any>([])
const colors = useTheme().colors as Colors
Expand Down
6 changes: 1 addition & 5 deletions src/app/(tabs)/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { type RoomsOverlay } from '@/types/asset-api'
import { type AvailableRoom, type RoomEntry } from '@/types/utils'
import { formatISODate, formatISOTime } from '@/utils/date-utils'
import { filterRooms, getNextValidDate } from '@/utils/room-utils'
import { LoadingState } from '@/utils/ui-utils'
import { Ionicons } from '@expo/vector-icons'
import { useTheme } from '@react-navigation/native'
import * as Haptics from 'expo-haptics'
Expand Down Expand Up @@ -65,11 +66,6 @@ export const MapScreen = (): JSX.Element => {
4: '4',
}

enum LoadingState {
LOADING,
LOADED,
ERROR,
}
const [errorMsg, setErrorMsg] = useState('')
const colors = useTheme().colors as Colors
const { userKind, userFaculty } = React.useContext(UserKindContext)
Expand Down
146 changes: 99 additions & 47 deletions src/app/(tabs)/timetable.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import ErrorPage from '@/components/Elements/Universal/ErrorPage'
import WorkaroundStack from '@/components/Elements/Universal/WorkaroundStack'
import { type Colors } from '@/components/colors'
import { UserKindContext } from '@/components/provider'
import { type LanguageKey } from '@/localization/i18n'
import { type Calendar as CalendarType } from '@/types/data'
import { type FriendlyTimetableEntry } from '@/types/utils'
import { calendar } from '@/utils/calendar-utils'
import { ignoreTime } from '@/utils/date-utils'
import { PAGE_PADDING } from '@/utils/style-utils'
import { getFriendlyTimetable } from '@/utils/timetable-utils'
import { LoadingState } from '@/utils/ui-utils'
import { Ionicons } from '@expo/vector-icons'
import { useTheme } from '@react-navigation/native'
import Color from 'color'
Expand All @@ -16,6 +17,7 @@ import { useRouter } from 'expo-router'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
ActivityIndicator,
Dimensions,
StyleSheet,
Text,
Expand Down Expand Up @@ -44,7 +46,7 @@ interface CalendarEvent extends ICalendarEventBase {
export default function TimetableScreen(): JSX.Element {
const router = useRouter()

const { t, i18n } = useTranslation('navigation')
const { t, i18n } = useTranslation(['navigation', 'timetable'])

const [calendarTheme, setCalendarTheme] = useState<Record<string, any>>({})
const [calendarDate, setCalendarDate] = useState<Date>(new Date())
Expand All @@ -57,7 +59,6 @@ export default function TimetableScreen(): JSX.Element {

const textColor = Color(colors.text)
const primaryColor = Color(colors.primary)
const { userKind } = React.useContext(UserKindContext)

const timetableTextColor =
textColor.contrast(primaryColor) > 5 ? textColor : textColor.negate()
Expand All @@ -66,36 +67,50 @@ export default function TimetableScreen(): JSX.Element {
const calendarTextColor =
textColor.contrast(calendarColor) > 5 ? textColor : textColor.negate()

const [rawTimetable, setRawTimetable] = useState<FriendlyTimetableEntry[]>(
[]
)
const [timetable, setTimetable] = useState<CalendarEvent[]>([])

const [loadingState, setLoadingState] = useState(LoadingState.LOADING)

async function load(): Promise<void> {
try {
const timetable = await getFriendlyTimetable(today, true)
setRawTimetable(timetable)
setLoadingState(LoadingState.LOADED)
} catch (e) {
setLoadingState(LoadingState.ERROR)
console.log(e)
}
}

useEffect(() => {
async function load(): Promise<void> {
try {
// timetable
const timetable = await getFriendlyTimetable(today, true)
const timetableEntries = timetableToWeekViewEvents(timetable)

// university calendar
const calendarEntries = calendarToWeekViewEvents(calendar)

// combine all events
const allEvents = [...timetableEntries, ...calendarEntries]

/**
* TODO:
* - Exams
* - Holidays (not just the ones from the university calendar)
* - Maybe Events from student clubs
*/

setTimetable(allEvents)
} catch (e) {
console.log(e)
}
void load()
}, [])

useEffect(() => {
function fillTimetable(): void {
const timetableEntries = timetableToWeekViewEvents(rawTimetable)

// university calendar
const calendarEntries = calendarToWeekViewEvents(calendar)

// combine all events
const allEvents = [...timetableEntries, ...calendarEntries]

/**
* TODO:
* - Exams
* - Holidays (not just the ones from the university calendar)
* - Maybe Events from student clubs
*/

setTimetable(allEvents)
}

load().catch(console.error)
}, [colors.primary, userKind])
fillTimetable()
}, [rawTimetable, colors])

useEffect(() => {
const theme = {
Expand All @@ -116,6 +131,11 @@ export default function TimetableScreen(): JSX.Element {
setCalendarTheme(theme)
}, [colors])

function onRefresh(): void {
setLoadingState(LoadingState.REFRESHING)
void load()
}

function timetableToWeekViewEvents(
entries: FriendlyTimetableEntry[]
): CalendarEvent[] {
Expand Down Expand Up @@ -383,28 +403,54 @@ export default function TimetableScreen(): JSX.Element {
})
}

function Timetable(): JSX.Element {
function LoadingView(): JSX.Element {
return (
<Calendar
date={calendarDate}
onChangeDate={(range) => {
setCalendarDate(range[0])
}}
mode={mode}
events={timetable}
height={-1}
showAllDayEventCell={true}
renderEvent={renderEvent}
renderHeader={renderHeader}
onPressEvent={showEventDetails}
dayHeaderHighlightColor={colors.primary}
theme={calendarTheme}
scrollOffsetMinutes={480}
weekStartsOn={1}
weekEndsOn={6}
/>
<View style={styles.loadingView}>
<ActivityIndicator size="small" color={colors.primary} />
</View>
)
}
function Timetable(): JSX.Element {
if (loadingState === LoadingState.LOADING) {
return <LoadingView />
} else if (
loadingState === LoadingState.ERROR ||
loadingState === LoadingState.REFRESHING
) {
return (
<ErrorPage
onRefresh={onRefresh}
message={t('error.unknown', {
ns: 'timetable',
})}
refreshing={loadingState === LoadingState.REFRESHING}
/>
)
} else if (loadingState === LoadingState.LOADED) {
return (
<Calendar
date={calendarDate}
onChangeDate={(range) => {
setCalendarDate(range[0])
}}
mode={mode}
events={timetable}
height={-1}
showAllDayEventCell={true}
renderEvent={renderEvent}
renderHeader={renderHeader}
onPressEvent={showEventDetails}
dayHeaderHighlightColor={colors.primary}
theme={calendarTheme}
scrollOffsetMinutes={480}
weekStartsOn={1}
weekEndsOn={6}
/>
)
} else {
return <></>
}
}

return (
<WorkaroundStack
Expand All @@ -425,6 +471,12 @@ export default function TimetableScreen(): JSX.Element {
}

const styles = StyleSheet.create({
loadingView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
errorView: {},
navRight: {
display: 'flex',
flexDirection: 'row',
Expand Down
8 changes: 1 addition & 7 deletions src/app/(user)/grades.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { type Grade } from '@/types/thi-api'
import { type GradeAverage } from '@/types/utils'
import { loadGradeAverage, loadGrades } from '@/utils/grades-utils'
import { PAGE_PADDING } from '@/utils/style-utils'
import { LoadingState } from '@/utils/ui-utils'
import { useTheme } from '@react-navigation/native'
import { router } from 'expo-router'
import React, { useEffect, useState } from 'react'
Expand All @@ -31,13 +32,6 @@ export default function GradesSCreen(): JSX.Element {
const [gradeAverage, setGradeAverage] = useState<GradeAverage>()
const [errorMsg, setErrorMsg] = useState('')

enum LoadingState {
LOADING,
LOADED,
ERROR,
REFRESHING,
}

const [loadingState, setLoadingState] = useState<LoadingState>(
LoadingState.LOADING
)
Expand Down
Loading

0 comments on commit c7b5a03

Please sign in to comment.