From c688528d0e7e2a9b018f9362fa746623415841a7 Mon Sep 17 00:00:00 2001 From: Robert Eggl Date: Sun, 15 Dec 2024 21:30:39 +0100 Subject: [PATCH] feat: add react native web support (#141) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakob Löw --- .dockerignore | 4 + .github/workflows/codeql.yml | 4 +- .github/workflows/deploy-webapp.yml | 54 +++ Dockerfile | 43 ++ app.config.json | 8 + babel.config.js | 14 +- bun.lockb | Bin 666552 -> 671432 bytes package.json | 11 +- src/api/neuland-api.ts | 5 +- src/api/thi-session-handler.ts | 65 ++- src/app/(flow)/onboarding.tsx | 13 +- src/app/(screens)/about.tsx | 46 +- src/app/(screens)/accent.tsx | 4 + src/app/(screens)/calendar.tsx | 3 +- src/app/(screens)/clEvent.tsx | 6 +- src/app/(screens)/clEvents.tsx | 2 +- src/app/(screens)/dashboard.tsx | 37 +- src/app/(screens)/foodPreferences.tsx | 6 +- src/app/(screens)/lecture.tsx | 16 + src/app/(screens)/lecturers.tsx | 3 +- src/app/(screens)/legal.tsx | 4 +- src/app/(screens)/login.tsx | 232 +--------- src/app/(screens)/meal.tsx | 6 + src/app/(screens)/news.tsx | 4 + src/app/(screens)/profile.tsx | 22 + src/app/(screens)/settings.tsx | 436 ++++++++++-------- src/app/(screens)/sportsEvent.tsx | 2 + src/app/(tabs)/(index)/index.tsx | 3 +- src/app/(tabs)/(index)/links.tsx | 3 + src/app/(tabs)/_layout.tsx | 49 +- src/app/(tabs)/food.tsx | 2 +- src/app/(tabs)/map.tsx | 9 +- src/app/(tabs)/timetable.tsx | 2 +- src/app/_layout.tsx | 8 +- src/assets/web/favicon.png | Bin 0 -> 1824 bytes src/assets/web/og-image.png | Bin 0 -> 26728 bytes src/components/Cards/AnnouncementCard.tsx | 2 + src/components/Cards/BaseCard.tsx | 15 +- src/components/Cards/LinkCard.tsx | 6 +- src/components/Dashboard/HeaderRight.tsx | 22 +- src/components/Error/CrashView.tsx | 4 + src/components/Error/ErrorView.tsx | 32 +- src/components/Events/ClEventsPage.tsx | 1 + src/components/Events/ClSportsPage.tsx | 5 + src/components/Exclusive/DragView.web.tsx | 11 + src/components/Flow/ContextMenu.tsx | 3 + src/components/Flow/ContextMenu.web.tsx | 10 + src/components/Flow/Login.tsx | 123 +++++ src/components/Flow/Login.web.tsx | 177 +++++++ src/components/Flow/LoginAnimatedText.tsx | 129 ++++++ src/components/Flow/WhatsnewBox.tsx | 37 +- src/components/Food/AllergensBanner.tsx | 4 + src/components/Food/FoodLanguageSection.tsx | 4 + src/components/Food/FoodScreen.tsx | 2 +- src/components/Food/HeaderRight.tsx | 6 +- src/components/Food/MealDay.tsx | 4 + src/components/Food/MealEntry.tsx | 7 +- src/components/Layout/PagerView.tsx | 3 + src/components/Layout/PagerView.web.tsx | 55 +++ src/components/Layout/Tabbar.tsx | 3 + src/components/Layout/Tabbar.web.tsx | 157 +++++++ .../Map/AvailableRoomsSuggestions.tsx | 4 + src/components/Map/BottomSheetDetailModal.tsx | 12 + src/components/Map/MapScreen.tsx | 9 +- src/components/Map/MapScreen.web.tsx | 15 + src/components/Map/NextLectureSuggestion.tsx | 4 + src/components/Rows/SportsRow.tsx | 35 +- src/components/Settings/GradesButton.tsx | 4 + src/components/Timetable/HeaderButtons.tsx | 10 +- src/components/Timetable/TimetableList.tsx | 1 + src/components/Timetable/TimetableScreen.tsx | 1 + src/components/Universal/Divider.tsx | 3 +- src/components/Universal/FormList.tsx | 4 + src/components/Universal/Icon.tsx | 39 +- src/components/Universal/LoginForm.tsx | 62 +-- .../Universal/MultiSectionPicker.tsx | 4 + src/components/Universal/ShareButton.tsx | 4 + .../Universal/ShareHeaderButton.tsx | 6 +- .../Universal/SingleSectionPicker.tsx | 4 + src/components/Universal/ToggleRow.tsx | 2 +- src/components/Universal/WorkaroundStack.tsx | 2 +- src/components/icons.ts | 36 +- src/components/provider.tsx | 22 +- src/contexts/userKind.ts | 2 - src/data/changelog.json | 27 +- src/data/constants.ts | 8 + src/localization/de/flow.json | 6 +- src/localization/en/flow.json | 6 +- src/types/components.ts | 6 +- src/types/data.ts | 14 +- src/utils/api-utils.ts | 5 +- src/utils/storage.ts | 39 ++ tsconfig.json | 2 +- 93 files changed, 1708 insertions(+), 638 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/deploy-webapp.yml create mode 100644 Dockerfile create mode 100644 src/assets/web/favicon.png create mode 100644 src/assets/web/og-image.png create mode 100644 src/components/Exclusive/DragView.web.tsx create mode 100644 src/components/Flow/ContextMenu.tsx create mode 100644 src/components/Flow/ContextMenu.web.tsx create mode 100644 src/components/Flow/Login.tsx create mode 100644 src/components/Flow/Login.web.tsx create mode 100644 src/components/Flow/LoginAnimatedText.tsx create mode 100644 src/components/Layout/PagerView.tsx create mode 100644 src/components/Layout/PagerView.web.tsx create mode 100644 src/components/Layout/Tabbar.web.tsx create mode 100644 src/components/Map/MapScreen.web.tsx diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..d8f43d8e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +node_modules +ios +android +dist \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7973df01..c1040420 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -13,9 +13,9 @@ name: 'CodeQL' on: push: - branches: ['main'] + branches: ['main', 'develop'] pull_request: - branches: ['main'] + branches: ['main', 'develop'] schedule: - cron: '19 20 * * 3' diff --git a/.github/workflows/deploy-webapp.yml b/.github/workflows/deploy-webapp.yml new file mode 100644 index 00000000..f26a6ae6 --- /dev/null +++ b/.github/workflows/deploy-webapp.yml @@ -0,0 +1,54 @@ +name: Create and publish a Docker image + +# Configures this workflow to run every time a change is pushed to the branch called `release`. +on: + push: + branches: ['main', 'develop'] + +# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +jobs: + build-and-push-image: + runs-on: ubuntu-latest + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. + permissions: + id-token: write + contents: read + attestations: write + packages: write + # + steps: + - name: Checkout repository + uses: actions/checkout@v4 + # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. + # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. + # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + - name: Build and push Docker image + id: push + uses: docker/build-push-action@v5.0.0 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + EXPO_PUBLIC_THI_API_KEY=${{ secrets.EXPO_PUBLIC_THI_API_KEY }} + EXPO_PUBLIC_NEULAND_GRAPHQL_ENDPOINT=${{ github.ref == 'refs/heads/develop' && vars.GRAPHQL_ENDPOINT_DEV || vars.GRAPHQL_ENDPOINT_PROD }} + EXPO_PUBLIC_APTABASE_KEY=${{ secrets.EXPO_PUBLIC_APTABASE_KEY }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..69531588 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,43 @@ +# Base stage: Install dependencies and build the project +FROM oven/bun:1 AS bun +WORKDIR /usr/src/app + +# Copy necessary files and install dependencies +COPY package.json bun.lockb ./ +COPY patches patches +RUN bun install --ignore-scripts --freeze-lockfile + +# Build stage: Use a Node.js image for the build process +FROM node:23 AS build +WORKDIR /usr/src/app + +# Copy dependencies and project files +COPY --from=bun /usr/src/app/node_modules ./node_modules +COPY . . + +# Build the project +ARG EXPO_PUBLIC_THI_API_KEY +ARG EXPO_PUBLIC_NEULAND_GRAPHQL_ENDPOINT +ARG EXPO_PUBLIC_APTABASE_KEY +ENV EXPO_PUBLIC_THI_API_KEY=${EXPO_PUBLIC_THI_API_KEY} +ENV EXPO_PUBLIC_NEULAND_GRAPHQL_ENDPOINT=${EXPO_PUBLIC_NEULAND_GRAPHQL_ENDPOINT} +ENV EXPO_PUBLIC_APTABASE_KEY=${EXPO_PUBLIC_APTABASE_KEY} +ENV NODE_ENV=production + +RUN npx expo export -p web -c + +# Final stage: Serve static files using npx serve +FROM node:23 AS final +WORKDIR /usr/src/app + +# Copy the build files +COPY --from=build /usr/src/app/dist ./dist + +# Install serve +RUN npm install -g serve + +# Expose the port +EXPOSE 3000 + +# Serve the static files +CMD ["serve", "-s", "dist", "-l", "3000"] \ No newline at end of file diff --git a/app.config.json b/app.config.json index 42f3050d..6c18b422 100644 --- a/app.config.json +++ b/app.config.json @@ -103,6 +103,14 @@ "eas": { "projectId": "b0ef9e3f-3115-44b0-abc7-99dd75821353" } + }, + "web": { + "favicon": "./src/assets/web/favicon.png", + "shortName": "Neuland Next", + "name": "Neuland Next - Deine inoffizielle App für die THI", + "description": "Neuland Next ist eine inoffizielle App für die Technische Hochschule Ingolstadt. Sie bietet dir alle Funktionen, die du für dein Studium benötigst, an einem Ort.", + "lang": "de", + "preferRelatedApplications": true } } } diff --git a/babel.config.js b/babel.config.js index 1cdfe386..6b910ce7 100644 --- a/babel.config.js +++ b/babel.config.js @@ -2,6 +2,18 @@ module.exports = function (api) { api.cache(true) return { presets: ['babel-preset-expo'], - plugins: ['react-native-reanimated/plugin'], + plugins: [ + ['react-native-reanimated/plugin'], + [ + 'transform-inline-environment-variables', + { + include: [ + 'EXPO_PUBLIC_THI_API_KEY', + 'EXPO_PUBLIC_NEULAND_GRAPHQL_ENDPOINT', + 'EXPO_PUBLIC_APTABASE_KEY', + ], + }, + ], + ], } } diff --git a/bun.lockb b/bun.lockb index 71c1301e46f993becab554c9ecad80c6aded6550..e773da9444d8fc39b16af357e465243b707918c6 100755 GIT binary patch delta 122786 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
    Khc~n9PFu2 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 { - await SecureStore.setItemAsync(key, value) -} - -function load(key: string): string | null { - return SecureStore.getItem(key) -} - /** * Thrown when the user is not logged in. */ @@ -53,11 +45,12 @@ export async function createSession( } storage.set('sessionCreated', Date.now().toString()) - - await save('session', session) + console.debug('Session created at', storage.getString('sessionCreated')) + await saveSecure('session', session, true) + console.debug('Session saved') if (stayLoggedIn) { - await save('username', username) - await save('password', password) + await saveSecure('username', username) + await saveSecure('password', password) } return isStudent } @@ -69,7 +62,7 @@ export async function createGuestSession(forget = true): Promise { if (forget) { await forgetSession() } - await save('session', 'guest') + await saveSecure('session', 'guest', true) } /** @@ -84,7 +77,7 @@ export async function createGuestSession(forget = true): Promise { export async function callWithSession( method: (session: string) => Promise ): Promise { - let session = load('session') + let session = loadSecure('session') const sessionCreated = parseInt(storage.getString('sessionCreated') ?? '0') // redirect user if he never had a session if (session == null) { @@ -93,14 +86,20 @@ export async function callWithSession( throw new UnavailableSessionError() } - const username = load('username') - if (username === null) { - throw new UnavailableSessionError() - } + const username = loadSecure('username') + const password = loadSecure('password') + if (Platform.OS === 'web') { + if (session === 'guest' || session == null) { + throw new NoSessionError() + } + } else { + if (username === null) { + throw new UnavailableSessionError() + } - const password = load('password') - if (password === null) { - throw new UnavailableSessionError() + if (password === null) { + throw new UnavailableSessionError() + } } // log in if the session is older than SESSION_EXPIRES if ( @@ -116,7 +115,7 @@ export async function callWithSession( ) session = newSession - await save('session', session) + await saveSecure('session', session) storage.set('sessionCreated', Date.now().toString()) storage.set('isStudent', isStudent.toString()) } catch (e) { @@ -143,7 +142,7 @@ export async function callWithSession( password ) session = newSession - await save('session', session) + await saveSecure('session', session) storage.set('sessionCreated', Date.now().toString()) storage.set('isStudent', isStudent.toString()) } catch (e) { @@ -169,11 +168,11 @@ export async function callWithSession( * @param {object} router Next.js router object */ export async function obtainSession(router: object): Promise { - let session = load('session') + let session = loadSecure('session') const age = parseInt(storage.getString('sessionCreated') ?? '0') - const username = load('username') - const password = load('password') + const username = loadSecure('username') + const password = loadSecure('password') // invalidate expired session if (age + SESSION_EXPIRES < Date.now() || !(await API.isAlive(session))) { @@ -191,7 +190,7 @@ export async function obtainSession(router: object): Promise { password ) session = newSession - await save('session', session) + await saveSecure('session', session) storage.set('sessionCreated', Date.now().toString()) storage.set('isStudent', isStudent.toString()) } catch (e) { @@ -207,7 +206,7 @@ export async function obtainSession(router: object): Promise { * Logs out the user by deleting the session from localStorage. */ export async function forgetSession(): Promise { - const session = load('session') + const session = loadSecure('session') if (session === null) { console.debug('No session to forget') } else { @@ -219,9 +218,9 @@ export async function forgetSession(): Promise { } await Promise.all([ - SecureStore.deleteItemAsync('session'), - SecureStore.deleteItemAsync('username'), - SecureStore.deleteItemAsync('password'), + deleteSecure('session'), + deleteSecure('username'), + deleteSecure('password'), ]) // clear the general storage (cache) diff --git a/src/app/(flow)/onboarding.tsx b/src/app/(flow)/onboarding.tsx index 4d07ae8d..0cd35be0 100644 --- a/src/app/(flow)/onboarding.tsx +++ b/src/app/(flow)/onboarding.tsx @@ -6,6 +6,7 @@ import LogoTextSVG from '@/components/Flow/svgs/logoText' import PlatformIcon from '@/components/Universal/Icon' import { PRIVACY_URL } from '@/data/constants' import { useFlowStore } from '@/hooks/useFlowStore' +import { type OnboardingCardData } from '@/types/data' import { getContrastColor } from '@/utils/ui-utils' import * as Haptics from 'expo-haptics' import { router } from 'expo-router' @@ -40,13 +41,15 @@ export default function OnboardingScreen(): JSX.Element { const setAnalyticsAllowed = useFlowStore( (state) => state.setAnalyticsAllowed ) - const data = [ + + const data: OnboardingCardData[] = [ { title: t('onboarding.cards.title1'), description: t('onboarding.cards.description1'), icon: { ios: 'square.stack.3d.up', android: 'hub', + web: 'Layers', }, }, @@ -56,6 +59,7 @@ export default function OnboardingScreen(): JSX.Element { icon: { ios: 'person.2.gobackward', android: 'volunteer_activism', + web: 'Users', }, }, { @@ -64,6 +68,7 @@ export default function OnboardingScreen(): JSX.Element { icon: { ios: 'lock.app.dashed', android: 'encrypted', + web: 'GlobeLock', }, }, ] @@ -382,7 +387,7 @@ export default function OnboardingScreen(): JSX.Element { const [buttonDisabled, setButtonDisabled] = useState(true) const scaleFontSize = (size: number): number => { - if (DeviceInfo.isTablet()) { + if (DeviceInfo.isTablet() || Platform.OS === 'web') { return size } const guidelineBaseWidth = 475 @@ -469,6 +474,10 @@ export default function OnboardingScreen(): JSX.Element { size: 25, variant: 'outlined', }} + web={{ + name: 'CircleHelp', + size: 25, + }} /> diff --git a/src/app/(screens)/about.tsx b/src/app/(screens)/about.tsx index c6f077e4..5f01127a 100644 --- a/src/app/(screens)/about.tsx +++ b/src/app/(screens)/about.tsx @@ -8,6 +8,7 @@ import { usePreferencesStore } from '@/hooks/usePreferencesStore' import i18n from '@/localization/i18n' import { type FormListSections } from '@/types/components' import { trackEvent } from '@aptabase/react-native' +import { alert } from 'burnt' import * as Application from 'expo-application' import * as Haptics from 'expo-haptics' import { useRouter } from 'expo-router' @@ -73,6 +74,7 @@ export default function About(): JSX.Element { icon: { ios: 'bubble.left.and.exclamationmark.bubble.right', android: 'troubleshoot', + web: 'HeartPulse', }, onPress: () => { void Linking.openURL(STATUS_URL) @@ -88,6 +90,7 @@ export default function About(): JSX.Element { icon: { ios: 'envelope', android: 'mail', + web: 'Mail', }, onPress: async () => await Linking.openURL( @@ -110,6 +113,7 @@ export default function About(): JSX.Element { icon: { ios: 'star', android: 'star', + web: 'Star', }, onPress: () => { if (Platform.OS === 'android') { @@ -144,19 +148,27 @@ export default function About(): JSX.Element { setPressCount(pressCount + 1) if (pressCount === 7) { - Alert.alert( - t('about.easterEgg.title'), - Platform.OS === 'ios' - ? t('about.easterEgg.message') - : t('about.easterEgg.messageAndroid'), - [ - { - text: t('about.easterEgg.confirm'), - style: 'cancel', - }, - ], - { cancelable: false } - ) + if (Platform.OS !== 'web') { + Alert.alert( + t('about.easterEgg.title'), + Platform.OS === 'ios' + ? t('about.easterEgg.message') + : t('about.easterEgg.messageAndroid'), + [ + { + text: t('about.easterEgg.confirm'), + style: 'cancel', + }, + ], + { cancelable: false } + ) + } else { + alert({ + title: t('about.easterEgg.title'), + message: t('about.easterEgg.messageAndroid'), + preset: 'done', + }) + } const isCollected = unlockedAppIcons?.includes('cat') if (!isCollected) { trackEvent('EasterEgg', { easterEgg: 'aboutLogo' }) @@ -189,9 +201,11 @@ export default function About(): JSX.Element { { - void Haptics.impactAsync( - Haptics.ImpactFeedbackStyle.Medium - ) + if (Platform.OS !== 'web') { + void Haptics.impactAsync( + Haptics.ImpactFeedbackStyle.Medium + ) + } handlePress() }} > diff --git a/src/app/(screens)/accent.tsx b/src/app/(screens)/accent.tsx index 932992aa..13d5992a 100644 --- a/src/app/(screens)/accent.tsx +++ b/src/app/(screens)/accent.tsx @@ -73,6 +73,10 @@ export default function Theme(): JSX.Element { name: 'check', size: 24, }} + web={{ + name: 'Check', + size: 24, + }} style={{ color: getContrastColor(themeAccentColor), }} diff --git a/src/app/(screens)/calendar.tsx b/src/app/(screens)/calendar.tsx index 224ad868..68cccf5f 100644 --- a/src/app/(screens)/calendar.tsx +++ b/src/app/(screens)/calendar.tsx @@ -1,5 +1,6 @@ import { NoSessionError } from '@/api/thi-session-handler' import ErrorView from '@/components/Error/ErrorView' +import PagerView from '@/components/Layout/PagerView' import { CalendarRow, ExamRow } from '@/components/Rows/CalendarRow' import Divider from '@/components/Universal/Divider' import LoadingIndicator from '@/components/Universal/LoadingIndicator' @@ -23,7 +24,6 @@ import { View, useWindowDimensions, } from 'react-native' -import PagerView from 'react-native-pager-view' import { createStyleSheet, useStyles } from 'react-native-unistyles' export default function CalendarPage(): JSX.Element { @@ -234,6 +234,7 @@ export default function CalendarPage(): JSX.Element { icon={{ ios: 'calendar.badge.clock', android: 'calendar_clock', + web: 'CalendarX2', }} buttonText="Primuss" onButtonPress={() => { diff --git a/src/app/(screens)/clEvent.tsx b/src/app/(screens)/clEvent.tsx index ad00e374..2da95571 100644 --- a/src/app/(screens)/clEvent.tsx +++ b/src/app/(screens)/clEvent.tsx @@ -9,7 +9,7 @@ import { formatFriendlyDateTimeRange, } from '@/utils/date-utils' import { trackEvent } from '@aptabase/react-native' -import { useFocusEffect, useNavigation } from 'expo-router' +import { Redirect, useFocusEffect, useNavigation } from 'expo-router' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { Linking, ScrollView, Share, Text, View } from 'react-native' @@ -129,6 +129,7 @@ export default function ClEventDetail(): JSX.Element { icon: { ios: 'instagram', android: 'instagram', + web: 'Instagram', iosFallback: true, }, onPress: () => { @@ -152,6 +153,9 @@ export default function ClEventDetail(): JSX.Element { : []), ] + if (clEvent == null) { + return + } return ( {t('dashboard.unavailable.title')} @@ -328,11 +331,16 @@ export default function DashboardEdit(): JSX.Element { android={{ name: cardIcons[ item.key as keyof typeof cardIcons - ] - ?.android as MaterialIcon, + ]?.android, size: 21, variant: 'outlined', }} + web={{ + name: cardIcons[ + item.key as keyof typeof cardIcons + ]?.web, + size: 21, + }} /> {t( @@ -354,6 +362,10 @@ export default function DashboardEdit(): JSX.Element { name: 'lock', size: 24, }} + web={{ + name: 'Lock', + size: 24, + }} /> ) : ( {item.text} @@ -475,6 +498,10 @@ function RowItem({ variant: 'outlined', size: 24, }} + web={{ + name: 'CircleMinus', + size: 24, + }} style={styles.minusIcon} /> )} diff --git a/src/app/(screens)/foodPreferences.tsx b/src/app/(screens)/foodPreferences.tsx index 4a13a328..bf74aef4 100644 --- a/src/app/(screens)/foodPreferences.tsx +++ b/src/app/(screens)/foodPreferences.tsx @@ -99,7 +99,7 @@ export default function FoodPreferences(): JSX.Element { state={false} /> - + @@ -122,6 +122,10 @@ export default function FoodPreferences(): JSX.Element { name: 'warning', size: 24, }} + web={{ + name: 'TriangleAlert', + size: 24, + }} style={styles.warningIcon} /> diff --git a/src/app/(screens)/lecture.tsx b/src/app/(screens)/lecture.tsx index c27b1e49..7bbe4e10 100644 --- a/src/app/(screens)/lecture.tsx +++ b/src/app/(screens)/lecture.tsx @@ -174,6 +174,10 @@ export default function TimetableDetails(): JSX.Element { name: 'calendar_month', size: 24, }} + web={{ + name: 'Clock', + size: 24, + }} /> @@ -202,6 +206,10 @@ export default function TimetableDetails(): JSX.Element { name: 'chevron_right', size: 16, }} + web={{ + name: 'ChevronRight', + size: 16, + }} /> @@ -235,6 +243,10 @@ export default function TimetableDetails(): JSX.Element { name: 'place', size: 24, }} + web={{ + name: 'MapPin', + size: 24, + }} style={styles.icon} /> @@ -297,6 +309,10 @@ export default function TimetableDetails(): JSX.Element { name: 'person', size: 24, }} + web={{ + name: 'User', + size: 24, + }} style={styles.icon} /> diff --git a/src/app/(screens)/lecturers.tsx b/src/app/(screens)/lecturers.tsx index a95bbf1e..895cb3a6 100644 --- a/src/app/(screens)/lecturers.tsx +++ b/src/app/(screens)/lecturers.tsx @@ -1,6 +1,7 @@ import API from '@/api/authenticated-api' import { NoSessionError } from '@/api/thi-session-handler' import ErrorView from '@/components/Error/ErrorView' +import PagerView from '@/components/Layout/PagerView' import LecturerRow from '@/components/Rows/LecturerRow' import Divider from '@/components/Universal/Divider' import LoadingIndicator from '@/components/Universal/LoadingIndicator' @@ -39,7 +40,6 @@ import { Text, View, } from 'react-native' -import PagerView from 'react-native-pager-view' import { UnistylesRuntime, createStyleSheet, @@ -369,6 +369,7 @@ export default function LecturersCard(): JSX.Element { icon={{ ios: 'calendar.badge.exclamationmark', android: 'edit_calendar', + web: 'CalendarCog', }} buttonText={t('error.empty.button', { ns: 'timetable', diff --git a/src/app/(screens)/legal.tsx b/src/app/(screens)/legal.tsx index 2316bb67..bad85519 100644 --- a/src/app/(screens)/legal.tsx +++ b/src/app/(screens)/legal.tsx @@ -50,6 +50,7 @@ export default function About(): JSX.Element { icon: { ios: 'safari', android: 'github', + web: 'Github', }, onPress: async () => @@ -61,7 +62,8 @@ export default function About(): JSX.Element { title: t('legal.formlist.us.faq'), icon: { ios: 'safari', - android: 'github', + android: 'link', + web: 'Link', }, onPress: async () => diff --git a/src/app/(screens)/login.tsx b/src/app/(screens)/login.tsx index 532a7b20..1d63f9d6 100644 --- a/src/app/(screens)/login.tsx +++ b/src/app/(screens)/login.tsx @@ -1,230 +1,6 @@ -import LoginForm from '@/components/Universal/LoginForm' -import { PRIVACY_URL } from '@/data/constants' -import * as Haptics from 'expo-haptics' -import { router, useLocalSearchParams } from 'expo-router' -import React, { useEffect, useRef, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { - Dimensions, - Keyboard, - KeyboardAvoidingView, - Linking, - Platform, - Pressable, - Text, - TouchableWithoutFeedback, - View, -} from 'react-native' -import Animated, { - runOnJS, - useAnimatedStyle, - useSharedValue, - withTiming, -} from 'react-native-reanimated' -import { useSafeAreaInsets } from 'react-native-safe-area-context' -import { createStyleSheet, useStyles } from 'react-native-unistyles' +import Login from '@/components/Flow/Login' +import React from 'react' -const useIsFloatingKeyboard = (): boolean => { - const windowWidth = Dimensions.get('window').width - const [floating, setFloating] = useState(false) - - useEffect(() => { - const onKeyboardWillChangeFrame = (event: any): void => { - setFloating(event.endCoordinates.width !== windowWidth) - } - - Keyboard.addListener( - 'keyboardWillChangeFrame', - onKeyboardWillChangeFrame - ) - return () => { - Keyboard.removeAllListeners('keyboardWillChangeFrame') - } - }, [windowWidth]) - - return floating +export default function LoginScreen(): JSX.Element { + return } -function shuffleArray(array: string[]): string[] { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)) - ;[array[i], array[j]] = [array[j], array[i]] // Swap elements - } - return array -} - -const textsEN = shuffleArray([ - 'to view your grades', - 'to search for free rooms', - 'to get map suggestions', - 'to book a library seat', - 'to view your timetable', - 'to see your exam schedule', - 'to search all lectureres', - 'to view your lectureres', - 'to view the latest THI news', - 'to check your printer balance', -]) - -const textsDE = shuffleArray([ - 'um deine Noten zu sehen', - 'um freie Räume zu suchen', - 'um Karten Vorschläge zu erhalten', - 'um einen Bibliotheksplatz zu buchen', - 'um deinen Stundenplan zu sehen', - 'um deinen Prüfungsplan zu sehen', - 'um alle Dozenten zu suchen', - 'um deine Dozenten zu sehen', - 'um die THI-News anzuzeigen', - 'um dein Drucker Guthaben zu prüfen', -]) - -export default function Login(): JSX.Element { - const { styles } = useStyles(stylesheet) - const floatingKeyboard = useIsFloatingKeyboard() - const { t, i18n } = useTranslation('flow') - const [currentTextIndex, setCurrentTextIndex] = useState(0) - const currentTextIndexRef = useRef(currentTextIndex) - const textOpacity = useSharedValue(1) - const textTranslateY = useSharedValue(0) - const texts3 = i18n.language === 'de' ? textsDE : textsEN - const shouldVibrate = Platform.OS === 'ios' - - const { fromOnboarding } = useLocalSearchParams<{ - fromOnboarding: string - }>() - - const navigateHome = (): void => { - if (fromOnboarding === 'true') { - router.dismissAll() - router.replace('/(tabs)/(index)') - return - } - router.dismissAll() - } - - useEffect(() => { - currentTextIndexRef.current = currentTextIndex - }, [currentTextIndex]) - - const goToNextText = (): void => { - textOpacity.value = withTiming(0, { duration: 300 }, () => { - const nextIndex = (currentTextIndex + 1) % texts3.length - runOnJS(setCurrentTextIndex)(nextIndex) - - textTranslateY.value = 5 - textOpacity.value = withTiming(1, { duration: 300 }) - textTranslateY.value = withTiming(0, { duration: 600 }) - }) - } - - useEffect(() => { - const interval = setInterval(goToNextText, 3500) - - return () => { - clearInterval(interval) - } - }, [currentTextIndex, texts3, textOpacity, textTranslateY]) - - const animatedStyle = useAnimatedStyle(() => { - return { - opacity: textOpacity.value, - transform: [{ translateY: textTranslateY.value }], - } - }) - - const insets = useSafeAreaInsets() - - return ( - <> - - - - - - {t('login.title1')} - - { - goToNextText() - if (shouldVibrate) { - void Haptics.selectionAsync() - } - }} - > - - - {texts3[currentTextIndex]} - - - - - - - - - - - - { - void Linking.openURL(PRIVACY_URL) - }} - > - - {t('onboarding.links.privacy')} - - - - - - - ) -} - -const stylesheet = createStyleSheet((theme) => ({ - container: { - alignSelf: 'center', - flex: 1, - width: '90%', - }, - header1: { - color: theme.colors.text, - fontSize: 42, - fontWeight: 'bold', - textAlign: 'left', - }, - - header3: { - color: theme.colors.labelColor, - fontSize: 26, - fontWeight: '400', - marginTop: 10, - minHeight: 30, - textAlign: 'left', - }, - - keyboardContainer: { - flex: 1, - justifyContent: 'space-evenly', - }, - linkContainer: { - alignItems: 'center', - alignSelf: 'center', - bottom: 70, - gap: 6, - position: 'absolute', - }, - privacyLink: { - color: theme.colors.labelColor, - fontSize: 14, - textAlign: 'center', - }, -})) diff --git a/src/app/(screens)/meal.tsx b/src/app/(screens)/meal.tsx index 90f49c56..3e5cebc3 100644 --- a/src/app/(screens)/meal.tsx +++ b/src/app/(screens)/meal.tsx @@ -239,6 +239,7 @@ export default function FoodDetail(): JSX.Element { ? { android: 'check_circle', ios: 'checkmark.seal', + web: 'BadgeCheck', } : undefined, iconColor: theme.colors.success, @@ -264,6 +265,7 @@ export default function FoodDetail(): JSX.Element { ? { android: 'warning', ios: 'exclamationmark.triangle', + web: 'TriangleAlert', } : undefined, iconColor: theme.colors.notification, @@ -417,6 +419,10 @@ export default function FoodDetail(): JSX.Element { name: 'warning', size: 24, }} + web={{ + name: 'TriangleAlert', + size: 24, + }} style={styles.iconWarning} /> diff --git a/src/app/(screens)/news.tsx b/src/app/(screens)/news.tsx index 337dac78..45758257 100644 --- a/src/app/(screens)/news.tsx +++ b/src/app/(screens)/news.tsx @@ -107,6 +107,10 @@ export default function NewsScreen(): JSX.Element { name: 'chevron_right', size: 16, }} + web={{ + name: 'ChevronRight', + size: 16, + }} style={styles.icon} /> diff --git a/src/app/(screens)/profile.tsx b/src/app/(screens)/profile.tsx index 47e58035..44e8aee0 100644 --- a/src/app/(screens)/profile.tsx +++ b/src/app/(screens)/profile.tsx @@ -99,6 +99,23 @@ export default function Profile(): JSX.Element { } const logoutAlert = (): void => { + if (Platform.OS === 'web') { + if (!window.confirm(t('profile.logout.alert.message'))) { + return + } else { + setIsLoggingOut(true) + resetFood() + resetPreferences() + performLogout(toggleUserKind, resetOrder, queryClient) + .catch((e) => { + console.log(e) + }) + .finally(() => { + setIsLoggingOut(false) + }) + return + } + } Alert.alert( t('profile.logout.alert.title'), t('profile.logout.alert.message'), @@ -292,6 +309,7 @@ export default function Profile(): JSX.Element { icon={{ ios: 'person.crop.circle.badge.exclamationmark', android: 'account_circle_off', + web: 'UserRoundX', }} isCritical={false} /> @@ -315,6 +333,10 @@ export default function Profile(): JSX.Element { name: 'logout', size: 22, }} + web={{ + name: 'LogOut', + size: 22, + }} style={styles.notification} /> diff --git a/src/app/(screens)/settings.tsx b/src/app/(screens)/settings.tsx index 4b205b31..4d979468 100644 --- a/src/app/(screens)/settings.tsx +++ b/src/app/(screens)/settings.tsx @@ -5,7 +5,7 @@ import { Avatar, NameBox } from '@/components/Settings' import GradesButton from '@/components/Settings/GradesButton' import Divider from '@/components/Universal/Divider' import FormList from '@/components/Universal/FormList' -import PlatformIcon from '@/components/Universal/Icon' +import PlatformIcon, { type LucideIcon } from '@/components/Universal/Icon' import LoadingIndicator from '@/components/Universal/LoadingIndicator' import { DashboardContext, UserKindContext } from '@/components/contexts' import { queryClient } from '@/components/provider' @@ -22,12 +22,11 @@ import { withBouncing, } from '@/utils/animation-utils' import { getPersonalData, performLogout } from '@/utils/api-utils' -import { storage } from '@/utils/storage' +import { loadSecure, storage } from '@/utils/storage' import { getContrastColor, getInitials } from '@/utils/ui-utils' import { trackEvent } from '@aptabase/react-native' import { useQuery } from '@tanstack/react-query' import { useRouter } from 'expo-router' -import * as SecureStore from 'expo-secure-store' import React, { useContext, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { @@ -75,8 +74,7 @@ export default function Settings(): JSX.Element { const scrollY = useRef(0) const logoRotation = useSharedValue(0) const velocity = 110 - const username = - userKind === USER_EMPLOYEE && SecureStore.getItem('username') + const username = userKind === USER_EMPLOYEE && loadSecure('username') const { color, randomizeColor } = useRandomColor() const resetPreferences = usePreferencesStore((state) => state.reset) const resetFood = useFoodFilterStore((state) => state.reset) @@ -227,6 +225,7 @@ export default function Settings(): JSX.Element { icon: { ios: 'rectangle.stack', android: 'dashboard_customize', + web: 'LayoutDashboard', }, onPress: () => { @@ -238,6 +237,7 @@ export default function Settings(): JSX.Element { icon: { android: 'restaurant', ios: 'fork.knife', + web: 'Utensils', }, onPress: () => { router.navigate('/foodPreferences') @@ -248,6 +248,7 @@ export default function Settings(): JSX.Element { icon: { ios: 'globe', android: 'language', + web: 'Globe', }, onPress: async () => { @@ -272,6 +273,7 @@ export default function Settings(): JSX.Element { icon: { ios: 'paintpalette', android: 'palette', + web: 'Palette', }, onPress: () => { router.navigate('/accent') @@ -282,6 +284,7 @@ export default function Settings(): JSX.Element { icon: { ios: 'moon.stars', android: 'routine', + web: 'MoonStar', }, onPress: () => { router.navigate('/theme') @@ -295,6 +298,7 @@ export default function Settings(): JSX.Element { icon: { ios: 'star.square.on.square', android: '' as MaterialIcon, + web: 'StarSquare' as LucideIcon, }, onPress: () => { router.navigate('/appIcon') @@ -312,6 +316,7 @@ export default function Settings(): JSX.Element { icon: { ios: 'chevron.forward', android: 'chevron_right', + web: 'ChevronRight', }, onPress: () => { router.navigate('/about') @@ -323,6 +328,7 @@ export default function Settings(): JSX.Element { icon: { ios: 'square.and.arrow.up', android: 'share', + web: 'Share', }, onPress: () => { trackEvent('Share', { type: 'app' }) @@ -372,123 +378,65 @@ export default function Settings(): JSX.Element { } } }} + style={styles.container} > - - - {isSuccess && - userKind === 'student' && - data?.mtknr !== undefined ? ( - - - - - - {getInitials( - data?.vname + - ' ' + - data?.name - )} - - - - - - - - - - ) : isSuccess && - userKind === 'student' && - data?.mtknr === undefined ? ( - - - - - - - - - - - - - ) : userKind === 'employee' ? ( + + {isSuccess && + userKind === 'student' && + data?.mtknr !== undefined ? ( + {getInitials( - (username as string) ?? '' + data?.vname + + ' ' + + data?.name )} + + - ) : userKind === 'guest' ? ( + + + + ) : isSuccess && + userKind === 'student' && + data?.mtknr === undefined ? ( + @@ -519,48 +471,121 @@ export default function Settings(): JSX.Element { name: 'chevron_right', size: 26, }} + web={{ + name: 'ChevronRight', + size: 26, + }} style={styles.iconAlign} /> - ) : isLoading ? ( - <> - - - - + + + + ) : userKind === 'employee' ? ( + + + + + {getInitials( + (username as string) ?? '' + )} + + + + + ) : userKind === 'guest' ? ( + + + + + + + + + ) : isLoading ? ( + <> + + + - - ) : ( - <> - + + ) : ( + <> + + - - - - - - )} - + + + + + )} @@ -571,59 +596,73 @@ export default function Settings(): JSX.Element { {t('menu.copyright', { year: new Date().getFullYear() })} - + - { - setTapCount(0) - }} - disabled={!isBouncing} - hitSlop={{ top: 10, right: 10, bottom: 10, left: 10 }} - > - - - + { + opacity: logoActiveOpacity, + height: logoActiveHeight, + }, + ]} + > + { + setTapCount(0) + }} + disabled={!isBouncing} + hitSlop={{ + top: 10, + right: 10, + bottom: 10, + left: 10, + }} + > + + + - - { - handlePress() - }} - disabled={isBouncing} - accessibilityLabel={t('button.settingsLogo', { - ns: 'accessibility', - })} - hitSlop={{ top: 10, right: 10, bottom: 10, left: 10 }} - > - - - + + { + handlePress() + }} + disabled={isBouncing} + accessibilityLabel={t('button.settingsLogo', { + ns: 'accessibility', + })} + hitSlop={{ + top: 10, + right: 10, + bottom: 10, + left: 10, + }} + > + + + + + )} ) } @@ -679,6 +718,7 @@ const stylesheet = createStyleSheet((theme) => ({ flexDirection: 'row', paddingHorizontal: 14, paddingVertical: 20, + width: '100%', }, nameOuterContainer: { flexDirection: 'column', flex: 1 }, diff --git a/src/app/(screens)/sportsEvent.tsx b/src/app/(screens)/sportsEvent.tsx index 5e13a42e..0dfd25a8 100644 --- a/src/app/(screens)/sportsEvent.tsx +++ b/src/app/(screens)/sportsEvent.tsx @@ -115,11 +115,13 @@ export default function SportsEventDetail(): JSX.Element { ? { ios: 'exclamationmark.triangle.fill', android: 'warning', + web: 'TriangleAlert', } : { ios: 'checkmark.seal', android: 'new_releases', androidVariant: 'outlined', + web: 'BadgeCheck', }, iconColor: styles.warning( sportsEvent?.requiresRegistration ?? false diff --git a/src/app/(tabs)/(index)/index.tsx b/src/app/(tabs)/(index)/index.tsx index 0c64fb99..9c981285 100644 --- a/src/app/(tabs)/(index)/index.tsx +++ b/src/app/(tabs)/(index)/index.tsx @@ -32,7 +32,7 @@ export default function HomeRootScreen(): JSX.Element { <>} largeTitle={true} @@ -94,6 +94,7 @@ function HomeScreen(): JSX.Element { ios: 'rainbow', multiColor: true, android: 'dashboard_customize', + web: 'Cog', }} buttonText={t('dashboard.noShownButton', { ns: 'settings' })} onButtonPress={() => { diff --git a/src/app/(tabs)/(index)/links.tsx b/src/app/(tabs)/(index)/links.tsx index 4486a07e..20395818 100644 --- a/src/app/(tabs)/(index)/links.tsx +++ b/src/app/(tabs)/(index)/links.tsx @@ -1,4 +1,5 @@ import FormList from '@/components/Universal/FormList' +import { type LucideIcon } from '@/components/Universal/Icon' import { quicklinks } from '@/data/constants' import { usePreferencesStore } from '@/hooks/usePreferencesStore' import { type FormListSections } from '@/types/components' @@ -30,9 +31,11 @@ const LinkScreen = (): JSX.Element => { icon: { ios: string android: MaterialIcon + web: LucideIcon } } function generateSections(links: Quicklink[]): FormListSections[] { + console.log(links) return [ { items: links.map((link) => ({ diff --git a/src/app/(tabs)/_layout.tsx b/src/app/(tabs)/_layout.tsx index 57053436..ea490358 100644 --- a/src/app/(tabs)/_layout.tsx +++ b/src/app/(tabs)/_layout.tsx @@ -54,7 +54,8 @@ export default function HomeLayout(): JSX.Element { (state) => state.toggleSelectedAllergens ) const setAccentColor = usePreferencesStore((state) => state.setAccentColor) - const { userKind = USER_GUEST } = useContext(UserKindContext) + const { userKind: userKindInitial } = useContext(UserKindContext) + const userKind = userKindInitial ?? USER_GUEST const updatedVersion = useFlowStore((state) => state.updatedVersion) const [isOnboardedV1] = useMMKVBoolean('isOnboardedv1') const [analyticsV1] = useMMKVBoolean('analytics') @@ -85,7 +86,21 @@ export default function HomeLayout(): JSX.Element { } storage.delete('selectedUserAllergens') } + + const version = Application.nativeApplicationVersion + const processedVersion = convertToMajorMinorPatch(version ?? '0.0.0') + const isChangelogAvailable = + version != null + ? Object.keys(changelog.version).some( + (changelogVersion) => changelogVersion === processedVersion + ) + : false + useEffect(() => { + if (Platform.OS === 'web') { + return + } + const shortcuts = [ { id: 'timetable', @@ -188,24 +203,22 @@ export default function HomeLayout(): JSX.Element { } }, [appIcon]) - if (isOnboarded !== true) { - return - } + if (Platform.OS === 'web') { + if (userKindInitial === undefined) { + return + } + } else { + if (isOnboarded !== true) { + return + } - const version = Application.nativeApplicationVersion - const processedVersion = convertToMajorMinorPatch(version ?? '0.0.0') - const isChangelogAvailable = - version != null - ? Object.keys(changelog.version).some( - (changelogVersion) => changelogVersion === processedVersion - ) - : false - if ( - updatedVersion !== processedVersion && - isChangelogAvailable && - isOnboarded - ) { - return + if ( + updatedVersion !== processedVersion && + isChangelogAvailable && + isOnboarded + ) { + return + } } return diff --git a/src/app/(tabs)/food.tsx b/src/app/(tabs)/food.tsx index 5db681fe..79aaef17 100644 --- a/src/app/(tabs)/food.tsx +++ b/src/app/(tabs)/food.tsx @@ -30,7 +30,7 @@ export default function FoodRootScreen(): JSX.Element { <>} headerRightElement={FoodHeaderRight} diff --git a/src/app/(tabs)/map.tsx b/src/app/(tabs)/map.tsx index 2a2007fe..35f8516d 100644 --- a/src/app/(tabs)/map.tsx +++ b/src/app/(tabs)/map.tsx @@ -1,14 +1,13 @@ /* eslint-disable react-native/no-color-literals */ -import MapScreen from '@/components/Map/MapScreen' +import MapScreen, { requestPermission } from '@/components/Map/MapScreen' import { MapContext } from '@/contexts/map' import { type ClickedMapElement, type SearchResult } from '@/types/map' import { type AvailableRoom, type FriendlyTimetableEntry } from '@/types/utils' import { storage } from '@/utils/storage' -import Maplibre from '@maplibre/maplibre-react-native' import Head from 'expo-router/head' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { Platform, View } from 'react-native' +import { View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' export default function MapRootScreen(): JSX.Element { @@ -74,9 +73,7 @@ export default function MapRootScreen(): JSX.Element { updateSearchHistory, } - if (Platform.OS === 'android') { - void Maplibre.requestAndroidLocationPermissions() - } + requestPermission() return ( <> diff --git a/src/app/(tabs)/timetable.tsx b/src/app/(tabs)/timetable.tsx index e09feab0..3c9abb45 100644 --- a/src/app/(tabs)/timetable.tsx +++ b/src/app/(tabs)/timetable.tsx @@ -16,7 +16,7 @@ export default function FoodRootScreen(): JSX.Element { { - if (isPad) { + if (Platform.OS === 'web') { + // do nothing + } else if (isPad) { void ScreenOrientation.unlockAsync() } else { void ScreenOrientation.lockAsync( @@ -385,6 +387,10 @@ function RootLayout(): JSX.Element { name: 'barcode_scanner', size: 24, }} + web={{ + name: 'Barcode', + size: 22, + }} /> ), diff --git a/src/assets/web/favicon.png b/src/assets/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..aba3030ad86fde3eb9e90462ee9b35b2e80c373c GIT binary patch literal 1824 zcmV+*2jBRKP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NIib+I4RA>d&T3bj}T@aq*r37!s zQcpKhQY-{fP_q!}BG#`NYDI;F4=VU1`mBh)22lt;^b)9q=|e;p>ZcUDV4$Yp1xXW8 z#Gn$b3^h}C^DWw^vyXebFXtFFFs`#Mv)0U7GqYya)fd(N9$Z?yxhqGyd%W9__LaHvHfQNcBHs zChGqHbTE(qu}ZD_{9?A6%YTJ=unb_at*@_dr8 z@@0apY!&F9z_)MTgsO~=j#}mc7g~CHI+c`^P)<&cw>*&GM^^x-&Ct*g`S|$IlP6E8 zudmOtARs(*<_y)>*LzK+bOn&P<(rj`9z9C4v$JxOW(hBoo106=jvb?Y`}R>#P!P?} z&(rYmFtxY0)7aP;J$Udyv};acw1W-N5L8!Ji!cG9f*v+DHf}oS`}gl9z5u-v>=`-% z>^^SL#l=O-G0y}iAH0Y5)Ku^=Za!XVLjE5O^gZ^cD~oxXA7hH!(JbzxyaOkPw}q*w&r zM~1GhE_(Ckja*ffaLqEXQPbzopXI8lQdfYfsVOnxxpU{}(xpp^$str$R+gd-t~MYb zzzhUshd33z0?%|yR&WEHsXv+L5si(aX*4ESJ=LNJEf$gIGd0cKn>l& zJ>fw>hZxH&=nBB;BAq&QYJ*EmN=l+#yLPFrmynPk+9i^N_i}XtGQr7{Cxy+(b(9bm z36h)yx%O%O))gQ)I9S9l?i0Y{3 zS3R)}6Z~q>jYg3M#>dC2(iaxuL1QCCMMWtZpFe-DsLK_+U>rJhNaPf_50I%PCnt*w zQ68gPvecUb$1M_joK45Lce3Y_!t8d5?Zb%^6BF*kyi(Tp=7)*@2gW>$L)w1;FCSxCRFY73&&dT0y`S zJaXXzvjUcF0Jr9E<~P@Q2qmK$lgPUfN>~BQ0^l)MW=c)fMZz~-MWRc7?{^`(}U7V+AQZh5y=h8k|d12HznBz%AXVS7pZ0RI`E^e zghwn+;3XEu+w<3gc^m%l%jza7BigKMSZGHl@=W{0gz?4A% O0000+ z$mV;TSMT@t^Z5(DKYj1V<9^(3*X8v(U(fS7j^lVf&(~cw75R+DYIJ5nf{UXy>9)m%zGi(fK39oKWxymZdV<+OtZ#l^*i z-^$j;(fqWX1^*=n%ZOi6EELKvio&tOTCPt=8i!XN=Dt(8mV0hft^dfDo7`KDoblPSCCBpn zLR^S0dwbx!ccbs#Efn$}G*s{!@cNvzcfq}$IpXzKyR}#s{>kQ+ZCdv4$BO%h%>MmY z(a3}cBmbCL(dqsBDO-5;{(nE|v8>qs@2CCumu>j>Q%JSXs((M7GOt)p`^hIk;Qzne zKb!pjw)_9yQN;BP48k|DikneaP$(5PD<5ydUW_?ONJxAxcF7XG_&NT4gxGM!)fEOs zPAP-LwXEJ8`E-XG6Hc|wQ@y>tFLo5D+uPgQ@{>O>>9~sP+Og$R4HlZmzP@J8 z!p)xeNse5i!z3p!QrTSb;#3 zYuqe59}^(EsOR|o-NzKoq*|xy>G3|=^^APT^?Vv~zWl`E?|pw47N?!2laidA{LWH4 zMdNs}%c#!z^U2#bVBNdy*C2q$4(sdd&;E!DnD{8qF*r9`?ou0OUgK|3g7CGY|3#u$ zrf+`cPq>Ip$hmiqpN@=NNQ&IKeS2c*l__39!A2`fe4}TnZxd|SGbt!2IMH=w)CxDQ zt*djKor#kVI7hqGZrY_xh9?G_+(%2Ngk63aF)}a|d}BF$`0(e_EBUc;amEfZum$ZI zU7rR8F_g~#uA7}`Qq?vx8fmoIxKq?`97(sL>fbl+cXSw)&iyJ5r#h$p{`1G!OBxpU zoNUoe)y;~k%Yvi&JRBrtI6!hr13$GU^%>3)86`NsL)ON)NGxS4GQ*=(uLAqNbF zts3{R$#{JF*~Tt(&hJasI$Yo$Nq|1`iYh9tLJd-J&cm(Dg1< zB*uz3h8;U>TGRF22NObrA|h8sV3;ZSA-tzjhhLCxK1MRZUfD z{Kh7Bsq@r+--n#?;VSsMF&^3~0)6k@o2=!_`Vr~UrLLfGckl^cxah^y@9!fb>i^HT z95ggFC&ylIw!w-LHNDQgd9XWi8$R~fG)gu*B*(gi7Z3VAurD||I{H`P0DBWNK9;#n zlDys#d;4c5SAL4XCl$lO!V(=bQ!ajak*fbZagYw*ihj*`1J6@{-IJald;K}jCUh`p zXVFyG6=g1bJiUk1oX7t7vCO8k$l3kdL;0Y{$cxneTk{JGdEQH2+F2%@PS|CihOhKT z5L@p$3LQlpzMZTK&<$*UdUQ=SAO7~J>y0h2mNA~u#C>otEY{*wdtm1wGoL|38YQ}l zwulUVPxHj#7hm=GtARd{x=bKv0)1(0nJ??gmS&UG)D96x>>cg`@(xBYi#c2bn> zupj3J1H9+s6crVfl7b8~4Dz({ZH@nJSP#%5m)k|dHkbv zlTrg@n1VJxm_7FRh9*PuvB#*de4IGV@%YLt@-l>U5wPjjsv zc@hIL&HGfIBqPSc>+Mns3dCvao~EZ=G z)xEgT9{ctYS46#CA#xqBlvIZk0d!uKBtzILC@T6^-`|+xG);oG8?Bi3*CJaa>6O*(9s zkdQEl3P5%I?uSwl6cUn%e4LP!G<=)GOYPjb#GITQEj_&-xx-zlzKnd>1fz!!AEug= z7NI;avaw~bh*%rV)GW;nynOl6b`+^|nuE4`!FRWD4dKFxLVkDs{rOwk1_lO(EAMX8 zU9kWCO(7vZp8LRo_l-x*U`z)`$Ka@_%=q~Don9AVGp%wj&z3AxQv`YOP{Z!syKQ-~ z?`6JZmyDiyEw~>rDf#4dOTDdl1+hm>07o10Zsz>MprCjm%lbDvWSS+UrHuy%2R|bn zGsLc7in3!Ev)zv1Ov z!otmFJn&*I(c1^-4;+QzH8m5JKFTi5s5UWIx1?ywGcz;SHZ&lPoZ$i_(o&TpM6Fv= zgR$N;Wax-`EQ{TTJnxT)OJ(X)zd8$FueH@p(>aiL@rw>}iw;UfOR_p=lLGwe8u6|% z`%SlxUSG4hY-s#oMG#VI>e1VqhB^wLyH5@!Abi4AaLw(cxcUUl0n~_`ezmfRxuW6I z1yN!Xa9XWq4Tk{my7#MW&USLJNgg;%G!psJ=ni=<4rh1) z4E%6M{_DP(xw$mYxxYi*<>jmCm|~KXxoOY!miAog0W!16TeogSsHpUhj%N9@Nm`>q z77avQVc5F$3>A4$d>Kh6GC$rtX zMxG#9X*y;NyQ`c{Tc$|lvV!+N^Sd*FvrMt_80+Tb)6_K-O3+r?HO~QJl64KzRjdV? znf&qbXkYdHzDzu=)LPm%zW*v7ZOt?;#`>x|?Q7WFd#h<3nkZz+*|#wxJSWHqU+1x{29G zKE*IUlcY&{&J)~w_Eb7;f}!rwc3=#TJrr6#pD=xqgF6D&`thuWz3H)lIa3a*T1A5s zGM8PcRa2sX$+CXC#d$a7C@K8|2Du-aQ#1iYE)mN|zanMVwa{^(KE>_NcV2GpLy^l} z@5B9u0A5=P?E4s4SuYTKX4U-rCbQ7FLng~qF8}I~D4p)TXWd;^`n^6fjbw57va1LI zr4{!8w^MPw+IR1SalO3N8(Gdb#w!ec$fu-bK1!Ale#f=D*9imTn; z#3mTIg5uM5ns%K7df63D%X>6m*mkByNnat^k5<9RH+l%1DQQN9X-QPxDdV}tgiBmp z@9ER0>o;#MAIg$HcI;#zzex$VLhakP1ibzJ{oAi(4ZpCk7OBJRo)hZY$!afuwymL5 zM9@Md!Njefv2?t8XQGgg;Z}C`%p&JulbJY!JZq9KQ?*iJ5#uTNcm0M9ny#(|09dCy zy}Z22kYh7vIrr{8Ze?Xfz4A95crZ9TJhdrFRdV6xh=72=Y>n*VQ-qtuB%}+9=;U_u7y z8b8L#WB*JupA-5NYT~YdJE)|tqExuh9^d4_(K0d?~i2rw`}Q&HGrKr z(-!>$UZH*D$hASb>BpZw>E&JOHesVWsUe$bm%2MQDez-qSx5y|_|4}s&td}SIyyQG zXS?9T!;@{Mx^*E3jE&Y&d|b@P2G^anwB(bKDeDN3Tqd@Skukxxvna)~A&Mlvw#_4;Mtj*;DJv8vy7(f37{U%$nVk!hKww2OE`n=!0l!9X@<@kioTJs3py2tmh>Q)hQX3_bjz0?W~1`)nH?S zL5b_L%OjtjMlXtc&Hc9uRunx9*$ z;=)QmG(DQXJ9<#74YJLyqJkym<_aq;d5X1i21vywMSB?_livbA&0bBj(VN$sWz#Dw zk2Num9fDJUSH%gRsay+ReDCD1N)Qmjf8y(H4r_RhgE%Yf*ph42)SPRj+2lr{Fx8M6 z6w1xRqaJtSJ`ItsVvj}rasTo3_C%WLg5uSyR}&q_;=X-5`~I=O(GA=8t0DUKDTl{D zGp_+o3{zP_sbHclr4o79X{yr&+f_9)GedUX{r6k0mK=*yO%erH3Euho>ZUs?Y;(H4 z(893kl7^O65D^vp#)R0A-=3I*J0@;3JB@p?h+cS1ASd#UfQg&WojYp*6jWeCjR1CO zVdR&epY!UR5PB!<2u(MMz0_Uy56$T*$;tWL+PE{3E80T?!IbEzw&~{0n?yTc+`2Uu zxOV*HZc0TGExc9%8G=(u2L}Z`R=Ik7dS=F|r{e1DpXjA@ld;n47g_3N3k?2bpvcuv5ru@9)% zM!UGVPfiZEiA4gP8j@mUCWnMmw(!@Gpc#-64Z!He#>SwB51(S&Ei5e~RBo&R_-X21 zT6A$9Y^AIl~Q7<+7ml!Kf0j>{C>cz>>G#b&7(g-82%czJ6{Ve+Y?S!gA$Yf$Q* z2iTU-S?uEI!f2F<^_u|R4@z%G`)woVJ0Uh=TQ!iv8yuk zV@Hh(>|Q!;^3Z~IBy#byaNhZkuV@w&ATc=4!KL!#L%RNRBJJ?V%68+MpR&9cY!tH5C<;z{Ix2}mJDkaCJ-n(30!@+Fh3lHb0drWW~M zlm{wm;1NXb!6pht0>_y#lUqkSlL{Uos*I|ld-^oekX)(zWMlc#!lmQKj?s8vHUpl< zrvbas2nIg+yY3(#7gvqbrd<*56KmM7C^bm^@tdEYR~o?0^)?}&S0s7B_~GLAv4Mes zSbAW5cJ?8n01W}j(AHQ-lGyYO4zKCMkGND@!EoIUOYY~SQVqZ-bv-;v{yhnird>;` z?(G;JOp4aIeEG#7LyxcIXo=?1OoO);U@vWFLdc$v8SFS{(hMSz=QNM3x@hXr{k2WgeM1J{#GK!auwRP$s zYY#7>i*PPRCZ_XLu*-_Lv9*X;n zeIHOIa)t5Cz_Mk_)^FUXefDhp|4pqN--nAZva{P#Ij|~{^?Yi;-#$7vNGF8}Swc54 z;{WeIXh8O;Enl_0GeKPjg|R`QG@Hi1dUZq^S|XH=FkoV+j@%;F%_j+i`10Z+4flNA zg4)1FyKte?U?9;yP}<-S`)j(pO-boN%#v@SSVtA-5;{q{bP#wVsUK@Lu@cPZ2W~(4 ztJ4rnUnfHJ;`E+l;Jq~SuoGlMOzq&1gEg2}RaPdeM#~T%0kg`gsE{Mbk0M6AerzpL zI8iDl7N)u*RF;*TfH}PWM0B5r_F&zb9yZ8|1<*>PUKdd3L`3)e38afeO-dFc>g>~_ zw}%$zMs-oJX>MvnsJ)rh4t}E#xF}N=MQj}%omQ4fDXCb_qg|0x7RXbyNYZPC|B0Oc zu!$#xHsifj_~QIAXim2_b7bOI1p6nVALX-|09Czuo2=5rHj^vw zomTq{6D@MoYf4gjak{VLA-u8U8hHwyQ=$AVpPrdlT~hU#gvvBX;QIfCuw4bnCR&Vi zl^T}MyuXAhVMbjg$5UBZdA_&uE{l|F_WuU&LE7SOAh#t$AOn>ZiCiAz*W1@O2~8sn z^$&|hiT--&;WCP!qd2R3?_Dm~ZnMi+&tVi3RqxrRXo6t~<*2wv606@8)Yx2yenqGu zPbwgt|LQ0l`tbDn_5%iI3Hoe|-Es}_ zET%_cVJW{li-+HZ36XE9!D@+0VX@d-noP*G*+YZxb2_ffd$}EL+fNx-_f%K1zBnynH(=YdRo34{{hDM+ct9Ku`ZvF_8KpmY!ipk`#3~PovdDEjwmw=m zYhuF5;_TTUs7lxjZS+T`FVfpZU;g>o3KDjzL0&3JlVW++~VS&oT@hhQKUjCM1dBJTt0o{!GkTO3xB_`5EYl)U)^Ipa$-h0(ZPdwG)Xj} zr9fP8qW9jxTHE{xV*Krugy_u^Xx3cr?M;I@%RDs7y%vN~4{zSOMcc&fM5(I?0Hmdz} zJr_HRw6Cw;xV8Mit8J@RuH=RQ*61yJW0#m%o83dlj>}&z5@c*!{8NhsJNH(uYvLKR2j}wz(Sa&W1ihAYeNBiJI{mf5!253f4fr^x{Q~$~qjJN$_wQSX z9QWu^-2dz(YCl+k;;B=`zXwp`hTyd!SC$L`=O^BBMJJzreT(LBhiOH43f?~aUaT9u zfJ5USUrWI^2ruaE?InsZ0-O0KWIPn3q=`gAEgn>b|BQHfGwVk-1>0b;lB z*NZ`PG1p*J$uGq3T&6nPMocQ*4Jv* zR1K^5xxE22c%VKK_S^xWd7K7Q&mPm!2_YyPP{j&>n}2TQOQ=`Y?Ku{yP%=){Bbl_4 z^fjgcs}Fs876(AAlvJ?-c1uB3DFXUv$+OX2wPsC(N)7|^Z&vxDfvLy%O_`;6M^A7C z73rnFUoN$wB?$TQXR{-Ku`|tnjU0<(YiNSOYN?aR<4$Ob4Mkn~r4G)_kmw%+_xU#q zuw{!G^{qLpw7b^2b?X#Qp8SA9{KVm$kpEg(wb&k?uGte zqNbVs;^HC^#n6LDHC-6e?Xc2Txm;{)>NS&qO^OHqGYUOu%HQUO65MPV?O9!Z5Ga#E zOs8ru&N|zY<&^-nAA51LCBsk`So_0+-AA>apHpsP&}R%I6~8r+ys(5@}M9F$XXRm%j3%Y`S*? zBO_z*>|C_xUwu-G__-04I;0sp#*E}e@;jIVjDG}DstE42usBt&3o{I|Mu!9j#zLr4 z1qIZ!wZ*m3>xm}#c=G3$wwuma4}s1;4X)!=JN{|j*>|9%lHx$B3re(flf?90lsv1K z*H)2^Vq;U&rNJvljvmePm~jAzc~eEMvXW*RFcEbSHPmIGx9vRx14(eE;d;qY;mB({ z3p{3Wy1bV>3HXJeF19cWUB(*e(nD}J(NSnZ*RMkswG7pKMyK})tLsdQ3U}aHb1MGq%t^Fz9hX=ww%&)lLr4re|(IC8yt#UcCS7A^!0NMQ5UHz z@^>KI4)_6f!%F$7}iBUx#dd zmbxk-7xJHSxUj<8w{K{asD7Z%}Z^M6QN!!B5!-R+oV{S$%6WgAYZ zs_KGkqayk^NLKjIq*h2WE5-9&KX+|;O8 z7*;Gn*nQ&s#m~8ewVh7rib9(Y{E8=6GojgS;(NrUa2205q%_DZOlUSS zb0@(mcm)L1y_e>?VZ(S;nVDn2v4WAnU$s{W;xMrUrz=`ooI~s!)lSt?*ePbK1*ANsTe<+J6RC1inM9zTnjJ?VFLvNX59n#o9(&2nKVZTbtm9aj+w?%(&!+3qTdKD z``{L4<`kg&RG@=I&7NZrMWjwZm0Ht_V(HWNsoYz(kMYSqKE6|`vJ1I`bmGi?j1)@L zU6SiX78gdnBUF4s^+6Uw4w~B1TB8FbHWE75xm5F%MU+qkkK4&%^+mAd+~36;-k#RVwa+zNM4|=#*(Z?s;k89kJEVX zactXd(2fkFLJgGLqTg-Re#~(_J!aRnm!e;P{Sdu0Z`nUE0Azu?DUgj1Kr@dhQ{lK< z?)i^LAi$}k6Pr0Z+>)x@^x^5zjoJ$xc(x>6Q_mLCZ1hnj1lBbdF3Bq)(eAXmHW>*3 zb+Y8wTqY!ZzKHiws)w(jROF&p{(t)NJs%6^Y8{<_d^pVEIq~qwkt3ONA|PqA;D3YF zsXO_gl5s3e%QNoS@o#ywd4T|;`qEN?9CNO1rvXWxRlbauX7e^nPdwP^YukykE#mlH z>EC?_x9NEX!LBCq5PPykU5G(}9jgB;+ris*i4#1pM= zQjl=mA-U38r>@duX*CKbPpYY_KOCg%xHdLs2b^vQ0LMQ?x)o|LVx(HX5rRsS5KuZj zJ+vI5SLC|=Ifu_rqTC6gFXdCS?htZ{oE3ie#%a0j)$;Q4MAhg__*YV-1QNLN<3lg4 z1|*Jxy_>jxUFA9b^)?DRJ@VTu7$<>}K(T!2J8rt&SDPhv{zHWst{3(8`}E*wA_Nm7 zz%y(KA|t;CTYnqgGu$i$3M7nD!1Ju{C$L}=*y)-c&s5xGGkZGZcxcx>2ADpiBLr3I+5P$-gsF}$maOOr+4WGh#R1KoB zp&RgJ&@!|wUg1$L`lqW_j`al}ECl(uJld53Jkep7JDY+IP(cCm^5|>Ap4eYw0lRm< zbh>3hR4-`GpL4Aqfkr6}7=6q%mQ)TCtg;vgfN1UI)jCK29mF(T(j{|{uHY?l1%bXl zz?ni3xgK?W!7wH&x3SS0iqF)uE>&-|0c%hofiQC}CJ@ZV<=IAZEK8Jwsbk%<3= zy7-qqZoc}F#sR&I_ngZwKlv|f2 za^cgVP}8{mXnc~{9n|FwMnz5vckkZCBuY5J;W=d|MF0Em)my--X`+ey$Y+1~X;9`- zI7nws=p;h;JOnt5J#}f9|D73#-kDnTnW*Mvo^wV029SmV{^R8AbjWB77qaw+&Uc2o zLZ#PEoTQKi%#gIAT~H@ww6Hl~^Ky!sC@phkI}WHsdw3j@kuf1{11M>X92`r}O=Usz z$TVHdh&AcXL;xSgu~PMzSBW zhQ~%yi8=N91teTjbTY!bJpL$W=s!R4^GkmDuQt!cnSuT?&iE3f7Gcj>r_i#gA%pZ; zrl)%@c9rN4w`GY%UUfiaDJR2d$c_?|9qSo+#V3J*Jywwm|As(p!T!im1OQX|_Zv>a zU?GEeHK~3<)ryR$%^V%|ULvC5=-**Dj1iK#!<_S_mU{=w(5^upN+60$gUp-~HUZqo zX?ErpyZbWA*mh!?x-S@3QQL1V(`KN0(!?-a zEW@{pzioPG$jHKyD%S0xDH`!!?=^^p<)EYiWQsEr-JBgoAoQMNFW1bX))=`*Ms#X} zQDxrmKD>+)GVp!{5y(g!l7Xy*G9h#1CjQhS5u&f6Q*EA0)C4qoLO86G>L#HWcRCdQfc z-1AQ%^bz6P!s`hUDX0$L?mViqRaR4bfPo2b{qM#y(CKv%J%fJ3wY9a4kZ#e%ZhP?L zN&3wVJBpmOdLL>4M%^MZmQ|Oq8EJQM$eLhbO>8b5>Y2~c4(7r1W_(T~v#eLi8FTad zMso&aIHKEgDy+WX=~TD3DW)zGg~qmZdoPuO{Pz5nj3sc1dneg03)FcVTrN@QNi2*P zF6&(e^ia-o=umdYz|Z#QHS4q!bTbS@r2l?0KauIldGr>YK&0JKz$16eC&Ie&AfiMj z26p-O*QR(_{rLZyA&0asN=l@fnEPQ6Ny2cEF`Yh#5uW>5KViJXv4M<1yve(?ho4^s zpj#l){wRm{{4;+JSzGGsf&Tt}!ePk7hl3|U%P|+o!bAEux|rTan-C;26{>AheeRc^ zCswDU4{@q{P#8jF(>gIN4UJehS(DQ()#qP+lF>^dy@xgTi&seQZzZp#f4AhVxKTDo)kUdX9Fba>K`8hS%^ z+`$wS%6e6Q+e|dDTpF%qTO1`s$1e5B>6J2BA4bFyg-W6TUy*W1+bPNDCaK5VG^Eo% zf*z#9()B+E7#^7XY?DQ}mBSa2M~@zL{$-E$@u5i1e0yYD7$QU_Rn*nhu`|}d4pgTf z4+iP7F8}zjJCf>*@#F?tJItMyKc9W>lWE(^#FR|N@xb?NOEV=oI3_BKv-a?yeF(`^ zB+_1U)9vdnrwa+pLNmmY+80i^KiB_?{2^$)vBO~*K%G#EQ_<|UEx!!FWteIHynO~R|lyU#gw+Min!6=(e6vszM@4j z6VUmFHQ>xOEuZTg?QmezUAKPy?>nlTLGd?ktgNQv z`T~!)7%iKPM$;@p1#&|iG28yC)Y0!jjnlG-xT;4V7i$+577|9OWa+vpZNcTuB)U;WKO@mb zb6r8pso(nh2hkZ7inI?dpYK12d4r%qIt2|31)_mwQM9YE6?%iUJ-`>!Z8B$joDY0NBK=BnLh8RAeg@R4z z%`B7TLLP>U;y5VMaR4d|sr`}mb(n&ehQb|QKc+_$8`OBw7P9-obq_+$AT1-G4lbrA z^Vx7<-d(#YoNBc(nrQ4^mR~YAv_$$l1S2_ij2`#J#LbJUld;0_Tg+_Wh= zTUuV<3g;c)!LnxcYHI=|p|pPVAGYsj(*sp_F|yRzuZ^ygh*e{yQ}qE!_labh%fIf^ z`?+G5tu`9&Xe(SIq^gdzRP>IZ%a7sfv$3$OUcmQ|bg(;^5$*WEs>BXLoaxMW2rdh}ky)Ja!ge200^gn|WKgwwG7t z%*A5V74%J<-nQ#}EOB$x&M;`f^YNkKiJ@*Y>T7chi9S~04#oJxDbMwMkRxf8Wu>Nz z+3^!6hR`(B1t@tq9C|Bl^?udapt2k?hl{~Rte_SX+QzfJUY7zNK0Hl*+5yZ#D^9e7 zQF_de;dW8cjxX8T0`K1(K3Hj9{TIzAU)FybRqiO@)uu?X0OwCOGNqQp9` z?p45Pl0(FEEJ(%hWsGi2O%*`2zh=Tf4OuQ-K8|03_Ob> zGOOr?Pq0WMIG&&PwUpBset5ljZ3ilkZ;J5P+D}0eX4`1q%a?|JPN1Ev|e9C0d7o@?nw4-N?5o1KKzU zLn3S_OsB2T(Xwe>!VBQpfTbX5U0NFN(E?LzLOme@f|&#L`kKW+T0_@SwG-?MnK(2frjnRl(7H(gB_A1bZG-08BTA$IeZ7vJRd!p6w zQsGF`yio?4ZYt7Qz#uBp4ckD1Q%HwTNDf>Sq-=aqCXO*!u<$*D_kWzp3F9` z<9~?6LE0$w$c2I=-BcIOZrU#^D@#iL`n7BA2Az!#pxmNg=Txf?hU)Y-iN*aIMjw+g z2tytTz08u1ITon}**38pn48w_#MQQvtDQgv3@u+_0}+tU<6A$pvpzB~Y^o(TaxjBE~*m-{LT zQ-7-bTI9(z@W-NW7alzsUwax-?KVXzQwT=Lm1F>tMkDoZiAn$GAp}a-2)&->@8q08 z1-CAp9!5F{_ik#)yM`8RT!*DF`WEZus#{5svcQO8IraoX#g=>H~`+UNjOv@ zbfz1P_1xUth`Elo^V@f>tsLz$+{2&rQR$7#sz_3h0rwO3-()6v4TSnR?i zN0LY2)BLe-HEU&|s*~1lefG*rOAYgRtm1ZZzy`EAv@f(sF2b-$Q$3+BIE3{z_fLAN z)CwIXB|iY$NWH#x&>Hkq`!ol=zTI09vghaLyn9O+ftjY2ef9d16~kB8u&aS`Hq}2* z{Y|F_%2WL3NQvB|eaZ|hEY{SxciRt|T%f+(N``63RL|=>gv4S?Np7=~AP7gjzGwv> zcn4~cORc`n4h{|^Tb;zw{smp#4QtmD=@2QqeTS*X*XCj-X`*7mA{Nxwk3ozE0as|3 z;-Os%f~ktc05FC&_9N0Eq=g*r18hCmH03^&^s7(6hKhRG;qJh_fB>;bHGT*{0+D9x5L}|r zv^s4`Gc(V=?*!>^7tow989CA+`Xi)7(kWXf5=9OjTFjOq=%|I2e`vVjNZ|$OH{{3| zPCSqSW$iSGaoi~WxUe;JXObS!Ww7J@XRB!rePB7~BX=!%hLZ;D*R(cZT1JlMxV7mx zhe6xk)9cE-cC;>`{dkmUH3aD`r}#9{b{s9x{@KOZ$>B6iov5UavqBI@vEM&AMLIgG zM50J`>KpAbBnLlNo+|iLFR9F>SABKG8e#86592bAb~3e~Lh!?()DjmnY49|Z_tNhA z3!;W$zh3ojO1JqlF=d94fZ&?7lif9I^mZ19?6c6;3&5pZ6xok?a$tW!PACef@SNtKSDk zCA0ry<(%&T$c!Vi0_JBGjg5_Q4W3^`!VY0W zZ@MG7O)49`JRzY(2q>5R%&FrCd3;E%KIev6$1`sO_okTd(N$Nc2cK~nVhWpp!`3K^ zbuwU#PnB&Ig+=-5DmlRjN0%0V$zM!!BhOX0CB99nX5~yzO|`DxGt&x~dm{L%zLuuu zHuN&oaEWXPl=46`8Z~d?F;P(sA&A}^Bf3AUm+#VL(4*|pn2a{X^lIkeEaw|`uN9wGptEef-|)qLXFsJ+-b)L0v;HNwrit~D z69S;ANJDCRK;1;S@BljyFKV%-Rc3DbA-yMs@^2PkWZM_p;)_D@Z=+G9tLWufKgP6u zzM6M>#cH~fY^R??Mcubg>G1>ViG8_99qN({b?s2!7{$6=&HZ+Y)k|Ab&W6yg+~0el zIIGq9oj{H759cVkg41M|*`6v$VPYbM^K3PSm)O5tc&k^Sq+_}WN#u5Eb>AtB|Y5_$328K!#$`!Vy?%+?{|6{D=RD0H-6J?h?0)O`Op;e z3op2$T>nGw^br=vu>Phvl-bVEwzYYAdE@Vd8Y)55`q0=20On2bUrC{V3a@!q-Bp)6 zS~gc5VrWY|E+sXU*Pl9+e80>V!t3#qk4$0fDb@e{?&!U^psy4mdcfK5-6ss+oB7ld~tyTD) zq2*66gXO^vwNG!eW7KluBpYXbm)k%jlbEfM(UqTh0;gZy&?|AZhaG};|Fc7d^^cqV z{pT<^_z^>i8_TLUrQhef$)-0sIk`UlAWmT4=?jqUx=ttBghS=MXju5kE{+F`52a-# z`mc}+Q#V`oK+Al6I?|8(Y>&H^j!recQ}VJ+OILRXkFr<4^BNsb&xOyI=$G0B{$Q?$ zOC|V#;l;e*?HBm+!(m6nxt3B0=h>GW+jQN87fFP=3H-9r{|muI!V(Rnl?8IENCY8{vG?BFxX z2L=*ZEgv{g;^-9_a5(y&avCvni!&E>J>BUhx8h7*njIR_b?M-6@A zP|KqzO9|8<`+&i7mI-Rjnw^PoNBe4K%k-&XjJoij3HMN(-191tJEQCZ4hSj#HAzSW zZnQ^Dl?Ut7kbj%oP{+6V17(*8%_*GLRA!%k9WJ3TC_dS&*;f2q`D&hh)&A`8xZGKC z)>Q1vmC$QwOx0}mo__rUW5q+aq041nW$G5w@%vg`pf&EMb9 zze0QO#8WlyBjJtqt@@UpKUd9MHoIu#$iQ5+Kcnm&c6QDq>0@T`uW+j3zKNk0zQy74 z#kxpc%2{8UOE}z}0}{FSAU>BEPX+lhYrZI{@uCqc(GakXU?D|QtVbLCBfq>Z_n;bwF! z!pV2;D%JpE&IKFmk)jc_&y?kg-yxfXmL2H8^G3OS4{H{1NYKqPvHu=sd17gCK~tW` z7otH8&X=4)e_aJ-ru7ucj0T=$XUUp09KZO8vhv?P-UG4=gQ{&3>vJ&~G-W1IfevZ@ z$F{SDxwARg({{s){vp}D>ign%$O(KHT< zXx6+T=Txim%O*3#htr>*2{pd_*7@smw0o;C6wi)iedg`2{VQUG|M#*OX!d;^FMRP? z)pq-rt9GO>6fU>vBTb(|&Tlqq&8J#l&d0EPfLmXw*ar+Cj{kaY;-L#H>wX zwmLB_HPsiVOQ!5GR?M5t5(6|y;y+C{ONA}rtx~j%wDXO?CahB&SMI}$5duO@y(FM6 zGLM%0Q5n)B>HHHi1YFA9rXv$j&;zxgU9-JbRG#Z6dQ&VY|1zzBkft3DO(#5s}NBYJw zF|<8lWd=&?#TV`SX8-=&i4uS3Kz>I~yX-cKFTp(hHNcyz+UEC)hVSu_92g!WVFsr_{YC|su_1Z^@ zTW}jlP zJKd@0Or$ZOox&MZ3X_sdN(vT=#GBylDJv*)m2DiH6UkHGfimg4XX;&m0!uVXLXmCvA53b6P>(z^#4*EnQPPsmbQGTxny3=% z(02SomCXVudAt1T{3q+Q3=R47+o9d7;;k?-ACZ*K9>E-P>x>&}13={-#T9rSpZ%oN z|NG;c+Q^055|_bcWRe3<_Q?!l3gSWn;T@=BeGnT}*;Y_u)X846B?VOJ+v- zfBiX9cm4jH7_ERUUi0HM<9H84y#F%F2QyqmFiwrT5?p}u$kpSbK|_0g(fiXcr_2!e zuPGyY;uAurQejstdhjWn!aMQE56Jck_W!xp!N!@5sgd+*wJ*w-8< zrx)dyQxtucZ=i5X5NQ8~GoTV>iy4!x$C{g) z>(>P?qa?^-fHBAq=~0;YBG}c=f8`*kq}uNxG3*nM-*4?}d6JF+qJ;}EQq+8jTl^vi z2gdP+9|^)d^~yZlhQ#+?bd;|lXbAJholGLuS`(-?cQtSm72M=o>z@W$4jjYsM~NO) z*VPTBs=T%Kw|@p72D05g@WfGtlvd@%MGR@d{V9bS;6WtSBHPtys!9jr4|u=dDZo*+ z%2hjzwsg<7<|DyvKR#zf*(FO>Wvle~(C$nO54mDQL_?lOpGW>R4i;8nLWiSrDyN>l#K~BYILBhPe0g*z}np0ry_pz<-BW3F@XW zDK|p4or3}&-KMw0ILdvz-$-f`bc$wmicck3!uS39IeQidycfA_OD9{CQ&JSzlFoi} z9+4cYSkdRpmpRsa|kuZPJkJxFnRe^zR znVMKG;RO)(uQ!TSf&o3CuBUvsF@l?icj8n^Z^equS|%bsojMVg7zed~v-@@>=G`^r zalHrRde2ibGVZ5kl~FUxX0273Iw*a5f2O;p%64Gp(-|GzeJE09LI7Dl=bpKa_tLP~ z?{9BY;?4%rr;A?(>0}Z*r!)eEgxDwICEg+s^A=MdJmh3o$?Vpe757H-+Dz_o`iuXE zL+hbfUN$q?`bU^+Uu58v_xFR+p5!06PBCo)2g&JiY|Qr*e_^HO`enP4Sa+|agjWJE z^kGic47)p-=P=^n1uFOBP6!Gjl*p zqRgD%h1q4F_rF1FN#4;9=@0I^Ix~UMfhw`G@%ye|O2jrSC*j5yaxh6JShn!Z+r%ux!&K~J2Jt575AR?Ip(?85iw$JeC4cJg5gJX_ zxbrK9j$=L&50jm1Z-~vzSb*$M())1RdpIe797^60w`tvvBkR!{@=y?r>m$TvH{oAD;FI_Ku(DDjB51V-`+7vru!Ht zy=5tC^<=2t!WuCt;PCAYQm9pHgMjsey~?HRfGy@71@`t8%hm`F&Ml`5Lp+dU##r=e zR*$il&rmVXxq0`k(r@e-euk3;0`|R?M4bwsbfm;QkvOuJVv(4bxQ{TSeFw8xH}5&+ zTgWa87BKxWz>5Jy4TOHVk3&Mmav#AzcvAr{=x6Jm(S+RpPHAM;fc)`i7WDeYdh=6) zHX!O^{FI)E3#dqqSH54AovvP!^WP~OQqG9di9;7>!MW8O5KLgu z%D0E`%i0PMTw-6F4>o_)wyMeeE@0HCqF??1$5ww^YRgLl26W0 zDSN*o$!M9VYh5V6~zy|)Oi0RcZH6Kk{7&TOScKw z^;97GcDbPR_-u7(w+Q9QIxqSw@3RzOmOWlOmGW2n6b>2KE#Vvr{WIERX{HgR;}o>12_pUJHYCAu0$de86JPPuS{ ztXC8HA?>&Co$N2X4L~^T_+J}hdpEMkP1@G{F^f4v8_LmKUqe<3ccl-8`Jj`>%=K~?J4!fg6YlT-NFv64yAd`Z1DEn_7 zf*4ohZ7WINxak+Ke5;EB7G^qRCP$t@f;@wN2Tm#|20VG*+M;`* zuH6Ikt3r@t#~=C`3O8>zyg*OH2=Y!u0nu@Y}EP@k>ify9Z&o2A!Tzs$#p4sQP~B?RQ9b zg+N|xzY@s`g0>h{u1&i!lKbCHy#4eLi9up8A!0Cl42*gYt+kN1I=)>B$QC?U?wmOC zD>HJ(4jk9@#i@{Ze~sUM$IdEvdU`rR#W3-jv8aIxN4!TVg1kaBmAT#Oc!-CaG`~SE zH&ShgI3wlkb!0ffcl5D|NyxgQFWB+kjhFv%E%&+LfHzPI68eQcIRg&er~|XVd~;Ms z_}qcNAWk@y$9D6`Q|4{U~*uX=zNFh(HiHSxcP# zF%GTm!Mi~7ngx98arHem!}i$pXQ1R{YnYKe&%Pt#EkR;_v2P3%(Cw1U|Ib z_q7#r>rw%$p#xN6Y!#~7)@r((J)^_Vz*b0`COI`#5n$A}o`lT(+`xQ-j zGHQqt1wbB-Jhlg5(1*?qhe9>wKkZ4-+WNpdp@iKOZ*gs=r|eos9^ef|cOdIzYw)jo z6YZcM;C7&JnVBO%!>*(Cchqd`pht&Ls)xpmT2@1I7G~9ZC%!F5=0iYqT zpT>G>c}`kKTOTMIDTz_*KM|K$rXoXI3ZibHbEm86g zRn7nXRyF9jn?1x4BD+P5NC&Sxd$Tch9L^TRi3k2xfwmJXX-7hAba>Q#R9!Aa?_LBr zvz!EZFcnf$QIAwBfn-{Hy5r!l={1StNQ3c62giv^u@3$}?VbBmlw}mh-*uW{YD&dW zQQ06!XS0M@p>`1iQ-ocWkRr6egjK|ZXc5hbR=cl)f_X`h9I|pE6Iuq1@kZ%_L|#BQ z1u}1IYJ`oD5-PcU&TIcgy~E7DKfL$ndCvKs@AsSo1YBg_xp%FunXx-*P5 z+&4dZ;f6DpV?WeXl>2nf@K+M_aHLz*=oWF&L0k<0sISpeGtD#Hc{pU6R)LeOD(b&!_WF%&^kiZyToBBpB66ab^ge7>3ew z5(jmE=zxr~Ptke9cVd2?7&LrKoG=^nE5MXh^>=R7QMF`&46byS*c~DM)OZfBr+&gs z0C&SVN$HCcbQ~m-D0sg?o#vm$73JU5giN&(#B7&>)fnjlhbanB-{Ux6=|%g&bjTfh)9 z8J#%HQS4W`TjCn)3Ko&Rl9JaNV`X%vs`8OivXJ8g&Jc^Kqa`@5Qy9u#L}@kt zLGRfNbiMcv18=u`e1d>uIVr5>ZszpoYYqYmC##D3ec&n z*nvZ^_?sfkv&V1Uuo0N-9I>F>TLOsBkTomaFimoFbvSfg-Sy+s+`oSOG9tp)zDLZc zS6vTEUUxbLG1HkexoSL>OdN&s9vocX-=hk~hS5J7UMK@(d!x9_7UpcG&PVbjQiPcs zwKJ=|rmnYRk-KqJ&h^>@W76+=ER_P=HFTdIAH4V4{4G~^D~eaE{EvVGD#$V@2}4-B z(eIMP7$vQ1oKnaN74kz{(yf6}{nz>^v-PL#?utuyhp=dPm%qfv_X8>fZrTOVQG=3CvYf+Wx1XFXaJ-zhTb zk8}U;GAc3a1Qh``cs(9|@7joX{nW2|j-xjBwI8z$V=cWlX@cVOwmia+@bGXe2;xvu za_UgjwmF@Kn6#p?QKA+V2JhCz8_vH+_nzEktMJMkucFz1T(T$-YZPUrP1+R2L> zht#=y8KaLE@v$V{>_C{12%s48{IoV%DbdK0nIZ>4ENAz-{XKT{6PI=W#?~(j7%shN zJ}PZfy|35I1NRNUj3(p>CO8dR3#Coo-H|tM*PkLA)z_FiuYB=QqXUE{T2jFS9A9!+ z=nODzta{EHJr}pyc6r-BS=#YvJjrbaG;`@u!4yIJ1r8GR}%-)^zrO@Y>b7vRqeK>F{(vh{xai|dmbJ&|*j@V8E!!GPYX)-Vi zFfi{lm+NksJg5P$T;DS?=mBq!F|coSPC<~$hg^E9egu;_UVE>SIY3r(vVebp|1xnF z-BfK59X^&A8;BqO%0Mp31=N_roVR{|i4xE~uC>aFG23|e-0i5r0O-LI)I^e~;h;N^ zYpw1h&VuEe<+u27Xg9^H6Tm^`5kr)gB3U4&S!69n5?yx_U^&H*ks_Mb85iatQ{5@# zl%%A%OC}F=oVZVEw)53=CQ68M)#!_Sk4;Zzzo_!_&M{)b{H#uI{$icT;i6tal?RBB zlhA{xH55-&EX#~$0+N9er(JpD3Km$Lr?q90R|8zVpSmdTRhkqzYpLj(+@hl+M@+Iz zF-LPE8m5Je5@8se8ZW=Woo<)DZTU(IH5|Rx32#dO7{WL`_id>ue59^hoEW$ z30YWNYh+FL61K==g9}`(%8DYfHU*~a?Cit1m5d{0Lxs+z;oGCKPsX1~J1%+GS=;S4 z{Gf%ET1?(RBds@8Luk<`MnX*`vqF$}_NP)B)Ih$wn{u=h)9KNo)^nkD61q6%4r zpJrdTol%q(+BTg?tB6VyIq{S&YWbahQ6k)s-6n0dDGQz1ad6;-DKr>7Y4hAH{nbye3#E3oT$47485m#yXb=msJxn+wI8QlGZlz z#(uu`N^*6p{#0c;q^&U?@;t*N?SrlKWFXx_*xE#rVk?z-t0(~{n2BujNUz~%#dH}3 x<*sYvSC6dAPxLnYJzG=Q|0n+^K3J{CDoB{{S%%+d%*T literal 0 HcmV?d00001 diff --git a/src/components/Cards/AnnouncementCard.tsx b/src/components/Cards/AnnouncementCard.tsx index 150216f6..9d8c6639 100644 --- a/src/components/Cards/AnnouncementCard.tsx +++ b/src/components/Cards/AnnouncementCard.tsx @@ -82,6 +82,7 @@ const AnnouncementCard: React.FC = ({ data }) => { {/* @ts-expect-error cannot verify that title is a valid key */} @@ -91,6 +92,7 @@ const AnnouncementCard: React.FC = ({ data }) => { diff --git a/src/components/Cards/BaseCard.tsx b/src/components/Cards/BaseCard.tsx index 14095fd4..d90e9075 100644 --- a/src/components/Cards/BaseCard.tsx +++ b/src/components/Cards/BaseCard.tsx @@ -1,11 +1,10 @@ // BaseCard Component to show the card on the dashboard to navigate to the corresponding page +import ContextMenu from '@/components/Flow/ContextMenu' import { USER_GUEST } from '@/data/constants' -import { type MaterialIcon } from '@/types/material-icons' import { type RelativePathString, router } from 'expo-router' import React, { useContext } from 'react' import { useTranslation } from 'react-i18next' import { Platform, Pressable, Text, View } from 'react-native' -import ContextMenu from 'react-native-context-menu-view' import { createStyleSheet, useStyles } from 'react-native-unistyles' import PlatformIcon from '../Universal/Icon' @@ -98,10 +97,16 @@ const BaseCard: React.FC = ({ android={{ name: cardIcons[ dynamicTitle as keyof typeof cardIcons - ]?.android as MaterialIcon, + ]?.android, size: 24, variant: 'outlined', }} + web={{ + name: cardIcons[ + dynamicTitle as keyof typeof cardIcons + ]?.web, + size: 24, + }} /> {/* @ts-expect-error cannot verify that title is a valid key */} @@ -117,6 +122,10 @@ const BaseCard: React.FC = ({ name: 'chevron_right', size: 26, }} + web={{ + name: 'ChevronRight', + size: 24, + }} style={styles.labelColor} /> )} diff --git a/src/components/Cards/LinkCard.tsx b/src/components/Cards/LinkCard.tsx index 1ebdcd18..6be39bad 100644 --- a/src/components/Cards/LinkCard.tsx +++ b/src/components/Cards/LinkCard.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next' import { Linking, Platform, Pressable, Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -import PlatformIcon from '../Universal/Icon' +import PlatformIcon, { type LucideIcon } from '../Universal/Icon' import BaseCard from './BaseCard' const LinkCard = (): JSX.Element => { @@ -55,6 +55,10 @@ const LinkCard = (): JSX.Element => { size: 21, variant: 'outlined', }} + web={{ + name: link.icon.web as LucideIcon, + size: 21, + }} /> { useContext(UserKindContext) const { toggleUserKind } = useContext(UserKindContext) const { resetOrder } = useContext(DashboardContext) - const username = userKind === USER_EMPLOYEE && getItem('username') + const username = userKind === USER_EMPLOYEE && loadSecure('username') const [showLoadingIndicator, setShowLoadingIndicator] = useState(false) const [initials, setInitials] = useState('') - const androidPadding = Platform.OS === 'android' ? 16 : 0 const { data: persData, @@ -135,6 +134,10 @@ export const IndexHeaderRight = (): JSX.Element => { name: 'account_circle', size: 26, }} + web={{ + name: 'CircleUser', + size: 24, + }} style={styles.icon} /> ) : userKind === USER_STUDENT && @@ -149,6 +152,10 @@ export const IndexHeaderRight = (): JSX.Element => { name: 'account_circle_off', size: 26, }} + web={{ + name: 'UserX', + size: 24, + }} style={styles.icon} /> ) : initials !== '' || !showLoadingIndicator ? ( @@ -266,9 +273,7 @@ export const IndexHeaderRight = (): JSX.Element => { delayLongPress={300} onLongPress={() => {}} accessibilityLabel={t('navigation.settings')} - style={{ - paddingRight: androidPadding, - }} + style={styles.element} > {MemoIcon} @@ -280,6 +285,9 @@ const stylesheet = createStyleSheet((theme) => ({ alignItems: 'center', justifyContent: 'center', }, + element: { + marginEnd: Platform.OS !== 'ios' ? 14 : 0, + }, icon: { color: theme.colors.text, }, diff --git a/src/components/Error/CrashView.tsx b/src/components/Error/CrashView.tsx index 6abb1669..29280932 100644 --- a/src/components/Error/CrashView.tsx +++ b/src/components/Error/CrashView.tsx @@ -60,6 +60,10 @@ export default function CrashView({ name: 'error', size: 80, }} + web={{ + name: 'ServerCrash', + size: 80, + }} /> {t('error.crash.title')} diff --git a/src/components/Error/ErrorView.tsx b/src/components/Error/ErrorView.tsx index a955c192..5d443171 100644 --- a/src/components/Error/ErrorView.tsx +++ b/src/components/Error/ErrorView.tsx @@ -14,7 +14,7 @@ import { } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -import PlatformIcon from '../Universal/Icon' +import PlatformIcon, { type LucideIcon } from '../Universal/Icon' import StatusBox from './ActionBox' export default function ErrorView({ @@ -31,7 +31,12 @@ export default function ErrorView({ }: { title: string message?: string - icon?: { ios: string; android: string; multiColor?: boolean } + icon?: { + ios: string + android: string + web: LucideIcon + multiColor?: boolean + } buttonText?: string onButtonPress?: () => void onRefresh?: () => any @@ -45,25 +50,34 @@ export default function ErrorView({ const path = usePathname() const getIcon = (): MaterialIcon | any => { const ios = Platform.OS === 'ios' + const android = Platform.OS === 'android' switch (title) { case networkError: - return ios ? 'wifi.slash' : 'wifi_off' + return ios ? 'wifi.slash' : android ? 'wifi_off' : 'WifiOff' case guestError: return ios ? 'person.crop.circle.badge.questionmark' - : 'person_cancel' + : android + ? 'person_cancel' + : 'UserRoundX' case permissionError: return ios ? 'person.crop.circle.badge.exclamationmark' - : 'person_slash' + : android + ? 'person_slash' + : 'UserRoundX' default: return icon !== undefined ? ios ? icon.ios - : icon.android + : android + ? icon.android + : icon.web : ios ? 'exclamationmark.triangle.fill' - : 'error' + : android + ? 'error' + : 'TriangleAlert' } } @@ -186,6 +200,10 @@ export default function ErrorView({ name: getIcon(), size: 64, }} + web={{ + name: getIcon(), + size: 64, + }} /> {getTitle().slice(0, 150)} diff --git a/src/components/Events/ClEventsPage.tsx b/src/components/Events/ClEventsPage.tsx index f0867495..2ad2930c 100644 --- a/src/components/Events/ClEventsPage.tsx +++ b/src/components/Events/ClEventsPage.tsx @@ -91,6 +91,7 @@ export default function ClEventsPage({ icon={{ ios: 'calendar.badge.clock', android: 'calendar_clock', + web: 'CalendarClock', }} inModal isCritical={false} diff --git a/src/components/Events/ClSportsPage.tsx b/src/components/Events/ClSportsPage.tsx index 3e30c2e5..f1effe04 100644 --- a/src/components/Events/ClSportsPage.tsx +++ b/src/components/Events/ClSportsPage.tsx @@ -128,6 +128,10 @@ export default function ClSportsPage({ name: collapsed ? 'expand_more' : 'expand_less', size: 20, }} + web={{ + name: collapsed ? 'ChevronDown' : 'ChevronUp', + size: 20, + }} style={styles.toggleIcon} /> @@ -241,6 +245,7 @@ export default function ClSportsPage({ icon={{ ios: 'sportscourt', android: 'sports_gymnastics', + web: 'Dumbbell', }} message={t( 'pages.clEvents.sports.noEvents.subtitle' diff --git a/src/components/Exclusive/DragView.web.tsx b/src/components/Exclusive/DragView.web.tsx new file mode 100644 index 00000000..9df4b6f7 --- /dev/null +++ b/src/components/Exclusive/DragView.web.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +const DragDropView = ({ + children, +}: { + children: React.ReactNode +}): React.ReactElement => { + return React.createElement(React.Fragment, null, children) +} + +export default DragDropView diff --git a/src/components/Flow/ContextMenu.tsx b/src/components/Flow/ContextMenu.tsx new file mode 100644 index 00000000..16703116 --- /dev/null +++ b/src/components/Flow/ContextMenu.tsx @@ -0,0 +1,3 @@ +import ContextMenu from 'react-native-context-menu-view' + +export default ContextMenu diff --git a/src/components/Flow/ContextMenu.web.tsx b/src/components/Flow/ContextMenu.web.tsx new file mode 100644 index 00000000..31a5907b --- /dev/null +++ b/src/components/Flow/ContextMenu.web.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +export default function ContextMenu({ + children, +}: { + children: JSX.Element[] +}): JSX.Element { + // TODO hook right click and show actions there + return <>{children} +} diff --git a/src/components/Flow/Login.tsx b/src/components/Flow/Login.tsx new file mode 100644 index 00000000..be6bb96f --- /dev/null +++ b/src/components/Flow/Login.tsx @@ -0,0 +1,123 @@ +import LoginForm from '@/components/Universal/LoginForm' +import { PRIVACY_URL } from '@/data/constants' +import { router, useLocalSearchParams } from 'expo-router' +import React, { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + Dimensions, + Keyboard, + KeyboardAvoidingView, + Linking, + Platform, + Pressable, + Text, + TouchableWithoutFeedback, + View, +} from 'react-native' +import { useSafeAreaInsets } from 'react-native-safe-area-context' +import { createStyleSheet, useStyles } from 'react-native-unistyles' + +import LoginAnimatedText from './LoginAnimatedText' + +const useIsFloatingKeyboard = (): boolean => { + const windowWidth = Dimensions.get('window').width + const [floating, setFloating] = useState(false) + + useEffect(() => { + const onKeyboardWillChangeFrame = (event: any): void => { + setFloating(event.endCoordinates.width !== windowWidth) + } + + Keyboard.addListener( + 'keyboardWillChangeFrame', + onKeyboardWillChangeFrame + ) + return () => { + Keyboard.removeAllListeners('keyboardWillChangeFrame') + } + }, [windowWidth]) + + return floating +} + +export default function Login(): JSX.Element { + const { styles } = useStyles(stylesheet) + const floatingKeyboard = useIsFloatingKeyboard() + const { t } = useTranslation('flow') + + const { fromOnboarding } = useLocalSearchParams<{ + fromOnboarding: string + }>() + + const navigateHome = (): void => { + if (fromOnboarding === 'true') { + router.dismissAll() + router.replace('/') + return + } + router.dismissAll() + if (Platform.OS === 'web') { + router.replace('/') + } + } + + const insets = useSafeAreaInsets() + + return ( + <> + + + + + + + + + + { + void Linking.openURL(PRIVACY_URL) + }} + > + + {t('onboarding.links.privacy')} + + + + + + + ) +} + +const stylesheet = createStyleSheet((theme) => ({ + container: { + alignSelf: 'center', + flex: 1, + width: '90%', + }, + + keyboardContainer: { + flex: 1, + justifyContent: 'space-evenly', + }, + linkContainer: { + alignItems: 'center', + alignSelf: 'center', + bottom: 70, + gap: 6, + position: 'absolute', + }, + privacyLink: { + color: theme.colors.labelColor, + fontSize: 14, + textAlign: 'center', + }, +})) diff --git a/src/components/Flow/Login.web.tsx b/src/components/Flow/Login.web.tsx new file mode 100644 index 00000000..f08b8991 --- /dev/null +++ b/src/components/Flow/Login.web.tsx @@ -0,0 +1,177 @@ +import WhatsNewBox from '@/components/Flow/WhatsnewBox' +import LoginForm from '@/components/Universal/LoginForm' +import { IMPRINT_URL, PRIVACY_URL } from '@/data/constants' +import { type OnboardingCardData } from '@/types/data' +import { router, useLocalSearchParams } from 'expo-router' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { Linking, Platform, Text, View } from 'react-native' +import { ScrollView } from 'react-native-gesture-handler' +import { createStyleSheet, useStyles } from 'react-native-unistyles' + +import LoginAnimatedText from './LoginAnimatedText' + +export default function Login(): JSX.Element { + const { styles } = useStyles(stylesheet) + const { t } = useTranslation('flow') + const { fromOnboarding } = useLocalSearchParams<{ + fromOnboarding: string + }>() + + const navigateHome = (): void => { + if (fromOnboarding === 'true') { + router.dismissAll() + router.replace('/') + return + } + router.dismissAll() + if (Platform.OS === 'web') { + router.replace('/') + } + } + + const data: OnboardingCardData[] = [ + { + title: t('onboarding.cards.title1'), + description: t('onboarding.cards.description1'), + icon: { + ios: 'square.stack.3d.up', + android: 'hub', + web: 'Layers', + }, + }, + { + title: t('onboarding.cards.title2'), + description: t('onboarding.cards.description2'), + icon: { + ios: 'person.2.gobackward', + android: 'volunteer_activism', + web: 'Users', + }, + }, + { + title: t('onboarding.cards.title4'), + description: t('onboarding.cards.description4'), + icon: { + ios: 'person.3.fill', + android: 'smartphone', + web: 'Download', + }, + }, + { + title: t('onboarding.cards.title3'), + description: t('onboarding.cards.description3'), + icon: { + ios: 'lock.app.dashed', + android: 'encrypted', + web: 'GlobeLock', + }, + }, + ] + + return ( + <> + + + + + + + {t('onboarding.links.agree1')} + + + { + void Linking.openURL(PRIVACY_URL) + }} + > + {t('onboarding.links.privacy')} + + + {t('onboarding.links.agree2')} + + + + + {data.map((item, index) => ( + + ))} + + + + { + void Linking.openURL(PRIVACY_URL) + }} + > + {t('onboarding.links.faq')} + + { + void Linking.openURL(IMPRINT_URL) + }} + > + {t('onboarding.links.imprint')} + + + + + + ) +} + +const stylesheet = createStyleSheet((theme) => ({ + container: { + alignSelf: 'center', + flex: 1, + paddingTop: 20, + width: '90%', + }, + faqContainer: { + alignItems: 'center', + alignSelf: 'center', + gap: 14, + paddingBottom: theme.margins.bottomSafeArea, + }, + + infoContainer: { + alignItems: 'center', + alignSelf: 'center', + backgroundColor: theme.colors.card, + borderRadius: theme.radius.md, + gap: 10, + padding: 14, + width: '80%', + }, + innerContainer: { + flexDirection: 'column', + gap: 40, + paddingTop: 30, + }, + linkContainer: { + alignItems: 'center', + alignSelf: 'center', + }, + privacyContainer: { + flexDirection: 'row', + }, + privacyLink: { + color: theme.colors.labelColor, + fontSize: 14, + textAlign: 'center', + }, + privacyLinkButton: { + color: theme.colors.text, + fontSize: 14, + frontWeight: '800', + textAlign: 'center', + }, +})) diff --git a/src/components/Flow/LoginAnimatedText.tsx b/src/components/Flow/LoginAnimatedText.tsx new file mode 100644 index 00000000..e2e52a31 --- /dev/null +++ b/src/components/Flow/LoginAnimatedText.tsx @@ -0,0 +1,129 @@ +import { selectionAsync } from 'expo-haptics' +import React, { useEffect, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { Platform, Text, TouchableWithoutFeedback, View } from 'react-native' +import Animated, { + runOnJS, + useAnimatedStyle, + useSharedValue, + withTiming, +} from 'react-native-reanimated' +import { createStyleSheet, useStyles } from 'react-native-unistyles' + +function shuffleArray(array: string[]): string[] { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + ;[array[i], array[j]] = [array[j], array[i]] // Swap elements + } + return array +} + +const textsEN = shuffleArray([ + 'to view your grades', + 'to search for free rooms', + 'to get map suggestions', + 'to book a library seat', + 'to view your timetable', + 'to see your exam schedule', + 'to search all lectureres', + 'to view your lectureres', + 'to view the latest THI news', + 'to check your printer balance', +]) + +const textsDE = shuffleArray([ + 'um deine Noten zu sehen', + 'um freie Räume zu suchen', + 'um Karten Vorschläge zu erhalten', + 'um einen Bibliotheksplatz zu buchen', + 'um deinen Stundenplan zu sehen', + 'um deinen Prüfungsplan zu sehen', + 'um alle Dozenten zu suchen', + 'um deine Dozenten zu sehen', + 'um die THI-News anzuzeigen', + 'um dein Drucker Guthaben zu prüfen', +]) +const shouldVibrate = Platform.OS === 'ios' + +function LoginAnimatedText(): JSX.Element { + const { styles } = useStyles(stylesheet) + const { t, i18n } = useTranslation('flow') + const [currentTextIndex, setCurrentTextIndex] = useState(0) + const currentTextIndexRef = useRef(currentTextIndex) + const textOpacity = useSharedValue(1) + const textTranslateY = useSharedValue(0) + const texts3 = i18n.language === 'de' ? textsDE : textsEN + + useEffect(() => { + currentTextIndexRef.current = currentTextIndex + }, [currentTextIndex]) + + const goToNextText = (): void => { + textOpacity.value = withTiming(0, { duration: 300 }, () => { + const nextIndex = (currentTextIndex + 1) % texts3.length + runOnJS(setCurrentTextIndex)(nextIndex) + + textTranslateY.value = 5 + textOpacity.value = withTiming(1, { duration: 300 }) + textTranslateY.value = withTiming(0, { duration: 600 }) + }) + } + + useEffect(() => { + const interval = setInterval(goToNextText, 3500) + + return () => { + clearInterval(interval) + } + }, [currentTextIndex, texts3, textOpacity, textTranslateY]) + + const animatedStyle = useAnimatedStyle(() => { + return { + opacity: textOpacity.value, + transform: [{ translateY: textTranslateY.value }], + } + }) + return ( + + {t('login.title1')} + { + goToNextText() + if (shouldVibrate) { + void selectionAsync() + } + }} + > + + + {texts3[currentTextIndex]} + + + + + ) +} + +export default LoginAnimatedText + +const stylesheet = createStyleSheet((theme) => ({ + header1: { + color: theme.colors.text, + fontSize: 42, + fontWeight: 'bold', + textAlign: 'left', + }, + + header3: { + color: theme.colors.labelColor, + fontSize: 26, + fontWeight: '400', + marginTop: 10, + minHeight: 30, + textAlign: 'left', + }, +})) diff --git a/src/components/Flow/WhatsnewBox.tsx b/src/components/Flow/WhatsnewBox.tsx index 6e4134aa..221824c2 100644 --- a/src/components/Flow/WhatsnewBox.tsx +++ b/src/components/Flow/WhatsnewBox.tsx @@ -3,12 +3,12 @@ import React, { type FC } from 'react' import { Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' -import PlatformIcon from '../Universal/Icon' +import PlatformIcon, { type LucideIcon } from '../Universal/Icon' interface WhatsNewBoxProps { title: string description: string - icon: { ios: string; android: string } + icon: { ios: string; android: MaterialIcon; web: LucideIcon } } /** @@ -24,17 +24,24 @@ const WhatsNewBox: FC = ({ title, description, icon }) => { const { styles } = useStyles(stylesheet) return ( - + + + + {title} @@ -68,8 +75,12 @@ const stylesheet = createStyleSheet((theme) => ({ fontSize: 14.5, textAlign: 'left', }, + iconContainer: { + flexShrink: 0, + }, textContainer: { flexDirection: 'column', + flexShrink: 1, paddingRight: 40, }, title: { diff --git a/src/components/Food/AllergensBanner.tsx b/src/components/Food/AllergensBanner.tsx index 9e546d24..b48705cd 100644 --- a/src/components/Food/AllergensBanner.tsx +++ b/src/components/Food/AllergensBanner.tsx @@ -46,6 +46,10 @@ export const AllergensBanner = ({ name: 'close', size: 20, }} + web={{ + name: 'X', + size: 20, + }} style={styles.contrastColor} /> diff --git a/src/components/Food/FoodLanguageSection.tsx b/src/components/Food/FoodLanguageSection.tsx index d7236085..23357e96 100644 --- a/src/components/Food/FoodLanguageSection.tsx +++ b/src/components/Food/FoodLanguageSection.tsx @@ -57,6 +57,10 @@ const MultiSectionRadio: React.FC = ({ name: 'check', size: 18, }} + web={{ + name: 'Check', + size: 18, + }} /> ) : ( <> diff --git a/src/components/Food/FoodScreen.tsx b/src/components/Food/FoodScreen.tsx index bc19e9ff..77cfbca9 100644 --- a/src/components/Food/FoodScreen.tsx +++ b/src/components/Food/FoodScreen.tsx @@ -1,6 +1,7 @@ import ErrorView from '@/components/Error/ErrorView' import { MealDay } from '@/components/Food' import { AllergensBanner } from '@/components/Food/AllergensBanner' +import PagerView from '@/components/Layout/PagerView' import LoadingIndicator from '@/components/Universal/LoadingIndicator' import { useRefreshByUser } from '@/hooks' import { useFoodFilterStore } from '@/hooks/useFoodFilterStore' @@ -22,7 +23,6 @@ import { Text, View, } from 'react-native' -import PagerView from 'react-native-pager-view' import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context' import { createStyleSheet, useStyles } from 'react-native-unistyles' diff --git a/src/components/Food/HeaderRight.tsx b/src/components/Food/HeaderRight.tsx index 2474229c..d8b940b9 100644 --- a/src/components/Food/HeaderRight.tsx +++ b/src/components/Food/HeaderRight.tsx @@ -28,6 +28,10 @@ export const FoodHeaderRight = (): JSX.Element => { name: 'filter_list', size: 24, }} + web={{ + name: 'ListFilter', + size: 24, + }} style={styles.icon} /> @@ -37,7 +41,7 @@ export const FoodHeaderRight = (): JSX.Element => { const stylesheet = createStyleSheet((theme) => ({ headerButton: { - marginHorizontal: Platform.OS === 'android' ? 14 : 0, + marginHorizontal: Platform.OS !== 'ios' ? 14 : 0, }, icon: { color: theme.colors.text, diff --git a/src/components/Food/MealDay.tsx b/src/components/Food/MealDay.tsx index c729ac87..23964ac7 100644 --- a/src/components/Food/MealDay.tsx +++ b/src/components/Food/MealDay.tsx @@ -75,6 +75,10 @@ const MealCategory = ({ name: collapsed ? 'expand_more' : 'expand_less', size: 20, }} + web={{ + name: collapsed ? 'ChevronDown' : 'ChevronUp', + size: 20, + }} style={styles.toggleIcon} /> diff --git a/src/components/Food/MealEntry.tsx b/src/components/Food/MealEntry.tsx index ea5e6cf1..c01f198e 100644 --- a/src/components/Food/MealEntry.tsx +++ b/src/components/Food/MealEntry.tsx @@ -1,5 +1,6 @@ // @ts-expect-error - no types available import DragDropView from '@/components/Exclusive/DragView' +import ContextMenu from '@/components/Flow/ContextMenu' import PlatformIcon from '@/components/Universal/Icon' import { UserKindContext } from '@/components/contexts' import { type UserKindContextType } from '@/contexts/userKind' @@ -25,7 +26,6 @@ import { router } from 'expo-router' import React, { useContext, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { Platform, Pressable, Text, View } from 'react-native' -import ContextMenu from 'react-native-context-menu-view' import { createStyleSheet, useStyles } from 'react-native-unistyles' /** @@ -84,6 +84,7 @@ export const MealEntry = ({ ? 'exclamationmark.triangle' : 'info.circle' const androidName = hasUserAllergens ? 'warning' : 'info' + const webName = hasUserAllergens ? 'TriangleAlert' : 'Info' const textContent = hasUserAllergens ? userAllergens : t('empty.noAllergens') @@ -231,6 +232,10 @@ export const MealEntry = ({ size: 16, variant: 'outlined', }} + web={{ + name: webName, + size: 16, + }} style={styles.icon} /> { + const page = e.nativeEvent.position + setSelectedData(page) + trackEvent('Route', { + path: 'calendar/' + pages[page], + }) +}} +scrollEnabled +overdrag +*/ +import { + type Ref, + forwardRef, + useEffect, + useImperativeHandle, + useState, +} from 'react' + +function TabLayout( + { + initialPage, + onPageSelected, + children, + }: { + initialPage: number + onPageSelected: (e: any) => void + scrollEnabled: boolean + overdrag: boolean + children: JSX.Element[] + }, + ref: Ref<{ setPage: (i: number) => void }> +): JSX.Element { + const [page, setPage] = useState(initialPage) + + useImperativeHandle(ref, () => { + return { + setPage, + } + }, []) + + useEffect(() => { + onPageSelected({ nativeEvent: { page } }) + }, [page]) + + return children[page] +} + +export default forwardRef(TabLayout) diff --git a/src/components/Layout/Tabbar.tsx b/src/components/Layout/Tabbar.tsx index f80dd096..5776960a 100644 --- a/src/components/Layout/Tabbar.tsx +++ b/src/components/Layout/Tabbar.tsx @@ -2,10 +2,13 @@ import Color from 'color' import React from 'react' import { useTranslation } from 'react-i18next' import { Platform } from 'react-native' +import { useBottomTabBarHeight as _useBottomTabBarHeight } from 'react-native-bottom-tabs' import { UnistylesRuntime, useStyles } from 'react-native-unistyles' import { Tabs } from './NativeBottomTabs' +export const useBottomTabBarHeight = _useBottomTabBarHeight + export default function TabLayout(): JSX.Element { const { theme } = useStyles() const { t } = useTranslation('navigation') diff --git a/src/components/Layout/Tabbar.web.tsx b/src/components/Layout/Tabbar.web.tsx new file mode 100644 index 00000000..a3cbc23a --- /dev/null +++ b/src/components/Layout/Tabbar.web.tsx @@ -0,0 +1,157 @@ +import PlatformIcon from '@/components/Universal/Icon' +import { Tabs } from 'expo-router' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { Platform } from 'react-native' +import { createStyleSheet, useStyles } from 'react-native-unistyles' + +export function useBottomTabBarHeight(): number { + return 60 +} + +const DefaultTabs = (): JSX.Element => { + const { styles, theme: styleTheme } = useStyles(stylesheet) + const { t } = useTranslation('navigation') + + return ( + <> + + ( + + ), + + tabBarStyle: styles.tabbarStyle(false), + }} + /> + + ( + + ), + tabBarStyle: styles.tabbarStyle(false), + }} + /> + + ( + + ), + tabBarStyle: styles.tabbarStyle(false), + }} + /> + + ( + + ), + + tabBarStyle: styles.tabbarStyle(false), + }} + /> + + + ) +} + +const stylesheet = createStyleSheet((theme) => ({ + tabbarStyle: (blur: boolean) => ({ + borderTopColor: theme.colors.border, + backgroundColor: blur + ? Platform.OS === 'ios' + ? undefined + : theme.colors.card + : theme.colors.card, + alignItems: 'stretch', + }), +})) + +export default DefaultTabs diff --git a/src/components/Map/AvailableRoomsSuggestions.tsx b/src/components/Map/AvailableRoomsSuggestions.tsx index 15a84ca6..ecc4e78c 100644 --- a/src/components/Map/AvailableRoomsSuggestions.tsx +++ b/src/components/Map/AvailableRoomsSuggestions.tsx @@ -130,6 +130,10 @@ const AvailableRoomsSuggestions: React.FC = ({ name: 'school', size: 20, }} + web={{ + name: 'Notebook', + size: 20, + }} style={styles.primaryContrast} /> diff --git a/src/components/Map/BottomSheetDetailModal.tsx b/src/components/Map/BottomSheetDetailModal.tsx index 3bfafe2d..a34f7f7a 100644 --- a/src/components/Map/BottomSheetDetailModal.tsx +++ b/src/components/Map/BottomSheetDetailModal.tsx @@ -60,6 +60,10 @@ const ReportLink: React.FC = () => { name: 'chevron_right', size: 16, }} + web={{ + name: 'ChevronRight', + size: 16, + }} /> @@ -116,6 +120,10 @@ export const BottomSheetDetailModal = ({ name: 'share', size: 16, }} + web={{ + name: 'Share', + size: 16, + }} style={styles.shareIcon(Platform.OS)} /> @@ -136,6 +144,10 @@ export const BottomSheetDetailModal = ({ name: 'expand_more', size: 22, }} + web={{ + name: 'X', + size: 22, + }} style={styles.xIcon(Platform.OS)} /> diff --git a/src/components/Map/MapScreen.tsx b/src/components/Map/MapScreen.tsx index b9d7e132..f68c2888 100644 --- a/src/components/Map/MapScreen.tsx +++ b/src/components/Map/MapScreen.tsx @@ -5,6 +5,7 @@ import { UnavailableSessionError, } from '@/api/thi-session-handler' import ErrorView from '@/components/Error/ErrorView' +import { useBottomTabBarHeight } from '@/components/Layout/Tabbar' import { BottomSheetDetailModal } from '@/components/Map/BottomSheetDetailModal' import MapBottomSheet from '@/components/Map/BottomSheetMap' import FloorPicker from '@/components/Map/FloorPicker' @@ -31,6 +32,7 @@ import { LoadingState, roomNotFoundToast } from '@/utils/ui-utils' import { trackEvent } from '@aptabase/react-native' import type BottomSheet from '@gorhom/bottom-sheet' import { type BottomSheetModal } from '@gorhom/bottom-sheet' +import Maplibre from '@maplibre/maplibre-react-native' import MapLibreGL, { type CameraRef, type MapViewRef, @@ -63,7 +65,6 @@ import { Text, View, } from 'react-native' -import { useBottomTabBarHeight } from 'react-native-bottom-tabs' import Animated, { runOnJS, useAnimatedStyle, @@ -80,6 +81,12 @@ import packageInfo from '../../../package.json' import LoadingIndicator from '../Universal/LoadingIndicator' import { modalSection } from './ModalSections' +export function requestPermission(): void { + if (Platform.OS === 'android') { + void Maplibre.requestAndroidLocationPermissions() + } +} + const isIpadOS = Platform.OS === 'ios' && Platform.isPad const MapScreen = (): JSX.Element => { diff --git a/src/components/Map/MapScreen.web.tsx b/src/components/Map/MapScreen.web.tsx new file mode 100644 index 00000000..9622c578 --- /dev/null +++ b/src/components/Map/MapScreen.web.tsx @@ -0,0 +1,15 @@ +import React from 'react' + +import ErrorView from '../Error/ErrorView' + +export function requestPermission(): void {} + +export default function MapScreen(): JSX.Element { + return ( + + ) +} diff --git a/src/components/Map/NextLectureSuggestion.tsx b/src/components/Map/NextLectureSuggestion.tsx index baf5f3db..143cf667 100644 --- a/src/components/Map/NextLectureSuggestion.tsx +++ b/src/components/Map/NextLectureSuggestion.tsx @@ -90,6 +90,10 @@ const NextLectureSuggestion: React.FC = ({ name: 'school', size: 20, }} + web={{ + name: 'Clock', + size: 20, + }} style={styles.primaryContrast} /> diff --git a/src/components/Rows/SportsRow.tsx b/src/components/Rows/SportsRow.tsx index f7d6b5d1..c69d75e1 100644 --- a/src/components/Rows/SportsRow.tsx +++ b/src/components/Rows/SportsRow.tsx @@ -5,7 +5,7 @@ import { formatFriendlyTimeRange } from '@/utils/date-utils' import { sportsCategories } from '@/utils/events-utils' import { router } from 'expo-router' import React from 'react' -import { Text, View } from 'react-native' +import { Platform, Text, View } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' import PlatformIcon from '../Universal/Icon' @@ -49,18 +49,27 @@ const SportsRow = ({ } icon={ - + Platform.OS === 'web' ? ( + <> + ) : ( + + ) } maxTitleWidth={'70%'} /> diff --git a/src/components/Settings/GradesButton.tsx b/src/components/Settings/GradesButton.tsx index b1dcfd87..131c17e3 100644 --- a/src/components/Settings/GradesButton.tsx +++ b/src/components/Settings/GradesButton.tsx @@ -28,6 +28,10 @@ const GradesButton = (): JSX.Element => { name: 'bar_chart_4_bars', size: 18, }} + web={{ + name: 'ChartColumnBig', + size: 18, + }} style={styles.icon} /> diff --git a/src/components/Timetable/HeaderButtons.tsx b/src/components/Timetable/HeaderButtons.tsx index 4b5091b1..358a9c81 100644 --- a/src/components/Timetable/HeaderButtons.tsx +++ b/src/components/Timetable/HeaderButtons.tsx @@ -43,6 +43,10 @@ export function HeaderLeft(): JSX.Element { : 'event_note', size: 24, }} + web={{ + name: timetableMode === 'list' ? 'CalendarRange' : 'List', + size: 24, + }} style={styles.icon} /> @@ -72,6 +76,10 @@ export function HeaderRight({ setToday }: HeaderRightProps): JSX.Element { name: 'keyboard_return', size: 24, }} + web={{ + name: 'Undo2', + size: 24, + }} style={styles.icon} /> @@ -80,7 +88,7 @@ export function HeaderRight({ setToday }: HeaderRightProps): JSX.Element { const stylesheet = createStyleSheet((theme) => ({ headerButton: { - marginHorizontal: Platform.OS === 'android' ? 14 : 0, + marginHorizontal: Platform.OS !== 'ios' ? 14 : 0, }, icon: { color: theme.colors.text, diff --git a/src/components/Timetable/TimetableList.tsx b/src/components/Timetable/TimetableList.tsx index 7c36354e..003a2cd8 100644 --- a/src/components/Timetable/TimetableList.tsx +++ b/src/components/Timetable/TimetableList.tsx @@ -247,6 +247,7 @@ export default function TimetableList({ icon={{ ios: 'fireworks', android: 'celebration', + web: 'PartyPopper', }} isCritical={false} /> diff --git a/src/components/Timetable/TimetableScreen.tsx b/src/components/Timetable/TimetableScreen.tsx index d0ce94a3..338b44f4 100644 --- a/src/components/Timetable/TimetableScreen.tsx +++ b/src/components/Timetable/TimetableScreen.tsx @@ -117,6 +117,7 @@ function TimetableScreen(): JSX.Element { icon={{ ios: 'calendar.badge.exclamationmark', android: 'edit_calendar', + web: 'CalendarX2', }} onButtonPress={() => { void Linking.openURL('https://hiplan.thi.de/') diff --git a/src/components/Universal/Divider.tsx b/src/components/Universal/Divider.tsx index 36425a0e..e9435c29 100644 --- a/src/components/Universal/Divider.tsx +++ b/src/components/Universal/Divider.tsx @@ -48,7 +48,8 @@ const stylesheet = createStyleSheet((theme) => ({ line: ({ width, color }) => ({ width: width ?? '100%', borderBottomColor: color ?? theme.colors.labelTertiaryColor, - borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomWidth: + Platform.OS !== 'web' ? StyleSheet.hairlineWidth : 0.1, }), })) diff --git a/src/components/Universal/FormList.tsx b/src/components/Universal/FormList.tsx index 49ee77d3..34c500ec 100644 --- a/src/components/Universal/FormList.tsx +++ b/src/components/Universal/FormList.tsx @@ -134,6 +134,10 @@ const RenderSectionItems: React.FC<{ size: 18, variant: item.icon.androidVariant, }} + web={{ + name: item.icon.web, + size: 18, + }} // eslint-disable-next-line react-native/no-inline-styles style={{ marginLeft: item.value != null ? 6 : 0, diff --git a/src/components/Universal/Icon.tsx b/src/components/Universal/Icon.tsx index 4568c714..2e958780 100644 --- a/src/components/Universal/Icon.tsx +++ b/src/components/Universal/Icon.tsx @@ -1,11 +1,13 @@ import { type MaterialIcon } from '@/types/material-icons' import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons' +import { FileWarning, icons } from 'lucide-react-native' import React from 'react' import { Platform, Text } from 'react-native' import { createStyleSheet, useStyles } from 'react-native-unistyles' import SweetSFSymbol from 'sweet-sfsymbols' import { type SystemName } from 'sweet-sfsymbols/build/SweetSFSymbols.types' +export type LucideIcon = keyof typeof icons interface PlatformIconProps { android: { name: MaterialIcon | CommunityIcon @@ -36,25 +38,56 @@ interface PlatformIconProps { variableValue?: number | undefined additionalColor?: string } + web: { + name: LucideIcon + size: number + } style?: any } + +export const lucidErrorIcon = { + name: 'error', + size: 24, + color: 'red', +} + export const linkIcon = { ios: 'safari', android: 'link' as MaterialIcon, + web: 'Link' as LucideIcon, } export const chevronIcon = { ios: 'chevron.forward', android: 'chevron_right' as MaterialIcon, + web: 'ChevronRight' satisfies LucideIcon as LucideIcon, } const PlatformIcon = ({ android, ios, + web, style, }: PlatformIconProps): JSX.Element => { const { styles, theme } = useStyles(stylesheet) - if (Platform.OS === 'ios') { + + const lucidFallback = + + if (Platform.OS === 'web') { + if (web != null) { + const LucideIcon = icons[web?.name] + + return ( + + ) + } else { + return lucidFallback + } + } else if (Platform.OS === 'ios') { return (ios.fallback ?? false) ? ( - await Linking.openURL(STATUS_URL), - }, - ] - : []), - ], - { - cancelable: false, - } - ) + if (Platform.OS === 'web') { + toast({ + title: msg, + preset: 'error', + duration: 2.5, + }) + } else { + Alert.alert( + title, + msg, + [ + { text: 'OK' }, + ...(showStatus + ? [ + { + text: t('error.crash.status', { + ns: 'common', + }), + onPress: async () => + await Linking.openURL(STATUS_URL), + }, + ] + : []), + ], + { + cancelable: false, + } + ) + } } } @@ -133,16 +143,12 @@ const LoginForm = ({ setLoading(false) } - async function load(key: string): Promise { - return SecureStore.getItem(key) - } - useEffect(() => { // on iOS secure store is synced with iCloud, so we can prefill the login form if (Platform.OS === 'ios') { const loadSavedData = async (): Promise => { - const savedUsername = await load('username') - const savedPassword = await load('password') + const savedUsername = loadSecure('username') + const savedPassword = loadSecure('password') if (savedUsername !== null && savedPassword !== null) { setUsername(savedUsername) setPassword(savedPassword) diff --git a/src/components/Universal/MultiSectionPicker.tsx b/src/components/Universal/MultiSectionPicker.tsx index d0ce068f..7ab3dea6 100644 --- a/src/components/Universal/MultiSectionPicker.tsx +++ b/src/components/Universal/MultiSectionPicker.tsx @@ -52,6 +52,10 @@ const MultiSectionPicker: React.FC = ({ name: 'check', size: 18, }} + web={{ + name: 'Check', + size: 18, + }} /> ) : ( <> diff --git a/src/components/Universal/ShareButton.tsx b/src/components/Universal/ShareButton.tsx index 9b21ff3e..2b03026c 100644 --- a/src/components/Universal/ShareButton.tsx +++ b/src/components/Universal/ShareButton.tsx @@ -27,6 +27,10 @@ export default function ShareButton({ name: 'share', size: 18, }} + web={{ + name: 'Share', + size: 18, + }} /> {t('misc.share')} diff --git a/src/components/Universal/ShareHeaderButton.tsx b/src/components/Universal/ShareHeaderButton.tsx index f9a223f1..e28d22c7 100644 --- a/src/components/Universal/ShareHeaderButton.tsx +++ b/src/components/Universal/ShareHeaderButton.tsx @@ -30,6 +30,10 @@ export default function ShareHeaderButton({ name: 'share', size: 20, }} + web={{ + name: 'Share', + size: 20, + }} style={styles.icon} /> @@ -61,7 +65,7 @@ const stylesheet = createStyleSheet((theme) => ({ }), height: 34, justifyContent: 'center', - marginRight: -5, + marginEnd: Platform.OS === 'web' ? 14 : -5, padding: 7, width: 34, }, diff --git a/src/components/Universal/SingleSectionPicker.tsx b/src/components/Universal/SingleSectionPicker.tsx index 53185201..db2d256c 100644 --- a/src/components/Universal/SingleSectionPicker.tsx +++ b/src/components/Universal/SingleSectionPicker.tsx @@ -54,6 +54,10 @@ const SingleSectionPicker: React.FC = ({ name: 'check', size: 18, }} + web={{ + name: 'Check', + size: 18, + }} /> ) : null} diff --git a/src/components/Universal/ToggleRow.tsx b/src/components/Universal/ToggleRow.tsx index 2da78ca0..1574b1f7 100644 --- a/src/components/Universal/ToggleRow.tsx +++ b/src/components/Universal/ToggleRow.tsx @@ -28,7 +28,7 @@ const ToggleRow = ({ selectedElement === index )} > - {item}{' '} + {item} diff --git a/src/components/Universal/WorkaroundStack.tsx b/src/components/Universal/WorkaroundStack.tsx index fb78ca62..a0025e46 100644 --- a/src/components/Universal/WorkaroundStack.tsx +++ b/src/components/Universal/WorkaroundStack.tsx @@ -39,7 +39,7 @@ function WorkaroundStack({ const Stack = createNativeStackNavigator() const StackAndroid = createStackNavigator() const { styles, theme } = useStyles(stylesheet) - if (Platform.OS === 'android' && androidFallback) { + if (Platform.OS !== 'ios' && androidFallback) { return ( { const subscription = Appearance.addChangeListener(() => {}) - if (theme === 'dark') { - Appearance.setColorScheme('dark') - UnistylesRuntime.setAdaptiveThemes(false) - UnistylesRuntime.setTheme('dark') - } else if (theme === 'light') { - Appearance.setColorScheme('light') - UnistylesRuntime.setAdaptiveThemes(false) - UnistylesRuntime.setTheme('light') - } else { - Appearance.setColorScheme(undefined) - UnistylesRuntime.setAdaptiveThemes(true) + + const isFixedTheme = theme === 'dark' || theme === 'light' + if (Platform.OS !== 'web') { + Appearance.setColorScheme(isFixedTheme ? theme : undefined) + } + + UnistylesRuntime.setAdaptiveThemes(!isFixedTheme) + if (isFixedTheme) { + UnistylesRuntime.setTheme(theme) } return () => { @@ -273,6 +272,7 @@ export default function Provider({ + {children} diff --git a/src/contexts/userKind.ts b/src/contexts/userKind.ts index 0f75820c..4266a4d8 100644 --- a/src/contexts/userKind.ts +++ b/src/contexts/userKind.ts @@ -3,7 +3,6 @@ import { extractFacultyFromPersonalData, getPersonalData, } from '@/utils/api-utils' -import * as SecureStore from 'expo-secure-store' import { useCallback, useEffect, useMemo } from 'react' import { useMMKVString } from 'react-native-mmkv' @@ -76,7 +75,6 @@ export function useUserKind(): UserKindContextType { } setUserKind(userType) - void SecureStore.setItemAsync('userType', userType) }, [setUserKind] ) diff --git a/src/data/changelog.json b/src/data/changelog.json index 6c6960a6..8abc5cec 100644 --- a/src/data/changelog.json +++ b/src/data/changelog.json @@ -13,7 +13,8 @@ }, "icon": { "ios": "moon.stars", - "android": "dark_mode" + "android": "dark_mode", + "web": "MoonStar" } }, { @@ -27,7 +28,8 @@ }, "icon": { "ios": "map", - "android": "map" + "android": "map", + "web": "Map" } }, @@ -42,7 +44,8 @@ }, "icon": { "ios": "party.popper", - "android": "event" + "android": "event", + "web": "PartyPopper" } } ], @@ -58,7 +61,8 @@ }, "icon": { "ios": "link", - "android": "captive_portal" + "android": "captive_portal", + "web": "Link" } }, { @@ -72,7 +76,8 @@ }, "icon": { "ios": "bolt", - "android": "bolt" + "android": "bolt", + "web": "CircleGauge" } }, { @@ -86,7 +91,8 @@ }, "icon": { "ios": "hammer", - "android": "bug_report" + "android": "bug_report", + "web": "Bug" } } ], @@ -102,7 +108,8 @@ }, "icon": { "ios": "dumbbell", - "android": "exercise" + "android": "exercise", + "web": "Dumbbell" } }, { @@ -116,7 +123,8 @@ }, "icon": { "ios": "link", - "android": "captive_portal" + "android": "captive_portal", + "web": "Link" } }, { @@ -130,7 +138,8 @@ }, "icon": { "ios": "hammer", - "android": "bug_report" + "android": "bug_report", + "web": "Bug" } } ] diff --git a/src/data/constants.ts b/src/data/constants.ts index c293e1c8..7248f933 100644 --- a/src/data/constants.ts +++ b/src/data/constants.ts @@ -21,6 +21,7 @@ export const quicklinks = [ icon: { ios: 'graduationcap', android: 'school', + web: 'GraduationCap', }, }, { @@ -29,6 +30,7 @@ export const quicklinks = [ icon: { ios: 'tray.full', android: 'note_stack', + web: 'FolderOpen', }, }, { @@ -37,6 +39,7 @@ export const quicklinks = [ icon: { ios: 'envelope', android: 'mail', + web: 'Mail', }, }, { @@ -45,6 +48,7 @@ export const quicklinks = [ icon: { ios: 'person.bubble', android: '3p', + web: 'CircleUser', }, }, { @@ -53,6 +57,7 @@ export const quicklinks = [ icon: { ios: 'globe', android: 'captive_portal', + web: 'Globe', }, }, { @@ -61,6 +66,7 @@ export const quicklinks = [ icon: { ios: 'cart', android: 'shopping_cart', + web: 'ShoppingCart', }, }, { @@ -69,6 +75,7 @@ export const quicklinks = [ icon: { ios: 'text.book.closed', android: 'book_4', + web: 'BookOpenText', }, }, { @@ -77,6 +84,7 @@ export const quicklinks = [ icon: { ios: 'newspaper', android: 'newspaper', + web: 'Newspaper', }, }, ] diff --git a/src/localization/de/flow.json b/src/localization/de/flow.json index 150d8933..a805bcdb 100644 --- a/src/localization/de/flow.json +++ b/src/localization/de/flow.json @@ -7,6 +7,8 @@ "onboarding": { "links": { "privacy": "Datenschutzerklärung", + "imprint": "Impressum", + "faq": "Mehr erfahren", "agree1": "Durch Fortfahren stimmst du der ", "agree2": " zu.", "safety": "Datensicherheit" @@ -20,7 +22,9 @@ "title2": "Von Studierenden entwickelt", "description2": "Diese App ist ein Open-Source-Projekt von Neuland Ingolstadt e.V. und ist eine Alternative zur offiziellen THI-App.", "title3": "Sicherheit an erster Stelle", - "description3": "Durch die Nutzung der offiziellen und verschlüsselten Dienste von der THI sind deine Daten streng geschützt und weder für uns noch für Dritte zugänglich." + "description3": "Durch die Nutzung der offiziellen und verschlüsselten Dienste von der THI sind deine Daten streng geschützt und weder für uns noch für Dritte zugänglich.", + "title4": "Noch besser in der App", + "description4": "Installiere Neuland Next auf deinem Smartphone, um alle Funktionen zu nutzen. Verfügbar für iOS, iPadOS, MacOS und Android." } }, "login": { diff --git a/src/localization/en/flow.json b/src/localization/en/flow.json index 7632cb12..6ed14843 100644 --- a/src/localization/en/flow.json +++ b/src/localization/en/flow.json @@ -7,6 +7,8 @@ "onboarding": { "links": { "privacy": "Privacy Policy", + "faq": "Learn more", + "imprint": "Imprint", "agree1": "By continuing, you agree to the ", "agree2": ".", "safety": "Data Safety" @@ -20,7 +22,9 @@ "title2": "Developed by students", "description2": "Neuland Next is an open-source project by Neuland Ingolstadt e.V. and is an alternative to the official THI app.", "title3": "Security comes first", - "description3": "Your data is strictly protected by using the official and encrypted services of THI and is never accessible to us or third parties." + "description3": "Your data is strictly protected by using the official and encrypted services of THI and is never accessible to us or third parties.", + "title4": "Even better in the app", + "description4": "Install Neuland Next on your smartphone to use all features. Available for iOS, iPadOS, MacOS, and Android." } }, "login": { diff --git a/src/types/components.ts b/src/types/components.ts index ff323684..a9e0d480 100644 --- a/src/types/components.ts +++ b/src/types/components.ts @@ -1,4 +1,7 @@ -import { type CommunityIcon } from '@/components/Universal/Icon' +import { + type CommunityIcon, + type LucideIcon, +} from '@/components/Universal/Icon' import { type ColorValue } from 'react-native' import { type MaterialIcon } from './material-icons' @@ -9,6 +12,7 @@ export interface SectionGroup { icon?: { ios: string android: MaterialIcon | CommunityIcon + web: LucideIcon iosFallback?: boolean androidVariant?: 'outlined' | 'filled' } diff --git a/src/types/data.ts b/src/types/data.ts index 0429f474..621a7da9 100644 --- a/src/types/data.ts +++ b/src/types/data.ts @@ -1,3 +1,5 @@ +import { type LucideIcon } from '@/components/Universal/Icon' + import { type MaterialIcon } from './material-icons' export interface Allergens { @@ -99,10 +101,20 @@ export interface Changelog { export interface Version { title: Description description: Description - icon: { ios: string; android: MaterialIcon } + icon: { ios: string; android: MaterialIcon; web: LucideIcon } } export interface Description { de: string en: string } + +export interface OnboardingCardData { + title: string + description: string + icon: { + ios: string + android: MaterialIcon + web: LucideIcon + } +} diff --git a/src/utils/api-utils.ts b/src/utils/api-utils.ts index def37578..50fccb89 100644 --- a/src/utils/api-utils.ts +++ b/src/utils/api-utils.ts @@ -6,7 +6,8 @@ import { type CourseShortNames } from '@/types/data' import { type PersDataDetails } from '@/types/thi-api' import { type QueryClient } from '@tanstack/react-query' import { router } from 'expo-router' -import { getItemAsync } from 'expo-secure-store' + +import { loadSecure } from './storage' export const networkError = 'Network request failed' export const guestError = 'User is logged in as guest' @@ -32,7 +33,7 @@ export const trimErrorMsg = (str: string): string => { export async function getUsername(): Promise { let username = '' try { - username = (await getItemAsync('username')) ?? '' + username = loadSecure('username') ?? '' } catch (e) { console.log(e) } diff --git a/src/utils/storage.ts b/src/utils/storage.ts index c98dbd70..f85644e2 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,4 +1,6 @@ import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister' +import * as SecureStore from 'expo-secure-store' +import { Platform } from 'react-native' import { MMKV } from 'react-native-mmkv' import { type StateStorage } from 'zustand/middleware' @@ -6,6 +8,43 @@ export const storage = new MMKV({ id: 'query-client-storage', }) +export const secureWebStorage = new MMKV({ + id: 'secure-web-storage', +}) + +export async function saveSecure( + key: string, + value: string, + insecureWeb = false +): Promise { + if (Platform.OS === 'web') { + if (!insecureWeb) { + // Saving secure data in a browser is not possible, so we do nothing here. The user needs to sign in again as long as we cannot use single sign-on. + return + } + secureWebStorage.set(key, value) + await Promise.resolve() + return + } + await SecureStore.setItemAsync(key, value) +} + +export function loadSecure(key: string): string | null { + if (Platform.OS === 'web') { + return secureWebStorage.getString(key) ?? null + } + return SecureStore.getItem(key) +} + +export async function deleteSecure(key: string): Promise { + if (Platform.OS === 'web') { + secureWebStorage.delete(key) + await Promise.resolve() + return + } + await SecureStore.deleteItemAsync(key) +} + const clientStorage = { setItem: (key: string, value: string | number | boolean | ArrayBuffer) => { storage.set(key, value) diff --git a/tsconfig.json b/tsconfig.json index dee85d8d..fbcf7690 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "expo/tsconfig.base", "compilerOptions": { - "baseUrl": ".", + "baseUrl": "./", "paths": { "@/*": ["./src/*"], "ios/*": ["./ios/NeulandNext/Images.xcassets/*"]