diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index b59c9c47..291670d5 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -6,44 +6,46 @@ To contribute, please follow these guidelines:
## Getting Started
-1. Fork the repository and clone it to your local machine.
-2. Install the required software:
+1. Fork the repository and clone it to your local machine.
+2. Install the required software:
- - [Visual Studio Code](https://code.visualstudio.com/)
+ - [Visual Studio Code](https://code.visualstudio.com/)
- [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12) (for iOS development, macOS only)
- [Android Studio](https://developer.android.com/studio) (for Android development)
-3. Install the required dependencies:
+3. Install the required dependencies:
- [Node.js 18 LTS](https://nodejs.org/en/) or higher
- [Bun](https://bun.sh) or simply use npm or yarn
- [Watchman](https://facebook.github.io/watchman/docs/install) (for Linux or macOS users)
-4. In addition is recommended to use the [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) and [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) extensions for your IDE to get real-time feedback on your code.
+4. In addition is recommended to use the [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) and [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) extensions for your IDE to get real-time feedback on your code.
-5. Install project dependencies by running the following command in the project directory:
+5. Install project dependencies by running the following command in the project directory:
```bash
bun install
```
-6. Setup the emulator
+6. Setup the emulator
Android (_Windows, macOS, and Linux_):
- - Follow the [official guide](https://docs.expo.dev/workflow/android-studio-emulator/) to set up the Android emulator.
-
+
+ - Follow the [official guide](https://docs.expo.dev/workflow/android-studio-emulator/) to set up the Android emulator.
+
iOS (_macOS only_):
- - Follow the [official guide](https://docs.expo.dev/workflow/ios-simulator/) to set up the iOS simulator.
+ - Follow the [official guide](https://docs.expo.dev/workflow/ios-simulator/) to set up the iOS simulator.
-5. Create a development build for your desired platform.
+7. Create a development build for your desired platform.
- ```bash
- bun ios
- bun android
- ```
-> [!NOTE]
-> Step 7 is required every time the dependencies are updated.
+ ```bash
+ bun ios
+ bun android
+ ```
+
+ > [!NOTE]
+ > Step 7 is required every time the dependencies are updated.
## Development
@@ -51,11 +53,11 @@ To contribute, please follow these guidelines:
2. Make your changes and ensure that the code follows our coding style and conventions.
3. Run the app locally with Expo by running the following command in the project directory:
- ```bash
+ ```bash
bun start
```
- This allows you to run the app one the previously created development build. Expo Go is not supported for development.
+ This allows you to run the app one the previously created development build. Expo Go is not supported for development.
## Submitting Changes
diff --git a/android/.gitignore b/android/.gitignore
index 877b87e9..8a6be077 100644
--- a/android/.gitignore
+++ b/android/.gitignore
@@ -10,6 +10,7 @@ build/
local.properties
*.iml
*.hprof
+.cxx/
# Bundle artifacts
*.jsbundle
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 7cbac98a..a6ce7f1f 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -1,4 +1,5 @@
apply plugin: "com.android.application"
+apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
@@ -11,11 +12,11 @@ react {
entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim())
reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc"
- codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
+ codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
// Use Expo CLI to bundle the app, this ensures the Metro config
// works correctly with Expo projects.
- cliFile = new File(["node", "--print", "require.resolve('@expo/cli')"].execute(null, rootDir).text.trim())
+ cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim())
bundleCommand = "export:embed"
/* Folders */
@@ -76,21 +77,21 @@ def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInRelea
*/
def jscFlavor = 'org.webkit:android-jsc:+'
-apply from: new File(["node", "--print", "require.resolve('@sentry/react-native/package.json')"].execute().text.trim(), "../sentry.gradle")
+apply from: new File(["node", "--print", "require('path').dirname(require.resolve('@sentry/react-native/package.json'))"].execute().text.trim(), "sentry.gradle")
+
android {
ndkVersion rootProject.ext.ndkVersion
- compileSdkVersion rootProject.ext.compileSdkVersion
+ buildToolsVersion rootProject.ext.buildToolsVersion
+ compileSdk rootProject.ext.compileSdkVersion
namespace 'app.neuland'
defaultConfig {
applicationId 'app.neuland'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 78
- versionName "0.7.1"
-
- buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString())
+ versionCode 83
+ versionName "0.8.1"
}
signingConfigs {
debug {
@@ -113,6 +114,11 @@ android {
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
+ packagingOptions {
+ jniLibs {
+ useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false)
+ }
+ }
}
// Apply static values from `gradle.properties` to the `android.packagingOptions`
@@ -142,34 +148,21 @@ dependencies {
def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true";
- def frescoVersion = rootProject.ext.frescoVersion
-
- // If your app supports Android versions before Ice Cream Sandwich (API level 14)
- if (isGifEnabled || isWebpEnabled) {
- implementation("com.facebook.fresco:fresco:${frescoVersion}")
- implementation("com.facebook.fresco:imagepipeline-okhttp3:${frescoVersion}")
- }
if (isGifEnabled) {
// For animated gif support
- implementation("com.facebook.fresco:animated-gif:${frescoVersion}")
+ implementation("com.facebook.fresco:animated-gif:${reactAndroidLibs.versions.fresco.get()}")
}
if (isWebpEnabled) {
// For webp support
- implementation("com.facebook.fresco:webpsupport:${frescoVersion}")
+ implementation("com.facebook.fresco:webpsupport:${reactAndroidLibs.versions.fresco.get()}")
if (isWebpAnimatedEnabled) {
// Animated webp support
- implementation("com.facebook.fresco:animated-webp:${frescoVersion}")
+ implementation("com.facebook.fresco:animated-webp:${reactAndroidLibs.versions.fresco.get()}")
}
}
- debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
- debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
- exclude group:'com.squareup.okhttp3', module:'okhttp'
- }
- debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
-
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
@@ -177,5 +170,5 @@ dependencies {
}
}
-apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
+apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
applyNativeModulesAppBuildGradle(project)
diff --git a/android/app/src/debug/java/app/neuland/ReactNativeFlipper.java b/android/app/src/debug/java/app/neuland/ReactNativeFlipper.java
deleted file mode 100644
index 6ba7abb3..00000000
--- a/android/app/src/debug/java/app/neuland/ReactNativeFlipper.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- *
This source code is licensed under the MIT license found in the LICENSE file in the root
- * directory of this source tree.
- */
-package app.neuland;
-
-import android.content.Context;
-import com.facebook.flipper.android.AndroidFlipperClient;
-import com.facebook.flipper.android.utils.FlipperUtils;
-import com.facebook.flipper.core.FlipperClient;
-import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
-import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
-import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
-import com.facebook.flipper.plugins.inspector.DescriptorMapping;
-import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
-import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
-import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
-import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
-import com.facebook.react.ReactInstanceEventListener;
-import com.facebook.react.ReactInstanceManager;
-import com.facebook.react.bridge.ReactContext;
-import com.facebook.react.modules.network.NetworkingModule;
-import okhttp3.OkHttpClient;
-
-/**
- * Class responsible of loading Flipper inside your React Native application. This is the debug
- * flavor of it. Here you can add your own plugins and customize the Flipper setup.
- */
-public class ReactNativeFlipper {
- public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
- if (FlipperUtils.shouldEnableFlipper(context)) {
- final FlipperClient client = AndroidFlipperClient.getInstance(context);
-
- client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
- client.addPlugin(new DatabasesFlipperPlugin(context));
- client.addPlugin(new SharedPreferencesFlipperPlugin(context));
- client.addPlugin(CrashReporterPlugin.getInstance());
-
- NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
- NetworkingModule.setCustomClientBuilder(
- new NetworkingModule.CustomClientBuilder() {
- @Override
- public void apply(OkHttpClient.Builder builder) {
- builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
- }
- });
- client.addPlugin(networkFlipperPlugin);
- client.start();
-
- // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
- // Hence we run if after all native modules have been initialized
- ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
- if (reactContext == null) {
- reactInstanceManager.addReactInstanceEventListener(
- new ReactInstanceEventListener() {
- @Override
- public void onReactContextInitialized(ReactContext reactContext) {
- reactInstanceManager.removeReactInstanceEventListener(this);
- reactContext.runOnNativeModulesQueueThread(
- new Runnable() {
- @Override
- public void run() {
- client.addPlugin(new FrescoFlipperPlugin());
- }
- });
- }
- });
- } else {
- client.addPlugin(new FrescoFlipperPlugin());
- }
- }
- }
-}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 33ead621..f986f06b 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -2,7 +2,6 @@
-
@@ -18,12 +17,11 @@
-
+
-
-
+
diff --git a/android/app/src/main/ic_launcher-playstore.png b/android/app/src/main/ic_launcher-playstore.png
index afe7d9ff..b8dfe191 100644
Binary files a/android/app/src/main/ic_launcher-playstore.png and b/android/app/src/main/ic_launcher-playstore.png differ
diff --git a/android/app/src/main/java/app/neuland/MainActivity.java b/android/app/src/main/java/app/neuland/MainActivity.java
deleted file mode 100644
index 2087b356..00000000
--- a/android/app/src/main/java/app/neuland/MainActivity.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package app.neuland;
-
-import android.os.Build;
-import android.os.Bundle;
-
-import com.facebook.react.ReactActivity;
-import com.facebook.react.ReactActivityDelegate;
-import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
-import com.facebook.react.defaults.DefaultReactActivityDelegate;
-
-import expo.modules.ReactActivityDelegateWrapper;
-
-public class MainActivity extends ReactActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // Set the theme to AppTheme BEFORE onCreate to support
- // coloring the background, status bar, and navigation bar.
- // This is required for expo-splash-screen.
- setTheme(R.style.AppTheme);
- super.onCreate(null);
- }
-
- /**
- * Returns the name of the main component registered from JavaScript.
- * This is used to schedule rendering of the component.
- */
- @Override
- protected String getMainComponentName() {
- return "main";
- }
-
- /**
- * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
- * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
- * (aka React 18) with two boolean flags.
- */
- @Override
- protected ReactActivityDelegate createReactActivityDelegate() {
- return new ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, new DefaultReactActivityDelegate(
- this,
- getMainComponentName(),
- // If you opted-in for the New Architecture, we enable the Fabric Renderer.
- DefaultNewArchitectureEntryPoint.getFabricEnabled()));
- }
-
- /**
- * Align the back button behavior with Android S
- * where moving root activities to background instead of finishing activities.
- * @see onBackPressed
- */
- @Override
- public void invokeDefaultOnBackPressed() {
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
- if (!moveTaskToBack(false)) {
- // For non-root activities, use the default implementation to finish them.
- super.invokeDefaultOnBackPressed();
- }
- return;
- }
-
- // Use the default back button implementation on Android S
- // because it's doing more than {@link Activity#moveTaskToBack} in fact.
- super.invokeDefaultOnBackPressed();
- }
-}
diff --git a/android/app/src/main/java/app/neuland/MainActivity.kt b/android/app/src/main/java/app/neuland/MainActivity.kt
new file mode 100644
index 00000000..22bc8766
--- /dev/null
+++ b/android/app/src/main/java/app/neuland/MainActivity.kt
@@ -0,0 +1,61 @@
+package app.neuland
+
+import android.os.Build
+import android.os.Bundle
+
+import com.facebook.react.ReactActivity
+import com.facebook.react.ReactActivityDelegate
+import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
+import com.facebook.react.defaults.DefaultReactActivityDelegate
+
+import expo.modules.ReactActivityDelegateWrapper
+
+class MainActivity : ReactActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ // Set the theme to AppTheme BEFORE onCreate to support
+ // coloring the background, status bar, and navigation bar.
+ // This is required for expo-splash-screen.
+ setTheme(R.style.AppTheme);
+ super.onCreate(null)
+ }
+
+ /**
+ * Returns the name of the main component registered from JavaScript. This is used to schedule
+ * rendering of the component.
+ */
+ override fun getMainComponentName(): String = "main"
+
+ /**
+ * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
+ * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
+ */
+ override fun createReactActivityDelegate(): ReactActivityDelegate {
+ return ReactActivityDelegateWrapper(
+ this,
+ BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
+ object : DefaultReactActivityDelegate(
+ this,
+ mainComponentName,
+ fabricEnabled
+ ){})
+ }
+
+ /**
+ * Align the back button behavior with Android S
+ * where moving root activities to background instead of finishing activities.
+ * @see onBackPressed
+ */
+ override fun invokeDefaultOnBackPressed() {
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
+ if (!moveTaskToBack(false)) {
+ // For non-root activities, use the default implementation to finish them.
+ super.invokeDefaultOnBackPressed()
+ }
+ return
+ }
+
+ // Use the default back button implementation on Android S
+ // because it's doing more than [Activity.moveTaskToBack] in fact.
+ super.invokeDefaultOnBackPressed()
+ }
+}
diff --git a/android/app/src/main/java/app/neuland/MainApplication.java b/android/app/src/main/java/app/neuland/MainApplication.java
deleted file mode 100644
index c1ff74ef..00000000
--- a/android/app/src/main/java/app/neuland/MainApplication.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package app.neuland;
-
-import android.app.Application;
-import android.content.res.Configuration;
-import androidx.annotation.NonNull;
-
-import com.facebook.react.PackageList;
-import com.facebook.react.ReactApplication;
-import com.facebook.react.ReactNativeHost;
-import com.facebook.react.ReactPackage;
-import com.facebook.react.config.ReactFeatureFlags;
-import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
-import com.facebook.react.defaults.DefaultReactNativeHost;
-import com.facebook.soloader.SoLoader;
-
-import expo.modules.ApplicationLifecycleDispatcher;
-import expo.modules.ReactNativeHostWrapper;
-
-import java.util.List;
-
-public class MainApplication extends Application implements ReactApplication {
-
- private final ReactNativeHost mReactNativeHost =
- new ReactNativeHostWrapper(this, new DefaultReactNativeHost(this) {
- @Override
- public boolean getUseDeveloperSupport() {
- return BuildConfig.DEBUG;
- }
-
- @Override
- protected List getPackages() {
- @SuppressWarnings("UnnecessaryLocalVariable")
- List packages = new PackageList(this).getPackages();
- // Packages that cannot be autolinked yet can be added manually here, for example:
- // packages.add(new MyReactNativePackage());
- return packages;
- }
-
- @Override
- protected String getJSMainModuleName() {
- return ".expo/.virtual-metro-entry";
- }
-
- @Override
- protected boolean isNewArchEnabled() {
- return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
- }
-
- @Override
- protected Boolean isHermesEnabled() {
- return BuildConfig.IS_HERMES_ENABLED;
- }
- });
-
- @Override
- public ReactNativeHost getReactNativeHost() {
- return mReactNativeHost;
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- SoLoader.init(this, /* native exopackage */ false);
- if (!BuildConfig.REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS) {
- ReactFeatureFlags.unstable_useRuntimeSchedulerAlways = false;
- }
- if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
- // If you opted-in for the New Architecture, we load the native entry point for this app.
- DefaultNewArchitectureEntryPoint.load();
- }
- ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
- ApplicationLifecycleDispatcher.onApplicationCreate(this);
- }
-
- @Override
- public void onConfigurationChanged(@NonNull Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
- }
-}
diff --git a/android/app/src/main/java/app/neuland/MainApplication.kt b/android/app/src/main/java/app/neuland/MainApplication.kt
new file mode 100644
index 00000000..a905c998
--- /dev/null
+++ b/android/app/src/main/java/app/neuland/MainApplication.kt
@@ -0,0 +1,55 @@
+package app.neuland
+
+import android.app.Application
+import android.content.res.Configuration
+
+import com.facebook.react.PackageList
+import com.facebook.react.ReactApplication
+import com.facebook.react.ReactNativeHost
+import com.facebook.react.ReactPackage
+import com.facebook.react.ReactHost
+import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
+import com.facebook.react.defaults.DefaultReactNativeHost
+import com.facebook.soloader.SoLoader
+
+import expo.modules.ApplicationLifecycleDispatcher
+import expo.modules.ReactNativeHostWrapper
+
+class MainApplication : Application(), ReactApplication {
+
+ override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(
+ this,
+ object : DefaultReactNativeHost(this) {
+ override fun getPackages(): List {
+ // Packages that cannot be autolinked yet can be added manually here, for example:
+ // packages.add(new MyReactNativePackage());
+ return PackageList(this).packages
+ }
+
+ override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
+
+ override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
+
+ override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
+ override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
+ }
+ )
+
+ override val reactHost: ReactHost
+ get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost)
+
+ override fun onCreate() {
+ super.onCreate()
+ SoLoader.init(this, false)
+ if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
+ // If you opted-in for the New Architecture, we load the native entry point for this app.
+ load()
+ }
+ ApplicationLifecycleDispatcher.onApplicationCreate(this)
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig)
+ }
+}
diff --git a/android/app/src/main/res/drawable-hdpi/splashscreen_image.png b/android/app/src/main/res/drawable-hdpi/splashscreen_image.png
index fe65e568..29815e62 100644
Binary files a/android/app/src/main/res/drawable-hdpi/splashscreen_image.png and b/android/app/src/main/res/drawable-hdpi/splashscreen_image.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/splashscreen_image.png b/android/app/src/main/res/drawable-mdpi/splashscreen_image.png
index fe65e568..29815e62 100644
Binary files a/android/app/src/main/res/drawable-mdpi/splashscreen_image.png and b/android/app/src/main/res/drawable-mdpi/splashscreen_image.png differ
diff --git a/android/app/src/main/res/drawable-night-hdpi/splashscreen_image.png b/android/app/src/main/res/drawable-night-hdpi/splashscreen_image.png
new file mode 100644
index 00000000..58aee83c
Binary files /dev/null and b/android/app/src/main/res/drawable-night-hdpi/splashscreen_image.png differ
diff --git a/android/app/src/main/res/drawable-night-mdpi/splashscreen_image.png b/android/app/src/main/res/drawable-night-mdpi/splashscreen_image.png
new file mode 100644
index 00000000..58aee83c
Binary files /dev/null and b/android/app/src/main/res/drawable-night-mdpi/splashscreen_image.png differ
diff --git a/android/app/src/main/res/drawable-night-xhdpi/splashscreen_image.png b/android/app/src/main/res/drawable-night-xhdpi/splashscreen_image.png
new file mode 100644
index 00000000..58aee83c
Binary files /dev/null and b/android/app/src/main/res/drawable-night-xhdpi/splashscreen_image.png differ
diff --git a/android/app/src/main/res/drawable-night-xxhdpi/splashscreen_image.png b/android/app/src/main/res/drawable-night-xxhdpi/splashscreen_image.png
new file mode 100644
index 00000000..58aee83c
Binary files /dev/null and b/android/app/src/main/res/drawable-night-xxhdpi/splashscreen_image.png differ
diff --git a/android/app/src/main/res/drawable-night-xxxhdpi/splashscreen_image.png b/android/app/src/main/res/drawable-night-xxxhdpi/splashscreen_image.png
new file mode 100644
index 00000000..58aee83c
Binary files /dev/null and b/android/app/src/main/res/drawable-night-xxxhdpi/splashscreen_image.png differ
diff --git a/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png b/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png
index fe65e568..29815e62 100644
Binary files a/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png and b/android/app/src/main/res/drawable-xhdpi/splashscreen_image.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png b/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png
index fe65e568..29815e62 100644
Binary files a/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png and b/android/app/src/main/res/drawable-xxhdpi/splashscreen_image.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png b/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png
index fe65e568..29815e62 100644
Binary files a/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png and b/android/app/src/main/res/drawable-xxxhdpi/splashscreen_image.png differ
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 036d09bc..f7258f46 100644
--- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -2,4 +2,5 @@
-
\ No newline at end of file
+
+
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 036d09bc..f7258f46 100644
--- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -2,4 +2,5 @@
-
\ No newline at end of file
+
+
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
index da4b4c1f..043bc8c5 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
index 1236a66a..f7af698d 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
index 12fb0caa..a411475f 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
index b6881203..f98e57ee 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
index c2860610..26ee4b6c 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
index 07ae99d4..3cdfdb14 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
index e395b4f2..01d16ae6 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
index c2be608b..41103c70 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
index 533bb62f..3883cc32 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
index e6938322..75b16271 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
index 6571abde..9a85f59d 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
index 13e9f3a8..f560947c 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
index 012fd69d..db6643ee 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
index 7b74547e..d864add5 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
index 27c3d4f1..542ce168 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/android/app/src/main/res/resources.properties b/android/app/src/main/res/resources.properties
new file mode 100644
index 00000000..d5a3ddc9
--- /dev/null
+++ b/android/app/src/main/res/resources.properties
@@ -0,0 +1 @@
+unqualifiedResLocale=en-US
\ No newline at end of file
diff --git a/android/app/src/release/java/app/neuland/ReactNativeFlipper.java b/android/app/src/release/java/app/neuland/ReactNativeFlipper.java
deleted file mode 100644
index e2f98c1e..00000000
--- a/android/app/src/release/java/app/neuland/ReactNativeFlipper.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the LICENSE file in the root
- * directory of this source tree.
- */
-package app.neuland;
-
-import android.content.Context;
-import com.facebook.react.ReactInstanceManager;
-
-/**
- * Class responsible of loading Flipper inside your React Native application. This is the release
- * flavor of it so it's empty as we don't want to load Flipper.
- */
-public class ReactNativeFlipper {
- public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
- // Do nothing as we don't want to initialize Flipper on Release.
- }
-}
diff --git a/android/build.gradle b/android/build.gradle
index bf861dbf..932bf7b3 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -2,26 +2,27 @@
buildscript {
ext {
- buildToolsVersion = findProperty('android.buildToolsVersion') ?: '33.0.0'
- minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '21')
- compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '33')
- targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '33')
- kotlinVersion = findProperty('android.kotlinVersion') ?: '1.8.10'
- frescoVersion = findProperty('expo.frescoVersion') ?: '2.5.0'
+ buildToolsVersion = findProperty('android.buildToolsVersion') ?: '34.0.0'
+ minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '23')
+ compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '34')
+ targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '34')
+ kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.23'
- // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
- ndkVersion = "23.1.7779620"
+ ndkVersion = "26.1.10909125"
}
repositories {
google()
mavenCentral()
}
dependencies {
- classpath('com.android.tools.build:gradle:7.4.2')
+ classpath('com.android.tools.build:gradle')
classpath('com.facebook.react:react-native-gradle-plugin')
+ classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
}
}
+apply plugin: "com.facebook.react.rootproject"
+
allprojects {
repositories {
maven {
@@ -30,7 +31,7 @@ allprojects {
}
maven {
// Android JSC is installed from npm
- url(new File(['node', '--print', "require.resolve('jsc-android/package.json')"].execute(null, rootDir).text.trim(), '../dist'))
+ url(new File(['node', '--print', "require.resolve('jsc-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), '../dist'))
}
google()
diff --git a/android/gradle.properties b/android/gradle.properties
index 8281ccbe..65ce3a08 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -25,9 +25,6 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
-# Version of flipper SDK to use with React Native
-FLIPPER_VERSION=0.182.0
-
# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
# ./gradlew -PreactNativeArchitectures=x86_64
@@ -55,5 +52,7 @@ expo.webp.animated=false
# Enable network inspector
EX_DEV_CLIENT_NETWORK_INSPECTOR=true
-android.compileSdkVersion=34
-android.targetSdkVersion=34
\ No newline at end of file
+# Use legacy packaging to compress native libraries in the resulting APK.
+expo.useLegacyPackaging=false
+
+android.extraMavenRepos=[]
\ No newline at end of file
diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar
index 249e5832..d64cd491 100644
Binary files a/android/gradle/wrapper/gradle-wrapper.jar and b/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 6ec1567a..2ea3535d 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/android/gradlew b/android/gradlew
index a69d9cb6..1aa94a42 100755
--- a/android/gradlew
+++ b/android/gradlew
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +80,11 @@ do
esac
done
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-APP_NAME="Gradle"
+# This is normally unused
+# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,22 +131,29 @@ location of your Java installation."
fi
else
JAVACMD=java
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then
done
fi
-# Collect all arguments for the java command;
-# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-# shell script including quotes and variable substitutions, so put them in
-# double quotes to make sure that they get re-expanded; and
-# * put everything else in single quotes, so that it's not re-expanded.
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
diff --git a/android/gradlew.bat b/android/gradlew.bat
index f127cfd4..25da30db 100644
--- a/android/gradlew.bat
+++ b/android/gradlew.bat
@@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -42,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
@@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
diff --git a/android/settings.gradle b/android/settings.gradle
index 039c0355..4c85dc06 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -1,10 +1,18 @@
rootProject.name = 'Neuland Next'
+dependencyResolutionManagement {
+ versionCatalogs {
+ reactAndroidLibs {
+ from(files(new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../gradle/libs.versions.toml")))
+ }
+ }
+}
+
apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle");
useExpoModules()
-apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
+apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
applyNativeModulesSettingsGradle(settings)
include ':app'
-includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json')"].execute(null, rootDir).text.trim()).getParentFile())
+includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile())
diff --git a/app.config.ts b/app.config.ts
index 5dd703cd..abbc164d 100644
--- a/app.config.ts
+++ b/app.config.ts
@@ -29,24 +29,27 @@ module.exports = {
CFBundleDevelopmentRegion: 'en',
},
splash: {
- image: './src/assets/splash.png',
+ image: './src/assets/splash/splashLight.png',
resizeMode: 'contain',
backgroundColor: '#ffffff',
dark: {
+ image: './src/assets/splash/splashDark.png',
backgroundColor: '#000000',
},
},
+ icon: './src/assets/appIcons/default.png',
},
android: {
package: 'app.neuland',
userInterfaceStyle: 'automatic',
- versionCode: 78,
+ versionCode: 83,
splash: {
- image: './src/assets/splash.png',
+ image: './src/assets/splash/splashLight.png',
resizeMode: 'contain',
backgroundColor: '#ffffff',
dark: {
backgroundColor: '#000000',
+ image: './src/assets/splash/splashDark.png',
},
},
},
@@ -84,6 +87,7 @@ module.exports = {
},
],
['expo-build-properties'],
+ ['@maplibre/maplibre-react-native'],
],
extra: {
eas: {
diff --git a/bun.lockb b/bun.lockb
index 05bc10bc..54544f20 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 0bc549be..6053cecd 100644
--- a/ios/NeulandNext.xcodeproj/project.pbxproj
+++ b/ios/NeulandNext.xcodeproj/project.pbxproj
@@ -15,6 +15,7 @@
48253CD9557284557A309B5B /* libPods-NeulandNext.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BE89E54F9B2F4C95465DBC97 /* libPods-NeulandNext.a */; };
4DBFAAE1E5644C68B67780C7 /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25124D82DC7147C1A383882B /* noop-file.swift */; };
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; };
+ B77BAE7A2C2AD20500BF7D99 /* MapLibre in Frameworks */ = {isa = PBXBuildFile; productRef = 593828D4C3990B605265629B /* MapLibre */; };
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
/* End PBXBuildFile section */
@@ -42,6 +43,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ B77BAE7A2C2AD20500BF7D99 /* MapLibre in Frameworks */,
48253CD9557284557A309B5B /* libPods-NeulandNext.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -58,8 +60,8 @@
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
13B07FB71A68108700A75B9A /* main.m */,
- AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
25124D82DC7147C1A383882B /* noop-file.swift */,
+ AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
4CF7375F2B7F45769EF88A59 /* NeulandNext-Bridging-Header.h */,
6D9C36C5590640DA61E545A1 /* PrivacyInfo.xcprivacy */,
);
@@ -155,12 +157,20 @@
7DD2A0B2CBB9482199BAE73B /* Upload Debug Symbols to Sentry */,
4DEE41591059C6E17AF12B0C /* [CP] Embed Pods Frameworks */,
FC6914C1B30E9D871353D326 /* [CP] Copy Pods Resources */,
+ 5521C5867CF044158CA46F7B /* Remove signature files (Xcode 15 workaround) */,
+ DE94DDD297BF42F2B5882A0E /* Remove signature files (Xcode 15 workaround) */,
+ 416B75F9501048ACA40DB650 /* Remove signature files (Xcode 15 workaround) */,
+ EF820F10D3E648A28C78DECD /* Remove signature files (Xcode 15 workaround) */,
+ 1515044FA4184334AD7857BB /* Remove signature files (Xcode 15 workaround) */,
);
buildRules = (
);
dependencies = (
);
name = NeulandNext;
+ packageProductDependencies = (
+ 593828D4C3990B605265629B /* MapLibre */,
+ );
productName = NeulandNext;
productReference = 13B07F961A680F5B00A75B9A /* NeulandNext.app */;
productType = "com.apple.product-type.application";
@@ -189,6 +199,9 @@
Base,
);
mainGroup = 83CBB9F61A601CBA00E9B192;
+ packageReferences = (
+ E90CD075DD22378B5CD3BEC5 /* XCRemoteSwiftPackageReference "maplibre-gl-native-distribution" */,
+ );
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -228,6 +241,20 @@
shellPath = /bin/sh;
shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n/bin/sh `\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode.sh'\"` `\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
};
+ 416B75F9501048ACA40DB650 /* 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";
+ };
4770EA3B2F9A4E615EFE9B20 /* [Expo] Configure project */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -265,6 +292,20 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NeulandNext/Pods-NeulandNext-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
+ 5521C5867CF044158CA46F7B /* 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";
+ };
7DD2A0B2CBB9482199BAE73B /* Upload Debug Symbols to Sentry */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -301,6 +342,34 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
+ DE94DDD297BF42F2B5882A0E /* 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";
+ };
+ EF820F10D3E648A28C78DECD /* 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";
+ };
FC6914C1B30E9D871353D326 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -383,6 +452,23 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NeulandNext/Pods-NeulandNext-resources.sh\"\n";
showEnvVarsInLog = 0;
};
+ 1515044FA4184334AD7857BB /* 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 */
@@ -424,7 +510,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 0.7.2;
+ MARKETING_VERSION = 0.8.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -432,7 +518,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;
@@ -453,6 +539,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = FSXB76X6V2;
+ "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64";
INFOPLIST_FILE = NeulandNext/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
@@ -460,7 +547,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 0.7.2;
+ MARKETING_VERSION = 0.8.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -468,7 +555,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";
@@ -535,10 +622,7 @@
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;
@@ -596,10 +680,7 @@
);
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;
@@ -629,6 +710,25 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+ E90CD075DD22378B5CD3BEC5 /* XCRemoteSwiftPackageReference "maplibre-gl-native-distribution" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/maplibre/maplibre-gl-native-distribution";
+ requirement = {
+ kind = exactVersion;
+ version = 6.4.0;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 593828D4C3990B605265629B /* MapLibre */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = E90CD075DD22378B5CD3BEC5 /* XCRemoteSwiftPackageReference "maplibre-gl-native-distribution" */;
+ productName = MapLibre;
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}
diff --git a/ios/NeulandNext.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/NeulandNext.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 00000000..d223fad1
--- /dev/null
+++ b/ios/NeulandNext.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,15 @@
+{
+ "originHash" : "b440cbd994821d1c59ef3fae39cdd359458004248a336c94f8a8b08f5fed3877",
+ "pins" : [
+ {
+ "identity" : "maplibre-gl-native-distribution",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/maplibre/maplibre-gl-native-distribution",
+ "state" : {
+ "revision" : "6d0071977ed1f2380c739715f82ac650f99b0824",
+ "version" : "6.4.0"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/ios/NeulandNext/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png b/ios/NeulandNext/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png
index 7a74ea3c..fb4ea755 100644
Binary files a/ios/NeulandNext/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png and b/ios/NeulandNext/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png differ
diff --git a/ios/NeulandNext/Images.xcassets/Default.appiconset/default.png b/ios/NeulandNext/Images.xcassets/Default.appiconset/default.png
index 139f0215..fb4ea755 100644
Binary files a/ios/NeulandNext/Images.xcassets/Default.appiconset/default.png and b/ios/NeulandNext/Images.xcassets/Default.appiconset/default.png differ
diff --git a/ios/NeulandNext/Images.xcassets/ModernLight.appiconset/modernLight.png b/ios/NeulandNext/Images.xcassets/ModernLight.appiconset/modernLight.png
deleted file mode 100644
index 9faba57e..00000000
Binary files a/ios/NeulandNext/Images.xcassets/ModernLight.appiconset/modernLight.png and /dev/null differ
diff --git a/ios/NeulandNext/Images.xcassets/ModernPink.appiconset/Contents.json b/ios/NeulandNext/Images.xcassets/ModernPink.appiconset/Contents.json
deleted file mode 100644
index 05817e34..00000000
--- a/ios/NeulandNext/Images.xcassets/ModernPink.appiconset/Contents.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "images" : [
- {
- "filename" : "pink_dark.png",
- "idiom" : "universal",
- "platform" : "ios",
- "size" : "1024x1024"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
diff --git a/ios/NeulandNext/Images.xcassets/ModernPink.appiconset/pink_dark.png b/ios/NeulandNext/Images.xcassets/ModernPink.appiconset/pink_dark.png
deleted file mode 100644
index e6f6f7f8..00000000
Binary files a/ios/NeulandNext/Images.xcassets/ModernPink.appiconset/pink_dark.png and /dev/null differ
diff --git a/ios/NeulandNext/Images.xcassets/ModernLight.appiconset/Contents.json b/ios/NeulandNext/Images.xcassets/Retro.appiconset/Contents.json
similarity index 83%
rename from ios/NeulandNext/Images.xcassets/ModernLight.appiconset/Contents.json
rename to ios/NeulandNext/Images.xcassets/Retro.appiconset/Contents.json
index bd2994df..5aa2698f 100644
--- a/ios/NeulandNext/Images.xcassets/ModernLight.appiconset/Contents.json
+++ b/ios/NeulandNext/Images.xcassets/Retro.appiconset/Contents.json
@@ -1,7 +1,7 @@
{
"images": [
{
- "filename": "modernLight.png",
+ "filename": "retro.png",
"idiom": "universal",
"platform": "ios",
"size": "1024x1024"
diff --git a/ios/NeulandNext/Images.xcassets/Retro.appiconset/retro.png b/ios/NeulandNext/Images.xcassets/Retro.appiconset/retro.png
new file mode 100644
index 00000000..7a74ea3c
Binary files /dev/null and b/ios/NeulandNext/Images.xcassets/Retro.appiconset/retro.png differ
diff --git a/ios/NeulandNext/Images.xcassets/SplashScreen.imageset/Contents.json b/ios/NeulandNext/Images.xcassets/SplashScreen.imageset/Contents.json
index 3cf84897..7cbe3f08 100644
--- a/ios/NeulandNext/Images.xcassets/SplashScreen.imageset/Contents.json
+++ b/ios/NeulandNext/Images.xcassets/SplashScreen.imageset/Contents.json
@@ -12,6 +12,37 @@
{
"idiom": "universal",
"scale": "3x"
+ },
+ {
+ "idiom": "universal",
+ "appearances": [
+ {
+ "appearance": "luminosity",
+ "value": "dark"
+ }
+ ],
+ "filename": "dark_image.png",
+ "scale": "1x"
+ },
+ {
+ "idiom": "universal",
+ "appearances": [
+ {
+ "appearance": "luminosity",
+ "value": "dark"
+ }
+ ],
+ "scale": "2x"
+ },
+ {
+ "idiom": "universal",
+ "appearances": [
+ {
+ "appearance": "luminosity",
+ "value": "dark"
+ }
+ ],
+ "scale": "3x"
}
],
"info": {
diff --git a/ios/NeulandNext/Images.xcassets/SplashScreen.imageset/dark_image.png b/ios/NeulandNext/Images.xcassets/SplashScreen.imageset/dark_image.png
new file mode 100644
index 00000000..58aee83c
Binary files /dev/null and b/ios/NeulandNext/Images.xcassets/SplashScreen.imageset/dark_image.png differ
diff --git a/ios/NeulandNext/Images.xcassets/SplashScreen.imageset/image.png b/ios/NeulandNext/Images.xcassets/SplashScreen.imageset/image.png
index fe65e568..29815e62 100644
Binary files a/ios/NeulandNext/Images.xcassets/SplashScreen.imageset/image.png and b/ios/NeulandNext/Images.xcassets/SplashScreen.imageset/image.png differ
diff --git a/ios/NeulandNext/Info.plist b/ios/NeulandNext/Info.plist
index 33b79ec0..7f2febdb 100644
--- a/ios/NeulandNext/Info.plist
+++ b/ios/NeulandNext/Info.plist
@@ -43,20 +43,11 @@
UIPrerenderedIcon
- ModernLight
+ Retro
CFBundleIconFiles
- ModernLight
-
- UIPrerenderedIcon
-
-
- ModernPink
-
- CFBundleIconFiles
-
- ModernPink
+ Retro
UIPrerenderedIcon
@@ -95,7 +86,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 0.7.2
+ 0.8.1
CFBundleSignature
????
CFBundleURLTypes
@@ -139,6 +130,11 @@
$(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
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
RCTAsyncStorageExcludeFromBackup
diff --git a/ios/Podfile b/ios/Podfile
index bc000b39..28239583 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -29,6 +29,9 @@ target 'NeulandNext' do
)
post_install do |installer|
+# @generated begin @maplibre/maplibre-react-native-post_installer - expo prebuild (DO NOT MODIFY) sync-72b8976cc2231a4441a5b54389fb6e10bd42a1be
+ $RCTMLN.post_install(installer)
+# @generated end @maplibre/maplibre-react-native-post_installer
react_native_post_install(
installer,
config[:reactNativePath],
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index a9d0887b..4cafc183 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -313,6 +313,13 @@ PODS:
- hermes-engine (0.74.2):
- hermes-engine/Pre-built (= 0.74.2)
- hermes-engine/Pre-built (0.74.2)
+ - maplibre-react-native (10.0.0-alpha.5):
+ - maplibre-react-native/DynamicLibrary (= 10.0.0-alpha.5)
+ - React
+ - React-Core
+ - maplibre-react-native/DynamicLibrary (10.0.0-alpha.5):
+ - React
+ - React-Core
- RCT-Folly (2024.01.01.00):
- boost
- DoubleConversion
@@ -1245,8 +1252,6 @@ PODS:
- React
- react-native-drag-drop-ios (0.1.1):
- React-Core
- - react-native-menu (0.9.1):
- - React
- react-native-netinfo (11.3.1):
- React-Core
- react-native-pager-view (6.3.0):
@@ -1673,6 +1678,7 @@ DEPENDENCIES:
- fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
+ - "maplibre-react-native (from `../node_modules/@maplibre/maplibre-react-native`)"
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
@@ -1701,7 +1707,6 @@ DEPENDENCIES:
- React-Mapbuffer (from `../node_modules/react-native/ReactCommon`)
- react-native-context-menu-view (from `../node_modules/react-native-context-menu-view`)
- react-native-drag-drop-ios (from `../node_modules/react-native-drag-drop-ios`)
- - "react-native-menu (from `../node_modules/@react-native-menu/menu`)"
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
@@ -1830,6 +1835,8 @@ EXTERNAL SOURCES:
hermes-engine:
:podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
:tag: hermes-2024-06-03-RNv0.74.2-bb1e74fe1e95c2b5a2f4f9311152da052badc2bc
+ maplibre-react-native:
+ :path: "../node_modules/@maplibre/maplibre-react-native"
RCT-Folly:
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
RCTDeprecation:
@@ -1882,8 +1889,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-context-menu-view"
react-native-drag-drop-ios:
:path: "../node_modules/react-native-drag-drop-ios"
- react-native-menu:
- :path: "../node_modules/@react-native-menu/menu"
react-native-netinfo:
:path: "../node_modules/@react-native-community/netinfo"
react-native-pager-view:
@@ -2009,6 +2014,7 @@ SPEC CHECKSUMS:
fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120
glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f
hermes-engine: 01d3e052018c2a13937aca1860fbedbccd4a41b7
+ maplibre-react-native: bcb8d0ed567f4a67f651e2d1a24521ef273c23de
RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47
RCTDeprecation: b03c35057846b685b3ccadc9bfe43e349989cdb2
RCTRequired: 194626909cfa8d39ca6663138c417bc6c431648c
@@ -2035,7 +2041,6 @@ SPEC CHECKSUMS:
React-Mapbuffer: bf56147c9775491e53122a94c423ac201417e326
react-native-context-menu-view: 30915369a9b5887904c571b616653acf3f1c8edb
react-native-drag-drop-ios: 099293de149185e9116a057a8b6b98f95ce9b56e
- react-native-menu: 9728f90160c36b9a75481fc76e05354b99d54c59
react-native-netinfo: bdb108d340cdb41875c9ced535977cac6d2ff321
react-native-pager-view: c1e29e1a6105a02807392ba822ad322447a72f55
react-native-safe-area-context: dcab599c527c2d7de2d76507a523d20a0b83823d
@@ -2081,6 +2086,6 @@ SPEC CHECKSUMS:
UMAppLoader: f17a5ee8e85b536ace0fc254b447a37ed198d57e
Yoga: ae3c32c514802d30f687a04a6a35b348506d411f
-PODFILE CHECKSUM: 3a09a7a2ba19331e6aa5922ccde95dc10663a4a3
+PODFILE CHECKSUM: a2c2df1b6d6fb8a577a08d33880679b64c476742
COCOAPODS: 1.14.3
diff --git a/ios/ci_scripts/ci_post_clone.sh b/ios/ci_scripts/ci_post_clone.sh
new file mode 100755
index 00000000..78355c98
--- /dev/null
+++ b/ios/ci_scripts/ci_post_clone.sh
@@ -0,0 +1,15 @@
+#!/bin/zsh
+
+echo "===== Installling CocoaPods ====="
+export HOMEBREW_NO_INSTALL_CLEANUP=TRUE
+brew install cocoapods
+echo "===== Installing Node.js ====="
+brew install node@22
+
+# Install dependencies
+echo "===== Running bun install ====="
+cd ..
+npm install
+echo "===== Running pod install ====="
+cd ios
+pod install
\ No newline at end of file
diff --git a/package.json b/package.json
index b53c7acc..8e3c4e61 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,11 @@
{
- "version": "0.7.2",
+ "version": "0.8.1",
"name": "neuland",
"main": "expo-router/entry",
"homepage": "https://github.com/neuland-ingolstadt/neuland.app-native",
"private": true,
"scripts": {
- "start": "expo start -c",
+ "start": "EXPO_USE_FAST_RESOLVER=1 expo",
"tunnel": "expo start -c --tunnel",
"android": "expo run:android",
"ios": "expo run:ios",
@@ -27,17 +27,17 @@
"@expo/vector-icons": "^14.0.0",
"@gorhom/bottom-sheet": "^4",
"@kichiyaki/react-native-barcode-generator": "^0.6.7",
+ "@maplibre/maplibre-react-native": "^10.0.0-alpha.5",
"@react-native-async-storage/async-storage": "1.23.1",
"@react-native-community/datetimepicker": "8.0.1",
"@react-native-community/netinfo": "11.3.1",
- "@react-native-menu/menu": "^0.9.1",
"@sentry/react-native": "~5.22.0",
"@shopify/flash-list": "1.6.4",
"@tanstack/query-async-storage-persister": "^5.17.19",
"@tanstack/react-query": "^5.17.19",
"@tanstack/react-query-persist-client": "^5.17.19",
"color": "^4.2.3",
- "expo": "^51.0.11",
+ "expo": "^51.0.14",
"expo-blur": "~13.0.2",
"expo-brightness": "~12.0.1",
"expo-build-properties": "~0.12.3",
@@ -53,7 +53,7 @@
"expo-location": "~17.0.1",
"expo-navigation-bar": "~3.0.6",
"expo-notifications": "~0.28.7",
- "expo-router": "~3.5.15",
+ "expo-router": "~3.5.16",
"expo-secure-store": "~13.0.1",
"expo-sharing": "~12.0.1",
"expo-splash-screen": "~0.27.5",
@@ -105,6 +105,7 @@
"@expo/ngrok": "^4.1.0",
"@trivago/prettier-plugin-sort-imports": "^4.2.1",
"@types/color": "^3.0.5",
+ "@types/geojson": "^7946.0.14",
"@types/prop-types": "^15",
"@types/react": "~18.2.45",
"@types/react-native-actionsheet": "^2",
@@ -121,6 +122,7 @@
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "latest",
"eslint-plugin-react-native": "^4.1.0",
+ "expo-atlas": "^0.3.0",
"husky": "^9.0.6",
"lint-staged": ">=15",
"prettier": "3.2.5",
diff --git a/src/api/neuland-api.ts b/src/api/neuland-api.ts
index 16dab59d..e9fe0dde 100644
--- a/src/api/neuland-api.ts
+++ b/src/api/neuland-api.ts
@@ -3,7 +3,7 @@ import { gql, request } from 'graphql-request'
import packageInfo from '../../package.json'
-const GRAPHQL_ENDPOINT: string = 'https://api.neuland.app/graphql'
+const GRAPHQL_ENDPOINT: string = 'https://api.dev.neuland.app/graphql'
const ASSET_ENDPOINT: string = 'https://assets.neuland.app'
const USER_AGENT = `neuland.app-native/${packageInfo.version} (+${packageInfo.homepage})`
diff --git a/src/app/(tabs)/_layout.tsx b/src/app/(tabs)/_layout.tsx
index adf18aa9..19cfd870 100644
--- a/src/app/(tabs)/_layout.tsx
+++ b/src/app/(tabs)/_layout.tsx
@@ -23,6 +23,7 @@ import { Platform, StyleSheet } from 'react-native'
import Shortcuts, { type ShortcutItem } from 'rn-quick-actions'
import { humanLocations } from '../(food)/meal'
+import { appIcons } from '../(user)/appicon'
import packageInfo from '../../../package.json'
export default function HomeLayout(): JSX.Element {
@@ -32,7 +33,7 @@ export default function HomeLayout(): JSX.Element {
const flow = React.useContext(FlowContext)
const { t } = useTranslation('navigation')
const { selectedRestaurants } = useContext(FoodFilterContext)
- const { appIcon } = useContext(AppIconContext)
+ const { appIcon, toggleAppIcon } = useContext(AppIconContext)
const aptabaseKey = process.env.EXPO_PUBLIC_APTABASE_KEY
const { analyticsAllowed, initializeAnalytics } =
React.useContext(FlowContext)
@@ -185,6 +186,14 @@ export default function HomeLayout(): JSX.Element {
}
}, [analyticsAllowed])
+ useEffect(() => {
+ if (Platform.OS !== 'ios') return
+
+ if (!appIcons.includes(appIcon)) {
+ toggleAppIcon('default')
+ }
+ }, [appIcon])
+
return (
<>
(null)
- const [currentFloor, setCurrentFloor] = useState('EG')
+ const [currentFloor, setCurrentFloor] = useState({
+ floor: 'EG',
+ manual: false,
+ })
const [nextLecture, setNextLecture] = useState<
FriendlyTimetableEntry[] | null
>(null)
diff --git a/src/app/(user)/appicon.tsx b/src/app/(user)/appicon.tsx
index 07fcb3cc..d7dfe6c2 100644
--- a/src/app/(user)/appicon.tsx
+++ b/src/app/(user)/appicon.tsx
@@ -27,15 +27,16 @@ let iconImages: Record = {}
iconImages = {
default: require('@/assets/appIcons/default.png'),
modernDark: require('@/assets/appIcons/modernDark.png'),
- modernLight: require('@/assets/appIcons/modernLight.png'),
+ retro: require('@/assets/appIcons/retro.png'),
modernGreen: require('@/assets/appIcons/modernGreen.png'),
- modernPink: require('@/assets/appIcons/modernPink.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 const appIcons = Object.keys(iconImages)
+
export default function AppIconPicker(): JSX.Element {
const colors = useTheme().colors as Colors
const { appIcon, toggleAppIcon, unlockedAppIcons } =
@@ -43,8 +44,8 @@ export default function AppIconPicker(): JSX.Element {
const { t } = useTranslation(['settings'])
const isModal = useLocalSearchParams().fromAppShortcut === 'true'
const categories: Record = {
- exclusive: ['cat', 'modernPink'],
- default: ['default', 'modernLight', 'modernDark', 'modernGreen'],
+ exclusive: ['cat', 'retro'],
+ default: ['default', 'modernDark', 'modernGreen'],
rainbow: ['rainbowNeon', 'rainbowDark', 'rainbowMoonLight'],
}
diff --git a/src/app/(user)/theme.tsx b/src/app/(user)/theme.tsx
index 14561a04..d5526feb 100644
--- a/src/app/(user)/theme.tsx
+++ b/src/app/(user)/theme.tsx
@@ -25,8 +25,7 @@ if (Platform.OS === 'ios') {
iconImages = {
default: require('@/assets/appIcons/default.png'),
modernDark: require('@/assets/appIcons/modernDark.png'),
- modernLight: require('@/assets/appIcons/modernLight.png'),
- modernPink: require('@/assets/appIcons/modernPink.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'),
diff --git a/src/assets/appIcons/default.png b/src/assets/appIcons/default.png
index 139f0215..9faba57e 100644
Binary files a/src/assets/appIcons/default.png and b/src/assets/appIcons/default.png differ
diff --git a/src/assets/appIcons/modernLight.png b/src/assets/appIcons/modernLight.png
deleted file mode 100644
index 9faba57e..00000000
Binary files a/src/assets/appIcons/modernLight.png and /dev/null differ
diff --git a/src/assets/appIcons/modernPink.png b/src/assets/appIcons/modernPink.png
deleted file mode 100644
index e6f6f7f8..00000000
Binary files a/src/assets/appIcons/modernPink.png and /dev/null differ
diff --git a/src/assets/appIcons/retro.png b/src/assets/appIcons/retro.png
new file mode 100644
index 00000000..139f0215
Binary files /dev/null and b/src/assets/appIcons/retro.png differ
diff --git a/src/assets/splash/splashDark.png b/src/assets/splash/splashDark.png
new file mode 100644
index 00000000..069f3ddc
Binary files /dev/null and b/src/assets/splash/splashDark.png differ
diff --git a/src/assets/splash/splashLight.png b/src/assets/splash/splashLight.png
new file mode 100644
index 00000000..41251d31
Binary files /dev/null and b/src/assets/splash/splashLight.png differ
diff --git a/src/components/Elements/Food/MealEntry.tsx b/src/components/Elements/Food/MealEntry.tsx
index 398e22c8..b0418e53 100644
--- a/src/components/Elements/Food/MealEntry.tsx
+++ b/src/components/Elements/Food/MealEntry.tsx
@@ -81,7 +81,7 @@ export const MealEntry = ({
const iconName = hasUserAllergens
? 'exclamationmark.triangle'
: 'info.circle'
- const androidName = hasUserAllergens ? 'warning' : 'check'
+ const androidName = hasUserAllergens ? 'warning' : 'info'
const textContent = hasUserAllergens
? userAllergens
: t('empty.noAllergens')
@@ -275,6 +275,7 @@ export const MealEntry = ({
android={{
name: androidName,
size: 16,
+ variant: 'outlined',
}}
style={styles.icon}
color={colors.notification}
diff --git a/src/components/Elements/Map/BottomSheetBackground.tsx b/src/components/Elements/Map/BottomSheetBackground.tsx
index bb96d7cc..4d3f8d86 100644
--- a/src/components/Elements/Map/BottomSheetBackground.tsx
+++ b/src/components/Elements/Map/BottomSheetBackground.tsx
@@ -6,7 +6,7 @@ import { Platform, StyleSheet, View } from 'react-native'
const BottomSheetBackground = (): JSX.Element => {
const { colors, dark } = useTheme()
const darkIos = 'rgba(0, 0, 0, 0.55)'
- const lightIos = 'rgba(255, 255, 255, 0.2)'
+ const lightIos = 'rgba(200, 200, 200, 0.2)'
return Platform.OS === 'ios' ? (
- handleSheetChangesModal: (index: number) => void
+ handleSheetChangesModal: () => void
currentPositionModal: SharedValue
roomData: RoomData
modalSection: FormListSections[]
@@ -47,7 +47,6 @@ const handleShareModal = (room: string): void => {
* BottomSheetDetailModal component for displaying room details
* @param {React.RefObject} bottomSheetModalRef - Reference to the bottom sheet modal
* @param {Function} handleSheetChangesModal - Function to handle changes in the bottom sheet modal
- * @param {SharedValue} currentPositionModal - Current position of the bottom sheet modal
* @param {any} roomData - Data for the room
* @param {FormListSections[]} modalSection - Sections for the room
* @returns {JSX.Element}
@@ -66,7 +65,7 @@ export const BottomSheetDetailModal = ({
index={1}
ref={bottomSheetModalRef}
snapPoints={['15%', '30%', '45%', '70%']}
- onChange={handleSheetChangesModal}
+ onDismiss={handleSheetChangesModal}
backgroundComponent={BottomSheetBackground}
animatedPosition={currentPositionModal}
>
diff --git a/src/components/Elements/Map/BottomSheetMap.tsx b/src/components/Elements/Map/BottomSheetMap.tsx
index 7f45090f..ee7c7aa8 100644
--- a/src/components/Elements/Map/BottomSheetMap.tsx
+++ b/src/components/Elements/Map/BottomSheetMap.tsx
@@ -1,22 +1,23 @@
/* eslint-disable react-native/no-color-literals */
import { type Colors } from '@/components/colors'
-import { UserKindContext } from '@/components/contexts'
+import { AppIconContext, UserKindContext } from '@/components/contexts'
import { MapContext } from '@/hooks/contexts/map'
import { USER_GUEST } from '@/hooks/contexts/userKind'
import { SEARCH_TYPES } from '@/types/map'
-import { type RoomEntry } from '@/types/utils'
import { formatFriendlyDate, formatFriendlyTime } from '@/utils/date-utils'
-import { getCenterSingle } from '@/utils/map-utils'
import { PAGE_BOTTOM_SAFE_AREA, PAGE_PADDING } from '@/utils/style-utils'
import { getContrastColor } from '@/utils/ui-utils'
import BottomSheet, { BottomSheetTextInput } from '@gorhom/bottom-sheet'
import { useTheme } from '@react-navigation/native'
import { useRouter } from 'expo-router'
import Fuse from 'fuse.js'
-import React, { useContext, useMemo } from 'react'
+import { type FeatureCollection } from 'geojson'
+import React, { useContext, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import {
ActivityIndicator,
+ Alert,
+ Linking,
Platform,
Pressable,
SectionList,
@@ -26,20 +27,17 @@ import {
View,
} from 'react-native'
import { type SharedValue } from 'react-native-reanimated'
-import type WebView from 'react-native-webview'
import Divider from '../Universal/Divider'
import PlatformIcon from '../Universal/Icon'
import BottomSheetBackground from './BottomSheetBackground'
import ResultRow from './SearchResultRow'
-import { _injectMarker, _setView } from './leaflet'
interface MapBottomSheetProps {
bottomSheetRef: React.RefObject
currentPosition: SharedValue
handlePresentModalPress: () => void
- allRooms: RoomEntry[]
- mapRef: React.RefObject
+ allRooms: FeatureCollection
}
const MapBottomSheet: React.FC = ({
@@ -47,7 +45,6 @@ const MapBottomSheet: React.FC = ({
currentPosition,
handlePresentModalPress,
allRooms,
- mapRef,
}) => {
const router = useRouter()
const colors = useTheme().colors as Colors
@@ -62,7 +59,9 @@ const MapBottomSheet: React.FC = ({
setCurrentFloor,
} = useContext(MapContext)
- const fuse = new Fuse(allRooms, {
+ const { unlockedAppIcons, addUnlockedAppIcon } = useContext(AppIconContext)
+
+ const fuse = new Fuse(allRooms.features, {
keys: [
'properties.Raum',
i18n.language === 'de'
@@ -77,10 +76,12 @@ const MapBottomSheet: React.FC = ({
const [searchResultsExact, searchResultsFuzzy] = useMemo(() => {
const results = fuse.search(localSearch.trim().toUpperCase())
const roomResults = results.map((result) => ({
- title: result.item.properties.Raum,
- subtitle: result.item.properties.Funktion_en,
- isExactMatch: result.item.properties.Raum.toUpperCase().includes(
- localSearch.toUpperCase()
+ title: result.item.properties?.Raum,
+ subtitle: result.item.properties?.Funktion_en,
+ isExactMatch: Boolean(
+ result.item.properties?.Raum.toUpperCase().includes(
+ localSearch.toUpperCase()
+ )
),
item: result.item,
}))
@@ -93,6 +94,31 @@ const MapBottomSheet: React.FC = ({
return [exactMatches, fuzzyMatches]
}, [localSearch, allRooms])
+ useEffect(() => {
+ if (
+ localSearch.toLocaleLowerCase() === 'neuland' &&
+ Platform.OS === 'ios'
+ ) {
+ if (unlockedAppIcons.includes('retro')) {
+ return
+ }
+ Alert.alert(
+ t('pages.map.easterEgg.title'),
+
+ t('pages.map.easterEgg.message'),
+ [
+ {
+ text: t('pages.map.easterEgg.confirm'),
+ style: 'cancel',
+ },
+ ],
+ { cancelable: false }
+ )
+
+ addUnlockedAppIcon('retro')
+ }
+ }, [localSearch])
+
return (
= ({
animatedPosition={currentPosition}
keyboardBehavior="extend"
>
-
- {Platform.OS === 'ios' ? (
- {
- setLocalSearch(text)
- }}
- onFocus={() => {
- bottomSheetRef.current?.snapToIndex(2)
- }}
- onEndEditing={() => {
- bottomSheetRef.current?.collapse()
- }}
- />
- ) : (
- {
- setLocalSearch(text)
- }}
- onFocus={() => {
- bottomSheetRef.current?.snapToIndex(2)
- }}
- onEndEditing={() => {
- bottomSheetRef.current?.collapse()
- }}
- />
- )}
- {localSearch !== '' ? (
- searchResultsExact.length > 0 ||
- searchResultsFuzzy.length > 0 ? (
-
+
+ {Platform.OS === 'ios' ? (
+ {
+ setLocalSearch(text)
+ }}
+ onFocus={() => {
+ bottomSheetRef.current?.snapToIndex(2)
+ }}
+ onEndEditing={() => {
+ bottomSheetRef.current?.collapse()
}}
- keyboardShouldPersistTaps="always"
- sections={[
- ...(searchResultsExact.length > 0
- ? [
- {
- title: t(
- 'pages.map.search.results'
- ),
- data: searchResultsExact,
- },
- ]
- : []),
- ...(searchResultsFuzzy.length > 0
- ? [
- {
- title: t(
- 'pages.map.search.fuzzy'
- ),
- data: searchResultsFuzzy,
- },
- ]
- : []),
- ]}
- keyExtractor={(item, index) => item.title + index}
- renderItem={({ item, index }) => (
-
- )}
- stickySectionHeadersEnabled={false}
- renderSectionHeader={({ section: { title } }) => (
-
- {title}
-
- )}
/>
) : (
-
- {t('pages.map.search.noResults')}
-
- )
- ) : userKind === USER_GUEST ? (
-
- {t('pages.map.details.room.signIn')}
-
- ) : (
- <>
- {nextLecture !== null && nextLecture.length > 0 && (
-
-
+ placeholder={t('pages.map.search.placeholder')}
+ placeholderTextColor={colors.labelColor}
+ value={localSearch}
+ enablesReturnKeyAutomatically
+ clearButtonMode="always"
+ onChangeText={(text) => {
+ setLocalSearch(text)
+ }}
+ onFocus={() => {
+ bottomSheetRef.current?.snapToIndex(2)
+ }}
+ onEndEditing={() => {
+ bottomSheetRef.current?.collapse()
+ }}
+ />
+ )}
+ {localSearch !== '' ? (
+ searchResultsExact.length > 0 ||
+ searchResultsFuzzy.length > 0 ? (
+ 0
+ ? [
+ {
+ title: t(
+ 'pages.map.search.results'
+ ),
+ data: searchResultsExact,
+ },
+ ]
+ : []),
+ ...(searchResultsFuzzy.length > 0
+ ? [
+ {
+ title: t(
+ 'pages.map.search.fuzzy'
+ ),
+ data: searchResultsFuzzy,
+ },
+ ]
+ : []),
+ ]}
+ keyExtractor={(item, index) =>
+ item.title + index
+ }
+ renderItem={({ item, index }) => (
+
+ )}
+ stickySectionHeadersEnabled={false}
+ renderSectionHeader={({
+ section: { title },
+ }) => (
- {t(
- 'pages.map.details.room.nextLecture'
- )}
-
-
- {formatFriendlyDate(
- nextLecture[0].date
- )}
-
-
-
- {nextLecture.map((lecture, key) => (
- <>
- {
- const details =
- allRooms.find(
- (x) =>
- x.properties
- .Raum ===
- lecture.rooms[0]
- )
-
- if (
- details?.coordinates !==
- undefined
- ) {
- const center =
- getCenterSingle(
- details?.coordinates
- )
- _setView(center, mapRef)
- _injectMarker(
- mapRef,
- center,
- colors
- )
- }
-
- const etage =
- details?.properties
- .Ebene
-
- setCurrentFloor(
- etage ?? 'EG'
- )
- setClickedElement({
- data: lecture.rooms[0],
- type: SEARCH_TYPES.ROOM,
- })
-
- handlePresentModalPress()
- bottomSheetRef.current?.close()
- }}
- >
-
-
-
-
-
-
-
- {lecture.name}
-
-
- {lecture.rooms.join(
- ', '
- )}
-
-
-
-
-
- {formatFriendlyTime(
- lecture.startDate
- )}
-
-
- {formatFriendlyTime(
- lecture.endDate
- )}
-
-
-
- {key !== nextLecture.length - 1 && (
-
- )}
- >
- ))}
-
-
- )}
-
-
-
-
- {t('pages.map.details.room.availableRooms')}
-
- {
- router.push('(map)/advanced')
- }}
- hitSlop={{
- top: 10,
- right: 10,
- bottom: 10,
- left: 10,
- }}
- >
-
- {t('misc.more')}
+ {title}
-
-
-
+ ) : (
+
- {availableRooms === null ? (
-
- ) : availableRooms.length === 0 ? (
-
+ )
+ ) : userKind === USER_GUEST ? (
+
+ {t('pages.map.details.room.signIn')}
+
+ ) : (
+ <>
+ {nextLecture !== null && nextLecture.length > 0 && (
+
+
+
+ {t(
+ 'pages.map.details.room.nextLecture'
+ )}
+
+
+ {formatFriendlyDate(
+ nextLecture[0].date
+ )}
+
+
+
- {t('pages.map.noAvailableRooms')}
-
- ) : (
- availableRooms
- .slice(0, 3)
- .map((room, key) => (
- <>
+ {nextLecture.map((lecture, key) => (
+
{
const details =
- allRooms.find(
+ allRooms.features.find(
(x) =>
x.properties
- .Raum ===
- room.room
+ ?.Raum ===
+ lecture
+ .rooms[0]
)
- if (
- details?.coordinates !==
- undefined
- ) {
- const center =
- getCenterSingle(
- details?.coordinates
- )
- _setView(
- center,
- mapRef
- )
- _injectMarker(
- mapRef,
- center,
- colors
- )
- }
-
const etage =
details?.properties
- .Ebene
-
- setCurrentFloor(
- etage ?? 'EG'
- )
+ ?.Ebene
+ bottomSheetRef.current?.close()
+ setCurrentFloor({
+ floor:
+ (etage as string) ??
+ 'EG',
+ manual: false,
+ })
setClickedElement({
- data: room.room,
+ data: lecture
+ .rooms[0],
type: SEARCH_TYPES.ROOM,
+ center: details
+ ?.properties
+ ?.center,
+ manual: false,
})
-
handlePresentModalPress()
- bottomSheetRef.current?.close()
}}
>
= ({
colors.primary
)}
ios={{
- name: 'studentdesk',
+ name: 'clock.fill',
size: 18,
}}
android={{
@@ -531,14 +356,21 @@ const MapBottomSheet: React.FC = ({
/>
-
+
- {room.room}
+ {lecture.name}
= ({
...styles.suggestionSubtitle,
}}
>
- {room.type} (
- {room.capacity}{' '}
- seats)
+ {lecture.rooms.join(
+ ', '
+ )}
@@ -566,7 +398,7 @@ const MapBottomSheet: React.FC = ({
}}
>
{formatFriendlyTime(
- room.from
+ lecture.startDate
)}
= ({
}}
>
{formatFriendlyTime(
- room.until
+ lecture.endDate
)}
- {key !== 2 && }
- >
- ))
- )}
+ {key !==
+ nextLecture.length - 1 && (
+
+ )}
+
+ ))}
+
+
+ )}
+
+
+
+
+ {t(
+ 'pages.map.details.room.availableRooms'
+ )}
+
+ {
+ router.push('(map)/advanced')
+ }}
+ hitSlop={{
+ top: 10,
+ right: 10,
+ bottom: 10,
+ left: 10,
+ }}
+ >
+
+ {t('misc.more')}
+
+
+
+
+ {availableRooms === null ? (
+
+ ) : availableRooms.length === 0 ? (
+
+ {t('pages.map.noAvailableRooms')}
+
+ ) : (
+ availableRooms
+ .slice(0, 3)
+ .map((room, key) => (
+
+ {
+ const details =
+ allRooms.features.find(
+ (x) =>
+ x
+ .properties
+ ?.Raum ===
+ room.room
+ )
+
+ const etage =
+ details
+ ?.properties
+ ?.Ebene
+
+ setCurrentFloor({
+ floor:
+ (etage as string) ??
+ 'EG',
+ manual: false,
+ })
+ setClickedElement({
+ data: room.room,
+ type: SEARCH_TYPES.ROOM,
+ center:
+ details
+ ?.properties
+ ?.center ??
+ undefined,
+ manual: false,
+ })
+
+ handlePresentModalPress()
+ bottomSheetRef.current?.close()
+ }}
+ >
+
+
+
+
+
+
+
+ {room.room}
+
+
+ {room.type}{' '}
+ (
+ {
+ room.capacity
+ }{' '}
+ seats)
+
+
+
+
+
+ {formatFriendlyTime(
+ room.from
+ )}
+
+
+ {formatFriendlyTime(
+ room.until
+ )}
+
+
+
+ {key !== 2 && }
+
+ ))
+ )}
+
-
- >
- )}
+ >
+ )}
+
+
+ {
+ void Linking.openURL(
+ 'https://www.openstreetmap.org/copyright'
+ )
+ }}
+ style={styles.attributionLink}
+ >
+
+ {t('pages.map.details.osm')}
+
+
+
+
)
@@ -605,10 +670,14 @@ const styles = StyleSheet.create({
suggestionSectionHeader: {
fontWeight: '600',
fontSize: 20,
- marginTop: 6,
+ paddingTop: 8,
marginBottom: 2,
textAlign: 'left',
},
+ suggestionContent: {
+ flex: 1,
+ paddingRight: 14,
+ },
suggestionSectionHeaderContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
@@ -635,13 +704,15 @@ const styles = StyleSheet.create({
fontSize: 17,
},
suggestionRow: {
- padding: 10,
+ paddingVertical: 10,
+ paddingHorizontal: 12,
flexDirection: 'row',
- justifyContent: 'space-between',
},
suggestionInnerRow: {
flexDirection: 'row',
alignItems: 'center',
+ justifyContent: 'space-between',
+ flex: 1,
},
suggestionIconContainer: {
marginRight: 14,
@@ -655,7 +726,6 @@ const styles = StyleSheet.create({
fontWeight: '600',
fontSize: 16,
marginBottom: 1,
- maxWidth: '90%',
},
suggestionSubtitle: {
fontWeight: '400',
@@ -682,4 +752,14 @@ const styles = StyleSheet.create({
loadingMargin: {
marginVertical: 30,
},
+ attributionContainer: { paddingVertical: 40 },
+ attributionLink: {
+ flexDirection: 'row',
+ gap: 4,
+ alignItems: 'center',
+ },
+ attributionText: {
+ fontSize: 15,
+ paddingStart: 12,
+ },
})
diff --git a/src/components/Elements/Map/FloorPicker.tsx b/src/components/Elements/Map/FloorPicker.tsx
index 6a19e7d9..e891675d 100644
--- a/src/components/Elements/Map/FloorPicker.tsx
+++ b/src/components/Elements/Map/FloorPicker.tsx
@@ -2,86 +2,29 @@ import { type Colors } from '@/components/colors'
import { MapContext } from '@/hooks/contexts/map'
import { useTheme } from '@react-navigation/native'
import * as Haptics from 'expo-haptics'
-import { getCurrentPositionAsync } from 'expo-location'
-import * as Location from 'expo-location'
import React, { useContext } from 'react'
-import { useTranslation } from 'react-i18next'
-import {
- Alert,
- Linking,
- Platform,
- Pressable,
- StyleSheet,
- Text,
- View,
-} from 'react-native'
-import { type WebView } from 'react-native-webview'
+import { Platform, Pressable, StyleSheet, Text, View } from 'react-native'
import PlatformIcon from '../Universal/Icon'
-import { _injectCurrentLocation, _setView } from './leaflet'
interface FloorPickerProps {
floors: string[]
showAllFloors: boolean
toggleShowAllFloors: () => void
- mapRef: React.RefObject
+ setCameraTriggerKey: React.Dispatch>
}
const FloorPicker: React.FC = ({
floors,
showAllFloors,
toggleShowAllFloors,
- mapRef,
+ setCameraTriggerKey,
}): JSX.Element => {
- const colors = useTheme().colors as Colors
- const { currentFloor, setCurrentFloor, setLocation } =
- useContext(MapContext)
- const { t } = useTranslation('common')
- const locationAlert = (): void => {
- Alert.alert(
- t('pages.map.details.location.title'),
- t('pages.map.details.location.alert'),
- [
- {
- text: t('misc.cancel'),
- style: 'cancel',
- },
- {
- text: t('pages.map.details.location.settings'),
- onPress: () => {
- void Linking.openSettings()
- },
- },
- ]
- )
- }
+ const theme = useTheme()
+ const colors = theme.colors as Colors
+ const isDark = theme.dark
+ const { currentFloor, setCurrentFloor } = useContext(MapContext)
- async function getCurrentPosition(): Promise {
- const { status } = await Location.requestForegroundPermissionsAsync()
- if (status !== 'granted') {
- setLocation('notGranted')
- locationAlert()
- } else {
- try {
- const location = await getCurrentPositionAsync({
- accuracy: Location.Accuracy.High,
- })
- setLocation(location)
- _injectCurrentLocation(
- mapRef,
- colors,
- Math.min(location.coords.accuracy ?? 5, 80),
- [location.coords.latitude, location.coords.longitude]
- )
- _setView(
- [location.coords.latitude, location.coords.longitude],
- mapRef
- )
- } catch (e) {
- console.error(e)
- }
- }
- }
return (
@@ -89,14 +32,20 @@ const FloorPicker: React.FC = ({
{
toggleShowAllFloors()
- if (Platform.OS === 'ios') {
- void Haptics.selectionAsync()
- }
}}
onLongPress={() => {
- setCurrentFloor('EG')
- if (Platform.OS === 'ios') {
- void Haptics.selectionAsync()
+ if (currentFloor?.floor === 'EG') {
+ toggleShowAllFloors()
+ } else {
+ setCurrentFloor({ floor: 'EG', manual: true })
+ if (
+ Platform.OS === 'ios' &&
+ currentFloor?.floor !== 'EG'
+ ) {
+ void Haptics.impactAsync(
+ Haptics.ImpactFeedbackStyle.Soft
+ )
+ }
}
}}
>
@@ -117,7 +66,9 @@ const FloorPicker: React.FC = ({
color: colors.text,
}}
>
- {currentFloor === 'EG' ? '0' : currentFloor}
+ {currentFloor?.floor === 'EG'
+ ? '0'
+ : currentFloor?.floor}
@@ -127,15 +78,12 @@ const FloorPicker: React.FC = ({
{
- if (Platform.OS === 'ios') {
- void Haptics.selectionAsync()
- }
toggleShowAllFloors()
}}
>
= ({
{floors.map((floor, index) => (
{
- setCurrentFloor(floor)
+ if (Platform.OS === 'ios') {
+ void Haptics.selectionAsync()
+ }
+ setCurrentFloor({ floor, manual: true })
}}
key={index}
>
@@ -172,7 +123,7 @@ const FloorPicker: React.FC = ({
{
borderBottomColor: colors.border,
backgroundColor:
- currentFloor === floor
+ currentFloor?.floor === floor
? colors.primary
: colors.card,
borderBottomWidth:
@@ -187,7 +138,8 @@ const FloorPicker: React.FC = ({
styles.ButtonText,
{
color:
- currentFloor === floor
+ currentFloor?.floor ===
+ floor
? colors.background
: colors.text,
},
@@ -203,7 +155,7 @@ const FloorPicker: React.FC = ({
{
{
- void getCurrentPosition()
+ setCameraTriggerKey((prev) => prev + 1)
}}
>
{
const navigation = useNavigation()
const isFocused = useNavigation().isFocused()
- const [errorMsg, setErrorMsg] = useState('')
- const colors = useTheme().colors as Colors
+ const [mapLoadState, setMapLoadState] = useState(LoadingState.LOADING)
+ const theme = useTheme()
+ const colors = theme.colors as Colors
+ const isDark = theme.dark
+
const { userKind, userFaculty } = useContext(UserKindContext)
const { routeParams, updateRouteParams } = useContext(RouteParamsContext)
- const [webViewKey, setWebViewKey] = useState(0)
- const [loadingState, setLoadingState] = useState(LoadingState.LOADING)
const [mapCenter, setMapCenter] = useState(INGOLSTADT_CENTER)
const { t } = useTranslation('common')
const bottomSheetRef = useRef(null)
const bottomSheetModalRef = useRef(null)
const currentPosition = useSharedValue(0)
const currentPositionModal = useSharedValue(0)
- const mapRef = useRef(null)
const {
localSearch,
clickedElement,
@@ -105,8 +101,25 @@ const MapScreen = (): JSX.Element => {
setCurrentFloor,
setNextLecture,
} = useContext(MapContext)
-
+ const [disableFollowUser, setDisableFollowUser] = useState(false)
const [showAllFloors, setShowAllFloors] = useState(false)
+ const mapRef = useRef(null)
+ const cameraRef = useRef(null)
+ const locationRef = useRef(null)
+ const currentDate = new Date()
+
+ enum Locations {
+ IN = 'Ingolstadt',
+ ND = 'Neuburg',
+ }
+ const lightStyle = 'https://maps.opheys.dev/styles/light/style.json'
+ const darkStyle = 'https://maps.opheys.dev/styles/dark/style.json'
+
+ type LocationsType = Record
+ const locations: LocationsType = Locations
+ const [isVisible, setIsVisible] = useState(true)
+ const opacity = useSharedValue(1)
+ void MapLibreGL.setAccessToken(null)
const toggleShowAllFloors = (): void => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
@@ -120,34 +133,43 @@ const MapScreen = (): JSX.Element => {
return {
transform: [{ translateY: bottom }],
+ opacity: opacity.value,
}
})
- const handleSheetChangesModal = useCallback((index: number) => {
- if (index === -1) {
- setClickedElement(null)
- _setView(mapCenter, mapRef)
- _removeMarker(mapRef)
- bottomSheetRef.current?.snapToIndex(1)
- }
+ const handleSheetChangesModal = useCallback(() => {
+ setClickedElement(null)
+
+ bottomSheetRef.current?.snapToIndex(1)
}, [])
const handlePresentModalPress = useCallback(() => {
bottomSheetRef.current?.close()
+
bottomSheetModalRef.current?.present()
}, [])
- const {
- data: mapOverlay,
- refetch,
- error: overlayError,
- } = useQuery({
- queryKey: ['mapOverlay', packageInfo.version],
- queryFn: async () => await NeulandAPI.getMapOverlay(),
- staleTime: 1000 * 60 * 60 * 24 * 7, // 1 week
- gcTime: 1000 * 60 * 60 * 24 * 90, // 90 days
- networkMode: 'always',
- })
+ const { data: mapOverlay, error: overlayError } =
+ useQuery({
+ queryKey: ['mapOverlay', packageInfo.version],
+ queryFn: async () => await NeulandAPI.getMapOverlay(),
+ staleTime: 1000 * 60 * 60 * 24 * 7, // 1 week
+ gcTime: 1000 * 60 * 60 * 24 * 90, // 90 days
+ networkMode: 'always',
+ })
+
+ useEffect(() => {
+ if (overlayError != null) {
+ Toast.show(t('toast.mapOverlay', { ns: 'common' }), {
+ duration: Toast.durations.SHORT,
+ position: 50,
+ shadow: false,
+ animation: true,
+ hideOnPress: true,
+ delay: 0,
+ })
+ }
+ }, [overlayError])
const { data: timetable } = useQuery({
queryKey: ['timetable', userKind],
@@ -209,17 +231,19 @@ const MapScreen = (): JSX.Element => {
}
}, [timetable])
- const allRooms = useMemo(() => {
+ const allRooms: FeatureCollection = useMemo(() => {
if (mapOverlay == null) {
- return []
+ console.log('No map overlay data')
+ return { type: 'FeatureCollection', features: [] }
}
const rooms = mapOverlay.features
.map((feature) => {
- const { geometry, properties } = feature
+ const { type, id, geometry, properties } = feature
if (
- geometry?.coordinates == null ||
+ geometry == null ||
+ properties == null ||
geometry.type !== 'Polygon'
) {
return []
@@ -228,21 +252,22 @@ const MapScreen = (): JSX.Element => {
if (properties.Ebene in FLOOR_SUBSTITUTES) {
properties.Ebene = FLOOR_SUBSTITUTES[properties.Ebene]
}
- if (!FLOOR_ORDER.includes(properties.Ebene)) {
- FLOOR_ORDER.push(properties.Ebene)
+ if (!FLOOR_ORDER.includes(properties.Ebene as string)) {
+ FLOOR_ORDER.push(properties.Ebene as string)
}
-
- return geometry.coordinates.map((points: number[][]) => ({
- properties,
- coordinates: points,
- options: {
- type: SEARCH_TYPES.ROOM,
- center: getCenterSingle(points),
+ return {
+ type,
+ id,
+ properties: {
+ ...properties,
+ rtype: SEARCH_TYPES.ROOM,
+ center: getCenterSingle(geometry.coordinates),
icon: getIcon(SEARCH_TYPES.ROOM, {
result: { item: { properties } },
}),
- },
- }))
+ } as unknown as FeatureProperties,
+ geometry,
+ }
})
.flat()
const buildings = BUILDINGS.map((building) => {
@@ -253,8 +278,12 @@ const MapScreen = (): JSX.Element => {
new Set(buildingRooms.map((room) => room.properties.Ebene))
).length
const location = buildingRooms[0].properties.Standort
-
+ const center = getCenter(
+ buildingRooms.map((x) => x.geometry.coordinates)
+ )
return {
+ type: 'Feature',
+ id: building,
properties: {
Raum: building,
Funktion_en: 'Building',
@@ -263,16 +292,20 @@ const MapScreen = (): JSX.Element => {
Ebene: 'EG', // Dummy value to not break the floor picker
Etage: floorCount.toString(),
Standort: location,
- } satisfies FeatureProperties,
- coordinates: [],
- options: {
- type: SEARCH_TYPES.BUILDING,
- center: getCenter(buildingRooms.map((x) => x.coordinates)),
+ rtype: SEARCH_TYPES.BUILDING,
+ center,
icon: getIcon(SEARCH_TYPES.BUILDING),
},
- }
+ geometry: {
+ type: 'Point',
+ coordinates: center,
+ },
+ } satisfies Feature
})
- return [...rooms, ...buildings]
+ return {
+ type: 'FeatureCollection',
+ features: [...rooms, ...buildings],
+ }
}, [mapOverlay])
useEffect(() => {
@@ -280,8 +313,9 @@ const MapScreen = (): JSX.Element => {
const unsubscribe = navigation.addListener('tabPress', (e) => {
// if already on the map screen, reset the map without animation
// if not on the map screen, reset the map with animation
- _setView(mapCenter, mapRef, isFocused)
+ // _setView(mapCenter, mapRef, isFocused)
bottomSheetModalRef.current?.close()
+ setView()
})
return unsubscribe
@@ -291,22 +325,27 @@ const MapScreen = (): JSX.Element => {
if (routeParams === null || routeParams === '') {
return
}
- setClickedElement({
- data: routeParams,
- type: SEARCH_TYPES.ROOM,
- })
- const room = allRooms.find((x) => x.properties.Raum === routeParams)
+ const room = allRooms.features.find(
+ (x) => x.properties?.Raum === routeParams
+ )?.properties
+
if (room == null) {
void showToast(t('toast.roomNotFound'))
updateRouteParams('')
return
}
- const center = room.options.center
- const etage = room?.properties.Ebene
- _setView(center, mapRef)
- setCurrentFloor(etage ?? 'EG')
- _injectMarker(mapRef, center, colors)
+
+ setClickedElement({
+ data: routeParams,
+ type: SEARCH_TYPES.ROOM,
+ center: room.center,
+ manual: false,
+ })
+ setCurrentFloor({
+ floor: room.Ebene,
+ manual: false,
+ })
handlePresentModalPress()
updateRouteParams('')
@@ -326,7 +365,6 @@ const MapScreen = (): JSX.Element => {
updateRouteParams('')
}
}, [localSearch])
- const currentDate = new Date()
const { data: roomStatusData } = useQuery({
queryKey: ['fnreeRooms', formatISODate(currentDate)],
@@ -366,25 +404,21 @@ const MapScreen = (): JSX.Element => {
}
setAvailableRooms(null)
void load()
- }, [userKind, webViewKey, roomStatusData])
-
- useEffect(() => {
- if (overlayError !== null) {
- void refetch()
- }
- }, [webViewKey])
+ }, [userKind, roomStatusData])
// if current floor changes hide the detail sheet and marker
useEffect(() => {
if (clickedElement != null) {
bottomSheetModalRef.current?.close()
- bottomSheetRef.current?.snapToIndex(1)
}
}, [currentFloor])
const uniqueEtages = Array.from(
new Set(
- (Array.isArray(allRooms) ? allRooms : Object.values(allRooms))
+ (Array.isArray(allRooms.features)
+ ? allRooms.features
+ : Object.values(allRooms.features)
+ )
.map((room: any) => room.properties?.Ebene?.toString())
.filter((etage) => etage != null)
)
@@ -392,39 +426,67 @@ const MapScreen = (): JSX.Element => {
(a: string, b: string) =>
FLOOR_ORDER.indexOf(a) - FLOOR_ORDER.indexOf(b)
)
+ const [filteredGeoJSON, setFilteredGeoJSON] = useState()
+ const [availableFilteredGeoJSON, setAvailableFilteredGeoJSON] =
+ useState()
useEffect(() => {
- if (LoadingState.LOADED !== loadingState) return
- // bottomSheetRef.current?.snapToIndex(1)
- _removeAllGeoJson(mapRef)
- _addGeoJson()
- if (clickedElement != null) {
- _updateMarkerColor(mapRef, colors.primary)
+ if (mapOverlay == null) {
+ return
}
- }, [currentFloor, allRooms, colors, availableRooms, allRooms, loadingState])
-
- const _addGeoJson = (): void => {
- const filterEtage = (etage: string): RoomEntry[] => {
- const result = allRooms.filter(
- (feature) => feature.properties.Ebene === etage
+ // filter the filteredGeoJSON to only show available rooms
+ const filterAvailableRooms = (
+ rooms: FeatureCollection | undefined
+ ): Array> => {
+ if (rooms == null) {
+ return []
+ }
+ const result = rooms.features.filter(
+ (feature) =>
+ feature.properties != null &&
+ availableRooms?.find(
+ (x) => x.room === feature.properties?.Raum
+ )
)
return result
}
- const filteredFeatures = filterEtage(currentFloor)
- filteredFeatures?.forEach((feature) => {
- _addRoom(feature, availableRooms, mapRef, colors)
- })
- }
+ const filteredFeatures = filterAvailableRooms(filteredGeoJSON)
+
+ const availableFilteredGeoJSON: FeatureCollection<
+ Geometry,
+ GeoJsonProperties
+ > = {
+ type: 'FeatureCollection',
+ features: filteredFeatures,
+ }
- const onContentProcessDidTerminate = (): void => {
- setWebViewKey((k) => k + 1)
- _addGeoJson()
- }
+ setAvailableFilteredGeoJSON(availableFilteredGeoJSON)
+ }, [availableRooms, filteredGeoJSON, mapOverlay])
+
+ useEffect(() => {
+ if (mapOverlay == null) {
+ return
+ }
+ const filterEtage = (etage: string): any => {
+ return allRooms.features.filter(
+ (feature) => feature.properties?.Ebene === etage
+ )
+ }
+
+ const filteredFeatures = filterEtage(currentFloor?.floor ?? 'EG')
+
+ const newGeoJSON: FeatureCollection = {
+ ...mapOverlay,
+ features: filteredFeatures,
+ }
+
+ setFilteredGeoJSON(newGeoJSON)
+ }, [currentFloor, allRooms, mapOverlay]) // Ensure dependencies are correctly listed
const getRoomData = (room: string): any => {
const occupancies = availableRooms?.find((x) => x.room === room)
- const properties = allRooms.find(
- (x) => x.properties.Raum === room
+ const properties = allRooms.features.find(
+ (x) => x.properties?.Raum === room
)?.properties
const roomData = {
@@ -446,18 +508,18 @@ const MapScreen = (): JSX.Element => {
}
const getBuildingData = (building: string): any => {
- const buildingDetails = allRooms.find(
+ const buildingDetails = allRooms.features.find(
(x) =>
- x.properties.Gebaeude === building &&
- x.options.type === SEARCH_TYPES.BUILDING
+ x.properties?.Gebaeude === building &&
+ x.properties?.rtype === SEARCH_TYPES.BUILDING
)
const numberOfFreeRooms = availableRooms?.filter(
(x) => x.room[0] === building || x.room.slice(0, 1) === building
).length
- const numberOfRooms = allRooms.filter(
+ const numberOfRooms = allRooms.features.filter(
(x) =>
- x.properties.Gebaeude === building &&
- x.options.type === SEARCH_TYPES.ROOM
+ x.properties?.Gebaeude === building &&
+ x.properties?.rtype === SEARCH_TYPES.ROOM
).length
const buildingData = {
title: building,
@@ -472,14 +534,6 @@ const MapScreen = (): JSX.Element => {
return buildingData
}
- enum Locations {
- IN = 'Ingolstadt',
- ND = 'Neuburg',
- }
-
- type LocationsType = Record
- const locations: LocationsType = Locations
-
const roomData: RoomData = useMemo(() => {
switch (clickedElement?.type) {
case SEARCH_TYPES.ROOM:
@@ -496,103 +550,276 @@ const MapScreen = (): JSX.Element => {
}
}, [clickedElement])
+ function setView(clickedElement: any = null): void {
+ if (clickedElement == null) {
+ cameraRef.current?.setCamera({
+ centerCoordinate: mapCenter,
+ zoomLevel: 16.5,
+ animationDuration: 400,
+ heading: 0,
+ })
+ return
+ }
+
+ const [longitude, latitude] = clickedElement.center
+ const adjustedLatitude = latitude - 0.0003
+ // Use the adjusted center for flyTo
+ cameraRef.current?.setCamera({
+ centerCoordinate: [longitude, adjustedLatitude],
+ zoomLevel: 17,
+ animationDuration: 500,
+ })
+ }
+
+ const [cameraTriggerKey, setCameraTriggerKey] = useState(0)
+
+ useEffect(() => {
+ if (mapOverlay == null) {
+ return
+ }
+ if (clickedElement == null) {
+ if (currentFloor?.manual !== true) {
+ setCurrentFloor({ floor: 'EG', manual: false })
+ }
+ cameraRef.current?.setCamera({
+ centerCoordinate: mapCenter,
+ zoomLevel: 16.5,
+ animationDuration: 500,
+ heading: 0,
+ })
+ } else if (clickedElement !== null && clickedElement.manual === false) {
+ setView(clickedElement)
+ }
+ }, [clickedElement])
+
+ useFocusEffect(() => {
+ StatusBar.setBarStyle(theme.dark ? 'light-content' : 'dark-content')
+ return () => {
+ StatusBar.setBarStyle('default')
+ }
+ })
+
+ useEffect(() => {
+ setDisableFollowUser(false)
+ }, [cameraTriggerKey])
+
+ useEffect(() => {
+ if (clickedElement !== null) {
+ setDisableFollowUser(true)
+ }
+ }, [clickedElement])
+
+ const layerStyles = {
+ allRooms: {
+ fillAntialias: true,
+ fillColor: isDark ? '#6a7178' : '#a4a4a4',
+ fillOpacity: 0.1,
+ },
+ allRoomsOutline: {
+ lineColor: isDark ? '#2d3035' : '#979797',
+ lineWidth: 2.3,
+ },
+ availableRooms: {
+ fillAntialias: true,
+ fillOpacity: 0.2,
+ },
+ availableRoomsOutline: {
+ lineWidth: 2.3,
+ },
+ osmBackground: {
+ backgroundColor: isDark
+ ? 'rgba(166, 173, 181, 0.69)'
+ : 'rgba(222, 221, 203, 0.69)',
+ paddingHorizontal: 4,
+ borderRadius: 4,
+ },
+ }
+
+ const [regionChange, setRegionChange] = useState(false)
+
+ useEffect(() => {
+ // As required by the OSM attribution, the attribution must be displayed until the user interacts with the map or 5 seconds after the map has loaded
+ let timer: NodeJS.Timeout
+ const startFadeOut = (): void => {
+ opacity.value = withTiming(0, { duration: 500 }, () => {
+ runOnJS(setIsVisible)(false)
+ })
+ }
+
+ if (regionChange) {
+ // If region changes, fade out directly without waiting for 5 seconds
+ startFadeOut()
+ } else if (!regionChange && isVisible) {
+ // Otherwise, wait for 5 seconds before fading out
+ timer = setTimeout(() => {
+ startFadeOut()
+ }, 5000)
+ }
+
+ return () => {
+ clearTimeout(timer)
+ }
+ }, [regionChange, isVisible, opacity])
+
return (
-
- {loadingState === LoadingState.ERROR ||
- (overlayError !== null && (
-
- {
- setWebViewKey(webViewKey + 1)
- setLoadingState(LoadingState.LOADING)
- }}
- />
-
- ))}
+ <>
+ {mapLoadState === LoadingState.ERROR && (
+
+ mapRef.current?.render()}
+ refreshing={true}
+ />
+
+ )}
+ {mapLoadState === LoadingState.LOADING && (
+
+
+
+ )}
+ >
+
- {
+ setMapLoadState(LoadingState.ERROR)
+ }}
+ onDidFinishLoadingMap={() => {
+ setMapLoadState(LoadingState.LOADED)
+ }}
ref={mapRef}
- source={{
- html: htmlScript,
+ onDidFinishRenderingMapFully={() => {
+ setRegionChange(false)
}}
- onLoadEnd={() => {
- if (loadingState !== LoadingState.ERROR) {
- console.log('Map loaded')
- setLoadingState(LoadingState.LOADED)
- _setView(mapCenter, mapRef)
- _addGeoJson()
- }
+ onRegionIsChanging={() => {
+ setRegionChange(true)
}}
- startInLoadingState={true}
- renderLoading={() => (
-
+
+
+
+ {filteredGeoJSON != null && (
+ {
+ setClickedElement({
+ data: e.features[0].properties?.Raum,
+ type: SEARCH_TYPES.ROOM,
+ center: e.features[0].properties
+ ?.center,
+ manual: true,
+ })
+
+ handlePresentModalPress()
}}
+ hitbox={{ width: 0, height: 0 }}
>
-
+
-
+
)}
- onContentProcessDidTerminate={
- onContentProcessDidTerminate
- }
- onMessage={(event) => {
- let data = event.nativeEvent.data as any
-
- if (typeof data === 'string') {
- try {
- data = JSON.parse(data)
- } catch (e) {
- console.error('Could not parse data:', e)
- }
- }
-
- if (data === 'noInternetConnection') {
- setLoadingState(LoadingState.ERROR)
- setErrorMsg(data as string)
- } else if (data.type === 'roomClick') {
- setClickedElement({
- data: data.payload.properties.room,
- type: SEARCH_TYPES.ROOM,
- })
- const center = data.payload.properties
- .center as number[]
- _injectMarker(mapRef, center, colors)
- Keyboard.dismiss()
- bottomSheetRef.current?.close()
- handlePresentModalPress()
- } else {
- console.log('Unhandled message:', data)
- }
- }}
- style={{
- backgroundColor: colors.background,
- }}
- >
- {loadingState === LoadingState.LOADED && (
- <>
+ {availableFilteredGeoJSON != null && (
+
+
+
+
+ )}
+ {clickedElement != null && (
+
+
+
+
+
+ )}
+
+ <>
+ {overlayError === null && (
- >
- )}
+ )}
+ >
+
{
@@ -600,17 +827,17 @@ const MapScreen = (): JSX.Element => {
'https://www.openstreetmap.org/copyright'
)
}}
- style={styles.osmBackground}
+ style={layerStyles.osmBackground}
>
{'© OpenStreetMap'}
+
@@ -635,47 +862,24 @@ const styles = StyleSheet.create({
innerContainer: { flex: 1 },
map: {
flex: 1,
- position: 'relative',
},
errorContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ zIndex: 100,
+ width: '100%',
+ height: '100%',
position: 'absolute',
- top: 0,
- bottom: 0,
- left: 0,
- right: 0,
- zIndex: 3,
- paddingTop: 70,
- alignItems: 'center',
- },
- loadingContainer: {
- top: 0,
- bottom: 0,
- left: 0,
- position: 'absolute',
- right: 0,
- zIndex: 2,
- },
- loadingIndicator: {
- position: 'absolute',
- top: 0,
- bottom: 0,
- left: 0,
- right: 0,
- zIndex: 3,
},
- osmAtrribution: { fontSize: 12 },
+ osmAtrribution: { fontSize: 13 },
osmContainer: {
height: 30,
right: 0,
- top: -17,
+ top: -19,
marginRight: 4,
alignItems: 'flex-end',
- zIndex: 100,
+ zIndex: 99,
position: 'absolute',
- },
- osmBackground: {
- backgroundColor: 'rgba(222, 221, 203, 0.69)',
- paddingHorizontal: 4,
- borderRadius: 4,
+ gap: 2,
},
})
diff --git a/src/components/Elements/Map/ModalSections.ts b/src/components/Elements/Map/ModalSections.ts
index fe2c51ab..24c9e4bb 100644
--- a/src/components/Elements/Map/ModalSections.ts
+++ b/src/components/Elements/Map/ModalSections.ts
@@ -87,26 +87,27 @@ export const modalSection = (
{
title: t('pages.map.details.room.building'),
value:
- roomData.properties.Gebaeude ??
+ roomData?.properties?.Gebaeude ??
t('misc.unknown'),
},
{
title: t('pages.map.details.room.floor'),
value:
- roomData.properties.Ebene ??
+ roomData?.properties?.Ebene ??
t('misc.unknown'),
},
{
title: t('pages.map.details.room.type'),
value:
- roomData.properties.Funktion_en ??
+ roomData?.properties?.Funktion_en ??
t('misc.unknown'),
},
{
title: 'Campus',
value:
- locations[roomData.properties.Standort] ??
- t('misc.unknown'),
+ locations[
+ roomData?.properties?.Standort
+ ] ?? t('misc.unknown'),
},
],
},
@@ -126,15 +127,19 @@ export const modalSection = (
items: [
{
title: t('pages.map.details.building.total'),
- value: occupancies.total,
+ value:
+ occupancies.total.toString() ?? t('misc.unknown'),
},
{
title: t('pages.map.details.building.free'),
- value: occupancies.available ?? t('misc.unknown'),
+ value:
+ occupancies.available.toString() ??
+ t('misc.unknown'),
},
{
title: t('pages.map.details.building.floors'),
- value: properties?.Etage ?? t('misc.unknown'),
+ value:
+ properties?.Etage.toString() ?? t('misc.unknown'),
},
{
title: 'Campus',
diff --git a/src/components/Elements/Map/SearchResultRow.tsx b/src/components/Elements/Map/SearchResultRow.tsx
index ff8463d6..c85f0841 100644
--- a/src/components/Elements/Map/SearchResultRow.tsx
+++ b/src/components/Elements/Map/SearchResultRow.tsx
@@ -5,45 +5,48 @@ import { getContrastColor } from '@/utils/ui-utils'
import { TouchableOpacity } from '@gorhom/bottom-sheet'
import React, { useContext } from 'react'
import { Keyboard, StyleSheet, Text, View } from 'react-native'
-import type WebView from 'react-native-webview'
import Divider from '../Universal/Divider'
import PlatformIcon from '../Universal/Icon'
-import { _injectMarker, _setView } from './leaflet'
const ResultRow: React.FC<{
result: SearchResult
index: number
colors: Colors
- mapRef: React.RefObject
handlePresentModalPress: () => void
bottomSheetRef: React.RefObject
}> = ({
result,
index,
colors,
- mapRef,
handlePresentModalPress,
bottomSheetRef,
}): JSX.Element => {
const { setClickedElement, setLocalSearch, setCurrentFloor } =
useContext(MapContext)
+ console.log(result)
return (
{
- const center = result.item.options.center
+ const center = result.item.properties?.center
Keyboard.dismiss()
bottomSheetRef.current?.collapse()
- _setView(center, mapRef)
+ // _setView(center, mapRef)
setClickedElement({
data: result.title,
- type: result.item.options.type,
+ type: result.item.properties?.rtype,
+ center,
+ manual: false,
+ })
+ setCurrentFloor({
+ floor:
+ (result.item.properties?.Ebene as string) ?? 'EG',
+ manual: false,
})
- setCurrentFloor(result.item.properties.Ebene)
handlePresentModalPress()
- _injectMarker(mapRef, center, colors)
+ // _injectMarker(mapRef, center, colors)
setLocalSearch('')
}}
>
@@ -56,11 +59,11 @@ const ResultRow: React.FC<{
,
- colors: Colors
-): void => {
- const coordinates = room.coordinates
- const name = room?.properties?.Raum
- const avail = availableRooms?.find((x) => x.room === name)
- const color = avail != null ? colors.primary : 'grey'
-
- const geojsonFeature = {
- type: 'Feature',
- geometry: {
- type: 'Polygon',
- coordinates: [coordinates],
- },
- properties: {
- room: room?.properties.Raum,
- center: room?.options?.center,
- },
- }
-
- if (mapRef.current == null) return
- mapRef.current.injectJavaScript(`
- var geojsonFeature = ${JSON.stringify(geojsonFeature)};
-
- var layer = L.geoJSON(geojsonFeature, {
- color: ${JSON.stringify(color)},
- fillOpacity: 0.2,
- }).addTo(mymap).bringToBack();
-
- // Add click event listener to the layer
- layer.on('click', function(e) {
- var properties = e.layer.feature.properties;
-
-
- // Send data to React Native app
- sendMessageToRN(JSON.stringify({
- type: 'roomClick',
- payload: {
- type: 'room',
- properties: properties,
- },
- }));
- });
- true;
- `)
-}
-
-/**
- * Removes all GeoJSON layers from the Leaflet map.
- *
- * @param mapRef - A reference to the Leaflet map.
- * @returns void
- */
-export const _removeAllGeoJson = (mapRef: React.RefObject): void => {
- mapRef.current?.injectJavaScript(`
- mymap.eachLayer(function (layer) {
- if (layer instanceof L.GeoJSON) {
- mymap.removeLayer(layer);
- }
- });
- true
- `)
-}
-
-/**
- * Sets the center of the Leaflet map to the specified coordinates.
- *
- * @param center - The coordinates to center the map on.
- * @param mapRef - A reference to the Leaflet map.
- * @param animate - Whether to animate the transition.
- * @returns void
- */
-export const _setView = (
- center: number[] | undefined,
- mapRef: React.RefObject,
- animate: boolean = true
-): void => {
- if (center == null) return
- mapRef.current?.injectJavaScript(`
-mymap.setView(${JSON.stringify(center)}, 17.5, { animate: ${animate} });
-true;
-`)
-}
-
-// inject the current location into the map
-export const _injectCurrentLocation = (
- mapRef: React.RefObject,
- colors: Colors,
- accuracy: number,
- currentLocation: number[]
-): void => {
- mapRef.current?.injectJavaScript(`
-if (window.currentLocationMarker) {
- window.currentLocationMarker.remove();
-}
-
-window.currentLocationMarker = L.circle(${JSON.stringify(currentLocation)}, {
- color: ${JSON.stringify(colors.primary)},
- fillColor: ${JSON.stringify(colors.primary)},
- fillOpacity: 0.6,
- radius: ${accuracy},
-}).addTo(mymap);
-true;
-`)
-}
-
-export const _injectMarker = (
- mapRef: React.RefObject,
- coordinates: number[],
- colors: Colors
-): void => {
- const color = colors.primary
- const svg = `
-
-
-
-
-`
- const base64Svg = Buffer.from(svg).toString('base64')
- mapRef.current?.injectJavaScript(`
-if (window.marker) {
- window.marker.remove();
-}
-
-window.marker = L.marker(${JSON.stringify(coordinates)}, {
- icon: L.icon({
- iconUrl: 'data:image/svg+xml;base64,${base64Svg}',
- iconSize: [45, 45],
- iconAnchor: [24, 42],
- })
-}).addTo(mymap);
-
-true;
-`)
-}
-
-// update marker color wihtout removing it
-export const _updateMarkerColor = (
- mapRef: React.RefObject,
- color: string
-): void => {
- mapRef.current?.injectJavaScript(`
-if (window.marker) {
- window.marker.setIcon(L.icon({
- iconUrl: 'data:image/svg+xml;base64,${Buffer.from(
- `
-
-
-
- `
- ).toString('base64')}',
- iconAnchor: [24, 42],
- iconSize: [45, 45],
- }));
-}
-true;
-`)
-}
-
-// remove the marker from the map
-export const _removeMarker = (mapRef: React.RefObject): void => {
- mapRef.current?.injectJavaScript(`
-if (window.marker) {
- window.marker.remove();
-}
-true;
-`)
-}
-
-/**
- * A string containing an HTML script that initializes a Leaflet map with OpenStreetMap tiles and event listeners for error and internet connection checking.
- */
-export const htmlScript = `
-
-
-
-
- Quick Start - Leaflet
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`
diff --git a/src/data/calendar.json b/src/data/calendar.json
index aa169760..e6d6b85a 100644
--- a/src/data/calendar.json
+++ b/src/data/calendar.json
@@ -1,9 +1,6 @@
[
{
- "name": {
- "de": "",
- "en": ""
- },
+ "name": "",
"begin": "1970-01-01",
"comments": [
"If you work on this file, please triple-check if you entered all days correctly.",
@@ -13,94 +10,144 @@
},
{
"name": {
- "de": "Semesterferien",
- "en": "Semester break"
+ "de": "Rückmeldung zum Wintersemester 2024/2025",
+ "en": "Re-registration for winter semester 2024/2025"
},
- "begin": "2023-08-01",
- "end": "2023-09-30"
+ "begin": "2024-07-02",
+ "end": "2024-08-07"
},
{
"name": {
- "de": "Vorlesungsbeginn und Semesterstart",
- "en": "Start of lectures and semester"
+ "de": "Erweiterter Prüfungszeitraum",
+ "en": "Additional exam period"
},
- "begin": "2023-10-02"
+ "begin": "2024-07-04",
+ "end": "2024-07-10"
},
{
"name": {
- "de": "Vorlesungsfrei",
- "en": "No lectures"
+ "de": "Ende der Vorlesungszeit",
+ "en": "End of lecture period"
+ },
+ "begin": "2024-07-10"
+ },
+ {
+ "name": {
+ "de": "Prüfungszeitraum",
+ "en": "Exam period"
+ },
+ "begin": "2024-07-11",
+ "end": "2024-07-23"
+ },
+ {
+ "name": {
+ "de": "Notenmeldung",
+ "en": "Recording grades"
+ },
+ "begin": "2024-07-26T9:00",
+ "hasHours": true
+ },
+
+ {
+ "name": {
+ "de": "Notenbekanntgabe",
+ "en": "Announcement of grades"
},
- "begin": "2023-10-03"
+ "begin": "2024-07-31T13:00",
+ "hasHours": true
+ },
+ {
+ "name": {
+ "de": "Semesterferien",
+ "en": "Semester break"
+ },
+ "begin": "2024-08-01",
+ "end": "2024-09-30"
+ },
+ {
+ "name": {
+ "de": "Vorlesungsbeginn und Semesterstart",
+ "en": "Start of lectures and semester"
+ },
+ "begin": "2024-10-01"
},
{
"name": {
"de": "Vorlesungsfrei",
"en": "No lectures"
},
- "begin": "2023-10-31"
+ "begin": "2024-10-31",
+ "end": "2024-11-01"
},
{
"name": {
"de": "Prüfungsanmeldung",
"en": "Registration for exams"
},
- "begin": "2023-11-06",
- "end": "2023-11-16"
+ "begin": "2024-11-08",
+ "end": "2024-11-17"
},
{
"name": {
"de": "Prüfungsabmeldung",
"en": "Deregistration for exams"
},
- "begin": "2023-11-06",
- "end": "2024-01-11"
+ "begin": "2024-11-08",
+ "end": "2025-01-10"
},
{
"name": {
"de": "Vorlesungsfrei (Weihnachten)",
"en": "No lectures (Christmas)"
},
- "begin": "2023-12-24",
- "end": "2024-01-07"
+ "begin": "2024-12-21",
+ "end": "2025-01-06"
},
{
"name": {
"de": "Notenmeldung Prädikate (ZV)",
"en": "Recording grades predicates (admission requirements)"
},
- "begin": "2024-01-09T9:00",
+ "begin": "2025-01-08T9:00",
"hasHours": true
},
+ {
+ "name": {
+ "de": "Rückmeldung zum Sommersemester 2025",
+ "en": "Re-registration for summer semester 2025"
+ },
+ "begin": "2025-01-14",
+ "end": "2025-01-31"
+ },
{
"name": {
"de": "Erweiterter Prüfungszeitraum",
"en": "Additional exam period"
},
- "begin": "2024-01-19",
- "end": "2024-01-25"
+ "begin": "2025-01-18",
+ "end": "2025-01-24"
},
{
"name": {
"de": "Ende der Vorlesungszeit",
"en": "End of lecture period"
},
- "begin": "2024-01-25"
+ "begin": "2025-01-24"
},
{
"name": {
"de": "Prüfungszeitraum",
"en": "Exam period"
},
- "begin": "2024-01-26",
- "end": "2024-02-05"
+ "begin": "2025-01-25",
+ "end": "2025-02-04"
},
{
"name": {
"de": "Notenmeldung",
"en": "Recording grades"
},
- "begin": "2024-02-09T9:00",
+ "begin": "2025-02-10T9:00",
"hasHours": true
},
{
@@ -108,7 +155,7 @@
"de": "Notenbekanntgabe",
"en": "Announcement of grades"
},
- "begin": "2024-02-14T13:00",
+ "begin": "2025-02-14T13:00",
"hasHours": true
},
{
@@ -116,100 +163,101 @@
"de": "Semesterferien",
"en": "Semester break"
},
- "begin": "2024-02-15",
- "end": "2024-03-14"
+ "begin": "2025-02-15",
+ "end": "2025-03-14"
},
{
"name": {
"de": "Vorlesungsbeginn und Semesterstart",
"en": "Start of lectures and semester"
},
- "begin": "2024-03-18"
+ "begin": "2025-03-17"
},
{
"name": {
"de": "Vorlesungsfrei (Ostern)",
"en": "No lectures (Easter)"
},
- "begin": "2024-03-28",
- "end": "2024-04-02"
+ "begin": "2025-04-17",
+ "end": "2025-04-22"
},
{
"name": {
"de": "Prüfungsanmeldung",
"en": "Registration for exams"
},
- "begin": "2024-04-23",
- "end": "2024-05-02"
+ "begin": "2025-04-30",
+ "end": "2025-05-09"
},
{
"name": {
"de": "Prüfungsabmeldung",
"en": "Deregistration for exams"
},
- "begin": "2024-04-23",
- "end": "2024-06-26"
- },
- {
- "name": {
- "de": "Vorlesungsfrei (Christi Himmelfahrt, Brückentag)",
- "en": "No lectures (Ascension Day, Bridge Day)"
- },
- "begin": "2024-05-09",
- "end": "2024-05-10"
+ "begin": "2025-04-30",
+ "end": "2025-06-26"
},
{
"name": {
"de": "Vorlesungsfrei (Pfingsten)",
"en": "No lectures (Whitsun)"
},
- "begin": "2024-05-17",
- "end": "2024-05-21"
+ "begin": "2025-06-06",
+ "end": "2025-06-10"
},
{
"name": {
- "de": "Vorlesungsfrei (Fronleichnam)",
- "en": "No lectures (Corpus Christi)"
+ "de": "Vorlesungsfrei (Fronleichnam, Brückentag)",
+ "en": "No lectures (Corpus Christi, Bridge Day)"
},
- "begin": "2024-05-30"
+ "begin": "2025-06-19",
+ "end": "2025-06-20"
},
{
"name": {
"de": "Notenmeldung Prädikate (ZV)",
"en": "Recording grades predicates (admission requirements)"
},
- "begin": "2024-06-19T9:00",
+ "begin": "2025-06-18T9:00",
"hasHours": true
},
+ {
+ "name": {
+ "de": "Rückmeldung zum Wintersemester 2025/2026",
+ "en": "Re-registration for winter semester 2025/2026"
+ },
+ "begin": "2025-07-02",
+ "end": "2025-08-07"
+ },
{
"name": {
"de": "Erweiterter Prüfungszeitraum",
"en": "Additional exam period"
},
- "begin": "2024-07-04",
- "end": "2024-07-10"
+ "begin": "2025-07-04",
+ "end": "2025-07-10"
},
{
"name": {
"de": "Ende der Vorlesungszeit",
"en": "End of lecture period"
},
- "begin": "2024-07-10"
+ "begin": "2025-07-10"
},
{
"name": {
"de": "Prüfungszeitraum",
"en": "Exam period"
},
- "begin": "2024-07-11",
- "end": "2024-07-23"
+ "begin": "2025-07-11",
+ "end": "2025-07-23"
},
{
"name": {
"de": "Notenmeldung",
"en": "Recording grades"
},
- "begin": "2024-07-26T9:00",
+ "begin": "2025-07-28T9:00",
"hasHours": true
},
{
@@ -217,7 +265,7 @@
"de": "Notenbekanntgabe",
"en": "Announcement of grades"
},
- "begin": "2024-07-31T13:00",
+ "begin": "2025-07-31T13:00",
"hasHours": true
},
{
@@ -225,7 +273,7 @@
"de": "Semesterferien",
"en": "Semester break"
},
- "begin": "2024-08-01",
- "end": "2024-09-30"
+ "begin": "2025-08-01",
+ "end": "2025-09-30"
}
]
diff --git a/src/data/changelog.json b/src/data/changelog.json
index 637c7b7f..85c830b0 100644
--- a/src/data/changelog.json
+++ b/src/data/changelog.json
@@ -236,6 +236,51 @@
"android": "bug_report"
}
}
+ ],
+ "0.8": [
+ {
+ "title": {
+ "de": "Karten Darkmode",
+ "en": "Map Darkmode"
+ },
+ "description": {
+ "de": "Aktiviere den Darkmode, um eine dunkle Kartenansicht zu erhalten. Zudem wurde auch die helle Ansicht komplett erneuert und optimiert.",
+ "en": "Enable dark mode to get a dark map view. In addition, the light view has also been completely renewed and optimized."
+ },
+ "icon": {
+ "ios": "moon.stars",
+ "android": "dark_mode"
+ }
+ },
+ {
+ "title": {
+ "de": "Native Karten",
+ "en": "Native Map"
+ },
+ "description": {
+ "de": "Die Karte komplett neu geschrieben und verfügt nun über natives Rendering, Gesten und noch mehr Informationen.",
+ "en": "The map has been completely rewritten and now has native rendering, gestures and even more information."
+ },
+ "icon": {
+ "ios": "map",
+ "android": "map"
+ }
+ },
+
+ {
+ "title": {
+ "de": "Event Details",
+ "en": "Event Details"
+ },
+ "description": {
+ "de": "Tippe auf ein Event, um mehr Informationen zu erhalten, wie zum Ort oder der Beschreibung.",
+ "en": "Tap on an event to get more information, such as the location or description."
+ },
+ "icon": {
+ "ios": "party.popper",
+ "android": "event"
+ }
+ }
]
}
}
diff --git a/src/data/licenses-static.json b/src/data/licenses-static.json
index e4504c63..deb4abf0 100644
--- a/src/data/licenses-static.json
+++ b/src/data/licenses-static.json
@@ -13,10 +13,10 @@
"parents": "neuland",
"platform": "ios"
},
- "leaftlet@1.7.4": {
- "licenses": "BSD-2-Clause",
- "repository": "https://github.com/Leaflet/Leaflet/",
- "licenseUrl": "https://raw.githubusercontent.com/Leaflet/Leaflet/main/LICENSE",
+ "OpenStreetMap": {
+ "licenses": "ODbL",
+ "repository": "https://www.openstreetmap.org/",
+ "licenseUrl": "https://www.openstreetmap.org/copyright",
"parents": "neuland",
"platform": "all"
}
diff --git a/src/data/licenses.json b/src/data/licenses.json
index 28e993f2..de77a106 100644
--- a/src/data/licenses.json
+++ b/src/data/licenses.json
@@ -35,6 +35,12 @@
"licenseUrl": "https://github.com/Kichiyaki/react-native-barcode-generator/raw/master/LICENSE",
"parents": "neuland"
},
+ "@maplibre/maplibre-react-native@10.0.0-alpha.5": {
+ "licenses": "MIT",
+ "repository": "https://github.com/maplibre/maplibre-react-native",
+ "licenseUrl": "https://github.com/maplibre/maplibre-react-native/raw/master/LICENSE.md",
+ "parents": "neuland"
+ },
"@react-native-async-storage/async-storage@1.23.1": {
"licenses": "MIT",
"repository": "https://github.com/react-native-async-storage/async-storage",
@@ -53,12 +59,6 @@
"licenseUrl": "https://github.com/react-native-netinfo/react-native-netinfo/raw/master/LICENSE",
"parents": "neuland"
},
- "@react-native-menu/menu@0.9.1": {
- "licenses": "MIT",
- "repository": "https://github.com/react-native-menu/menu",
- "licenseUrl": "https://github.com/react-native-menu/menu/raw/master/LICENSE",
- "parents": "neuland"
- },
"@sentry/react-native@5.22.3": {
"licenses": "MIT",
"repository": "https://github.com/getsentry/sentry-react-native",
@@ -77,13 +77,13 @@
"licenseUrl": "https://github.com/TanStack/query/raw/master/LICENSE",
"parents": "neuland"
},
- "@tanstack/react-query-persist-client@5.45.0": {
+ "@tanstack/react-query-persist-client@5.45.1": {
"licenses": "MIT",
"repository": "https://github.com/TanStack/query",
"licenseUrl": "https://github.com/TanStack/query/raw/master/LICENSE",
"parents": "neuland"
},
- "@tanstack/react-query@5.45.0": {
+ "@tanstack/react-query@5.45.1": {
"licenses": "MIT",
"repository": "https://github.com/TanStack/query",
"licenseUrl": "https://github.com/TanStack/query/raw/master/LICENSE",
diff --git a/src/hooks/contexts/map.ts b/src/hooks/contexts/map.ts
index c044c0c8..dd4c13ab 100644
--- a/src/hooks/contexts/map.ts
+++ b/src/hooks/contexts/map.ts
@@ -12,8 +12,8 @@ interface MapContextType {
setAvailableRooms: (_: AvailableRoom[] | null) => void
nextLecture: FriendlyTimetableEntry[] | null
setNextLecture: (_: FriendlyTimetableEntry[] | null) => void
- currentFloor: string
- setCurrentFloor: (_: string) => void
+ currentFloor: { floor: string; manual: boolean } | null
+ setCurrentFloor: (_: { floor: string; manual: boolean }) => void
location: LocationObject | null | 'notGranted'
setLocation: (_: LocationObject | null | 'notGranted') => void
}
@@ -28,8 +28,8 @@ export const MapContext = createContext({
availableRooms: null,
setAvailableRooms: (_: AvailableRoom[] | null) => {},
- currentFloor: '',
- setCurrentFloor: (_: string) => {},
+ currentFloor: null,
+ setCurrentFloor: (_: { floor: string; manual: boolean }) => {},
location: null,
setLocation: (_: LocationObject | null | 'notGranted') => {},
diff --git a/src/localization/de/common.ts b/src/localization/de/common.ts
index 114d894e..d0441406 100644
--- a/src/localization/de/common.ts
+++ b/src/localization/de/common.ts
@@ -3,6 +3,7 @@ export default {
clipboard: 'in Zwischenablage kopiert',
paused: 'Keine Internetverbindung',
roomNotFound: 'Raum nicht gefunden',
+ mapOverlay: 'Fehler beim Laden des Overlays',
},
error: {
title: 'Ein Fehler ist aufgetreten',
@@ -140,7 +141,7 @@ export default {
easterEgg: {
title: 'Easter Egg',
message:
- 'Du hast das exklusive App-Icon "Neuland Pink" freigeschaltet! 🩷',
+ 'Du hast das exklusive retro App-Icon "neuland.app" freigeschaltet!',
confirm: 'Nice!',
},
noAvailableRooms: 'Keine freien Räume verfügbar',
@@ -173,6 +174,7 @@ export default {
alert: 'Um deinen aktuellen Standort anzuzeigen, musst du die Standortberechtigung aktivieren.',
button: 'Einstellungen',
},
+ osm: 'Karten Daten von OpenStreetMap',
},
},
rooms: {
diff --git a/src/localization/de/settings.ts b/src/localization/de/settings.ts
index fd51e7d6..18a4f66b 100644
--- a/src/localization/de/settings.ts
+++ b/src/localization/de/settings.ts
@@ -167,7 +167,7 @@ export default {
names: {
default: 'Neuland Next',
modernDark: 'Modern Dunkel',
- modernLight: 'Modern Hell',
+ retro: 'neuland.app',
modernGreen: 'Modern Grün',
modernPink: 'Modern Pink',
rainbowMoonLight: 'Regenbogen Hell',
diff --git a/src/localization/en/common.ts b/src/localization/en/common.ts
index c2512eb5..ec2c22a1 100644
--- a/src/localization/en/common.ts
+++ b/src/localization/en/common.ts
@@ -3,6 +3,7 @@ export default {
clipboard: 'copied to clipboard',
paused: 'No internet connection',
roomNotFound: 'Room not found',
+ mapOverlay: 'Error while loading overlay',
},
error: {
title: 'An error occurred',
@@ -138,7 +139,7 @@ export default {
easterEgg: {
title: 'Easter Egg',
message:
- 'You unlocked the exclusive app icon "Neuland Pink"! 🩷',
+ 'You unlocked the exclusive retro app icon "neuland.app"!',
confirm: 'Nice!',
},
noAvailableRooms: 'No free rooms available',
@@ -172,6 +173,7 @@ export default {
alert: 'To see your current location, please enable location services.',
settings: 'Settings',
},
+ osm: 'Map data from OpenStreetMap',
},
},
diff --git a/src/localization/en/settings.ts b/src/localization/en/settings.ts
index 1557d119..62fe1524 100644
--- a/src/localization/en/settings.ts
+++ b/src/localization/en/settings.ts
@@ -164,7 +164,7 @@ export default {
names: {
default: 'Neuland Next',
modernDark: 'Modern Dark',
- modernLight: 'Modern Light',
+ retro: 'neuland.app',
modernGreen: 'Modern Green',
modernPink: 'Modern Pink',
rainbowMoonLight: 'Rainbow Light',
diff --git a/src/types/asset-api.ts b/src/types/asset-api.ts
index d5a24764..7d9399ca 100644
--- a/src/types/asset-api.ts
+++ b/src/types/asset-api.ts
@@ -1,3 +1,5 @@
+import { type MaterialIcon } from './material-icons'
+
export interface RoomsOverlay {
type: string
features: Feature[]
@@ -42,6 +44,9 @@ export interface FeatureProperties {
Raum: string
Funktion_de: string
Funktion_en: string
+ rtype?: number
+ center?: number[]
+ icon?: { ios: string; android: MaterialIcon }
}
export enum Gebaeude {
diff --git a/src/types/map.ts b/src/types/map.ts
index 51c55d3a..59cb3bad 100644
--- a/src/types/map.ts
+++ b/src/types/map.ts
@@ -1,4 +1,11 @@
-import { type AvailableRoom, type RoomEntry } from './utils'
+import {
+ type Feature,
+ type GeoJsonProperties,
+ type Geometry,
+ type Position,
+} from 'geojson'
+
+import { type AvailableRoom } from './utils'
export enum SEARCH_TYPES {
BUILDING,
@@ -8,7 +15,7 @@ export enum SEARCH_TYPES {
export interface RoomData {
title: string
subtitle: string
- properties: RoomEntry['properties']
+ properties: GeoJsonProperties
occupancies: AvailableRoom | BuildingOccupancy
type: SEARCH_TYPES
}
@@ -20,19 +27,13 @@ export interface BuildingOccupancy {
export interface ClickedMapElement {
type: SEARCH_TYPES
data: string
-}
-
-export interface searchResult {
- type: SEARCH_TYPES
- highlight: RoomEntry[]
- title: string
- subtitle: string
- center: number[]
+ center?: Position
+ manual?: boolean
}
export interface SearchResult {
title: string
subtitle: string
isExactMatch?: boolean
- item: RoomEntry
+ item: Feature
}
diff --git a/src/types/utils.ts b/src/types/utils.ts
index 5414e872..b068e5ae 100644
--- a/src/types/utils.ts
+++ b/src/types/utils.ts
@@ -1,5 +1,3 @@
-import { type SEARCH_TYPES } from './map'
-import { type MaterialIcon } from './material-icons'
import { type Lecturers } from './thi-api'
export interface Exam {
@@ -58,31 +56,6 @@ export interface AvailableRoom {
capacity: number
}
-export interface RoomEntry {
- coordinates: number[][]
- options: RoomOptions
- properties: Properties
-}
-
-interface RoomOptions {
- center: number[]
- type: SEARCH_TYPES
- icon: {
- ios: string
- android: MaterialIcon
- }
-}
-
-interface Properties {
- Ebene: string
- Etage: string
- Funktion_de: string
- Funktion_en: string
- Gebaeude: string
- Raum: string
- Standort: string
-}
-
export interface FriendlyTimetableEntry {
date: Date
startDate: Date
diff --git a/src/utils/map-utils.ts b/src/utils/map-utils.ts
index 8d0ff561..373461c7 100644
--- a/src/utils/map-utils.ts
+++ b/src/utils/map-utils.ts
@@ -1,9 +1,9 @@
-import { type FeatureProperties } from '@/types/asset-api'
import { SEARCH_TYPES } from '@/types/map'
import { type MaterialIcon } from '@/types/material-icons'
import { type Rooms } from '@/types/thi-api'
import { type AvailableRoom } from '@/types/utils'
import { trackEvent } from '@aptabase/react-native'
+import { type GeoJsonProperties, type Position } from 'geojson'
import { type TFunction } from 'i18next'
import { Platform, Share } from 'react-native'
@@ -29,8 +29,8 @@ export const BUILDINGS_IN = [
'X',
'Z',
]
-export const INGOLSTADT_CENTER = [48.7667, 11.4328]
-export const NEUBURG_CENTER = [48.73227, 11.17261]
+export const INGOLSTADT_CENTER = [11.4328, 48.7663]
+export const NEUBURG_CENTER = [11.17261, 48.732]
export const BUILDINGS_ND = ['BN', 'CN']
export const BUILDINGS = [...BUILDINGS_IN, ...BUILDINGS_ND]
export const BUILDINGS_ALL = 'Alle'
@@ -145,7 +145,6 @@ export function getRoomOpenings(rooms: Rooms[], date: Date): RoomOpenings {
})
)
)
- // iterate over every room
.forEach(
({
room,
@@ -274,20 +273,20 @@ export async function searchRooms(
.sort((a, b) => a.room.localeCompare(b.room))
}
-export function getCenter(rooms: number[][][]): number[] {
- const getCenterPoint = (points: number[][]): number[] => {
- const x = points.map((point) => point[0])
- const y = points.map((point) => point[1])
- const minX = Math.min(...x)
- const maxX = Math.max(...x)
- const minY = Math.min(...y)
- const maxY = Math.max(...y)
- return [(minX + maxX) / 2, (minY + maxY) / 2]
+export function getCenter(rooms: Position[][][]): Position {
+ const getCenterPoint = (points: Position[][]): Position[] => {
+ const x = points[0].map((point: any) => point[0])
+ const y = points[0].map((point: any) => point[1])
+ const minX = Math.min(...(x as number[]))
+ const maxX = Math.max(...(x as number[]))
+ const minY = Math.min(...(y as number[]))
+ const maxY = Math.max(...(y as number[]))
+ return [(minX + maxX) / 2, (minY + maxY) / 2] as unknown as Position[]
}
const centerPoints = rooms.reduce(
(acc, room) => {
- const centerPoint = getCenterPoint(room)
+ const centerPoint = getCenterPoint(room) as unknown as number[]
acc.lon += centerPoint[0]
acc.lat += centerPoint[1]
acc.count += 1
@@ -297,16 +296,18 @@ export function getCenter(rooms: number[][][]): number[] {
)
return [
- centerPoints.lat / centerPoints.count,
centerPoints.lon / centerPoints.count,
+ centerPoints.lat / centerPoints.count,
]
}
-export function getCenterSingle(coordinates: number[][] | undefined): number[] {
+export function getCenterSingle(
+ coordinates: number[][][] | undefined
+): number[] {
if (coordinates == null) {
return INGOLSTADT_CENTER
}
- const centerPoints = coordinates.reduce(
+ const centerPoints = coordinates[0].reduce(
(acc, coordinate) => {
acc.lon += coordinate[0]
acc.lat += coordinate[1]
@@ -317,8 +318,8 @@ export function getCenterSingle(coordinates: number[][] | undefined): number[] {
)
return [
- centerPoints.lat / centerPoints.count,
centerPoints.lon / centerPoints.count,
+ centerPoints.lat / centerPoints.count,
]
}
@@ -380,13 +381,16 @@ export const determineSearchType = (search: string): SEARCH_TYPES => {
export const getIcon = (
type: SEARCH_TYPES,
- properties?: { result: { item: { properties: FeatureProperties } } }
+ properties?: { result: { item: { properties: GeoJsonProperties } } }
): { ios: string; android: MaterialIcon } => {
const {
Funktion_en: funktionEn,
Raum: raum,
- }: { Funktion_en: string; Raum: string } = properties?.result.item
- .properties ?? { Funktion_en: '', Raum: '' }
+ }: { Funktion_en: string; Raum: string } = (properties?.result.item
+ .properties as { Funktion_en: string; Raum: string }) ?? {
+ Funktion_en: '',
+ Raum: '',
+ }
const food = ['M001', 'X001', 'F001']
switch (type) {
case SEARCH_TYPES.BUILDING: