diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 87b06467..bb2e1ffe 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -8,11 +8,11 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Use Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
- node-version: 18
+ node-version: 20
- name: Install modules
run: npm install
- name: Run ESLint
diff --git a/android/app/build.gradle b/android/app/build.gradle
index a6ce7f1f..a5fbcdc0 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -90,8 +90,8 @@ android {
applicationId 'app.neuland'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 83
- versionName "0.8.1"
+ versionCode 84
+ versionName "0.8.2"
}
signingConfigs {
debug {
diff --git a/app.config.ts b/app.config.ts
index abbc164d..a5f4ec84 100644
--- a/app.config.ts
+++ b/app.config.ts
@@ -27,6 +27,7 @@ module.exports = {
CFBundleAllowMixedLocalizations: true,
CFBundleLocalizations: ['en', 'de'],
CFBundleDevelopmentRegion: 'en',
+ UIViewControllerBasedStatusBarAppearance: true,
},
splash: {
image: './src/assets/splash/splashLight.png',
@@ -42,7 +43,7 @@ module.exports = {
android: {
package: 'app.neuland',
userInterfaceStyle: 'automatic',
- versionCode: 83,
+ versionCode: 84,
splash: {
image: './src/assets/splash/splashLight.png',
resizeMode: 'contain',
diff --git a/bun.lockb b/bun.lockb
index 54544f20..51819133 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/ios/NeulandNext.xcodeproj/project.pbxproj b/ios/NeulandNext.xcodeproj/project.pbxproj
index c73bba5a..e3b01f10 100644
--- a/ios/NeulandNext.xcodeproj/project.pbxproj
+++ b/ios/NeulandNext.xcodeproj/project.pbxproj
@@ -163,6 +163,8 @@
416B75F9501048ACA40DB650 /* Remove signature files (Xcode 15 workaround) */,
EF820F10D3E648A28C78DECD /* Remove signature files (Xcode 15 workaround) */,
1515044FA4184334AD7857BB /* Remove signature files (Xcode 15 workaround) */,
+ 8CFEE477FDEE49A683B8FAB4 /* Remove signature files (Xcode 15 workaround) */,
+ C9F03030C79E4397B5A02662 /* Remove signature files (Xcode 15 workaround) */,
);
buildRules = (
);
@@ -336,6 +338,20 @@
shellPath = /bin/sh;
shellScript = "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`";
};
+ 8CFEE477FDEE49A683B8FAB4 /* Remove signature files (Xcode 15 workaround) */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Remove signature files (Xcode 15 workaround)";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "if [ \"$XCODE_VERSION_MAJOR\" = \"1500\" ]; then\n echo \"Remove signature files (Xcode 15 workaround)\";\n rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";\n fi";
+ };
AD913B7B949DB5BB3812201A /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -468,6 +484,23 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NeulandNext/Pods-NeulandNext-resources.sh\"\n";
showEnvVarsInLog = 0;
};
+ C9F03030C79E4397B5A02662 /* Remove signature files (Xcode 15 workaround) */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ name = "Remove signature files (Xcode 15 workaround)";
+ inputPaths = (
+ );
+ outputPaths = (
+ );
+ shellPath = /bin/sh;
+ shellScript = "if [ \"$XCODE_VERSION_MAJOR\" = \"1500\" ]; then
+ echo \"Remove signature files (Xcode 15 workaround)\";
+ rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
+ fi";
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -522,7 +555,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 0.8.1;
+ MARKETING_VERSION = 0.8.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -530,7 +563,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = "de.neuland-ingolstadt.neuland-app";
- PRODUCT_NAME = NeulandNext;
+ PRODUCT_NAME = "NeulandNext";
SWIFT_OBJC_BRIDGING_HEADER = "NeulandNext/NeulandNext-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -551,7 +584,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = FSXB76X6V2;
- "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
+ "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64";
INFOPLIST_FILE = NeulandNext/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
@@ -559,7 +592,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 0.8.1;
+ MARKETING_VERSION = 0.8.2;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -567,7 +600,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = "de.neuland-ingolstadt.neuland-app";
- PRODUCT_NAME = NeulandNext;
+ PRODUCT_NAME = "NeulandNext";
SWIFT_OBJC_BRIDGING_HEADER = "NeulandNext/NeulandNext-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -634,7 +667,10 @@
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
- OTHER_LDFLAGS = "$(inherited) ";
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ " ",
+ );
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
@@ -692,7 +728,10 @@
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = NO;
- OTHER_LDFLAGS = "$(inherited) ";
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ " ",
+ );
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
diff --git a/ios/NeulandNext/Info.plist b/ios/NeulandNext/Info.plist
index a9c15975..d3736483 100644
--- a/ios/NeulandNext/Info.plist
+++ b/ios/NeulandNext/Info.plist
@@ -2,6 +2,11 @@
+ Allow $(PRODUCT_NAME) to access your location
+
+ en
+ de
+
CADisableMinimumFrameDurationOnPhone
CFBundleAllowMixedLocalizations
@@ -76,7 +81,7 @@
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
- Allow $(PRODUCT_NAME) to access your location
+ CFBundleLocalizations
en
de
@@ -86,7 +91,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 0.8.1
+ 0.8.2
CFBundleSignature
????
CFBundleURLTypes
@@ -135,6 +140,8 @@
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
RCTAsyncStorageExcludeFromBackup
@@ -167,6 +174,6 @@
UIUserInterfaceStyle
Automatic
UIViewControllerBasedStatusBarAppearance
-
+
\ No newline at end of file
diff --git a/ios/NeulandNext/de.lproj/Info.plist b/ios/NeulandNext/de.lproj/Info.plist
index 0b96670d..c1c756bb 100644
--- a/ios/NeulandNext/de.lproj/Info.plist
+++ b/ios/NeulandNext/de.lproj/Info.plist
@@ -86,7 +86,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 0.8.1
+ 0.8.2
CFBundleSignature
????
CFBundleURLTypes
@@ -167,6 +167,6 @@
UIUserInterfaceStyle
Automatic
UIViewControllerBasedStatusBarAppearance
-
+
diff --git a/ios/NeulandNext/en.lproj/Info.plist b/ios/NeulandNext/en.lproj/Info.plist
index 8d8d2839..d16592ac 100644
--- a/ios/NeulandNext/en.lproj/Info.plist
+++ b/ios/NeulandNext/en.lproj/Info.plist
@@ -86,7 +86,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 0.8.1
+ 0.8.2
CFBundleSignature
????
CFBundleURLTypes
@@ -167,6 +167,6 @@
UIUserInterfaceStyle
Automatic
UIViewControllerBasedStatusBarAppearance
-
+
diff --git a/ios/TestFlight/WhatToTest.de_DE.txt b/ios/TestFlight/WhatToTest.de_DE.txt
index 6c3fbb45..17b05ac8 100644
--- a/ios/TestFlight/WhatToTest.de_DE.txt
+++ b/ios/TestFlight/WhatToTest.de_DE.txt
@@ -1,8 +1,3 @@
-Was ist neu?
-- neues Karten-Design mit Darkmode
-- Vorschlag für die nächste Vorlesung auf der Karte
-- Speiseplan der Mensa Neuburg
-- mehr Details zu CL-Veranstaltungen
-- neues Standard-App-Icon
-- neues Easter Egg zum Freischalten des Retro-App-Icons
-- Fehlerbehebungen und Verbesserungen
\ No newline at end of file
+- Wähle das App Design unabhängig von deinem iOS Design (hell oder dunkel)
+- Fehlerbehebungen und Verbesserungen der Karte
+- Performance Verbesserungen
\ No newline at end of file
diff --git a/ios/TestFlight/WhatToTest.en-US.txt b/ios/TestFlight/WhatToTest.en-US.txt
index 4c5b553b..71a100bf 100644
--- a/ios/TestFlight/WhatToTest.en-US.txt
+++ b/ios/TestFlight/WhatToTest.en-US.txt
@@ -1,8 +1,3 @@
-What's new?
-- new map design with darkmode support
-- next lecture suggestion on map
-- Neuburg canteen meal plan
-- more CL event details
-- new default app icon
-- new easter egg to unlock retro app icon
-- bug fixes and improvements
\ No newline at end of file
+- choose the app design independently of your iOS design (light or dark)
+- bug fixes and improvements to the map
+- performance improvements
\ No newline at end of file
diff --git a/package.json b/package.json
index 8e3c4e61..4a6ee4aa 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "version": "0.8.1",
+ "version": "0.8.2",
"name": "neuland",
"main": "expo-router/entry",
"homepage": "https://github.com/neuland-ingolstadt/neuland.app-native",
diff --git a/src/app/(flow)/onboarding.tsx b/src/app/(flow)/onboarding.tsx
index 5ca606a7..12da5471 100644
--- a/src/app/(flow)/onboarding.tsx
+++ b/src/app/(flow)/onboarding.tsx
@@ -25,6 +25,7 @@ export default function OnboardingScreen(): JSX.Element {
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
?.goToPage(3, false)
}
+ controlStatusBar={false}
showDone={false}
nextLabel={t('onboarding.navigation.next')}
skipLabel={t('onboarding.navigation.skip')}
diff --git a/src/app/(flow)/whatsnew.tsx b/src/app/(flow)/whatsnew.tsx
index c3c7c429..1343dd40 100644
--- a/src/app/(flow)/whatsnew.tsx
+++ b/src/app/(flow)/whatsnew.tsx
@@ -5,10 +5,9 @@ import changelogData from '@/data/changelog.json'
import { type LanguageKey } from '@/localization/i18n'
import { type Changelog } from '@/types/data'
import { convertToMajorMinorPatch } from '@/utils/app-utils'
-import { getContrastColor, getStatusBarStyle } from '@/utils/ui-utils'
+import { getContrastColor } from '@/utils/ui-utils'
import { useTheme } from '@react-navigation/native'
import { router } from 'expo-router'
-import { StatusBar } from 'expo-status-bar'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { Pressable, StyleSheet, Text, View } from 'react-native'
@@ -23,7 +22,6 @@ export default function OnboardingScreen(): JSX.Element {
const { analyticsAllowed, toggleAnalytics } = React.useContext(FlowContext)
return (
-
-
-
-
-
-
(null)
const [displayesProfessors, setDisplayedProfessors] = useState(false)
@@ -225,6 +228,16 @@ export default function LecturersCard(): JSX.Element {
placeholder: t('navigation.lecturers.search', {
ns: 'navigation',
}),
+
+ barTintColor: getBarTintColor(theme, isDark),
+
+ ...Platform.select({
+ android: {
+ headerIconColor: colors.text,
+ hintTextColor: colors.text,
+ textColor: colors.text,
+ },
+ }),
shouldShowHintSearchIcon: false,
hideWhenScrolling: false,
hideNavigationBar: false,
@@ -243,7 +256,7 @@ export default function LecturersCard(): JSX.Element {
},
},
})
- }, [navigation])
+ }, [navigation, isDark])
const LecturerList = ({
lecturers,
diff --git a/src/app/(pages)/libraryCode.tsx b/src/app/(pages)/libraryCode.tsx
index a812ac50..d22ba580 100644
--- a/src/app/(pages)/libraryCode.tsx
+++ b/src/app/(pages)/libraryCode.tsx
@@ -16,13 +16,11 @@ import {
permissionError,
} from '@/utils/api-utils'
import { PAGE_PADDING } from '@/utils/style-utils'
-import { getStatusBarStyle } from '@/utils/ui-utils'
import Barcode from '@kichiyaki/react-native-barcode-generator'
import { useTheme } from '@react-navigation/native'
import { useQuery } from '@tanstack/react-query'
import * as Brightness from 'expo-brightness'
import { useFocusEffect } from 'expo-router'
-import { StatusBar } from 'expo-status-bar'
import React, { useContext, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
@@ -112,7 +110,6 @@ export default function LibraryCode(): JSX.Element {
}
return (
-
{userKind === USER_GUEST ? (
) : userKind === USER_EMPLOYEE ? (
diff --git a/src/app/(tabs)/_layout.tsx b/src/app/(tabs)/_layout.tsx
index 19cfd870..4d79a347 100644
--- a/src/app/(tabs)/_layout.tsx
+++ b/src/app/(tabs)/_layout.tsx
@@ -224,6 +224,7 @@ export default function HomeLayout(): JSX.Element {
}}
/>
),
+
tabBarStyle: { position: 'absolute' },
tabBarBackground: () =>
Platform.OS === 'ios' ? : null,
@@ -257,6 +258,7 @@ export default function HomeLayout(): JSX.Element {
options={{
title: t('navigation.map'),
headerShown: false,
+
tabBarHideOnKeyboard: true,
tabBarIcon: ({ color, size }) => (
(UserKindContext)
const { t } = useTranslation(['navigation'])
@@ -315,7 +316,11 @@ export default function Screen(): JSX.Element {
data?.mtknr === undefined ? (
{
setIsPageOpen(true)
}, [])
-
const [localSearch, setLocalSearch] = useState('')
const [clickedElement, setClickedElement] =
useState(null)
diff --git a/src/app/(timetable)/details.tsx b/src/app/(timetable)/details.tsx
index 50681212..4d0a79b8 100644
--- a/src/app/(timetable)/details.tsx
+++ b/src/app/(timetable)/details.tsx
@@ -8,48 +8,25 @@ import FormList from '@/components/Elements/Universal/FormList'
import PlatformIcon, { chevronIcon } from '@/components/Elements/Universal/Icon'
import ShareButton from '@/components/Elements/Universal/ShareButton'
import { type Colors } from '@/components/colors'
-import { NotificationContext, RouteParamsContext } from '@/components/contexts'
-import { useNotification } from '@/hooks'
-import i18n, { type LanguageKey } from '@/localization/i18n'
+import { RouteParamsContext } from '@/components/contexts'
import { type FormListSections, type SectionGroup } from '@/types/components'
-import { type FriendlyTimetableEntry } from '@/types/utils'
import { formatFriendlyDate, formatFriendlyTime } from '@/utils/date-utils'
import { PAGE_PADDING } from '@/utils/style-utils'
-import {
- getFriendlyTimetable,
- notificationAlert,
- scheduleLectureNotification,
-} from '@/utils/timetable-utils'
-import { getStatusBarStyle } from '@/utils/ui-utils'
-import ActionSheet from '@alessiocancian/react-native-actionsheet'
import { trackEvent } from '@aptabase/react-native'
import { useTheme } from '@react-navigation/native'
import { useRouter } from 'expo-router'
import * as Sharing from 'expo-sharing'
-import { StatusBar } from 'expo-status-bar'
import moment from 'moment'
-import React, { useContext, useEffect, useRef, useState } from 'react'
+import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
-import {
- ActivityIndicator,
- Pressable,
- ScrollView,
- StyleSheet,
- Text,
- View,
-} from 'react-native'
+import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'
import ViewShot, { captureRef } from 'react-native-view-shot'
export default function TimetableDetails(): JSX.Element {
const router = useRouter()
const { updateRouteParams } = useContext(RouteParamsContext)
- const {
- timetableNotifications,
- updateTimetableNotifications,
- deleteTimetableNotifications,
- } = useContext(NotificationContext)
- const { hasPermission, askForPermission } = useNotification()
+ // const { hasPermission, askForPermission } = useNotification()
const colors = useTheme().colors as Colors
const { lecture } = useContext(RouteParamsContext)
@@ -59,7 +36,7 @@ export default function TimetableDetails(): JSX.Element {
if (lecture === null) {
return
}
- const today = new Date()
+ // const today = new Date()
const startDate = new Date(lecture.startDate)
const endDate = new Date(lecture.endDate)
@@ -68,57 +45,57 @@ export default function TimetableDetails(): JSX.Element {
const exam = `${examSplit[0].toUpperCase()}${examSplit.slice(1)}`
const shareRef = React.useRef(null)
- const [rawTimetable, setRawTimetable] = useState(
- []
- )
-
- const [notificationsUpdating, setNotificationsUpdating] = useState(false)
-
- async function load(): Promise {
- try {
- const timetable = await getFriendlyTimetable(today, true)
- const filteredTimetable = timetable.filter(
- (lecture) =>
- lecture.shortName === lecture?.shortName &&
- lecture.startDate >= today
- )
-
- setRawTimetable(filteredTimetable)
- } catch (e) {
- console.log(e)
- }
- }
-
- useEffect(() => {
- void load()
- }, [])
- async function setupNotifications(mins: number): Promise {
- if (lecture?.shortName === undefined) {
- throw new Error('Event is undefined')
- }
- setNotificationsUpdating(true)
- deleteTimetableNotifications(lecture.shortName)
- const notificationPromises = rawTimetable.map(async (lecture) => {
- const startDate = new Date(lecture.startDate)
- return await scheduleLectureNotification(
- lecture.name,
- lecture.rooms.join(', '),
- mins,
- startDate,
- t
- )
- })
- const notifications = await Promise.all(notificationPromises)
- const flatNotifications = notifications.flat()
-
- updateTimetableNotifications(
- lecture.shortName,
- flatNotifications,
- mins,
- i18n.language as LanguageKey
- )
- setNotificationsUpdating(false)
- }
+ // const [rawTimetable, setRawTimetable] = useState(
+ // []
+ // )
+
+ // const [notificationsUpdating, setNotificationsUpdating] = useState(false)
+
+ // async function load(): Promise {
+ // try {
+ // const timetable = await getFriendlyTimetable(today, true)
+ // const filteredTimetable = timetable.filter(
+ // (lecture) =>
+ // lecture.shortName === lecture?.shortName &&
+ // lecture.startDate >= today
+ // )
+
+ // setRawTimetable(filteredTimetable)
+ // } catch (e) {
+ // console.log(e)
+ // }
+ // }
+
+ // useEffect(() => {
+ // void load()
+ // }, [])
+ // async function setupNotifications(mins: number): Promise {
+ // if (lecture?.shortName === undefined) {
+ // throw new Error('Event is undefined')
+ // }
+ // setNotificationsUpdating(true)
+ // deleteTimetableNotifications(lecture.shortName)
+ // const notificationPromises = rawTimetable.map(async (lecture) => {
+ // const startDate = new Date(lecture.startDate)
+ // return await scheduleLectureNotification(
+ // lecture.name,
+ // lecture.rooms.join(', '),
+ // mins,
+ // startDate,
+ // t
+ // )
+ // })
+ // const notifications = await Promise.all(notificationPromises)
+ // const flatNotifications = notifications.flat()
+
+ // updateTimetableNotifications(
+ // lecture.shortName,
+ // flatNotifications,
+ // mins,
+ // i18n.language as LanguageKey
+ // )
+ // setNotificationsUpdating(false)
+ // }
async function shareEvent(): Promise {
try {
@@ -138,8 +115,8 @@ export default function TimetableDetails(): JSX.Element {
}
}
- const notification = timetableNotifications[lecture.shortName]
- const minsBefore = notification != null ? notification.mins : undefined
+ // const notification = timetableNotifications[lecture.shortName]
+ // const minsBefore = notification != null ? notification.mins : undefined
interface HtmlItem {
title: 'overview.goal' | 'overview.content' | 'overview.literature'
@@ -201,50 +178,49 @@ export default function TimetableDetails(): JSX.Element {
},
]
- const actionSheetRef = useRef(null)
-
- /**
- * Shows the action sheet for setting up notifications
- * @returns {Promise} A promise that resolves when the action sheet has been shown.
- */
- const showActionSheet = async (): Promise => {
- let has = await hasPermission()
- if (!has) {
- has = await askForPermission()
- }
-
- if (!has) {
- notificationAlert(t)
- return
- }
-
- if (actionSheetRef.current != null) {
- actionSheetRef.current.show()
- }
- }
-
- const options = [
- { value: 5, label: t('notificatons.five') },
- { value: 15, label: t('notificatons.fifteen') },
- { value: 30, label: t('notificatons.thirty') },
- { value: 60, label: t('notificatons.sixty') },
- ]
-
- const filteredOptions = options.filter(
- (option) => option.value !== minsBefore
- )
-
- filteredOptions.push(
- ...(notification != null
- ? [{ value: 0, label: t('misc.disable', { ns: 'common' }) }]
- : []),
- { value: -1, label: t('misc.cancel', { ns: 'common' }) }
- )
+ // const actionSheetRef = useRef(null)
+
+ // /**
+ // * Shows the action sheet for setting up notifications
+ // * @returns {Promise} A promise that resolves when the action sheet has been shown.
+ // */
+ // const showActionSheet = async (): Promise => {
+ // let has = await hasPermission()
+ // if (!has) {
+ // has = await askForPermission()
+ // }
+
+ // if (!has) {
+ // notificationAlert(t)
+ // return
+ // }
+
+ // if (actionSheetRef.current != null) {
+ // actionSheetRef.current.show()
+ // }
+ // }
+
+ // const options = [
+ // { value: 5, label: t('notificatons.five') },
+ // { value: 15, label: t('notificatons.fifteen') },
+ // { value: 30, label: t('notificatons.thirty') },
+ // { value: 60, label: t('notificatons.sixty') },
+ // ]
+
+ // const filteredOptions = options.filter(
+ // (option) => option.value !== minsBefore
+ // )
+
+ // filteredOptions.push(
+ // ...(notification != null
+ // ? [{ value: 0, label: t('misc.disable', { ns: 'common' }) }]
+ // : []),
+ // { value: -1, label: t('misc.cancel', { ns: 'common' }) }
+ // )
return (
<>
-
-
+ /> */}
@@ -377,7 +353,7 @@ export default function TimetableDetails(): JSX.Element {
- {
showActionSheet().catch((error) => {
console.error(error)
@@ -427,7 +403,7 @@ export default function TimetableDetails(): JSX.Element {
{notification.mins + ' min'}
)}
-
+ */}
@@ -567,16 +543,16 @@ export const styles = StyleSheet.create({
position: 'absolute',
transform: [{ translateX: -1000 }],
},
- bellPressable: {
- flexDirection: 'column',
- alignItems: 'center',
- justifyContent: 'space-around',
- minWidth: 40,
- height: '100%',
- },
- bellTime: {
- fontSize: 12,
- },
+ // bellPressable: {
+ // flexDirection: 'column',
+ // alignItems: 'center',
+ // justifyContent: 'space-around',
+ // minWidth: 40,
+ // height: '100%',
+ // },
+ // bellTime: {
+ // fontSize: 12,
+ // },
dateRow: {
flex: 1,
flexDirection: 'row',
diff --git a/src/app/(timetable)/webView.tsx b/src/app/(timetable)/webView.tsx
index 36a8cded..8e1b2dff 100644
--- a/src/app/(timetable)/webView.tsx
+++ b/src/app/(timetable)/webView.tsx
@@ -1,8 +1,6 @@
import { type Colors } from '@/components/colors'
-import { getStatusBarStyle } from '@/utils/ui-utils'
import { useTheme } from '@react-navigation/native'
import { useLocalSearchParams, useNavigation } from 'expo-router'
-import { StatusBar } from 'expo-status-bar'
import React, { useLayoutEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Dimensions, Platform, StyleSheet, View } from 'react-native'
@@ -54,7 +52,6 @@ export default function NotesDetails(): JSX.Element {
return (
<>
-
{
void setDelayedIsLoaded()
diff --git a/src/app/(user)/appicon.tsx b/src/app/(user)/appicon.tsx
index d7dfe6c2..1a538c8a 100644
--- a/src/app/(user)/appicon.tsx
+++ b/src/app/(user)/appicon.tsx
@@ -4,10 +4,7 @@ import SectionView from '@/components/Elements/Universal/SectionsView'
import { type Colors } from '@/components/colors'
import { AppIconContext } from '@/components/contexts'
import { capitalizeFirstLetter } from '@/utils/app-utils'
-import { getStatusBarStyle } from '@/utils/ui-utils'
import { useTheme } from '@react-navigation/native'
-import { useLocalSearchParams } from 'expo-router'
-import { StatusBar } from 'expo-status-bar'
import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import {
@@ -42,7 +39,6 @@ export default function AppIconPicker(): JSX.Element {
const { appIcon, toggleAppIcon, unlockedAppIcons } =
useContext(AppIconContext)
const { t } = useTranslation(['settings'])
- const isModal = useLocalSearchParams().fromAppShortcut === 'true'
const categories: Record = {
exclusive: ['cat', 'retro'],
default: ['default', 'modernDark', 'modernGreen'],
@@ -60,7 +56,6 @@ export default function AppIconPicker(): JSX.Element {
return (
<>
-
{Object.entries(categories).map(([key, value]) => {
return (
diff --git a/src/app/(user)/license.tsx b/src/app/(user)/license.tsx
index eea06a64..78fabb66 100644
--- a/src/app/(user)/license.tsx
+++ b/src/app/(user)/license.tsx
@@ -4,10 +4,8 @@ import SectionView from '@/components/Elements/Universal/SectionsView'
import { type Colors } from '@/components/colors'
import { type FormListSections } from '@/types/components'
import { MODAL_BOTTOM_MARGIN, PAGE_PADDING } from '@/utils/style-utils'
-import { getStatusBarStyle } from '@/utils/ui-utils'
import { useTheme } from '@react-navigation/native'
import { useGlobalSearchParams } from 'expo-router'
-import { StatusBar } from 'expo-status-bar'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Linking, ScrollView, StyleSheet, Text, View } from 'react-native'
@@ -85,7 +83,6 @@ export default function License(): JSX.Element {
return (
<>
-
diff --git a/src/app/(user)/login.tsx b/src/app/(user)/login.tsx
index d2687e35..58c32bef 100644
--- a/src/app/(user)/login.tsx
+++ b/src/app/(user)/login.tsx
@@ -1,8 +1,6 @@
import LoginForm from '@/components/Elements/Universal/LoginForm'
import { type Colors } from '@/components/colors'
-import { getStatusBarStyle } from '@/utils/ui-utils'
import { useTheme } from '@react-navigation/native'
-import { StatusBar } from 'expo-status-bar'
import React, { useEffect, useState } from 'react'
import {
Dimensions,
@@ -41,7 +39,6 @@ export default function Login(): JSX.Element {
return (
<>
-
diff --git a/src/app/(user)/settings.tsx b/src/app/(user)/settings.tsx
index 27fbe1b6..65023a0b 100644
--- a/src/app/(user)/settings.tsx
+++ b/src/app/(user)/settings.tsx
@@ -120,16 +120,6 @@ export default function Settings(): JSX.Element {
{
header: t('menu.formlist.preferences.title'),
items: [
- {
- title: t('menu.formlist.preferences.theme'),
- icon: {
- ios: 'paintpalette',
- android: 'palette',
- },
- onPress: () => {
- router.push('(user)/theme')
- },
- },
{
title: 'Dashboard',
icon: {
@@ -172,6 +162,36 @@ export default function Settings(): JSX.Element {
},
],
},
+ {
+ header: t('menu.formlist.appearance.title'),
+ items: [
+ {
+ title: t('menu.formlist.appearance.theme'),
+ icon: {
+ ios: 'paintpalette',
+ android: 'palette',
+ },
+ onPress: () => {
+ router.push('(user)/theme')
+ },
+ },
+ ...(Platform.OS === 'ios'
+ ? [
+ {
+ title: 'App Icon',
+ icon: {
+ ios: 'star.square.on.square',
+ android: null,
+ },
+ onPress: () => {
+ router.push('(user)/appicon')
+ },
+ },
+ ]
+ : []),
+ ],
+ },
+
{
header: 'Quick Links',
items: [
diff --git a/src/app/(user)/theme.tsx b/src/app/(user)/theme.tsx
index d5526feb..c0f12dde 100644
--- a/src/app/(user)/theme.tsx
+++ b/src/app/(user)/theme.tsx
@@ -1,16 +1,14 @@
+import MultiSectionRadio from '@/components/Elements/Food/FoodLanguageSection'
import PlatformIcon from '@/components/Elements/Universal/Icon'
import SectionView from '@/components/Elements/Universal/SectionsView'
import { type Colors, accentColors } from '@/components/colors'
-import { AppIconContext, ThemeContext } from '@/components/contexts'
+import { ThemeContext } from '@/components/contexts'
import { getContrastColor } from '@/utils/ui-utils'
import { useTheme } from '@react-navigation/native'
import * as Haptics from 'expo-haptics'
-import { router } from 'expo-router'
import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import {
- Image,
- type ImageProps,
Platform,
Pressable,
ScrollView,
@@ -19,26 +17,11 @@ import {
View,
} from 'react-native'
-let iconImages: Record = {}
-
-if (Platform.OS === 'ios') {
- iconImages = {
- default: require('@/assets/appIcons/default.png'),
- modernDark: require('@/assets/appIcons/modernDark.png'),
- retro: require('@/assets/appIcons/retro.png'),
- modernGreen: require('@/assets/appIcons/modernGreen.png'),
- rainbowDark: require('@/assets/appIcons/rainbowDark.png'),
- rainbowNeon: require('@/assets/appIcons/rainbowNeon.png'),
- rainbowMoonLight: require('@/assets/appIcons/rainbowMoonLight.png'),
- cat: require('@/assets/appIcons/cat.png'),
- }
-}
-
export default function Theme(): JSX.Element {
const colors = useTheme().colors as Colors
const deviceTheme = useTheme()
- const { accentColor, toggleAccentColor } = useContext(ThemeContext)
- const { appIcon } = useContext(AppIconContext)
+ const { accentColor, toggleAccentColor, theme, toggleTheme } =
+ useContext(ThemeContext)
const { t } = useTranslation(['settings'])
interface ColorBoxColor {
@@ -137,6 +120,21 @@ export default function Theme(): JSX.Element {
}))
)
+ const elements = [
+ {
+ key: 'auto',
+ title: t('theme.themes.default'),
+ },
+ {
+ key: 'light',
+ title: t('theme.themes.light'),
+ },
+ {
+ key: 'dark',
+ title: t('theme.themes.dark'),
+ },
+ ]
+
return (
<>
@@ -157,52 +155,13 @@ export default function Theme(): JSX.Element {
))}
- {Platform.OS === 'ios' && (
-
- {
- router.push('(user)/appicon')
- }}
- >
-
-
-
- {/* @ts-expect-error cannot verify that appIcon is a valid key */}
- {t(`appIcon.names.${appIcon}`)}
-
-
-
-
-
- )}
+
+ void}
+ />
+
>
)
@@ -231,26 +190,6 @@ const styles = StyleSheet.create({
flexWrap: 'wrap',
paddingVertical: 18,
},
- iconText: {
- fontSize: 18,
- alignSelf: 'center',
- },
- iconPressable: {
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'space-between',
- paddingStart: 12,
- paddingEnd: 18,
- paddingVertical: 12,
- },
- iconInnerContainer: { flexDirection: 'row', gap: 20 },
- iconContainer: {
- width: 80,
- height: 80,
- justifyContent: 'center',
- borderRadius: 18,
- borderWidth: 1,
- },
colorBoxContainer: {
justifyContent: 'center',
},
diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx
index db1161c7..29734142 100644
--- a/src/app/_layout.tsx
+++ b/src/app/_layout.tsx
@@ -1,13 +1,16 @@
import PlatformIcon from '@/components/Elements/Universal/Icon'
+import { ThemeContext } from '@/components/contexts'
import Provider from '@/components/provider'
import i18n from '@/localization/i18n'
+import { getStatusBarStyle } from '@/utils/ui-utils'
import AsyncStorage from '@react-native-async-storage/async-storage'
+import { useTheme } from '@react-navigation/native'
import * as Sentry from '@sentry/react-native'
import { getLocales } from 'expo-localization'
import { Stack, useNavigationContainerRef, useRouter } from 'expo-router'
-import React, { useEffect } from 'react'
+import React, { useContext, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
-import { AppState, Platform, Pressable, useColorScheme } from 'react-native'
+import { AppState, Platform, Pressable } from 'react-native'
const sentryDsn = process.env.EXPO_PUBLIC_SENTRY_DSN
const routingInstrumentation = new Sentry.ReactNavigationInstrumentation()
@@ -29,8 +32,7 @@ Sentry.init({
function RootLayout(): JSX.Element {
const router = useRouter()
- const theme = useColorScheme()
- const colorText = theme === 'dark' ? 'white' : 'black' // Use the theme value instead of dark
+ const { theme: appTheme } = useContext(ThemeContext)
const { t } = useTranslation('navigation')
const ref = useNavigationContainerRef()
@@ -81,364 +83,380 @@ function RootLayout(): JSX.Element {
}
}, [])
+ const isDark = useTheme().dark
+
return (
<>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ({
- title: 'App Icon',
- animation: 'slide_from_right',
- presentation:
- route.params?.fromAppShortcut === 'true'
- ? 'modal'
- : undefined,
- })}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
- (
- {
- router.push('(pages)/libraryCode')
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ({
+ title: 'App Icon',
+ animation: 'slide_from_right',
+ presentation:
+ route.params?.fromAppShortcut === 'true'
+ ? 'modal'
+ : undefined,
+ })}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (
+ {
+ router.push('(pages)/libraryCode')
+ }}
+ >
+
-
-
- ),
- }}
- />
-
-
-
-
-
-
-
+ android={{
+ name: 'barcode_scanner',
+ size: 24,
+ }}
+ />
+
+ ),
+ }}
+ />
+
+
+
+
+
+
>
)
}
-export default Sentry.wrap(RootLayout)
+const ProviderComponent = (): JSX.Element => {
+ return (
+
+
+
+ )
+}
+
+export default Sentry.wrap(ProviderComponent)
diff --git a/src/components/Elements/Food/HeaderRight.tsx b/src/components/Elements/Food/HeaderRight.tsx
index 7eb8bc47..61cfa1fd 100644
--- a/src/components/Elements/Food/HeaderRight.tsx
+++ b/src/components/Elements/Food/HeaderRight.tsx
@@ -1,5 +1,4 @@
-import { type Colors } from '@/components/colors'
-import { type Theme, useTheme } from '@react-navigation/native'
+import { useTheme } from '@react-navigation/native'
import { router } from 'expo-router'
import React from 'react'
import { Pressable, StyleSheet, View } from 'react-native'
@@ -7,8 +6,7 @@ import { Pressable, StyleSheet, View } from 'react-native'
import PlatformIcon from '../Universal/Icon'
export const FoodHeaderRight = (): JSX.Element => {
- const theme: Theme = useTheme()
- const colors = theme.colors as Colors
+ const isDark = useTheme().dark
return (
{
>
('')
let filteredEntries = Object.entries(data)
@@ -56,11 +64,11 @@ const ItemsPickerScreen = (params: {
placeholder: t(`navigation.${placeholderKey}`, {
ns: 'navigation',
}),
-
+ textColor: colors.text,
+ barTintColor: getBarTintColor(theme, isDark),
...Platform.select({
android: {
headerIconColor: colors.text,
- textColor: colors.text,
hintTextColor: colors.text,
},
}),
@@ -71,11 +79,10 @@ const ItemsPickerScreen = (params: {
},
},
})
- }, [navigation, colors.text])
+ }, [navigation, colors.text, isDark])
return (
-
- {t('empty.' + type)}
+ {t(
+ // @ts-expect-error Translation key is dynamic
+ 'empty.' + type
+ )}
)}
diff --git a/src/components/Elements/Map/BottomSheetBackground.tsx b/src/components/Elements/Map/BottomSheetBackground.tsx
index 4d3f8d86..4d9dd356 100644
--- a/src/components/Elements/Map/BottomSheetBackground.tsx
+++ b/src/components/Elements/Map/BottomSheetBackground.tsx
@@ -16,7 +16,11 @@ const BottomSheetBackground = (): JSX.Element => {
},
]}
>
-
+
) : (
{
+ const colors = useTheme().colors as Colors
+ const { t } = useTranslation('common')
+
+ return (
+
+ {
+ void Linking.openURL(
+ 'https://www.openstreetmap.org/copyright'
+ )
+ }}
+ style={styles.attributionLink}
+ >
+
+ {t('pages.map.details.osm')}
+
+
+
+
+ )
+}
+
const MapBottomSheet: React.FC = ({
bottomSheetRef,
currentPosition,
@@ -118,7 +156,7 @@ const MapBottomSheet: React.FC = ({
addUnlockedAppIcon('retro')
}
}, [localSearch])
-
+ const textInputRef = useRef(null)
return (
= ({
backgroundComponent={BottomSheetBackground}
animatedPosition={currentPosition}
keyboardBehavior="extend"
+ onChange={(index) => {
+ if (index <= 1) {
+ localSearch !== '' && setLocalSearch('')
+ textInputRef.current?.blur()
+ }
+ }}
>
= ({
>
{Platform.OS === 'ios' ? (
= ({
onFocus={() => {
bottomSheetRef.current?.snapToIndex(2)
}}
- onEndEditing={() => {
- bottomSheetRef.current?.collapse()
- }}
/>
) : (
= ({
}}
/>
)}
+
{localSearch !== '' ? (
searchResultsExact.length > 0 ||
searchResultsFuzzy.length > 0 ? (
@@ -224,6 +268,9 @@ const MapBottomSheet: React.FC = ({
bottomSheetRef={bottomSheetRef}
/>
)}
+ ItemSeparatorComponent={() => (
+
+ )}
stickySectionHeadersEnabled={false}
renderSectionHeader={({
section: { title },
@@ -249,14 +296,17 @@ const MapBottomSheet: React.FC = ({
)
) : userKind === USER_GUEST ? (
-
- {t('pages.map.details.room.signIn')}
-
+
+
+ {t('pages.map.details.room.signIn')}
+
+
+
) : (
<>
{nextLecture !== null && nextLecture.length > 0 && (
@@ -623,39 +673,10 @@ const MapBottomSheet: React.FC = ({
)}
+
>
)}
-
- {
- void Linking.openURL(
- 'https://www.openstreetmap.org/copyright'
- )
- }}
- style={styles.attributionLink}
- >
-
- {t('pages.map.details.osm')}
-
-
-
-
)
@@ -760,6 +781,10 @@ const styles = StyleSheet.create({
},
attributionText: {
fontSize: 15,
- paddingStart: 12,
+ paddingStart: 4,
+ },
+ guestContainer: {
+ paddingTop: 15,
+ gap: 35,
},
})
diff --git a/src/components/Elements/Map/MapScreen.tsx b/src/components/Elements/Map/MapScreen.tsx
index dc0fb9f6..0544ce60 100644
--- a/src/components/Elements/Map/MapScreen.tsx
+++ b/src/components/Elements/Map/MapScreen.tsx
@@ -36,7 +36,7 @@ import { type BottomSheetModal } from '@gorhom/bottom-sheet'
import MapLibreGL from '@maplibre/maplibre-react-native'
import { useTheme } from '@react-navigation/native'
import { useQuery } from '@tanstack/react-query'
-import { useFocusEffect, useNavigation } from 'expo-router'
+import { useNavigation } from 'expo-router'
import {
type Feature,
type FeatureCollection,
@@ -47,6 +47,7 @@ import React, {
useCallback,
useContext,
useEffect,
+ useLayoutEffect,
useMemo,
useRef,
useState,
@@ -54,11 +55,11 @@ import React, {
import { useTranslation } from 'react-i18next'
import {
ActivityIndicator,
+ Appearance,
LayoutAnimation,
Linking,
Platform,
Pressable,
- StatusBar,
StyleSheet,
Text,
View,
@@ -119,8 +120,17 @@ const MapScreen = (): JSX.Element => {
const locations: LocationsType = Locations
const [isVisible, setIsVisible] = useState(true)
const opacity = useSharedValue(1)
+
+ // needed for Android
void MapLibreGL.setAccessToken(null)
+ useLayoutEffect(() => {
+ navigation.setOptions({
+ statusbarTranslucent: true,
+ statusBarColor: undefined,
+ })
+ }, [navigation])
+
const toggleShowAllFloors = (): void => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
setShowAllFloors(!showAllFloors)
@@ -133,6 +143,7 @@ const MapScreen = (): JSX.Element => {
return {
transform: [{ translateY: bottom }],
+ height: opacity.value === 0 ? 0 : 'auto',
opacity: opacity.value,
}
})
@@ -221,6 +232,16 @@ const MapScreen = (): JSX.Element => {
return nextEvent != null ? [nextEvent] : []
}
+ useEffect(() => {
+ const subscription = Appearance.addChangeListener(() => {
+ bottomSheetModalRef.current?.close()
+ })
+
+ return () => {
+ subscription.remove()
+ }
+ })
+
useEffect(() => {
if (timetable == null) {
return
@@ -592,13 +613,6 @@ const MapScreen = (): JSX.Element => {
}
}, [clickedElement])
- useFocusEffect(() => {
- StatusBar.setBarStyle(theme.dark ? 'light-content' : 'dark-content')
- return () => {
- StatusBar.setBarStyle('default')
- }
- })
-
useEffect(() => {
setDisableFollowUser(false)
}, [cameraTriggerKey])
@@ -616,7 +630,7 @@ const MapScreen = (): JSX.Element => {
fillOpacity: 0.1,
},
allRoomsOutline: {
- lineColor: isDark ? '#2d3035' : '#979797',
+ lineColor: isDark ? '#2d3035' : '#8e8e8e',
lineWidth: 2.3,
},
availableRooms: {
@@ -624,12 +638,12 @@ const MapScreen = (): JSX.Element => {
fillOpacity: 0.2,
},
availableRoomsOutline: {
- lineWidth: 2.3,
+ lineWidth: 2.4,
},
osmBackground: {
backgroundColor: isDark
- ? 'rgba(166, 173, 181, 0.69)'
- : 'rgba(222, 221, 203, 0.69)',
+ ? 'rgba(166, 173, 181, 0.70)'
+ : 'rgba(218, 218, 218, 0.70)',
paddingHorizontal: 4,
borderRadius: 4,
},
@@ -716,6 +730,14 @@ const MapScreen = (): JSX.Element => {
onRegionIsChanging={() => {
setRegionChange(true)
}}
+ compassViewMargins={
+ Platform.OS === 'android'
+ ? {
+ x: 5,
+ y: 70,
+ }
+ : undefined
+ }
>
{
clickedElement == null &&
!disableFollowUser
}
+ triggerKey={cameraTriggerKey}
followUserMode={MapLibreGL.UserTrackingMode.Follow}
/>
{
const { setClickedElement, setLocalSearch, setCurrentFloor } =
useContext(MapContext)
- console.log(result)
return (
-
- {
- const center = result.item.properties?.center
- Keyboard.dismiss()
- bottomSheetRef.current?.collapse()
- // _setView(center, mapRef)
- setClickedElement({
- data: result.title,
- type: result.item.properties?.rtype,
- center,
- manual: false,
- })
- setCurrentFloor({
- floor:
- (result.item.properties?.Ebene as string) ?? 'EG',
- manual: false,
- })
- handlePresentModalPress()
- // _injectMarker(mapRef, center, colors)
- setLocalSearch('')
+ {
+ const center = result.item.properties?.center
+ Keyboard.dismiss()
+ bottomSheetRef.current?.collapse()
+ // _setView(center, mapRef)
+ setClickedElement({
+ data: result.title,
+ type: result.item.properties?.rtype,
+ center,
+ manual: false,
+ })
+ setCurrentFloor({
+ floor: (result.item.properties?.Ebene as string) ?? 'EG',
+ manual: false,
+ })
+ handlePresentModalPress()
+ // _injectMarker(mapRef, center, colors)
+ setLocalSearch('')
+ }}
+ >
+
-
+
+
+
+
-
-
-
-
-
- {result.title}
-
-
- {result.subtitle}
-
-
-
- {index !== 9 && }
-
+ {result.title}
+
+
+ {result.subtitle}
+
+
+
)
}
diff --git a/src/components/Elements/Timetable/HeaderButtons.tsx b/src/components/Elements/Timetable/HeaderButtons.tsx
index 8659e292..a6ab66b1 100644
--- a/src/components/Elements/Timetable/HeaderButtons.tsx
+++ b/src/components/Elements/Timetable/HeaderButtons.tsx
@@ -1,4 +1,3 @@
-import { type Colors } from '@/components/colors'
import { TimetableContext } from '@/components/contexts'
import { trackEvent } from '@aptabase/react-native'
import { useTheme } from '@react-navigation/native'
@@ -8,7 +7,7 @@ import { Pressable, StyleSheet } from 'react-native'
import PlatformIcon from '../Universal/Icon'
export function HeaderLeft(): JSX.Element {
- const colors = useTheme().colors as Colors
+ const isDark = useTheme().dark
const { timetableMode, setTimetableMode } = useContext(TimetableContext)
return (
@@ -24,7 +23,7 @@ export function HeaderLeft(): JSX.Element {
style={styles.headerButton}
>
-
-
-
-
-
- >
+
+
+
+
+
)
}
diff --git a/src/components/contexts.ts b/src/components/contexts.ts
index b011c330..4c1f61b1 100644
--- a/src/components/contexts.ts
+++ b/src/components/contexts.ts
@@ -42,6 +42,8 @@ export const UserKindContext = createContext({
})
export const ThemeContext = createContext({
+ theme: 'auto',
+ toggleTheme: () => {},
accentColor: 'blue',
toggleAccentColor: () => {},
})
diff --git a/src/components/provider.tsx b/src/components/provider.tsx
index 4804860a..b2780c88 100644
--- a/src/components/provider.tsx
+++ b/src/components/provider.tsx
@@ -12,6 +12,7 @@ import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persi
import { QueryClient, focusManager } from '@tanstack/react-query'
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
import { usePathname } from 'expo-router'
+import 'expo-status-bar'
import React, { useEffect } from 'react'
import {
type AppStateStatus,
@@ -144,6 +145,15 @@ export default function Provider({
})
}, [themeHook.accentColor, flow.analyticsInitialized])
+ useEffect(() => {
+ if (!flow.analyticsInitialized) {
+ return
+ }
+ trackEvent('Theme', {
+ theme: themeHook.theme,
+ })
+ }, [themeHook.accentColor, flow.analyticsInitialized])
+
useEffect(() => {
if (!flow.analyticsInitialized) {
return
@@ -236,15 +246,20 @@ export default function Provider({
})
}, [flow.analyticsAllowed, flow.analyticsInitialized])
+ const getTheme = (): AppTheme => {
+ if (themeHook.theme === 'auto') {
+ return colorScheme === 'dark' ? darkTheme : lightTheme
+ }
+ return themeHook.theme === 'dark' ? darkTheme : lightTheme
+ }
+
return (
-
+
diff --git a/src/hooks/contexts/theme.ts b/src/hooks/contexts/theme.ts
index f9b38ed0..06de040d 100644
--- a/src/hooks/contexts/theme.ts
+++ b/src/hooks/contexts/theme.ts
@@ -3,7 +3,9 @@ import { useEffect, useState } from 'react'
export interface ThemeHook {
accentColor: string
+ theme: 'light' | 'dark' | 'auto'
toggleAccentColor: (name: string) => void
+ toggleTheme: (theme: 'light' | 'dark' | 'auto') => void
}
/**
@@ -13,16 +15,27 @@ export interface ThemeHook {
*/
export function useTheme(): ThemeHook {
const [accentColor, setAccentColor] = useState('blue')
+ const [theme, setTheme] = useState<'light' | 'dark' | 'auto'>('auto')
useEffect(() => {
const loadAsyncStorageData = async (): Promise => {
try {
- const data = await AsyncStorage.getItem('accentColor')
- if (data != null) {
- setAccentColor(data)
+ const keys = ['accentColor', 'theme']
+ const [accentColorData, themeData] = await Promise.all(
+ keys.map(async (key) => await AsyncStorage.getItem(key))
+ )
+
+ if (accentColorData != null) {
+ setAccentColor(accentColorData)
} else {
setAccentColor('blue')
}
+
+ if (themeData != null) {
+ setTheme(themeData as 'light' | 'dark' | 'auto')
+ } else {
+ setTheme('auto')
+ }
} catch (error) {
console.error(
'Error while retrieving theme data from AsyncStorage:',
@@ -42,8 +55,19 @@ export function useTheme(): ThemeHook {
void AsyncStorage.setItem('accentColor', name)
}
+ /**
+ * Function to toggle the theme of the app.
+ * @param theme - The new theme to be set.
+ */
+ function toggleTheme(theme: 'light' | 'dark' | 'auto'): void {
+ setTheme(theme)
+ void AsyncStorage.setItem('theme', theme)
+ }
+
return {
accentColor,
toggleAccentColor,
+ theme,
+ toggleTheme,
}
}
diff --git a/src/localization/de/settings.ts b/src/localization/de/settings.ts
index 18a4f66b..fbbf70e8 100644
--- a/src/localization/de/settings.ts
+++ b/src/localization/de/settings.ts
@@ -21,10 +21,13 @@ export default {
formlist: {
preferences: {
title: 'Einstellungen',
- theme: 'Design',
food: 'Essen',
language: 'Sprache',
},
+ appearance: {
+ title: 'Darstellung',
+ theme: 'Design',
+ },
language: {
title: 'Sprache wechseln',
message:
@@ -118,10 +121,6 @@ export default {
accent: {
title: 'Akzentfarbe',
},
- exclusive: {
- title: 'Exklusive Designs',
- description: 'Demnächst verfügbar',
- },
colors: {
teal: 'Türkis',
blue: 'Blau',
@@ -133,7 +132,13 @@ export default {
orange: 'Orange',
green: 'Neuland',
},
- footer: 'Ändere die Akzentfarbe, um deinen Stil zu entsprechen. Das ändert die Farbe der Symbole und Schaltflächen in der App.',
+ footer: 'Das ändert die Farbe der Symbole und Schaltflächen in der App.',
+ themes: {
+ title: 'Design',
+ default: 'Automatisch',
+ dark: 'Dunkel',
+ light: 'Hell',
+ },
},
dashboard: {
shown: 'Angezeigte Karten',
diff --git a/src/localization/en/navigation.ts b/src/localization/en/navigation.ts
index 73fb8fce..dc65a0f9 100644
--- a/src/localization/en/navigation.ts
+++ b/src/localization/en/navigation.ts
@@ -11,7 +11,7 @@ export default {
allergensSearch: 'Search allergens',
allergens: 'Allergens',
details: 'Details',
- theme: 'Appearance',
+ theme: 'Design',
profile: 'Profile',
about: 'About',
advancedSearch: 'Advanced Search',
diff --git a/src/localization/en/settings.ts b/src/localization/en/settings.ts
index 62fe1524..dab4db01 100644
--- a/src/localization/en/settings.ts
+++ b/src/localization/en/settings.ts
@@ -19,10 +19,13 @@ export default {
formlist: {
preferences: {
title: 'Preferences',
- theme: 'Appearance',
food: 'Food',
language: 'Language',
},
+ appearance: {
+ title: 'Appearance',
+ theme: 'Design',
+ },
language: {
title: 'Change language',
message: 'Confirm to change the app language to German.',
@@ -116,10 +119,6 @@ export default {
accent: {
title: 'Accent color',
},
- exclusive: {
- title: 'Exclusive designs',
- description: 'Coming soon',
- },
colors: {
teal: 'Teal',
blue: 'Blue',
@@ -131,7 +130,13 @@ export default {
orange: 'Orange',
green: 'Neuland',
},
- footer: 'Change the accent color to match your style. This changes the color of the icons and buttons in the app.',
+ footer: 'This changes the color of the icons and buttons in the app.',
+ themes: {
+ title: 'Theme',
+ default: 'Automatic',
+ dark: 'Dark',
+ light: 'Light',
+ },
},
dashboard: {
shown: 'Shown cards',
diff --git a/src/utils/ui-utils.ts b/src/utils/ui-utils.ts
index 19a69159..8d8bd3ad 100644
--- a/src/utils/ui-utils.ts
+++ b/src/utils/ui-utils.ts
@@ -110,17 +110,6 @@ export const lighten = (percentage: number, color: string): string => {
return newColor
}
-/**
- * Returns the appropriate status bar style based on the platform. Used for Status Bar component in modal screens.
- * @returns The appropriate status bar style.
- * @example
- * // Usage
- *
- */
-export const getStatusBarStyle = (): 'light' | 'auto' => {
- return Platform.OS === 'ios' ? (Platform.isPad ? 'auto' : 'light') : 'auto'
-}
-
let toast: any = null
export const showToast = async (
message: string,
@@ -140,3 +129,36 @@ export const showToast = async (
...options,
})
}
+
+export const getStatusBarStyle = (
+ theme: 'light' | 'dark' | 'auto'
+): 'light' | 'dark' | 'auto' => {
+ switch (theme) {
+ case 'light':
+ return 'dark'
+ case 'dark':
+ return 'light'
+ default:
+ return 'auto'
+ }
+}
+
+export function getBarTintColor(
+ theme: string,
+ isDark: boolean
+): string | undefined {
+ if (Platform.OS === 'android') {
+ return undefined
+ }
+ const darkDarkColor = '#2e2e2e'
+ const darkLightColor = '#8f8f8f'
+ const lightDarkColor = '#999999'
+ const lightLightColor = '#ebebeb'
+ if (theme === 'auto') {
+ return undefined
+ }
+ if (theme === 'dark') {
+ return isDark ? darkDarkColor : darkLightColor
+ }
+ return isDark ? lightDarkColor : lightLightColor
+}