+}
+
+const convertTtoC = (cookie: string | TCookie): Cookie => {
+ if (typeof cookie === 'string') {
+ return convertTtoC(TCookie.parse(cookie) as TCookie)
+ }
+ return {
+ name: cookie.key,
+ value: cookie.value,
+ domain: cookie.domain || undefined,
+ expires:
+ cookie.expires === 'Infinity' ? undefined : cookie.expires.toUTCString(),
+ httpOnly: cookie.httpOnly || undefined,
+ path: cookie.path || undefined,
+ secure: cookie.secure,
+ }
+}
+const convertCtoT = (cookie: Cookie): TCookie =>
+ new TCookie({
+ key: cookie.name,
+ value: cookie.value,
+ domain: cookie.domain,
+ expires: cookie.expires ? new Date(cookie.expires) : undefined,
+ httpOnly: cookie.httpOnly || false,
+ path: cookie.path,
+ secure: cookie.secure || false,
+ })
+const convertCookies = (cookies: TCookie[]): Cookies =>
+ cookies.reduce(
+ (map, cookie) => ({
+ ...map,
+ [cookie.key]: convertTtoC(cookie),
+ }),
+ {} as Cookies
+ )
+
+const jar = new CookieJar()
+const CookieManager: CookieManagerStatic = {
+ clearAll: async () => {
+ await jar.removeAllCookies()
+ return true
+ },
+ get: async (url) => {
+ const cookies = await jar.getCookies(url)
+ return convertCookies(cookies)
+ },
+ set: async (url, cookie) => {
+ await jar.setCookie(convertCtoT(cookie), url)
+ return true
+ },
+ setFromResponse: async (url, cookie) => {
+ await jar.setCookie(cookie, url)
+ return true
+ },
+}
+
+export default CookieManager
diff --git a/apps/skolplattformen-app-new/__mocks__/react-native-localize.js b/apps/skolplattformen-app-new/__mocks__/react-native-localize.js
new file mode 100644
index 000000000..aa1535691
--- /dev/null
+++ b/apps/skolplattformen-app-new/__mocks__/react-native-localize.js
@@ -0,0 +1,40 @@
+const getLocales = () => [
+ { countryCode: 'EN', languageTag: 'en-US', languageCode: 'en', isRTL: false },
+ { countryCode: 'SE', languageTag: 'sv-SE', languageCode: 'sv', isRTL: false },
+]
+
+const findBestAvailableLanguage = jest.fn(() => ({
+ languageTag: 'sv',
+ isRTL: false,
+}))
+
+const getNumberFormatSettings = () => ({
+ decimalSeparator: '.',
+ groupingSeparator: ',',
+})
+
+const getCalendar = () => 'gregorian'
+const getCountry = () => 'SE'
+const getCurrencies = () => ['USD', 'SEK']
+const getTemperatureUnit = () => 'celsius'
+const getTimeZone = () => 'Europe/Stockholm'
+const uses24HourClock = () => true
+const usesMetricSystem = () => true
+
+const addEventListener = jest.fn()
+const removeEventListener = jest.fn()
+
+export {
+ findBestAvailableLanguage,
+ getLocales,
+ getNumberFormatSettings,
+ getCalendar,
+ getCountry,
+ getCurrencies,
+ getTemperatureUnit,
+ getTimeZone,
+ uses24HourClock,
+ usesMetricSystem,
+ addEventListener,
+ removeEventListener,
+}
diff --git a/apps/skolplattformen-app-new/__mocks__/react-native-safe-area-context.js b/apps/skolplattformen-app-new/__mocks__/react-native-safe-area-context.js
new file mode 100644
index 000000000..d75b1ff61
--- /dev/null
+++ b/apps/skolplattformen-app-new/__mocks__/react-native-safe-area-context.js
@@ -0,0 +1,20 @@
+// __mocks__/react-native-safe-area-context.js
+import React from 'react'
+import { View } from 'react-native'
+
+const inset = {
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0,
+}
+
+export const SafeAreaProvider = ({ children }) => children
+
+export const SafeAreaConsumer = ({ children }) => children(inset)
+
+export const SafeAreaView = ({ children }) => (
+ {children}
+)
+
+export const useSafeAreaInsets = () => inset
diff --git a/apps/skolplattformen-app-new/android/app/build.gradle b/apps/skolplattformen-app-new/android/app/build.gradle
new file mode 100644
index 000000000..9ea1a1fc7
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/app/build.gradle
@@ -0,0 +1,129 @@
+apply plugin: "com.android.application"
+apply plugin: "com.facebook.react"
+
+/**
+ * This is the configuration block to customize your React Native Android app.
+ * By default you don't need to apply any configuration, just uncomment the lines you need.
+ */
+react {
+ /* Folders */
+ // The root of your project, i.e. where "package.json" lives. Default is '..'
+ // root = file("../")
+ // The folder where the react-native NPM package is. Default is ../node_modules/react-native
+ // reactNativeDir = file("../node_modules/react-native")
+ // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
+ // codegenDir = file("../node_modules/@react-native/codegen")
+ // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
+ // cliFile = file("../node_modules/react-native/cli.js")
+
+ /* Variants */
+ // The list of variants to that are debuggable. For those we're going to
+ // skip the bundling of the JS bundle and the assets. By default is just 'debug'.
+ // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
+ // debuggableVariants = ["liteDebug", "prodDebug"]
+
+ /* Bundling */
+ // A list containing the node command and its flags. Default is just 'node'.
+ // nodeExecutableAndArgs = ["node"]
+ //
+ // The command to run when bundling. By default is 'bundle'
+ // bundleCommand = "ram-bundle"
+ //
+ // The path to the CLI configuration file. Default is empty.
+ // bundleConfig = file(../rn-cli.config.js)
+ //
+ // The name of the generated asset file containing your JS bundle
+ // bundleAssetName = "MyApplication.android.bundle"
+ //
+ // The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
+ // entryFile = file("../js/MyApplication.android.js")
+ //
+ // A list of extra flags to pass to the 'bundle' commands.
+ // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
+ // extraPackagerArgs = []
+
+ /* Hermes Commands */
+ // The hermes compiler command to run. By default it is 'hermesc'
+ // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
+ //
+ // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
+ // hermesFlags = ["-O", "-output-source-map"]
+}
+
+/**
+ * Set this to true to Run Proguard on Release builds to minify the Java bytecode.
+ */
+def enableProguardInReleaseBuilds = false
+
+/**
+ * The preferred build flavor of JavaScriptCore (JSC)
+ *
+ * For example, to use the international variant, you can use:
+ * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
+ *
+ * The international variant includes ICU i18n library and necessary data
+ * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
+ * give correct results when using with locales other than en-US. Note that
+ * this variant is about 6MiB larger per architecture than default.
+ */
+def jscFlavor = 'org.webkit:android-jsc:+'
+
+android {
+ ndkVersion rootProject.ext.ndkVersion
+
+ compileSdkVersion rootProject.ext.compileSdkVersion
+
+ namespace "com.oppna_skolplattformen_new.app"
+ defaultConfig {
+ applicationId "com.oppna_skolplattformen_new.app"
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ versionCode 4
+ versionName "1.0"
+ }
+ signingConfigs {
+ debug {
+ storeFile file('debug.keystore')
+ storePassword 'android'
+ keyAlias 'androiddebugkey'
+ keyPassword 'android'
+ }
+ release {
+ storeFile file('release.jks')
+ storePassword 'leeandseb'
+ keyAlias 'upload'
+ keyPassword 'leeandseb'
+ }
+ }
+ buildTypes {
+ debug {
+ signingConfig signingConfigs.debug
+ }
+ release {
+ // Caution! In production, you need to generate your own keystore file.
+ // see https://reactnative.dev/docs/signed-apk-android.
+ signingConfig signingConfigs.release
+ minifyEnabled true
+ proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
+ }
+ }
+}
+
+dependencies {
+ // The version of react-native is set by the React Native Gradle Plugin
+ implementation("com.facebook.react:react-android")
+
+ 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 {
+ implementation jscFlavor
+ }
+}
+
+apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
diff --git a/apps/skolplattformen-app-new/android/app/debug.keystore b/apps/skolplattformen-app-new/android/app/debug.keystore
new file mode 100644
index 000000000..364e105ed
Binary files /dev/null and b/apps/skolplattformen-app-new/android/app/debug.keystore differ
diff --git a/apps/skolplattformen-app-new/android/app/proguard-rules.pro b/apps/skolplattformen-app-new/android/app/proguard-rules.pro
new file mode 100644
index 000000000..11b025724
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/app/proguard-rules.pro
@@ -0,0 +1,10 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
diff --git a/apps/skolplattformen-app-new/android/app/release.jks b/apps/skolplattformen-app-new/android/app/release.jks
new file mode 100644
index 000000000..59a8aef2a
Binary files /dev/null and b/apps/skolplattformen-app-new/android/app/release.jks differ
diff --git a/apps/skolplattformen-app-new/android/app/release.keystore b/apps/skolplattformen-app-new/android/app/release.keystore
new file mode 100644
index 000000000..3f0a4ced3
Binary files /dev/null and b/apps/skolplattformen-app-new/android/app/release.keystore differ
diff --git a/apps/skolplattformen-app-new/android/app/src/debug/AndroidManifest.xml b/apps/skolplattformen-app-new/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 000000000..4b185bc15
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/apps/skolplattformen-app-new/android/app/src/debug/java/com/app/ReactNativeFlipper.java b/apps/skolplattformen-app-new/android/app/src/debug/java/com/app/ReactNativeFlipper.java
new file mode 100644
index 000000000..9d6fd9205
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/app/src/debug/java/com/app/ReactNativeFlipper.java
@@ -0,0 +1,75 @@
+/**
+ * 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 com.oppna_skolplattformen_new.app;
+
+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/apps/skolplattformen-app-new/android/app/src/main/AndroidManifest.xml b/apps/skolplattformen-app-new/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..8af2453d5
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/skolplattformen-app-new/android/app/src/main/java/com/app/MainActivity.java b/apps/skolplattformen-app-new/android/app/src/main/java/com/app/MainActivity.java
new file mode 100644
index 000000000..09b8ffcc2
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/app/src/main/java/com/app/MainActivity.java
@@ -0,0 +1,32 @@
+package com.oppna_skolplattformen_new.app;
+
+import com.facebook.react.ReactActivity;
+import com.facebook.react.ReactActivityDelegate;
+import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
+import com.facebook.react.defaults.DefaultReactActivityDelegate;
+
+public class MainActivity extends ReactActivity {
+
+ /**
+ * Returns the name of the main component registered from JavaScript. This is used to schedule
+ * rendering of the component.
+ */
+ @Override
+ protected String getMainComponentName() {
+ return "app";
+ }
+
+ /**
+ * 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 DefaultReactActivityDelegate(
+ this,
+ getMainComponentName(),
+ // If you opted-in for the New Architecture, we enable the Fabric Renderer.
+ DefaultNewArchitectureEntryPoint.getFabricEnabled());
+ }
+}
diff --git a/apps/skolplattformen-app-new/android/app/src/main/java/com/app/MainApplication.java b/apps/skolplattformen-app-new/android/app/src/main/java/com/app/MainApplication.java
new file mode 100644
index 000000000..d15b08363
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/app/src/main/java/com/app/MainApplication.java
@@ -0,0 +1,62 @@
+package com.oppna_skolplattformen_new.app;
+
+import android.app.Application;
+import com.facebook.react.PackageList;
+import com.facebook.react.ReactApplication;
+import com.facebook.react.ReactNativeHost;
+import com.facebook.react.ReactPackage;
+import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
+import com.facebook.react.defaults.DefaultReactNativeHost;
+import com.facebook.soloader.SoLoader;
+import java.util.List;
+
+public class MainApplication extends Application implements ReactApplication {
+
+ private final ReactNativeHost mReactNativeHost =
+ 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 "index";
+ }
+
+ @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.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());
+ }
+}
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/drawable/rn_edit_text_material.xml b/apps/skolplattformen-app-new/android/app/src/main/res/drawable/rn_edit_text_material.xml
new file mode 100644
index 000000000..73b37e4d9
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/app/src/main/res/drawable/rn_edit_text_material.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..036d09bc5
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..86b42de1b
Binary files /dev/null and b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..3aeffa06e
Binary files /dev/null and b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..5ff72be1a
Binary files /dev/null and b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..03734664d
Binary files /dev/null and b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..833c039ae
Binary files /dev/null and b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..b7e9942e2
Binary files /dev/null and b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..ac3d3c450
Binary files /dev/null and b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..514d5c45b
Binary files /dev/null and b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..262960fa0
Binary files /dev/null and b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000..0f9612b59
Binary files /dev/null and b/apps/skolplattformen-app-new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/values/ic_launcher_background.xml b/apps/skolplattformen-app-new/android/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 000000000..c5d5899fd
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/values/strings.xml b/apps/skolplattformen-app-new/android/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..6614fac3a
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Öppna Skolplattformen NEW
+
diff --git a/apps/skolplattformen-app-new/android/app/src/main/res/values/styles.xml b/apps/skolplattformen-app-new/android/app/src/main/res/values/styles.xml
new file mode 100644
index 000000000..955d15716
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/apps/skolplattformen-app-new/android/app/src/release/java/com/app/ReactNativeFlipper.java b/apps/skolplattformen-app-new/android/app/src/release/java/com/app/ReactNativeFlipper.java
new file mode 100644
index 000000000..7cdbff353
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/app/src/release/java/com/app/ReactNativeFlipper.java
@@ -0,0 +1,20 @@
+/**
+ * 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 com.oppna_skolplattformen_new.app;
+
+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/apps/skolplattformen-app-new/android/build.gradle b/apps/skolplattformen-app-new/android/build.gradle
new file mode 100644
index 000000000..34ea71819
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/build.gradle
@@ -0,0 +1,21 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ ext {
+ buildToolsVersion = "33.0.0"
+ minSdkVersion = 21
+ compileSdkVersion = 33
+ targetSdkVersion = 33
+
+ // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
+ ndkVersion = "23.1.7779620"
+ }
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath("com.android.tools.build:gradle")
+ classpath("com.facebook.react:react-native-gradle-plugin")
+ }
+}
diff --git a/apps/skolplattformen-app-new/android/fastlane/Appfile b/apps/skolplattformen-app-new/android/fastlane/Appfile
new file mode 100644
index 000000000..e99ae0f01
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/fastlane/Appfile
@@ -0,0 +1,2 @@
+json_key_file("key.json")
+package_name("com.oppna_skolplattformen_new.app")
diff --git a/apps/skolplattformen-app-new/android/fastlane/Fastfile b/apps/skolplattformen-app-new/android/fastlane/Fastfile
new file mode 100755
index 000000000..4c3df2a17
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/fastlane/Fastfile
@@ -0,0 +1,109 @@
+# Filename: android/fastlane/Fastfile
+
+default_platform(:android)
+
+platform :android do
+ desc "Runs all the tests"
+ lane :test do
+ gradle(task: "test")
+ end
+
+ desc "Submit a new Beta Build to Play Store"
+ lane :beta do |options|
+
+ store_password = ENV['SIGNING_STORE_PASSWORD'] || prompt(text: "Signing Store Password: ", secure_text: true)
+ key_password = ENV['ALIAS_KEY_PASSWORD'] || prompt(text: "Alias Key Password: ", secure_text: true)
+
+ # Fetch version_code from play store and bump it. Annoyingly, we always need
+ # to increment it, even if our version name changes.
+ internalVersionCode = google_play_track_version_codes(track: 'internal').max
+ ENV['VERSION_CODE'] = (internalVersionCode + 1).to_s
+
+ versionNameOverride = nil
+ # versionNameOverride = "1.9.0"
+ if versionNameOverride.nil?
+ releaseNameSemVerArr = google_play_track_release_names(track: 'internal').max.split('.')
+ releaseNameSemVerArr[2] = (releaseNameSemVerArr.last.to_i + 1).to_s
+ ENV['VERSION_NAME'] = releaseNameSemVerArr.join('.')
+ ENV['GITTAGNAME'] = ENV['VERSION_NAME'].gsub(/\s+/, '').match(/\((.*?)\)/)[1] + '.' + ENV['VERSION_CODE']
+ ENV['SUPPLY_VERSION_NAME'] = ENV['VERSION_NAME']
+ versionFile = File.join(Dir.pwd, '..', 'version', 'version.properties').to_s
+ commandargs = "-n \"VERSION=#{ENV['VERSION_NAME']}\" > #{versionFile}".to_s
+ puts "echo #{commandargs}"
+ system("echo", commandargs)
+ else
+ ENV['VERSION_NAME'] = versionNameOverride
+ end
+ puts "Compiling #{ENV['VERSION_NAME']} (#{ENV['VERSION_CODE']}) "
+
+ # Dir.pwd when running through Fastlane is app/android/fastlane
+ releaseFilePath = File.join(Dir.pwd, '..', 'app', "release.jks")
+ mappingFilePath = File.join(
+ Dir.pwd,
+ "..",
+ "app",
+ "build",
+ "outputs",
+ "mapping",
+ "release",
+ "mapping.txt"
+ )
+ puts "Hello there - #{ENV['VERSION_CODE']}"
+
+ gradle(task: 'clean')
+ gradle(
+ task: 'bundle',
+ build_type: 'Release',
+ print_command: false,
+ properties: {
+ "android.injected.signing.store.file" => releaseFilePath,
+ "android.injected.signing.store.password" => store_password,
+ "android.injected.signing.key.alias" => "upload",
+ "android.injected.signing.key.password" => key_password,
+ "android.injected.version.code" => ENV['VERSION_CODE'],
+ "android.injected.version.name" => ENV['VERSION_NAME'],
+ }
+ )
+
+ symbolsFilePath = File.join(
+ Dir.pwd,
+ "..",
+ "native_debug_symbols.zip"
+ )
+ symbolsFolderPath = File.join(
+ Dir.pwd,
+ "..",
+ "app",
+ "build",
+ "intermediates",
+ "merged_native_libs",
+ "release",
+ "out",
+ "lib"
+ )
+ system("cd #{symbolsFolderPath} && zip -r #{symbolsFilePath} .")
+ upload_to_play_store(
+ track: 'internal',
+ release_status: 'draft',
+ version_code: ENV['VERSION_CODE'],
+ version_name: ENV['VERSION_NAME'],
+ version_codes_to_retain: [],
+ mapping_paths: [mappingFilePath, symbolsFilePath]
+ )
+
+ system('git config user.email "leesheppard2404@gmail.com"')
+ system('git config user.name "Github Actions Android Pipeline"')
+
+
+ add_git_tag(
+ grouping: "builds",
+ includes_lane: true,
+ # prefix: "v#{ENV['VERSION_NAME']}-",
+ # build_number: ENV['VERSION_CODE'],
+ tag: "v#{ENV['GITTAGNAME']}"
+ )
+ push_to_git_remote(
+ tags: true
+ )
+ end
+end
\ No newline at end of file
diff --git a/apps/skolplattformen-app-new/android/gradle.properties b/apps/skolplattformen-app-new/android/gradle.properties
new file mode 100644
index 000000000..a3b2fa124
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/gradle.properties
@@ -0,0 +1,44 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
+org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+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
+reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
+
+# Use this property to enable support to the new architecture.
+# This will allow you to use TurboModules and the Fabric render in
+# your application. You should enable this flag either if you want
+# to write custom TurboModules/Fabric components OR use libraries that
+# are providing them.
+newArchEnabled=false
+
+# Use this property to enable or disable the Hermes JS engine.
+# If set to false, you will be using JSC instead.
+hermesEnabled=true
diff --git a/apps/skolplattformen-app-new/android/gradle/wrapper/gradle-wrapper.jar b/apps/skolplattformen-app-new/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..943f0cbfa
Binary files /dev/null and b/apps/skolplattformen-app-new/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/apps/skolplattformen-app-new/android/gradle/wrapper/gradle-wrapper.properties b/apps/skolplattformen-app-new/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..6ec1567a0
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip
+networkTimeout=10000
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/apps/skolplattformen-app-new/android/gradlew b/apps/skolplattformen-app-new/android/gradlew
new file mode 100755
index 000000000..65dcd68d6
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/gradlew
@@ -0,0 +1,244 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# 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/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+# 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"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+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.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+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=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=SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ 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.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/apps/skolplattformen-app-new/android/gradlew.bat b/apps/skolplattformen-app-new/android/gradlew.bat
new file mode 100644
index 000000000..6689b85be
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+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%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+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.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+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.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/apps/skolplattformen-app-new/android/settings.gradle b/apps/skolplattformen-app-new/android/settings.gradle
new file mode 100644
index 000000000..e4bc88917
--- /dev/null
+++ b/apps/skolplattformen-app-new/android/settings.gradle
@@ -0,0 +1,4 @@
+rootProject.name = 'app'
+apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
+include ':app'
+includeBuild('../node_modules/@react-native/gradle-plugin')
diff --git a/apps/skolplattformen-app-new/app.json b/apps/skolplattformen-app-new/app.json
new file mode 100644
index 000000000..71c44557a
--- /dev/null
+++ b/apps/skolplattformen-app-new/app.json
@@ -0,0 +1,5 @@
+{
+ "name": "app",
+ "displayName": "app",
+ "schema": "oppnaskolplattformen://"
+}
diff --git a/apps/skolplattformen-app-new/assets/avatar.png b/apps/skolplattformen-app-new/assets/avatar.png
new file mode 100644
index 000000000..4bae0b00a
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/avatar.png differ
diff --git a/apps/skolplattformen-app-new/assets/bankid_low_rgb.png b/apps/skolplattformen-app-new/assets/bankid_low_rgb.png
new file mode 100644
index 000000000..3931b98c4
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/bankid_low_rgb.png differ
diff --git a/apps/skolplattformen-app-new/assets/bankid_vector_rgb.svg b/apps/skolplattformen-app-new/assets/bankid_vector_rgb.svg
new file mode 100755
index 000000000..6031cee07
--- /dev/null
+++ b/apps/skolplattformen-app-new/assets/bankid_vector_rgb.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/apps/skolplattformen-app-new/assets/boys.png b/apps/skolplattformen-app-new/assets/boys.png
new file mode 100644
index 000000000..e7ecb44da
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/boys.png differ
diff --git a/apps/skolplattformen-app-new/assets/children.png b/apps/skolplattformen-app-new/assets/children.png
new file mode 100644
index 000000000..0a21526ef
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/children.png differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/OFL.txt b/apps/skolplattformen-app-new/assets/fonts/OFL.txt
new file mode 100644
index 000000000..246c977c9
--- /dev/null
+++ b/apps/skolplattformen-app-new/assets/fonts/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-Black.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-Black.ttf
new file mode 100644
index 000000000..a9520b78a
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-Black.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-BlackItalic.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-BlackItalic.ttf
new file mode 100644
index 000000000..ebfdd707e
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-BlackItalic.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-Bold.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-Bold.ttf
new file mode 100644
index 000000000..b94d47f3a
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-Bold.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-BoldItalic.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-BoldItalic.ttf
new file mode 100644
index 000000000..e2e64456c
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-BoldItalic.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-ExtraBold.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-ExtraBold.ttf
new file mode 100644
index 000000000..8f008c368
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-ExtraBold.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-ExtraBoldItalic.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-ExtraBoldItalic.ttf
new file mode 100644
index 000000000..b2a9bf557
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-ExtraBoldItalic.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-ExtraLight.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-ExtraLight.ttf
new file mode 100644
index 000000000..ee6238251
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-ExtraLight.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-ExtraLightItalic.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-ExtraLightItalic.ttf
new file mode 100644
index 000000000..e392492ab
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-ExtraLightItalic.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-Italic.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-Italic.ttf
new file mode 100644
index 000000000..46203996d
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-Italic.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-Light.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-Light.ttf
new file mode 100644
index 000000000..2ab022196
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-Light.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-LightItalic.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-LightItalic.ttf
new file mode 100644
index 000000000..6f9279dae
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-LightItalic.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-Medium.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-Medium.ttf
new file mode 100644
index 000000000..e90e87ed6
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-Medium.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-MediumItalic.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-MediumItalic.ttf
new file mode 100644
index 000000000..d8a251c7c
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-MediumItalic.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-Regular.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-Regular.ttf
new file mode 100644
index 000000000..be06e7fdc
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-Regular.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-SemiBold.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-SemiBold.ttf
new file mode 100644
index 000000000..dabf7c242
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-SemiBold.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-SemiBoldItalic.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-SemiBoldItalic.ttf
new file mode 100644
index 000000000..29d5f7419
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-SemiBoldItalic.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-Thin.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-Thin.ttf
new file mode 100644
index 000000000..f5c0fdd53
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-Thin.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins-ThinItalic.ttf b/apps/skolplattformen-app-new/assets/fonts/Poppins-ThinItalic.ttf
new file mode 100644
index 000000000..b91008931
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins-ThinItalic.ttf differ
diff --git a/apps/skolplattformen-app-new/assets/fonts/Poppins.zip b/apps/skolplattformen-app-new/assets/fonts/Poppins.zip
new file mode 100644
index 000000000..30d1b11e2
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/fonts/Poppins.zip differ
diff --git a/apps/skolplattformen-app-new/assets/freja_eid_logo.png b/apps/skolplattformen-app-new/assets/freja_eid_logo.png
new file mode 100644
index 000000000..621831083
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/freja_eid_logo.png differ
diff --git a/apps/skolplattformen-app-new/assets/girls.png b/apps/skolplattformen-app-new/assets/girls.png
new file mode 100644
index 000000000..3a0a166a6
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/girls.png differ
diff --git a/apps/skolplattformen-app-new/assets/kvinna.png b/apps/skolplattformen-app-new/assets/kvinna.png
new file mode 100644
index 000000000..ea2e88508
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/kvinna.png differ
diff --git a/apps/skolplattformen-app-new/assets/logo_print.svg b/apps/skolplattformen-app-new/assets/logo_print.svg
new file mode 100644
index 000000000..a49325cee
--- /dev/null
+++ b/apps/skolplattformen-app-new/assets/logo_print.svg
@@ -0,0 +1,128 @@
+
+
+
+
diff --git a/apps/skolplattformen-app-new/assets/man.png b/apps/skolplattformen-app-new/assets/man.png
new file mode 100644
index 000000000..bd5d07d94
Binary files /dev/null and b/apps/skolplattformen-app-new/assets/man.png differ
diff --git a/apps/skolplattformen-app-new/babel.config.js b/apps/skolplattformen-app-new/babel.config.js
new file mode 100644
index 000000000..0df6ea6fa
--- /dev/null
+++ b/apps/skolplattformen-app-new/babel.config.js
@@ -0,0 +1,23 @@
+module.exports = {
+ presets: ['module:metro-react-native-babel-preset'],
+ plugins: [
+ [
+ 'module-resolver',
+ {
+ root: ['./src/apps/skolplattformen-app-new/'],
+ extensions: ['.ios.js', '.android.js', '.js', '.ts', '.tsx', '.json'],
+ alias: {
+ '@skolplattformen/api': ['libs/api/lib/index.ts'],
+ '@skolplattformen/api-hjarntorget': [
+ 'libs/api-hjarntorget/lib/index.ts',
+ ],
+ '@skolplattformen/api-skolplattformen': [
+ 'libs/api-skolplattformen/lib/index.ts',
+ ],
+ '@skolplattformen/curriculum': ['libs/curriculum/src/index.ts'],
+ '@skolplattformen/hooks': ['libs/hooks/src/index.ts'],
+ },
+ },
+ ],
+ ],
+}
diff --git a/apps/skolplattformen-app-new/components/__tests__/Absence.test.js b/apps/skolplattformen-app-new/components/__tests__/Absence.test.js
new file mode 100644
index 000000000..3a6901e67
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/__tests__/Absence.test.js
@@ -0,0 +1,107 @@
+import AsyncStorage from '@react-native-async-storage/async-storage'
+import { useRoute } from '@react-navigation/native'
+import { useUser } from '../../libs/hooks/src'
+import { fireEvent, waitFor } from '@testing-library/react-native'
+import Mockdate from 'mockdate'
+import React from 'react'
+import { useSMS } from '../../utils/SMS'
+import { render } from '../../utils/testHelpers'
+import Absence from '../absence.component'
+
+let sendSMS
+let user = { personalNumber: '201701092395' }
+
+jest.mock('../../utils/SMS')
+jest.mock('../../libs/hooks/src')
+
+const setup = (customProps = {}) => {
+ sendSMS = jest.fn()
+
+ useSMS.mockReturnValue({ sendSMS })
+ useRoute.mockReturnValue({ params: { child: { id: '1' } } })
+
+ const props = {
+ ...customProps,
+ }
+
+ return render()
+}
+
+beforeAll(() => {
+ // Hide errors from act
+ // https://github.com/callstack/react-native-testing-library/issues/379
+ jest.spyOn(console, 'error').mockImplementation(() => {
+ // noop
+ })
+})
+
+beforeEach(async () => {
+ jest.clearAllMocks()
+ useUser.mockReturnValue({
+ data: user,
+ status: 'loaded',
+ })
+ await AsyncStorage.clear()
+})
+
+test.skip('can fill out the form with full day absence', async () => {
+ const screen = setup()
+
+ await waitFor(() =>
+ fireEvent.changeText(
+ screen.getByTestId('personalIdentityNumberInput'),
+ '1212121212'
+ )
+ )
+ await waitFor(() => fireEvent.press(screen.getByText('Skicka')))
+
+ expect(screen.queryByText(/starttid/i)).toBeFalsy()
+ expect(screen.queryByText(/sluttid/i)).toBeFalsy()
+
+ expect(sendSMS).toHaveBeenCalledWith('121212-1212')
+})
+
+test.skip('handles missing social security number', async () => {
+ const screen = setup()
+
+ await waitFor(() => fireEvent.press(screen.getByText('Skicka')))
+
+ expect(screen.getByText(/Personnummer saknas/i)).toBeTruthy()
+ expect(sendSMS).not.toHaveBeenCalled()
+})
+
+test.skip('validates social security number', async () => {
+ const screen = setup()
+
+ await waitFor(() =>
+ fireEvent.changeText(
+ screen.getByTestId('personalIdentityNumberInput'),
+ '12121212'
+ )
+ )
+ await waitFor(() => fireEvent.press(screen.getByText('Skicka')))
+
+ expect(screen.getByText(/Personnumret är ogiltigt/i)).toBeTruthy()
+ expect(sendSMS).not.toHaveBeenCalled()
+})
+
+test.skip('can fill out the form with part of day absence', async () => {
+ Mockdate.set('2021-02-18 15:30')
+
+ const screen = setup()
+
+ await waitFor(() =>
+ fireEvent.changeText(
+ screen.getByTestId('personalIdentityNumberInput'),
+ '1212121212'
+ )
+ )
+ await waitFor(() => fireEvent.press(screen.getByText('Heldag')))
+
+ expect(screen.getByText(/starttid/i)).toBeTruthy()
+ expect(screen.getByText(/sluttid/i)).toBeTruthy()
+
+ await waitFor(() => fireEvent.press(screen.getByText('Skicka')))
+
+ expect(sendSMS).toHaveBeenCalledWith('121212-1212 1500-1700')
+})
diff --git a/apps/skolplattformen-app-new/components/__tests__/Children.test.js b/apps/skolplattformen-app-new/components/__tests__/Children.test.js
new file mode 100644
index 000000000..0d5189936
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/__tests__/Children.test.js
@@ -0,0 +1,239 @@
+import 'setImmediate'
+import { useNavigation } from '@react-navigation/core'
+import {
+ useApi,
+ useCalendar,
+ useChildList,
+ useClassmates,
+ useMenu,
+ useNews,
+ useNotifications,
+ useSchedule,
+ useTimetable,
+} from '../../libs/hooks/src'
+import React from 'react'
+import * as RNLocalize from 'react-native-localize'
+import { render } from '../../utils/testHelpers'
+import { translate } from '../../utils/translation'
+import { Children } from '../children.component'
+
+jest.mock('../../libs/hooks/src')
+
+const pause = (ms = 0) => new Promise((r) => setTimeout(r, ms))
+
+const setup = () => {
+ return render()
+}
+
+beforeEach(() => {
+ useApi.mockReturnValue({
+ api: { on: jest.fn(), off: jest.fn(), logout: jest.fn() },
+ isLoggedIn: false,
+ })
+ RNLocalize.findBestAvailableLanguage.mockImplementationOnce(() => ({
+ languageTag: 'sv',
+ isRTL: false,
+ }))
+ useCalendar.mockReturnValueOnce({ data: [], status: 'loaded' })
+ useNotifications.mockReturnValueOnce({ data: [], status: 'loaded' })
+ useNews.mockReturnValueOnce({ data: [], status: 'loaded' })
+ useSchedule.mockReturnValueOnce({ data: [], status: 'loaded' })
+ useMenu.mockReturnValueOnce({ data: [], status: 'loaded' })
+ useTimetable.mockReturnValueOnce({ data: [], status: 'loaded' })
+ useClassmates.mockReturnValueOnce({ data: [], status: 'loaded' })
+ useNavigation.mockReturnValue({ navigate: jest.fn(), setOptions: jest.fn() })
+})
+
+test('renders loading state', async () => {
+ useChildList.mockImplementationOnce(() => ({
+ data: [],
+ status: 'loading',
+ }))
+
+ const screen = setup()
+ expect(screen.getByText(translate('general.loading'))).toBeTruthy()
+})
+
+test('renders empty state message', () => {
+ useChildList.mockImplementationOnce(() => ({
+ data: [],
+ status: 'loaded',
+ }))
+ const screen = setup()
+
+ expect(
+ screen.getByText(translate('children.noKids_description'))
+ ).toBeTruthy()
+})
+
+test('renders error state message', () => {
+ useChildList.mockImplementationOnce(() => ({
+ data: [],
+ status: 'error',
+ }))
+
+ const screen = setup()
+
+ expect(
+ screen.getByText(translate('children.loadingErrorHeading'))
+ ).toBeTruthy()
+})
+
+test('renders child in preschool', () => {
+ useChildList.mockImplementationOnce(() => ({
+ data: [
+ {
+ name: 'Test Testsson',
+ status: 'F',
+ },
+ ],
+ status: 'loaded',
+ }))
+
+ const screen = setup()
+
+ expect(screen.getByText('Test Testsson')).toBeTruthy()
+})
+
+test('renders child in elementary school', () => {
+ useChildList.mockImplementationOnce(() => ({
+ data: [
+ {
+ name: 'Test Testsson',
+ status: 'GR',
+ },
+ ],
+ status: 'loaded',
+ }))
+
+ const screen = setup()
+
+ expect(screen.getByText('Test Testsson')).toBeTruthy()
+})
+
+test('renders child in high school', () => {
+ useChildList.mockImplementationOnce(() => ({
+ data: [
+ {
+ name: 'Test Testsson',
+ status: 'G',
+ },
+ ],
+ status: 'loaded',
+ }))
+
+ const screen = setup()
+
+ expect(screen.getByText('Test Testsson')).toBeTruthy()
+ expect(
+ screen.getByText(translate('abbrevations.upperSecondarySchool'))
+ ).toBeTruthy()
+})
+
+test('renders multiple children', () => {
+ useChildList.mockImplementationOnce(() => ({
+ data: [
+ {
+ name: 'Storasyster Testsson',
+ status: 'G',
+ },
+ {
+ name: 'Lillebror Testsson',
+ status: 'GR',
+ },
+ ],
+ status: 'loaded',
+ }))
+
+ const screen = setup()
+
+ expect(screen.getByText('Storasyster Testsson')).toBeTruthy()
+ expect(
+ screen.getByText(translate('abbrevations.upperSecondarySchool'))
+ ).toBeTruthy()
+
+ expect(screen.getByText('Lillebror Testsson')).toBeTruthy()
+ expect(
+ screen.getByText(translate('abbrevations.compulsorySchool'))
+ ).toBeTruthy()
+})
+
+test('renders child in class', () => {
+ useChildList.mockImplementationOnce(() => ({
+ data: [
+ {
+ name: 'Test Testsson',
+ status: 'G',
+ schoolID: 'Vallaskolan',
+ },
+ ],
+ status: 'loaded',
+ }))
+ useClassmates.mockReset()
+ useClassmates.mockImplementationOnce(() => ({
+ data: [
+ {
+ className: '8C',
+ },
+ ],
+ status: 'loaded',
+ }))
+ const screen = setup()
+
+ expect(screen.getByText('Test Testsson')).toBeTruthy()
+ expect(screen.getByText('8C • Vallaskolan')).toBeTruthy()
+})
+
+test('removes any parenthesis from name', () => {
+ useChildList.mockImplementationOnce(() => ({
+ data: [
+ {
+ name: 'Test Testsson (elev)',
+ status: 'G',
+ },
+ ],
+ status: 'loaded',
+ }))
+ const screen = setup()
+
+ expect(screen.getByText('Test Testsson')).toBeTruthy()
+})
+
+test('handles multiple statuses for a child', () => {
+ useChildList.mockImplementationOnce(() => ({
+ data: [
+ {
+ name: 'Test Testsson(elev)',
+ status: 'G;GR;F',
+ },
+ ],
+ status: 'loaded',
+ }))
+ const screen = setup()
+
+ var multipleStatusesRendered = `${translate(
+ 'abbrevations.upperSecondarySchool'
+ )}, ${translate('abbrevations.compulsorySchool')}, ${translate(
+ 'abbrevations.leisureTimeCentre'
+ )}`
+
+ expect(screen.getByText('Test Testsson')).toBeTruthy()
+ expect(screen.getByText(multipleStatusesRendered)).toBeTruthy()
+})
+
+test('says if there is nothing new this week', () => {
+ useChildList.mockImplementationOnce(() => ({
+ data: [
+ {
+ name: 'Kanye West',
+ status: 'F',
+ },
+ ],
+ status: 'loaded',
+ }))
+ const screen = setup()
+
+ expect(
+ screen.getByText(translate('news.noNewNewsItemsThisWeek'))
+ ).toBeTruthy()
+})
diff --git a/apps/skolplattformen-app-new/components/__tests__/Classmates.test.js b/apps/skolplattformen-app-new/components/__tests__/Classmates.test.js
new file mode 100644
index 000000000..3b47326ea
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/__tests__/Classmates.test.js
@@ -0,0 +1,86 @@
+import { useClassmates } from '../../libs/hooks/src'
+import React from 'react'
+import { render } from '../../utils/testHelpers'
+import { ChildProvider } from '../childContext.component'
+import { Classmates } from '../classmates.component'
+
+jest.mock('../../libs/hooks/src')
+
+const defaultClassmates = [
+ {
+ className: '2B',
+ firstname: 'Tyrell',
+ lastname: 'Eriksson',
+ guardians: [
+ {
+ firstname: 'Margaery',
+ lastname: 'Eriksson',
+ },
+ {
+ firstname: 'Loras',
+ lastname: 'Eriksson',
+ },
+ ],
+ },
+ {
+ className: '2B',
+ firstname: 'Adam',
+ lastname: 'Svensson',
+ guardians: [
+ {
+ firstname: 'Eva',
+ lastname: 'Svensson',
+ },
+ ],
+ },
+]
+
+const setup = ({ classmates } = { classmates: defaultClassmates }) => {
+ useClassmates.mockReturnValue({
+ data: classmates,
+ })
+
+ return render(
+
+
+
+ )
+}
+
+test('gets the classmates for a child from context', () => {
+ setup()
+
+ expect(useClassmates).toHaveBeenCalledWith({ id: 1 })
+})
+
+test('renders class name', () => {
+ const screen = setup()
+
+ expect(screen.getByText(/^klass 2b$/i)).toBeTruthy()
+})
+
+test('renders class without name', () => {
+ const screen = setup({
+ classmates: [],
+ })
+
+ expect(screen.getByText(/^klass$/i)).toBeTruthy()
+})
+
+test('renders classmates sorted by first name', () => {
+ const screen = setup()
+
+ expect(screen.getByLabelText('Barn 1')).toContainElement(
+ screen.getByText(/adam svensson/i)
+ )
+ expect(screen.getByLabelText('Barn 2')).toContainElement(
+ screen.getByText(/tyrell eriksson/i)
+ )
+})
+
+test('renders guardians sorted by first name', () => {
+ const screen = setup()
+
+ expect(screen.getByText(/eva svensson/i)).toBeTruthy()
+ expect(screen.getByText(/^loras eriksson, margaery eriksson$/i)).toBeTruthy()
+})
diff --git a/apps/skolplattformen-app-new/components/__tests__/ContactMenu.test.js b/apps/skolplattformen-app-new/components/__tests__/ContactMenu.test.js
new file mode 100644
index 000000000..a9bf97208
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/__tests__/ContactMenu.test.js
@@ -0,0 +1,137 @@
+import { fireEvent } from '@testing-library/react-native'
+import React from 'react'
+import { Linking } from 'react-native'
+import { render } from '../../utils/testHelpers'
+import { ContactMenu } from '../contactMenu.component'
+import { act } from 'react-test-renderer'
+
+const defaultGuardian = {
+ address: 'Testgatan',
+ email: 'adam@adamsson.se',
+ firstname: 'Adam',
+ lastname: 'Adamsson',
+ mobile: '0701234567',
+}
+
+const defaultProps = {
+ contact: {
+ guardians: [defaultGuardian],
+ },
+}
+
+const setup = (customProps = {}) => {
+ const props = {
+ ...defaultProps,
+ ...customProps,
+ }
+
+ return render()
+}
+
+beforeAll(() => {
+ // Hide errors from state illegal state transition
+ // Probably due to mock
+ jest.spyOn(console, 'error').mockImplementation(() => {
+ // noop
+ })
+})
+
+beforeEach(jest.clearAllMocks)
+
+test('renders a parent', () => {
+ const screen = setup()
+
+ fireEvent.press(screen.getByTestId('ShowContactInfoButton'))
+
+ expect(screen.getByText(/adam adamsson/i)).toBeTruthy()
+})
+
+test('displays option to call and text guardian', () => {
+ const screen = setup()
+
+ fireEvent.press(screen.getByTestId('ShowContactInfoButton'))
+
+ fireEvent.press(screen.getByText(/ring/i))
+ expect(Linking.openURL).toHaveBeenCalledWith('tel:0701234567')
+
+ fireEvent.press(screen.getByText(/sms/i))
+ expect(Linking.openURL).toHaveBeenCalledWith('sms:0701234567')
+})
+
+test('hides options to call and text if no phone number', () => {
+ const guardianWithoutPhoneNumber = {
+ contact: {
+ guardians: [
+ {
+ ...defaultGuardian,
+ mobile: null,
+ },
+ ],
+ },
+ }
+
+ const screen = setup(guardianWithoutPhoneNumber)
+
+ fireEvent.press(screen.getByTestId('ShowContactInfoButton'))
+
+ expect(screen.queryByTestId('CallMenuItem')).toBeNull()
+ expect(screen.queryByTestId('SMSMenuItem')).toBeNull()
+})
+
+test('displays option to email guardian', () => {
+ const screen = setup()
+
+ fireEvent.press(screen.getByTestId('ShowContactInfoButton'))
+
+ fireEvent.press(screen.getByText(/maila/i))
+ expect(Linking.openURL).toHaveBeenCalledWith('mailto:adam@adamsson.se')
+})
+
+test('hides options to email phone number', () => {
+ const guardianWithoutEmail = {
+ contact: {
+ guardians: [
+ {
+ ...defaultGuardian,
+ email: null,
+ },
+ ],
+ },
+ }
+
+ const screen = setup(guardianWithoutEmail)
+
+ fireEvent.press(screen.getByTestId('ShowContactInfoButton'))
+
+ expect(screen.queryByTestId('SendEmailMenuItem')).toBeNull()
+})
+
+test('displays address of guardian', () => {
+ const screen = setup()
+
+ fireEvent.press(screen.getByTestId('ShowContactInfoButton'))
+
+ fireEvent.press(screen.getByText(/adress/i))
+ expect(Linking.openURL).toHaveBeenCalledWith(
+ 'http://maps.apple.com/?daddr=Testgatan'
+ )
+})
+
+test('hides address if it does not exist', () => {
+ const guardianWithoutAddress = {
+ contact: {
+ guardians: [
+ {
+ ...defaultGuardian,
+ address: null,
+ },
+ ],
+ },
+ }
+
+ const screen = setup(guardianWithoutAddress)
+
+ fireEvent.press(screen.getByTestId('ShowContactInfoButton'))
+
+ expect(screen.queryByTestId('ShowHomeMenuItem')).toBeNull()
+})
diff --git a/apps/skolplattformen-app-new/components/__tests__/Menu.test.js b/apps/skolplattformen-app-new/components/__tests__/Menu.test.js
new file mode 100644
index 000000000..4f7fad18e
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/__tests__/Menu.test.js
@@ -0,0 +1,50 @@
+import { useMenu } from '../../libs/hooks/src'
+import React from 'react'
+import { render } from '../../utils/testHelpers'
+import { translate } from '../../utils/translation'
+import { Menu } from '../menu.component'
+
+jest.mock('../../libs/hooks/src')
+
+const defaultItemList = [
+ {
+ title: 'Måndag vecka 10',
+ description: 'Krämiga köttbullar',
+ },
+ {
+ title: 'Tisdag vecka 10',
+ description: 'Kryddig falukorv',
+ },
+ {
+ title: 'Onsdag vecka 10',
+ description: 'Sushi',
+ },
+]
+
+const setup = (itemList = defaultItemList) => {
+ useMenu.mockReturnValue({
+ data: itemList,
+ })
+
+ return render()
+}
+
+test('renders multiple days', () => {
+ const screen = setup()
+
+ expect(screen.getByText('Måndag vecka 10')).toBeTruthy()
+ expect(screen.getByText('Tisdag vecka 10')).toBeTruthy()
+ expect(screen.getByText('Onsdag vecka 10')).toBeTruthy()
+})
+
+test('renders title and description', () => {
+ const screen = setup()
+
+ expect(screen.getByText('Måndag vecka 10')).toBeTruthy()
+ expect(screen.getByText('Krämiga köttbullar')).toBeTruthy()
+})
+
+test('renders empty menu', () => {
+ const screen = setup([])
+ expect(screen.getByText(translate('menu.emptyText'))).toBeTruthy()
+})
diff --git a/apps/skolplattformen-app-new/components/__tests__/NewsItem.test.js b/apps/skolplattformen-app-new/components/__tests__/NewsItem.test.js
new file mode 100644
index 000000000..7d7670cf2
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/__tests__/NewsItem.test.js
@@ -0,0 +1,86 @@
+import { useApi, useNewsDetails } from '../../libs/hooks/src'
+import React from 'react'
+import { render } from '../../utils/testHelpers'
+import { NewsItem } from '../newsItem.component'
+
+jest.mock('../../libs/hooks/src')
+
+const defaultNewsItem = {
+ author: 'Köket',
+ fullImageUrl: 'test.png',
+ header: 'K-bullar!',
+ published: '2021-02-15T09:13:28.484Z',
+ modified: '2021-02-15T09:13:28.484Z',
+}
+
+let navigation
+
+const setup = (customProps = { newsItem: {} }) => {
+ useApi.mockReturnValue({ api: { getSessionCookie: jest.fn() } })
+
+ useNewsDetails.mockReturnValue({
+ data: {
+ body: 'Nu blir det köttbullar',
+ },
+ })
+
+ navigation = {
+ goBack: jest.fn(),
+ }
+
+ const newsItem = {
+ ...defaultNewsItem,
+ ...customProps.newsItem,
+ }
+
+ const props = {
+ navigation,
+ route: {
+ params: {
+ child: { id: 1 },
+ newsItem,
+ },
+ },
+ ...customProps,
+ }
+
+ return render()
+}
+
+test('gets article details using useNewsDetails', async () => {
+ setup()
+
+ expect(useNewsDetails).toHaveBeenCalledWith({ id: 1 }, defaultNewsItem)
+})
+
+test('renders an article', () => {
+ const screen = setup()
+
+ expect(screen.getByText(/nu blir det köttbullar/i)).toBeTruthy()
+ expect(screen.getByText('Publicerad: 15 feb 2021 10:13')).toBeTruthy()
+ expect(screen.getByText('Uppdaterad: 15 feb 2021 10:13')).toBeTruthy()
+})
+
+test('renders an article without published date if date is invalid', () => {
+ const newsItemWithoutPublishedDate = {
+ ...defaultNewsItem,
+ published: '2020-08-16T21:10:00.000+02:0',
+ }
+ const screen = setup({ newsItem: newsItemWithoutPublishedDate })
+
+ expect(screen.getByText(/nu blir det köttbullar/i)).toBeTruthy()
+ expect(screen.getByText('Uppdaterad: 15 feb 2021 10:13')).toBeTruthy()
+ expect(screen.queryByText('Publicerad: Invalid DateTime')).toBeFalsy()
+})
+
+test('renders an article without modified date if date is invalid', () => {
+ const newsItemWithoutPublishedDate = {
+ ...defaultNewsItem,
+ modified: null,
+ }
+ const screen = setup({ newsItem: newsItemWithoutPublishedDate })
+
+ expect(screen.getByText(/nu blir det köttbullar/i)).toBeTruthy()
+ expect(screen.getByText('Publicerad: 15 feb 2021 10:13')).toBeTruthy()
+ expect(screen.queryByText('Uppdaterad: Invalid DateTime')).toBeFalsy()
+})
diff --git a/apps/skolplattformen-app-new/components/__tests__/NewsListItem.test.js b/apps/skolplattformen-app-new/components/__tests__/NewsListItem.test.js
new file mode 100644
index 000000000..51321df03
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/__tests__/NewsListItem.test.js
@@ -0,0 +1,81 @@
+import { useNavigation } from '@react-navigation/native'
+import { fireEvent } from '@testing-library/react-native'
+import MockDate from 'mockdate'
+import React from 'react'
+import { render } from '../../utils/testHelpers'
+import { ChildProvider } from '../childContext.component'
+import { NewsListItem } from '../newsListItem.component'
+
+const defaultItem = {
+ author: 'Köket',
+ intro: 'Nu blir det köttbullar',
+ header: 'K-bullar!',
+ published: '2021-02-15T09:13:28.484Z',
+ modified: '2021-02-15T09:13:28.484Z',
+}
+
+const setup = (customProps = {}) => {
+ const props = {
+ item: defaultItem,
+ ...customProps,
+ }
+
+ return render(
+
+
+
+ )
+}
+
+beforeEach(() => {
+ MockDate.set('2021-02-15T09:30:28.484Z')
+})
+
+test('renders an article', () => {
+ const screen = setup()
+
+ expect(screen.getByText(/k-bullar!/i)).toBeTruthy()
+ expect(screen.getByText(/nu blir det köttbullar/i)).toBeTruthy()
+ expect(screen.getByText('Köket • för 17 minuter sedan')).toBeTruthy()
+})
+
+test('renders article without date', () => {
+ const itemWithInvalidDate = {
+ ...defaultItem,
+ published: null,
+ modified: null,
+ }
+
+ const screen = setup({ item: itemWithInvalidDate })
+
+ expect(screen.getByText(/k-bullar!/i)).toBeTruthy()
+ expect(screen.getByText(/nu blir det köttbullar/i)).toBeTruthy()
+ expect(screen.getByText(/^köket$/i)).toBeTruthy()
+})
+
+test('falls back to modified date if no published date', () => {
+ const itemWithInvalidDate = {
+ ...defaultItem,
+ published: null,
+ }
+
+ const screen = setup({ item: itemWithInvalidDate })
+
+ expect(screen.getByText(/k-bullar!/i)).toBeTruthy()
+ expect(screen.getByText(/nu blir det köttbullar/i)).toBeTruthy()
+ expect(screen.getByText('Köket • för 17 minuter sedan')).toBeTruthy()
+})
+
+test('navigates to news article on press', () => {
+ const navigate = jest.fn()
+ useNavigation.mockReturnValue({ navigate })
+
+ const screen = setup()
+
+ fireEvent.press(screen.getByText(/k-bullar!/i))
+
+ expect(navigate).toHaveBeenCalledWith('NewsItem', {
+ child: { id: 1 },
+ newsItem: defaultItem,
+ })
+})
diff --git a/apps/skolplattformen-app-new/components/__tests__/Notification.test.js b/apps/skolplattformen-app-new/components/__tests__/Notification.test.js
new file mode 100644
index 000000000..91bcec083
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/__tests__/Notification.test.js
@@ -0,0 +1,73 @@
+import React from 'react'
+import { render } from '../../utils/testHelpers'
+import { Notification } from '../notification.component'
+import MockDate from 'mockdate'
+
+const defaultItem = {
+ sender: 'Planering',
+ category: 'Bedömning',
+ dateCreated: '2021-02-15T09:13:28.484Z',
+ dateModified: '2021-02-15T09:14:28.484Z',
+}
+
+// copied from https://github.com/react-native-webview/react-native-webview/issues/2934#issuecomment-1524101977
+jest.mock('react-native-webview', () => {
+ const { View } = require('react-native')
+ return {
+ WebView: View,
+ }
+})
+//
+
+const setup = (customProps = {}) => {
+ const props = {
+ item: defaultItem,
+ ...customProps,
+ }
+
+ return render()
+}
+
+beforeEach(() => {
+ MockDate.set('2021-02-15T09:30:28.484Z')
+})
+
+test('renders subtitle with modified date', () => {
+ const screen = setup()
+
+ expect(screen.getByText('Bedömning • för 16 minuter sedan')).toBeTruthy()
+})
+
+test('renders subtitle with created date', () => {
+ const itemWithoutModifiedDate = {
+ ...defaultItem,
+ dateModified: undefined,
+ }
+
+ const screen = setup({ item: itemWithoutModifiedDate })
+
+ expect(screen.getByText('Bedömning • för 17 minuter sedan')).toBeTruthy()
+})
+
+test('renders subtitle without date', () => {
+ const itemWithoutDate = {
+ ...defaultItem,
+ dateCreated: undefined,
+ dateModified: undefined,
+ }
+
+ const screen = setup({ item: itemWithoutDate })
+
+ expect(screen.getByText('Bedömning')).toBeTruthy()
+})
+
+test('renders subtitle without category', () => {
+ const itemWithoutCategory = {
+ ...defaultItem,
+ category: undefined,
+ }
+
+ const screen = setup({ item: itemWithoutCategory })
+
+ expect(screen.getByText('för 16 minuter sedan')).toBeTruthy()
+})
diff --git a/apps/skolplattformen-app-new/components/__tests__/saveToCalendar.test.js b/apps/skolplattformen-app-new/components/__tests__/saveToCalendar.test.js
new file mode 100644
index 000000000..0dfbcc1b5
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/__tests__/saveToCalendar.test.js
@@ -0,0 +1,133 @@
+import { fireEvent } from '@testing-library/react-native'
+import React from 'react'
+import RNCalendarEvents from 'react-native-calendar-events'
+import Toast from 'react-native-simple-toast'
+import { render } from '../../utils/testHelpers'
+import { SaveToCalendar } from '../saveToCalendar.component'
+
+const defaultEvent = {
+ title: 'Utvecklingssamtal',
+ startDate: '2021-06-19 13:00',
+ endDate: '2021-06-19 14:00',
+ location: 'Gubbängsskolan',
+}
+
+const defaultProps = {
+ event: defaultEvent,
+}
+
+const setup = (customProps = {}) => {
+ const props = {
+ ...defaultProps,
+ ...customProps,
+ }
+
+ return render()
+}
+
+beforeAll(() => {
+ // Hide errors from state illegal state transition
+ // Probably due to mock
+ jest.spyOn(console, 'error').mockImplementation(() => {
+ // noop
+ })
+})
+
+beforeEach(jest.clearAllMocks)
+
+test('renders save to calendar', () => {
+ const screen = setup()
+
+ fireEvent.press(screen.getByTestId('actionsButton'))
+
+ expect(screen.getByText(/Spara/i)).toBeTruthy()
+})
+
+test('requests calendar permissons', () => {
+ const screen = setup()
+
+ fireEvent.press(screen.getByTestId('actionsButton'))
+ fireEvent.press(screen.getByText(/Spara/i))
+
+ expect(RNCalendarEvents.requestPermissions).toHaveBeenCalled()
+})
+
+test('can save an event to the calendar', async () => {
+ const screen = setup({
+ event: {
+ ...defaultEvent,
+ location: null,
+ description: null,
+ },
+ })
+
+ fireEvent.press(screen.getByTestId('actionsButton'))
+ fireEvent.press(screen.getByText(/Spara/i))
+ await RNCalendarEvents.requestPermissions()
+
+ expect(RNCalendarEvents.saveEvent).toHaveBeenCalledWith('Utvecklingssamtal', {
+ startDate: '2021-06-19T11:00:00.000Z',
+ endDate: '2021-06-19T12:00:00.000Z',
+ })
+})
+
+test('removes any null values from the event', async () => {
+ const screen = setup()
+
+ fireEvent.press(screen.getByTestId('actionsButton'))
+ fireEvent.press(screen.getByText(/Spara/i))
+ await RNCalendarEvents.requestPermissions()
+
+ expect(RNCalendarEvents.saveEvent).toHaveBeenCalledWith('Utvecklingssamtal', {
+ startDate: '2021-06-19T11:00:00.000Z',
+ endDate: '2021-06-19T12:00:00.000Z',
+ location: 'Gubbängsskolan',
+ })
+})
+
+test('calls toast with success', async () => {
+ const screen = setup()
+
+ fireEvent.press(screen.getByTestId('actionsButton'))
+ fireEvent.press(screen.getByText(/Spara/i))
+ await RNCalendarEvents.requestPermissions()
+ await RNCalendarEvents.saveEvent()
+
+ expect(Toast.showWithGravity).toHaveBeenCalledWith(
+ '✔️ Sparad till kalender',
+ 'short',
+ 'bottom'
+ )
+})
+
+test('says if something goes wrong', async () => {
+ const screen = setup()
+ RNCalendarEvents.saveEvent.mockRejectedValueOnce()
+
+ fireEvent.press(screen.getByTestId('actionsButton'))
+ fireEvent.press(screen.getByText(/Spara/i))
+ await RNCalendarEvents.requestPermissions()
+ await RNCalendarEvents.saveEvent()
+
+ expect(Toast.showWithGravity).toHaveBeenCalledWith(
+ 'Något gick fel',
+ 'short',
+ 'bottom'
+ )
+})
+
+test('tells user if they havent authorized calendar', async () => {
+ const screen = setup()
+ RNCalendarEvents.requestPermissions.mockResolvedValueOnce('not auth')
+
+ fireEvent.press(screen.getByTestId('actionsButton'))
+ fireEvent.press(screen.getByText(/Spara/i))
+ await RNCalendarEvents.requestPermissions()
+ await RNCalendarEvents.saveEvent()
+
+ expect(Toast.showWithGravity).toHaveBeenCalledWith(
+ 'Du måste godkänna åtkomst till kalendern',
+ 'short',
+ 'bottom'
+ )
+})
diff --git a/apps/skolplattformen-app-new/components/absence.component.tsx b/apps/skolplattformen-app-new/components/absence.component.tsx
new file mode 100644
index 000000000..05bfad47a
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/absence.component.tsx
@@ -0,0 +1,280 @@
+import { RouteProp, useRoute } from '@react-navigation/native'
+import { NativeStackNavigationOptions } from '@react-navigation/native-stack'
+import { useUser } from '../libs/hooks/src'
+import {
+ Button,
+ CheckBox,
+ Input,
+ StyleService,
+ Text,
+ useStyleSheet,
+} from '@ui-kitten/components'
+import { Formik } from 'formik'
+import moment from 'moment'
+import Personnummer from 'personnummer'
+import React, { useCallback } from 'react'
+import { View } from 'react-native'
+import DateTimePickerModal from 'react-native-modal-datetime-picker'
+import * as Yup from 'yup'
+import { defaultStackStyling } from '../design/navigationThemes'
+// import usePersonalStorage from '../hooks/usePersonalStorage';
+import useSettingsStorage from '../hooks/useSettingsStorage'
+import { Layout as LayoutStyle, Sizing, Typography } from '../styles'
+import { studentName } from '../utils/peopleHelpers'
+import { useSMS } from '../utils/SMS'
+import { translate } from '../utils/translation'
+import { AlertIcon } from './icon.component'
+import { RootStackParamList } from './navigation.component'
+import { NavigationTitle } from './navigationTitle.component'
+
+type AbsenceRouteProps = RouteProp
+
+interface AbsenceFormValues {
+ displayStartTimePicker: boolean
+ displayEndTimePicker: boolean
+ personalIdentityNumber: string
+ isFullDay: boolean
+ startTime: moment.Moment
+ endTime: moment.Moment
+}
+
+export const absenceRouteOptions =
+ (darkMode: boolean) =>
+ ({
+ route,
+ }: {
+ route: RouteProp
+ }): NativeStackNavigationOptions => {
+ const child = route.params.child
+ return {
+ ...defaultStackStyling(darkMode),
+ headerTitle: () => (
+
+ ),
+ }
+ }
+
+const Absence = () => {
+ const AbsenceSchema = Yup.object().shape({
+ personalIdentityNumber: Yup.string()
+ .required(translate('abscense.personalNumberMissing'))
+ .test('is-valid', translate('abscense.invalidPersonalNumber'), (value) =>
+ value ? Personnummer.valid(value) : true
+ ),
+ isFullDay: Yup.bool().required(),
+ })
+
+ const { data: user } = useUser()
+ const route = useRoute()
+ const { sendSMS } = useSMS()
+ const { child } = route.params
+ const [personalIdentityNumber, setPersonalIdentityNumber] = React.useState('')
+ const [personalIdsFromStorage, setPersonalIdInStorage] = useSettingsStorage(
+ 'childPersonalIdentityNumber'
+ )
+ const personalIdKey = `@childPersonalIdNumber.${child.id}`
+ const minumumDate = moment().hours(8).minute(0)
+ const maximumDate = moment().hours(17).minute(0)
+ const styles = useStyleSheet(themedStyles)
+
+ const submit = useCallback(
+ async (values: AbsenceFormValues) => {
+ const personalIdNumber = Personnummer.parse(
+ values.personalIdentityNumber
+ ).format()
+
+ if (values.isFullDay) {
+ sendSMS(personalIdNumber)
+ } else {
+ sendSMS(
+ `${personalIdNumber} ${moment(values.startTime).format(
+ 'HHmm'
+ )}-${moment(values.endTime).format('HHmm')}`
+ )
+ }
+
+ const toStore = {
+ ...personalIdsFromStorage,
+ ...{ [personalIdKey]: personalIdNumber },
+ }
+ setPersonalIdInStorage(toStore)
+ },
+ [personalIdKey, personalIdsFromStorage, sendSMS, setPersonalIdInStorage]
+ )
+
+ React.useEffect(() => {
+ const personalIdFromStorage = personalIdsFromStorage[personalIdKey] || ''
+ setPersonalIdentityNumber(personalIdFromStorage || '')
+ }, [child, personalIdKey, personalIdsFromStorage, user])
+
+ const initialValues: AbsenceFormValues = {
+ displayStartTimePicker: false,
+ displayEndTimePicker: false,
+ personalIdentityNumber: personalIdentityNumber || '',
+ isFullDay: true,
+ startTime: moment().hours(Math.max(8, new Date().getHours())).minute(0),
+ endTime: maximumDate,
+ }
+
+ return (
+
+ {({
+ handleChange,
+ handleBlur,
+ handleSubmit,
+ setFieldValue,
+ values,
+ touched,
+ errors,
+ }) => {
+ const hasError = (field: keyof typeof values) =>
+ errors[field] && touched[field]
+
+ return (
+
+
+
+ {translate('abscense.childsPersonalNumber')}
+
+
+ {hasError('personalIdentityNumber') && (
+
+ {errors.personalIdentityNumber}
+
+ )}
+
+
+ setFieldValue('isFullDay', checked)}
+ >
+ {translate('abscense.entireDay')}
+
+
+ {!values.isFullDay && (
+
+
+
+ {translate('abscense.startTime')}
+
+
+ {
+ setFieldValue('startTime', date)
+ setFieldValue('displayStartTimePicker', false)
+ }}
+ onCancel={() =>
+ setFieldValue('displayStartTimePicker', false)
+ }
+ />
+
+
+
+
+ {translate('abscense.endTime')}
+
+
+ {
+ setFieldValue('endTime', date)
+ setFieldValue('displayEndTimePicker', false)
+ }}
+ onCancel={() =>
+ setFieldValue('displayEndTimePicker', false)
+ }
+ />
+
+
+ )}
+
+
+ )
+ }}
+
+ )
+}
+
+export default Absence
+
+const themedStyles = StyleService.create({
+ wrap: {
+ ...LayoutStyle.flex.full,
+ padding: Sizing.t4,
+ backgroundColor: 'background-basic-color-2',
+ },
+ field: { marginBottom: Sizing.t4 },
+ partOfDay: { ...LayoutStyle.flex.row, marginBottom: Sizing.t4 },
+ spacer: { width: Sizing.t2 },
+ inputHalf: { ...LayoutStyle.flex.full },
+ input: {
+ backgroundColor: 'background-basic-color-1',
+ borderColor: 'color-input-border',
+ },
+ // TODO: Refactor to use mapping.json in eva design
+ pickerButton: {
+ backgroundColor: 'background-basic-color-1',
+ },
+ label: {
+ ...Typography.fontSize.sm,
+ ...Typography.fontWeight.bold,
+ marginBottom: Sizing.t2,
+ },
+ error: {
+ color: 'color-primary-600',
+ },
+})
diff --git a/apps/skolplattformen-app-new/components/auth.component.tsx b/apps/skolplattformen-app-new/components/auth.component.tsx
new file mode 100644
index 000000000..c1d6ba309
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/auth.component.tsx
@@ -0,0 +1,166 @@
+import { NativeStackNavigationOptions } from '@react-navigation/native-stack'
+import { StackNavigationProp } from '@react-navigation/stack'
+import {
+ StyleService,
+ Text,
+ useStyleSheet,
+ useTheme,
+} from '@ui-kitten/components'
+import React from 'react'
+import {
+ Image,
+ ImageStyle,
+ Keyboard,
+ TouchableOpacity,
+ TouchableWithoutFeedback,
+ View,
+} from 'react-native'
+import { useTranslation } from '../hooks/useTranslation'
+import { Layout as LayoutStyle, Sizing, Typography } from '../styles'
+import { fontSize } from '../styles/typography'
+import { KeyboardAvoidingView } from '../ui/keyboardAvoidingView.component'
+import { SafeAreaView } from '../ui/safeAreaView.component'
+import { SettingsIcon } from './icon.component'
+import { Login } from './login.component'
+import { RootStackParamList } from './navigation.component'
+
+const randomWord = (
+ t: (scope: I18n.Scope, options?: I18n.TranslateOptions | undefined) => string
+) => {
+ const words = t('auth.words')
+ const keys = Object.keys(words)
+
+ const randomIndex: number = Math.floor(Math.random() * keys.length)
+ const argumentKey: string = keys[randomIndex]
+
+ return words[argumentKey]
+}
+
+interface AuthProps {
+ navigation: StackNavigationProp
+}
+
+export const authRouteOptions = (): NativeStackNavigationOptions => {
+ return {
+ headerShown: false,
+ animationTypeForReplace: 'push',
+ animation: 'fade',
+ }
+}
+
+export const Auth: React.FC = ({ navigation }) => {
+ const styles = useStyleSheet(themeStyles)
+ const colors = useTheme()
+ const { t } = useTranslation()
+ // const t = (key: string) => key;
+
+ return (
+
+
+
+ navigation.navigate('Settings')}
+ accessibilityHint={t(
+ 'auth.a11y_navigate_to_settings'
+ // defaultValue: 'Navigerar till vyn för inställningar',
+ )}
+ accessibilityLabel={t(
+ 'auth.a11y_settings'
+ // {
+ // // defaultValue: 'Inställningar',
+ // }
+ )}
+ >
+
+
+ {t('general.settings')}
+
+
+
+
+
+
+
+
+
+ Öppna skolplattformen
+
+
+
+ {t('auth.subtitle', {
+ word: randomWord(t),
+ })}
+
+
+
+
+
+
+
+ )
+}
+
+const themeStyles = StyleService.create({
+ container: {
+ ...LayoutStyle.mainAxis.flexStart,
+ ...LayoutStyle.crossAxis.flexEnd,
+ padding: Sizing.t6,
+ },
+ imageWrapper: {
+ flex: 1,
+ justifyContent: 'flex-end',
+ },
+ image: {
+ ...Sizing.aspectRatio(1.5, Sizing.Ratio['4:3']),
+ },
+ content: {
+ ...LayoutStyle.flex.full,
+ },
+ header: {
+ width: '100%',
+ marginBottom: Sizing.t5,
+ fontFamily: 'Poppins-Black',
+ fontWeight: '900',
+ },
+ subtitle: {
+ width: '100%',
+ textAlign: 'center',
+ ...Typography.fontSize.xs,
+ marginTop: Sizing.t5,
+ },
+ language: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: Sizing.t3,
+ paddingLeft: Sizing.t5,
+ },
+ languageText: {
+ ...fontSize.sm,
+ marginLeft: Sizing.t1,
+ },
+ settingsLink: {
+ alignSelf: 'flex-start',
+ zIndex: 1,
+ },
+})
diff --git a/apps/skolplattformen-app-new/components/calendar.component.tsx b/apps/skolplattformen-app-new/components/calendar.component.tsx
new file mode 100644
index 000000000..a5b9ead43
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/calendar.component.tsx
@@ -0,0 +1,114 @@
+import { useCalendar } from '../libs/hooks/src'
+import { CalendarItem } from '@skolplattformen/api'
+import {
+ Divider,
+ List,
+ ListItem,
+ StyleService,
+ Text,
+ useStyleSheet,
+} from '@ui-kitten/components'
+import moment from 'moment'
+import React from 'react'
+import { Layout as LayoutStyle, Sizing, Typography } from '../styles'
+import { ListRenderItemInfo, RefreshControl, View } from 'react-native'
+
+import { translate } from '../utils/translation'
+import { useChild } from './childContext.component'
+import { CalendarOutlineIcon } from './icon.component'
+import { SaveToCalendar } from './saveToCalendar.component'
+import { Week } from './week.component'
+
+// const translate = (key: string) => key;
+
+export const Calendar = () => {
+ const child = useChild()
+ const { data, status, reload } = useCalendar(child)
+ const styles = useStyleSheet(themedStyles)
+
+ const formatStartDate = (startDate: moment.MomentInput) => {
+ const date = moment(startDate)
+ const output = `${date.format('dddd')} ${date.format(
+ 'll'
+ )} • ${date.fromNow()}`
+
+ // Hack to remove year if it is this year
+ const currentYear = moment().year().toString(10)
+ return output.replace(currentYear, '')
+ }
+
+ const sortedData = () => {
+ if (!data) {
+ return []
+ }
+
+ return data.sort((a, b) =>
+ a.startDate && b.startDate ? a.startDate.localeCompare(b.startDate) : 0
+ )
+ }
+
+ return (
+
+
+
+
+ {translate('calender.emptyHeadline')}
+
+
+ {translate('calender.emptyText')}
+
+
+ }
+ renderItem={({ item }: ListRenderItemInfo) => (
+ (
+
+ {formatStartDate(item.startDate)}
+
+ )}
+ accessoryLeft={CalendarOutlineIcon}
+ accessoryRight={() => }
+ />
+ )}
+ refreshControl={
+
+ }
+ />
+
+ )
+}
+
+const themedStyles = StyleService.create({
+ container: {
+ backgroundColor: 'background-basic-color-1',
+ height: '100%',
+ width: '100%',
+ },
+ description: {
+ ...Typography.fontSize.xs,
+ color: 'text-hint-color',
+ },
+ emptyState: {
+ ...LayoutStyle.center,
+ ...LayoutStyle.flex.full,
+ },
+ emptyStateHeadline: {
+ ...Typography.align.center,
+ margin: Sizing.t4,
+ },
+ emptyStateDescription: {
+ ...Typography.align.center,
+ lineHeight: 21,
+ paddingHorizontal: Sizing.t3,
+ margin: Sizing.t4,
+ },
+})
diff --git a/apps/skolplattformen-app-new/components/child.component.tsx b/apps/skolplattformen-app-new/components/child.component.tsx
new file mode 100644
index 000000000..e5bd91fb2
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/child.component.tsx
@@ -0,0 +1,216 @@
+import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
+import {
+ getFocusedRouteNameFromRoute,
+ RouteProp,
+ useNavigation,
+ useRoute,
+} from '@react-navigation/native'
+import { NativeStackNavigationOptions } from '@react-navigation/native-stack'
+// import {StackNavigationProp} from '@react-navigation/stack';
+import { Icon } from '@ui-kitten/components'
+import React, { useEffect } from 'react'
+// import {StyleProp, TextProps} from 'react-native';
+import { defaultStackStyling } from '../design/navigationThemes'
+import { useFeature } from '../hooks/useFeature'
+import { studentName } from '../utils/peopleHelpers'
+import { translate } from '../utils/translation'
+import { Calendar } from './calendar.component'
+import { ChildProvider } from './childContext.component'
+import { Classmates } from './classmates.component'
+import { Menu } from './menu.component'
+import { RootStackParamList } from './navigation.component'
+import { NavigationTitle } from './navigationTitle.component'
+import { NewsList } from './newsList.component'
+import { NotificationsList } from './notificationsList.component'
+import { TabBarLabel } from './tabBarLabel.component'
+
+// const translate = (key: string) => key;
+
+// type ChildNavigationProp = StackNavigationProp;
+type ChildRouteProps = RouteProp
+
+export type ChildTabParamList = {
+ News: undefined
+ Notifications: undefined
+ Calendar: undefined
+ Menu: undefined
+ Classmates: undefined
+}
+
+// interface TabTitleProps {
+// children: string;
+// style?: StyleProp;
+// }
+
+const { Navigator, Screen } = createBottomTabNavigator()
+
+const NewsScreen = () =>
+const NotificationsScreen = () =>
+const CalendarScreen = () =>
+const MenuScreen = () =>
+const ClassmatesScreen = () =>
+
+interface ScreenSettings {
+ NEWS_SCREEN: boolean
+ NOTIFICATIONS_SCREEN: boolean
+ CALENDER_SCREEN: boolean
+ MENU_SCREEN: boolean
+ CLASSMATES_SCREEN: boolean
+}
+
+const TabNavigator = ({
+ initialRouteName = 'News',
+ screenSettings,
+}: {
+ initialRouteName?: keyof ChildTabParamList
+ screenSettings: ScreenSettings
+}) => (
+ {
+ return {
+ tabBarLabel: ({ focused }) => (
+
+ ),
+ tabBarIcon: ({ focused, color }) => {
+ let iconName = 'news'
+
+ if (route.name === 'News') {
+ iconName = focused ? 'book-open' : 'book-open-outline'
+ } else if (route.name === 'Notifications') {
+ iconName = focused ? 'alert-circle' : 'alert-circle-outline'
+ } else if (route.name === 'Calendar') {
+ iconName = focused ? 'calendar' : 'calendar-outline'
+ } else if (route.name === 'Menu') {
+ iconName = focused ? 'clipboard' : 'clipboard-outline'
+ } else if (route.name === 'Classmates') {
+ iconName = focused ? 'people' : 'people-outline'
+ }
+ return
+ },
+ }
+ }}
+ >
+ {screenSettings.NEWS_SCREEN && (
+
+ )}
+ {screenSettings.NOTIFICATIONS_SCREEN && (
+
+ )}
+ {screenSettings.CALENDER_SCREEN && (
+
+ )}
+ {screenSettings.MENU_SCREEN && (
+
+ )}
+ {screenSettings.CLASSMATES_SCREEN && (
+
+ )}
+
+)
+
+const getHeaderTitle = (route: any) => {
+ const routeName =
+ getFocusedRouteNameFromRoute(route) ??
+ route.params.initialRouteName ??
+ 'News'
+ return getRouteTitleFromName(routeName)
+}
+
+const getRouteTitleFromName = (routeName: string) => {
+ switch (routeName) {
+ case 'News':
+ return translate('navigation.news')
+ case 'Notifications':
+ return translate('navigation.notifications')
+ case 'Calendar':
+ return translate('navigation.calender')
+ case 'Menu':
+ return translate('navigation.menu')
+ case 'Classmates':
+ return translate('navigation.classmates')
+ default:
+ return ''
+ }
+}
+
+export const childRouteOptions =
+ (darkMode: boolean) =>
+ ({
+ route,
+ }: {
+ route: RouteProp
+ }): NativeStackNavigationOptions => {
+ const { child } = route.params
+
+ return {
+ ...defaultStackStyling(darkMode),
+ headerTitle: () => (
+
+ ),
+ }
+ }
+
+export const Child = () => {
+ const route = useRoute()
+ const { child, initialRouteName } = route.params
+ const useFoodMenu = useFeature('FOOD_MENU')
+ const useClassList = useFeature('CLASS_LIST')
+
+ const navigation = useNavigation()
+
+ useEffect(() => {
+ navigation.setOptions({ title: getHeaderTitle(route) })
+ }, [navigation, route])
+
+ const screenSettings: ScreenSettings = {
+ NEWS_SCREEN: true,
+ NOTIFICATIONS_SCREEN: true,
+ CALENDER_SCREEN: true,
+ MENU_SCREEN: useFoodMenu,
+ CLASSMATES_SCREEN: useClassList,
+ }
+ return (
+
+
+
+ )
+}
diff --git a/apps/skolplattformen-app-new/components/childContext.component.tsx b/apps/skolplattformen-app-new/components/childContext.component.tsx
new file mode 100644
index 000000000..d4bbc51bb
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/childContext.component.tsx
@@ -0,0 +1,19 @@
+import { Child } from '@skolplattformen/api'
+import React, { createContext, useContext } from 'react'
+
+interface ChildProviderProps {
+ child: Child
+ children: React.ReactNode
+}
+
+export const ChildContext = createContext({
+ id: '',
+ sdsId: '',
+ name: '',
+})
+
+export const ChildProvider = ({ child, children }: ChildProviderProps) => {
+ return {children}
+}
+
+export const useChild = () => useContext(ChildContext)
diff --git a/apps/skolplattformen-app-new/components/childListItem.component.tsx b/apps/skolplattformen-app-new/components/childListItem.component.tsx
new file mode 100644
index 000000000..ee3a36b11
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/childListItem.component.tsx
@@ -0,0 +1,337 @@
+import { useNavigation } from '@react-navigation/native'
+import { StackNavigationProp } from '@react-navigation/stack'
+import { Child } from '@skolplattformen/api'
+import {
+ useCalendar,
+ useClassmates,
+ useMenu,
+ useNews,
+ useNotifications,
+ useSchedule,
+} from '../libs/hooks/src'
+import {
+ Button,
+ StyleService,
+ Text,
+ useStyleSheet,
+} from '@ui-kitten/components'
+import moment, { Moment } from 'moment'
+import React, { useEffect } from 'react'
+import { Pressable, useColorScheme, View } from 'react-native'
+import { useTranslation } from '../hooks/useTranslation'
+import { Colors, Layout, Sizing } from '../styles'
+import { getMeaningfulStartingDate } from '../utils/calendarHelpers'
+import { studentName } from '../utils/peopleHelpers'
+import { DaySummary } from './daySummary.component'
+import { AlertIcon, RightArrowIcon } from './icon.component'
+import { RootStackParamList } from './navigation.component'
+import { StudentAvatar } from './studentAvatar.component'
+
+interface ChildListItemProps {
+ child: Child
+ color: string
+ updated: string
+ currentDate?: Moment
+}
+type ChildListItemNavigationProp = StackNavigationProp<
+ RootStackParamList,
+ 'Children'
+>
+
+export const ChildListItem = ({
+ child,
+ color,
+ updated,
+ currentDate = moment(),
+}: ChildListItemProps) => {
+ // Forces rerender when child.id changes
+ React.useEffect(() => {
+ // noop
+ }, [child.id])
+
+ const navigation = useNavigation()
+
+ const { t } = useTranslation()
+
+ const { data: notifications, reload: notificationsReload } =
+ useNotifications(child)
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { data: news, status: newsStatus, reload: newsReload } = useNews(child)
+ const { data: classmates, reload: classmatesReload } = useClassmates(child)
+ const { data: calendar, reload: calendarReload } = useCalendar(child)
+ const { data: menu, reload: menuReload } = useMenu(child)
+ const { data: schedule, reload: scheduleReload } = useSchedule(
+ child,
+ moment(currentDate).toISOString(),
+ moment(currentDate).add(7, 'days').toISOString()
+ )
+
+ useEffect(() => {
+ // Do not refresh if updated is empty (first render of component)
+ if (updated === '') {
+ return
+ }
+
+ newsReload()
+ classmatesReload()
+ notificationsReload()
+ calendarReload()
+ menuReload()
+ scheduleReload()
+
+ // Without eslint-disable below we get into a forever loop
+ // because the function pointers to reload functions change on every reload.
+ // I do not know a workaround for this.
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [updated])
+
+ const notificationsThisWeek = notifications.filter(
+ ({ dateCreated, dateModified }) => {
+ const date = dateModified || dateCreated
+ return date ? moment(date).isSame(moment(), 'week') : false
+ }
+ )
+
+ const newsThisWeek = news.filter(({ modified, published }) => {
+ const newsDate = modified || published
+ return newsDate ? moment(newsDate).isSame(currentDate, 'week') : false
+ })
+
+ const scheduleAndCalendarThisWeek = [
+ ...(calendar ?? []),
+ ...(schedule ?? []),
+ ].filter(({ startDate }) =>
+ startDate
+ ? moment(startDate).isBetween(
+ moment(currentDate).startOf('day'),
+ moment(currentDate).add(7, 'days')
+ )
+ : false
+ )
+
+ const displayDate = (inputDate: moment.MomentInput) => {
+ return moment(inputDate).fromNow()
+ }
+
+ const getClassName = () => {
+ // hack: we can find the class name (ex. 8C) from the classmates.
+ // let's pick the first one and select theirs class
+ // hack 2: we can find school namn in skola24 if child data is there
+ if (classmates.length > 0) {
+ return (
+ classmates[0].className +
+ (child.schoolID == null ? '' : ' • ' + child.schoolID)
+ )
+ }
+
+ // Taken from Skolverket
+ // https://www.skolverket.se/skolutveckling/anordna-och-administrera-utbildning/administrera-utbildning/skoltermer-pa-engelska
+ const abbrevations = {
+ G: t('abbrevations.upperSecondarySchool'),
+ GR: t('abbrevations.compulsorySchool'),
+ F: t('abbrevations.leisureTimeCentre'),
+ FS: t('abbrevations.preSchool'),
+ }
+
+ return child.status
+ ? child.status
+ .split(';')
+ .map((status) => {
+ const statusAsAbbreviation = status as keyof typeof abbrevations
+
+ return abbrevations[statusAsAbbreviation] || status
+ })
+ .join(', ')
+ : null
+ }
+
+ const className = getClassName()
+ const styles = useStyleSheet(themeStyles)
+ const isDarkMode = useColorScheme() === 'dark'
+ const meaningfulStartingDate = getMeaningfulStartingDate(currentDate)
+
+ // Hide menu if we want to show monday but it is not monday yet.
+ // The menu for next week is not available until monday
+ const shouldShowLunchMenu =
+ menu[meaningfulStartingDate.isoWeekday() - 1] &&
+ !(
+ meaningfulStartingDate.isoWeekday() === 1 &&
+ currentDate.isoWeekday() !== 1
+ )
+
+ return (
+ <>
+
+
+ [
+ styles.cardHeaderLeft || {},
+ { opacity: pressed ? 0.5 : 1 },
+ ]}
+ onPress={() => navigation.navigate('Child', { child, color })}
+ >
+
+
+
+ {studentName(child.name)}
+ {className ? {className} : null}
+
+
+
+
+
+
+
+ ['' || {}, { opacity: pressed ? 0.5 : 1 }]}
+ onPress={() =>
+ navigation.navigate('Child', {
+ child,
+ color,
+ initialRouteName: 'Calendar',
+ })
+ }
+ >
+
+
+ {scheduleAndCalendarThisWeek.slice(0, 3).map((calendarItem, i) => (
+
+ {`${calendarItem.title} (${displayDate(calendarItem.startDate)})`}
+
+ ))}
+
+ ['' || {}, { opacity: pressed ? 0.5 : 1 }]}
+ onPress={() =>
+ navigation.navigate('Child', {
+ child,
+ color,
+ initialRouteName: 'News',
+ })
+ }
+ >
+
+ {t('navigation.news')}
+
+ {notificationsThisWeek.slice(0, 3).map((notification, i) => (
+
+ {notification.message}
+
+ ))}
+ {newsThisWeek.slice(0, 3).map((newsItem, i) => (
+
+ {newsItem.header ?? ''}
+
+ ))}
+
+
+ {scheduleAndCalendarThisWeek.length ||
+ notificationsThisWeek.length ||
+ newsThisWeek.length ? null : (
+
+ {t('news.noNewNewsItemsThisWeek')}
+
+ )}
+
+ {shouldShowLunchMenu ? (
+ ['' || {}, { opacity: pressed ? 0.5 : 1 }]}
+ onPress={() =>
+ navigation.navigate('Child', {
+ child,
+ color,
+ initialRouteName: 'Menu',
+ })
+ }
+ >
+
+ {meaningfulStartingDate.format(
+ '[' + t('schedule.lunch') + '] dddd'
+ )}
+
+
+ {menu[meaningfulStartingDate.isoWeekday() - 1]?.description}
+
+
+ ) : null}
+
+
+
+
+
+ >
+ )
+}
+
+const themeStyles = StyleService.create({
+ card: {
+ borderRadius: 25,
+ padding: Sizing.t5,
+ marginBottom: Sizing.t4,
+ backgroundColor: 'background-basic-color-1',
+ },
+ cardHeader: {
+ ...Layout.flex.row,
+ ...Layout.mainAxis.center,
+ ...Layout.crossAxis.spaceBetween,
+ marginBottom: Sizing.t4,
+ },
+ cardHeaderLeft: {
+ ...Layout.flex.row,
+ ...Layout.mainAxis.center,
+ flex: 10,
+ },
+ cardHeaderRight: {
+ ...Layout.flex.row,
+ ...Layout.crossAxis.flexEnd,
+ flex: 1,
+ },
+ cardHeaderText: {
+ marginHorizontal: Sizing.t4,
+ flex: 10,
+ },
+ icon: {
+ width: 32,
+ height: 32,
+ },
+ label: {
+ marginTop: 10,
+ },
+ itemFooter: {
+ ...Layout.flex.row,
+ justifyContent: 'space-between',
+ alignItems: 'flex-end',
+ marginTop: Sizing.t4,
+ },
+ absenceButton: {
+ marginLeft: -20,
+ },
+ itemFooterSpinner: {
+ alignSelf: 'flex-end',
+ },
+ item: {
+ marginRight: 12,
+ paddingHorizontal: 2,
+ paddingVertical: 0,
+ marginBottom: 0,
+ },
+ noNewNewsItemsText: {},
+})
diff --git a/apps/skolplattformen-app-new/components/children.component.tsx b/apps/skolplattformen-app-new/components/children.component.tsx
new file mode 100644
index 000000000..8a7eba89d
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/children.component.tsx
@@ -0,0 +1,214 @@
+import { NavigationProp, useNavigation } from '@react-navigation/core'
+import { NativeStackNavigationOptions } from '@react-navigation/native-stack'
+import { Child } from '@skolplattformen/api'
+import { useApi, useChildList } from '../libs/hooks/src'
+import {
+ Button,
+ List,
+ Spinner,
+ StyleService,
+ Text,
+ TopNavigationAction,
+ useStyleSheet,
+} from '@ui-kitten/components'
+import moment from 'moment'
+import React, { useCallback, useEffect, useState } from 'react'
+import {
+ Image,
+ ImageStyle,
+ Linking,
+ ListRenderItemInfo,
+ View,
+} from 'react-native'
+import { defaultStackStyling } from '../design/navigationThemes'
+import AppStorage from '../services/appStorage'
+import { Layout as LayoutStyle, Sizing, Typography } from '../styles'
+import { translate } from '../utils/translation'
+import { ChildListItem } from './childListItem.component'
+import { RefreshIcon, SettingsIcon } from './icon.component'
+import { RootStackParamList } from './navigation.component'
+
+const colors = ['primary', 'success', 'info', 'warning', 'danger']
+
+export const childenRouteOptions =
+ (darkMode: boolean) => (): NativeStackNavigationOptions => {
+ return {
+ ...defaultStackStyling(darkMode),
+ title: translate('children.title'),
+ headerLargeTitle: false,
+ headerLargeTitleShadowVisible: false,
+ }
+ }
+
+export const Children = () => {
+ const styles = useStyleSheet(themedStyles)
+
+ const navigation = useNavigation>()
+
+ const { api } = useApi()
+ const { data: childList, status, reload } = useChildList()
+ const reloadChildren = useCallback(() => {
+ reload()
+ setUpdated(moment().toISOString())
+ }, [reload])
+
+ const [updatedAt, setUpdated] = useState('')
+
+ const logout = useCallback(() => {
+ AppStorage.clearTemporaryItems().then(() => api.logout())
+ }, [api])
+
+ useEffect(() => {
+ navigation.setOptions({
+ headerLeft: () => {
+ return (
+ navigation.navigate('Settings')}
+ />
+ )
+ },
+ headerRight: () => {
+ return (
+ reloadChildren()}
+ accessibilityHint="Reload"
+ accessibilityLabel="Reload"
+ />
+ )
+ },
+ })
+ }, [navigation, reloadChildren])
+
+ // We need to skip safe area view here, due to the reason that it's adding a white border
+ // when this view is actually lightgrey. Taking the padding top value from the use inset hook.
+ return status === 'loaded' ? (
+
+ {translate('children.noKids_title')}
+
+ {translate('children.noKids_description')}
+
+
+
+ }
+ renderItem={({ item: child, index }: ListRenderItemInfo) => (
+
+ )}
+ />
+ ) : (
+
+
+ {status === 'error' ? (
+
+ {translate('children.loadingErrorHeading')}
+
+ {translate('children.loadingErrorInformationText')}
+
+
+
+
+
+
+
+ ) : (
+
+
+
+ {translate('general.loading')}
+
+
+ )}
+
+ )
+}
+
+const themedStyles = StyleService.create({
+ topContainer: {
+ ...LayoutStyle.flex.full,
+ paddingBottom: 0,
+ },
+ loading: {
+ ...LayoutStyle.center,
+ ...LayoutStyle.flex.full,
+ },
+ loadingImage: {
+ ...Sizing.aspectRatio(),
+ },
+ loadingMessage: {
+ ...LayoutStyle.mainAxis.center,
+ ...LayoutStyle.flex.row,
+ marginTop: Sizing.t2,
+ },
+ loadingText: {
+ marginLeft: Sizing.t5,
+ },
+ errorButtons: {
+ height: Sizing.screen.height * 0.2,
+ width: Sizing.screen.width * 0.73,
+ justifyContent: 'space-evenly',
+ },
+ errorMessage: {
+ height: Sizing.screen.height * 0.4,
+ width: Sizing.screen.width * 0.73,
+ justifyContent: 'space-evenly',
+ alignItems: 'center',
+ marginTop: Sizing.t2,
+ },
+ errorText: {
+ marginBottom: Sizing.t3,
+ },
+ childList: {
+ ...LayoutStyle.flex.full,
+ },
+ childListContainer: {
+ paddingVertical: Sizing.t4,
+ paddingHorizontal: Sizing.t3,
+ },
+ emptyState: {
+ ...LayoutStyle.center,
+ ...LayoutStyle.flex.full,
+ paddingHorizontal: Sizing.t5,
+ },
+ emptyStateDescription: {
+ ...Typography.align.center,
+ lineHeight: 21,
+ marginTop: Sizing.t2,
+ },
+ emptyStateImage: {
+ ...Sizing.aspectRatio(0.8),
+ marginTop: Sizing.t5,
+ },
+ topNavigationTitle: {
+ ...Typography.fontWeight.semibold,
+ },
+})
diff --git a/apps/skolplattformen-app-new/components/classmates.component.tsx b/apps/skolplattformen-app-new/components/classmates.component.tsx
new file mode 100644
index 000000000..f9c701eff
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/classmates.component.tsx
@@ -0,0 +1,90 @@
+import { Classmate } from '@skolplattformen/api'
+import { useClassmates } from '../libs/hooks/src'
+import {
+ Divider,
+ Icon,
+ IconProps,
+ List,
+ ListItem,
+ Text,
+} from '@ui-kitten/components'
+import React from 'react'
+import { ListRenderItemInfo, RefreshControl, StyleSheet } from 'react-native'
+import { fullName, guardians, sortByFirstName } from '../utils/peopleHelpers'
+import { translate } from '../utils/translation'
+import { useChild } from './childContext.component'
+import { ContactMenu } from './contactMenu.component'
+
+// const translate = (key: string) => key;
+
+// interface ClassmatesProps {
+// setSelected: (value?: number | null) => void;
+// }
+
+export const Classmates = () => {
+ const child = useChild()
+
+ const { data, status, reload } = useClassmates(child)
+ const renderItemIcon = (props: IconProps) => (
+
+ )
+ const [selected, setSelected] = React.useState()
+ const renderItem = ({ item, index }: ListRenderItemInfo) => (
+ setSelected(item)}
+ description={guardians(item.guardians)}
+ accessoryLeft={renderItemIcon}
+ accessoryRight={() => (
+ setSelected(undefined)}
+ />
+ )}
+ />
+ )
+ return (
+
+ {data?.length
+ ? `${translate('classmates.class')} ${data[0].className}`
+ : translate('classmates.class')}
+
+ }
+ renderItem={renderItem}
+ contentContainerStyle={styles.contentContainer}
+ refreshControl={
+
+ }
+ />
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ height: '100%',
+ width: '100%',
+ },
+ contentContainer: {
+ margin: 10,
+ justifyContent: 'flex-start',
+ },
+ topContainer: {
+ margin: 5,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ },
+ listHeader: {
+ paddingTop: 10,
+ paddingLeft: 15,
+ },
+})
diff --git a/apps/skolplattformen-app-new/components/contactMenu.component.tsx b/apps/skolplattformen-app-new/components/contactMenu.component.tsx
new file mode 100644
index 000000000..264de9e88
--- /dev/null
+++ b/apps/skolplattformen-app-new/components/contactMenu.component.tsx
@@ -0,0 +1,130 @@
+import { Classmate } from '@skolplattformen/api'
+import {
+ Button,
+ MenuGroup,
+ MenuItem,
+ OverflowMenu,
+} from '@ui-kitten/components'
+import React from 'react'
+import { Linking, StyleSheet } from 'react-native'
+import { fullName } from '../utils/peopleHelpers'
+import { translate } from '../utils/translation'
+import {
+ CallIcon,
+ EmailIcon,
+ MapIcon,
+ MoreIcon,
+ SMSIcon,
+} from './icon.component'
+
+interface ContactMenuProps {
+ contact: Classmate
+ selected: boolean
+ setSelected: (value?: number | null) => void
+}
+
+// const translate = (key: string) => key;
+
+export const ContactMenu = ({
+ contact,
+ selected,
+ setSelected,
+}: ContactMenuProps) => {
+ const [visible, setVisible] = React.useState(selected)
+
+ const renderToggleButton = () => (
+