diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..79cf399
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,47 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/.metadata b/.metadata
new file mode 100644
index 0000000..617606b
--- /dev/null
+++ b/.metadata
@@ -0,0 +1,33 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled.
+
+version:
+ revision: b4bce91dd0f168179d46a7ae5eceb3572ba9637a
+ channel: stable
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: b4bce91dd0f168179d46a7ae5eceb3572ba9637a
+ base_revision: b4bce91dd0f168179d46a7ae5eceb3572ba9637a
+ - platform: android
+ create_revision: b4bce91dd0f168179d46a7ae5eceb3572ba9637a
+ base_revision: b4bce91dd0f168179d46a7ae5eceb3572ba9637a
+ - platform: ios
+ create_revision: b4bce91dd0f168179d46a7ae5eceb3572ba9637a
+ base_revision: b4bce91dd0f168179d46a7ae5eceb3572ba9637a
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fcec17a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,35 @@
+# Müsli App
+This is an Android/iOS Application for fetching data from the MÜSLI System of University of Heidelberg.
+
+## Contribution
+If you would like to contribute, feel free to make a pull request or open an issue about it.
+
+## Prerequisites
+Android: minimum Android 4.3
+iOS: minimum iOS 9.0
+
+## Installation
+Go to releases page and download the .apk (Android) or .ipa (iOS) and install it on your device or build it from source.
+Currently I'm not able to compile an iOS version since I'm not using a MacBook which is required to compile an app for iOS.
+
+### Is the application available on Google Play/App Store?
+Currently the answer is no. For adding it to these Stores I have to do payments.
+
+- Google Play: single payment of 25$
+- Apple App Store: yearly payment of 100$
+
+Since I'm a student at Heidelberg University I could afford the one time payment for Google Play but I just can't afford 100$ per year for the App to be visible on the Apple App Store.
+But: For universities or other teaching facilities the payment of 100$ for the App Store may be waived (if anyone from the MathPhysInfo student council or from the University of Heidelberg found this repository, I would be very happy if they would want to help me get the app into the respective app stores).
+
+## Build from source
+1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install).
+2. Clone this repository:
+```git clone https://github.com/niels-beier/muesli_app.git && cd muesli_app```
+3. Run `flutter pub get`.
+4. You should be good to go.
+
+## Issues
+For suggestions and bug reports, just open an issue. When reporting a bug or an error it would be very helpful to include log files which can be generated in the settings of the app.
+
+## Disclaimer
+I am not affiliated with the University of Heidelberg or any other institution. I'm just a computer science student there.
\ No newline at end of file
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 0000000..61b6c4d
--- /dev/null
+++ b/analysis_options.yaml
@@ -0,0 +1,29 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at
+ # https://dart-lang.github.io/linter/lints/index.html.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/android/app/build.gradle b/android/app/build.gradle
new file mode 100644
index 0000000..eb93fb5
--- /dev/null
+++ b/android/app/build.gradle
@@ -0,0 +1,86 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+ localPropertiesFile.withReader('UTF-8') { reader ->
+ localProperties.load(reader)
+ }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+ throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+ flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+ flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+def keystoreProperties = new Properties()
+def keystorePropertiesFile = rootProject.file('key.properties')
+if (keystorePropertiesFile.exists()) {
+ keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
+}
+
+android {
+ compileSdkVersion flutter.compileSdkVersion
+ ndkVersion flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId "de.shuzo.muesli_app"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
+ minSdkVersion 18
+ targetSdkVersion flutter.targetSdkVersion
+ versionCode flutterVersionCode.toInteger()
+ versionName flutterVersionName
+ multiDexEnabled true
+ }
+
+ signingConfigs {
+ release {
+ keyAlias keystoreProperties['keyAlias']
+ keyPassword keystoreProperties['keyPassword']
+ storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
+ storePassword keystoreProperties['storePassword']
+ }
+ }
+ buildTypes {
+ release {
+ signingConfig signingConfigs.release
+ }
+ }
+}
+
+flutter {
+ source '../..'
+}
+
+dependencies {
+ implementation 'com.google.android.material:material:1.8.0'
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.multidex:multidex: 2.0.1'
+}
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..5629ba0
--- /dev/null
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..8a807a9
--- /dev/null
+++ b/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java b/android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java
new file mode 100644
index 0000000..752fc18
--- /dev/null
+++ b/android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java
@@ -0,0 +1,25 @@
+// Generated file.
+//
+// If you wish to remove Flutter's multidex support, delete this entire file.
+//
+// Modifications to this file should be done in a copy under a different name
+// as this file may be regenerated.
+
+package io.flutter.app;
+
+import android.app.Application;
+import android.content.Context;
+import androidx.annotation.CallSuper;
+import androidx.multidex.MultiDex;
+
+/**
+ * Extension of {@link android.app.Application}, adding multidex support.
+ */
+public class FlutterMultiDexApplication extends Application {
+ @Override
+ @CallSuper
+ protected void attachBaseContext(Context base) {
+ super.attachBaseContext(base);
+ MultiDex.install(this);
+ }
+}
diff --git a/android/app/src/main/kotlin/de/nielsbeier/muesli_app/muesli_app/MainActivity.kt b/android/app/src/main/kotlin/de/nielsbeier/muesli_app/muesli_app/MainActivity.kt
new file mode 100644
index 0000000..5d15bad
--- /dev/null
+++ b/android/app/src/main/kotlin/de/nielsbeier/muesli_app/muesli_app/MainActivity.kt
@@ -0,0 +1,6 @@
+package de.nielsbeier.muesli_app.muesli_app
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/android/app/src/main/kotlin/de/shuzo/muesli_app/MainActivity.kt b/android/app/src/main/kotlin/de/shuzo/muesli_app/MainActivity.kt
new file mode 100644
index 0000000..a3de010
--- /dev/null
+++ b/android/app/src/main/kotlin/de/shuzo/muesli_app/MainActivity.kt
@@ -0,0 +1,6 @@
+package de.shuzo.muesli_app
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
new file mode 100644
index 0000000..31a385d
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
new file mode 100644
index 0000000..3772f35
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
new file mode 100644
index 0000000..5b7a82b
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
new file mode 100644
index 0000000..236ff40
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
new file mode 100644
index 0000000..a64b86b
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ
diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..6533ba2
--- /dev/null
+++ b/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..eee9055
--- /dev/null
+++ b/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..5629ba0
--- /dev/null
+++ b/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000..83ae220
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,31 @@
+buildscript {
+ ext.kotlin_version = '1.6.10'
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:7.1.2'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 0000000..94adc3a
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..cc5527d
--- /dev/null
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 0000000..44e62bc
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/assets/cereal.png b/assets/cereal.png
new file mode 100644
index 0000000..17f3a9a
Binary files /dev/null and b/assets/cereal.png differ
diff --git a/assets/cereal_icon.png b/assets/cereal_icon.png
new file mode 100644
index 0000000..5043926
Binary files /dev/null and b/assets/cereal_icon.png differ
diff --git a/assets/logout.svg b/assets/logout.svg
new file mode 100755
index 0000000..bf717e3
--- /dev/null
+++ b/assets/logout.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/multi_person.svg b/assets/multi_person.svg
new file mode 100644
index 0000000..540de21
--- /dev/null
+++ b/assets/multi_person.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/person.svg b/assets/person.svg
new file mode 100755
index 0000000..5ec8590
--- /dev/null
+++ b/assets/person.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/place.svg b/assets/place.svg
new file mode 100755
index 0000000..92fcf87
--- /dev/null
+++ b/assets/place.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/reload.svg b/assets/reload.svg
new file mode 100644
index 0000000..f17c0a4
--- /dev/null
+++ b/assets/reload.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/settings.svg b/assets/settings.svg
new file mode 100755
index 0000000..1e9e515
--- /dev/null
+++ b/assets/settings.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/sheet.svg b/assets/sheet.svg
new file mode 100644
index 0000000..b2d2e63
--- /dev/null
+++ b/assets/sheet.svg
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/assets/time.svg b/assets/time.svg
new file mode 100755
index 0000000..d6dd8a6
--- /dev/null
+++ b/assets/time.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/flutter_launcher_icons.yaml b/flutter_launcher_icons.yaml
new file mode 100644
index 0000000..d60775e
--- /dev/null
+++ b/flutter_launcher_icons.yaml
@@ -0,0 +1,8 @@
+dev_dependencies:
+ flutter_launcher_icons: "^0.11.0"
+
+flutter_icons:
+ android: "launcher_icon"
+ ios: true
+ image_path: "assets/cereal_icon.png"
+ min_sdk_android: 18
\ No newline at end of file
diff --git a/ios/.gitignore b/ios/.gitignore
new file mode 100644
index 0000000..7a7f987
--- /dev/null
+++ b/ios/.gitignore
@@ -0,0 +1,34 @@
+**/dgph
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/ephemeral/
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000..8d4492f
--- /dev/null
+++ b/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ App
+ CFBundleIdentifier
+ io.flutter.flutter.app
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ App
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 9.0
+
+
diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/ios/Flutter/Debug.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/ios/Flutter/Release.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..3cdeb6d
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,481 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 50;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
+ 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 97C146EB1CF9000F007C117D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 9740EEB11CF90186004384FC /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */,
+ );
+ name = Flutter;
+ sourceTree = "";
+ };
+ 97C146E51CF9000F007C117D = {
+ isa = PBXGroup;
+ children = (
+ 9740EEB11CF90186004384FC /* Flutter */,
+ 97C146F01CF9000F007C117D /* Runner */,
+ 97C146EF1CF9000F007C117D /* Products */,
+ );
+ sourceTree = "";
+ };
+ 97C146EF1CF9000F007C117D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146EE1CF9000F007C117D /* Runner.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 97C146F01CF9000F007C117D /* Runner */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146FA1CF9000F007C117D /* Main.storyboard */,
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */,
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+ 97C147021CF9000F007C117D /* Info.plist */,
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+ );
+ path = Runner;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 97C146ED1CF9000F007C117D /* Runner */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+ buildPhases = (
+ 9740EEB61CF901F6004384FC /* Run Script */,
+ 97C146EA1CF9000F007C117D /* Sources */,
+ 97C146EB1CF9000F007C117D /* Frameworks */,
+ 97C146EC1CF9000F007C117D /* Resources */,
+ 9705A1C41CF9048500538489 /* Embed Frameworks */,
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Runner;
+ productName = Runner;
+ productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 97C146E61CF9000F007C117D /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 1300;
+ ORGANIZATIONNAME = "";
+ TargetAttributes = {
+ 97C146ED1CF9000F007C117D = {
+ CreatedOnToolsVersion = 7.3.1;
+ LastSwiftMigration = 1100;
+ };
+ };
+ };
+ buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 97C146E51CF9000F007C117D;
+ productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 97C146ED1CF9000F007C117D /* Runner */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 97C146EC1CF9000F007C117D /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Thin Binary";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+ };
+ 9740EEB61CF901F6004384FC /* Run Script */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Run Script";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 97C146EA1CF9000F007C117D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C146FB1CF9000F007C117D /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C147001CF9000F007C117D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 249021D3217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Profile;
+ };
+ 249021D4217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = de.shuzo.muesliApp;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Profile;
+ };
+ 97C147031CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 97C147041CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 97C147061CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = de.shuzo.muesliApp;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Debug;
+ };
+ 97C147071CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = de.shuzo.muesliApp;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147031CF9000F007C117D /* Debug */,
+ 97C147041CF9000F007C117D /* Release */,
+ 249021D3217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147061CF9000F007C117D /* Debug */,
+ 97C147071CF9000F007C117D /* Release */,
+ 249021D4217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..c87d15a
--- /dev/null
+++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
new file mode 100644
index 0000000..70693e4
--- /dev/null
+++ b/ios/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+import UIKit
+import Flutter
+
+@UIApplicationMain
+@objc class AppDelegate: FlutterAppDelegate {
+ override func application(
+ _ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+ ) -> Bool {
+ GeneratedPluginRegistrant.register(with: self)
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..d36b1fa
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+ "images" : [
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-83.5x83.5@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "Icon-App-1024x1024@1x.png",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 0000000..6ef7de5
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 0000000..57f7a5d
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 0000000..92847f8
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 0000000..0fb565c
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 0000000..9858e98
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 0000000..2b62845
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 0000000..9a4caad
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 0000000..92847f8
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 0000000..34a5b1e
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 0000000..c5aa6e5
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
new file mode 100644
index 0000000..7ec2dc4
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
new file mode 100644
index 0000000..5c6fcf3
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
new file mode 100644
index 0000000..dfb7fa0
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
new file mode 100644
index 0000000..7030b8f
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 0000000..c5aa6e5
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 0000000..1542e5f
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
new file mode 100644
index 0000000..31a385d
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
new file mode 100644
index 0000000..236ff40
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 0000000..bddb692
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 0000000..86bb5f6
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000..46e55a1
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
new file mode 100644
index 0000000..fab5a7a
--- /dev/null
+++ b/ios/Runner/Info.plist
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+ NSPhotoLibraryAddUsageDescription
+ Müsli would like to save photos from the app to your gallery
+ NSPhotoLibraryUsageDescription
+ Müsli would like to access your photo gallery for uploading images to the app
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Muesli App
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ muesli_app
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+ CADisableMinimumFrameDurationOnPhone
+
+ LSApplicationQueriesSchemes
+
+ https
+ http
+
+
+
diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..308a2a5
--- /dev/null
+++ b/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb
new file mode 100644
index 0000000..7b15798
--- /dev/null
+++ b/lib/l10n/app_de.arb
@@ -0,0 +1,30 @@
+{
+ "app_name": "Müsli App",
+ "muesli": "Müsli",
+ "login_description": "MÜSLI verwaltet viele Übungsgruppen und Vorlesungen an der Fakultät für Mathematik und Informatik der Universität Heidelberg.",
+ "no_connection_to_server": "Keine Verbindung zum Server.",
+ "no_email_or_password_entered": "E-Mail oder Passwort nicht eingegeben.",
+ "login": "Login",
+ "email_login": "E-Mail",
+ "password_login": "Passwort",
+ "execute_login": "Anmelden",
+ "tutorials_navbar": "Tutorien",
+ "lectures_navbar": "Vorlesungen",
+ "overview_header": "Übersicht",
+ "no_exams_available": "Keine Prüfungen vorhanden.",
+ "total_points_examlist": "Punkte gesamt",
+ "no_assistants_lecture": "Keine Assistenten",
+ "no_lecturer_lecture": "Kein Lehrender",
+ "available_soon": "Bald verfügbar",
+ "notifications_new_lectures": "Benachrichtigungen zu neuen Vorlesungen",
+ "save_settings": "Speichern",
+ "github_settings": "GitHub",
+ "legal_notice_settings": "Rechtliche Hinweise",
+ "version_settings": "Version",
+ "settings": "Einstellungen",
+ "o_clock_tutorial": "Uhr",
+ "privacy_settings": "Datenschutz",
+ "privacy_content": "Die Müsli App sendet keine persönlichen Daten an Server. Lediglich zur Authentifizierung benötigte Informationen (E-Mail-Adresse und Passwort) oder Kennnummern (IDs) zum Abfragen von z.B. Vorlesungen werden per HTTP-Anfrage an den Server des Müsli Systems gesendet. Persönliche Informationen werden nicht gesendet. Lokal und verschlüsselt auf dem Gerät werden folgende Informationen gespeichert: API-Token (zur Authentifizierung), E-Mail-Adresse, Passwort.\nSollte es zu einem Fehler beim Abrufen einer Vorlesung, Tutorium, etc. kommen, wird außerdem die ID des betroffenen Elements in einer Log-Datei gespeichert.",
+ "export_logs_settings": "Logs exportieren",
+ "exported_logs_success": "Logs wurden erfolgreich exportiert nach"
+}
\ No newline at end of file
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
new file mode 100644
index 0000000..7641b80
--- /dev/null
+++ b/lib/l10n/app_en.arb
@@ -0,0 +1,30 @@
+{
+ "app_name": "Müsli App",
+ "muesli": "Müsli",
+ "login_description": "MÜSLI manages many practice groups and lectures at the Faculty of Mathematics and Computer Science at Heidelberg University.",
+ "no_connection_to_server": "No connection to Server.",
+ "no_email_or_password_entered": "No email or password entered.",
+ "login": "Login",
+ "email_login": "Email",
+ "password_login": "Password",
+ "execute_login": "Login",
+ "tutorials_navbar": "Tutorials",
+ "lectures_navbar": "Lectures",
+ "overview_header": "Overview",
+ "no_exams_available": "No exams available.",
+ "total_points_examlist": "total points",
+ "no_assistants_lecture": "No assistants",
+ "no_lecturer_lecture": "No lecturer",
+ "available_soon": "Available soon",
+ "notifications_new_lectures": "Notifications for new lectures",
+ "save_settings": "Save",
+ "github_settings": "GitHub",
+ "legal_notice_settings": "Legal Notice",
+ "version_settings": "Version",
+ "settings": "Settings",
+ "o_clock_tutorial": "o'clock",
+ "privacy_settings": "Privacy",
+ "privacy_content": "The Müsli app does not send any personal data to servers. Only information required for authentication (email address and password) or identification numbers (IDs) to query e.g. lectures are sent to the server of the Müsli system via HTTP request. Personal information is not sent. The following information is stored locally and encrypted on the device: API token (for authentication), email address, password.\nIf an error occurs while retrieving a lecture, tutorial, etc., the ID of the affected item is also stored in a log file.",
+ "export_logs_settings": "Export logs",
+ "exported_logs_success": "Successfully exported logs to"
+}
\ No newline at end of file
diff --git a/lib/main.dart b/lib/main.dart
new file mode 100755
index 0000000..c3e7e29
--- /dev/null
+++ b/lib/main.dart
@@ -0,0 +1,120 @@
+import 'dart:io';
+
+import 'package:device_info_plus/device_info_plus.dart';
+import 'package:dynamic_color/dynamic_color.dart';
+import 'package:f_logs/f_logs.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:flutter/services.dart';
+import 'package:muesli_app/ui/pages/login.dart';
+import 'package:muesli_app/ui/pages/overview.dart';
+import 'package:muesli_app/services/globals.dart' as globals;
+import 'package:muesli_app/storage/storage.dart';
+import 'package:overlay_support/overlay_support.dart';
+import 'package:package_info_plus/package_info_plus.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:sdk_int/sdk_int.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+final secureStorage = SecureStorage();
+const className = "Main";
+late bool tokenExpired;
+late bool isDarkMode;
+
+void logMetaData(bool isAndroid, AndroidDeviceInfo androidInfo, IosDeviceInfo iosInfo) async {
+ FLog.info(className: "MetaInfo", text: "Platform: ${Platform.isAndroid ? "Android" : "iOS"}");
+ FLog.info(className: "MetaInfo", text: "App Version: ${globals.packageInfo.version}");
+ FLog.info(className: "MetaInfo", text: "System Version: ${isAndroid ? await SDKInt.currentSDKVersion : iosInfo.systemVersion}");
+ FLog.info(className: "MetaInfo", text: "Manufacturer: ${isAndroid ? androidInfo.manufacturer : "Apple"}");
+ FLog.info(className: "MetaInfo", text: "Model: ${isAndroid ? androidInfo.model : iosInfo.model}");
+ FLog.info(className: "MetaInfo", text: "Physical device: ${isAndroid ? androidInfo.isPhysicalDevice : iosInfo.isPhysicalDevice}");
+ FLog.info(className: "MetaInfo", text: "System Features: ${isAndroid ? androidInfo.systemFeatures : "Not available for iOS devices."}");
+}
+
+void main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ final bool isAndroid = Platform.isAndroid;
+
+ globals.prefs = await SharedPreferences.getInstance();
+
+ globals.packageInfo = await PackageInfo.fromPlatform();
+
+ final deviceInfoPlugin = DeviceInfoPlugin();
+ final androidInfo = await deviceInfoPlugin.androidInfo;
+ final iosInfo = await deviceInfoPlugin.iosInfo;
+
+ LogsConfig config = LogsConfig()..formatType = FormatType.FORMAT_SQUARE;
+ FLog.applyConfigurations(config);
+
+ await FLog.clearLogs();
+ FLog.info(className: className, text: "Cleared logs.");
+
+ logMetaData(isAndroid, androidInfo, iosInfo);
+
+ isDarkMode =
+ SchedulerBinding.instance.window.platformBrightness == Brightness.dark &&
+ await SDKInt.currentSDKVersion >= 29;
+
+ FLog.info(
+ className: className,
+ methodName: "main",
+ text: "isDarkMode: $isDarkMode");
+
+ tokenExpired = globals.prefs.getInt("expireDate") != null
+ ? DateTime.fromMillisecondsSinceEpoch(globals.prefs.getInt("expireDate")!)
+ .isBefore(DateTime.now())
+ : true;
+
+ FLog.info(
+ className: className,
+ methodName: "main",
+ text: "tokenExpired: $tokenExpired");
+
+ FLog.info(
+ className: className,
+ text:
+ "Dynamic Color enabled: ${await DynamicColorPlugin.getCorePalette() != null}");
+
+ runApp(const MuesliApp());
+}
+
+class MuesliApp extends StatelessWidget {
+ const MuesliApp({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ globals.context = context;
+
+ // tell app to use fullscreen mode with rendering system ui like status bar
+ SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
+
+ // set color of system navigation bar to transparent
+ SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
+ statusBarColor: Colors.transparent,
+ statusBarIconBrightness: isDarkMode ? Brightness.light : Brightness.dark,
+ systemNavigationBarColor: Colors.transparent,
+ systemNavigationBarIconBrightness: Brightness.dark,
+ ));
+
+ // force portrait mode
+ SystemChrome.setPreferredOrientations(
+ [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
+
+ return OverlaySupport(
+ child: DynamicColorBuilder(
+ builder: (lightDynamic, darkDynamic) => MaterialApp(
+ title: "Müsli",
+ localizationsDelegates: AppLocalizations.localizationsDelegates,
+ supportedLocales: AppLocalizations.supportedLocales,
+ theme: ThemeData(
+ colorScheme: lightDynamic?.harmonized(), useMaterial3: true),
+ darkTheme: ThemeData(
+ colorScheme: darkDynamic?.harmonized(), useMaterial3: true),
+ // ignore: unrelated_type_equality_checks
+ home: tokenExpired ? const LoginPage() : const OverviewPage(),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/model/exam.dart b/lib/model/exam.dart
new file mode 100755
index 0000000..7fb2fe0
--- /dev/null
+++ b/lib/model/exam.dart
@@ -0,0 +1,14 @@
+import 'package:muesli_app/model/exerciselist.dart';
+
+class Exam {
+ int id;
+ String name;
+ String url;
+ ExerciseList exercises;
+
+ Exam({required this.id, required this.name, required this.url, required this.exercises});
+
+ @override String toString() {
+ return 'Exam{id: $id, name: $name, url: $url, exercises: ${exercises.toString()}}';
+ }
+}
diff --git a/lib/model/examlist.dart b/lib/model/examlist.dart
new file mode 100755
index 0000000..c0873c6
--- /dev/null
+++ b/lib/model/examlist.dart
@@ -0,0 +1,27 @@
+import 'package:muesli_app/model/exam.dart';
+
+class ExamList {
+ List exams;
+
+ ExamList({required this.exams});
+
+ Future fromJson(List json) async {
+ for (var exam in json) {
+ exams.add(await exam.fromJson(exam));
+ }
+ return ExamList(exams: exams);
+ }
+
+ @override
+ String toString() {
+ String result = 'ExamList:';
+ for (Exam exam in exams) {
+ result += '\n\t${exam.toString()}';
+ }
+ return result;
+ }
+
+ void add(Exam exam) {
+ exams.add(exam);
+ }
+}
diff --git a/lib/model/exercise.dart b/lib/model/exercise.dart
new file mode 100755
index 0000000..899fa0e
--- /dev/null
+++ b/lib/model/exercise.dart
@@ -0,0 +1,29 @@
+import 'package:muesli_app/model/exerciseresult.dart';
+
+class Exercise {
+ int id;
+ double maxPoints;
+ int number;
+ ExerciseResult exerciseResult;
+
+ Exercise({
+ required this.id,
+ required this.maxPoints,
+ required this.number,
+ required this.exerciseResult,
+ });
+
+ static Exercise fromJson(Map json, ExerciseResult exerciseResult) {
+ return Exercise(
+ id: json["id"],
+ maxPoints: json["maxpoints"],
+ number: json["nr"],
+ exerciseResult: exerciseResult,
+ );
+ }
+
+ @override
+ String toString() {
+ return 'Exercise{id: $id, maxPoints: $maxPoints, number: $number, exerciseResultList: ${exerciseResult.toString()}}';
+ }
+}
diff --git a/lib/model/exerciselist.dart b/lib/model/exerciselist.dart
new file mode 100755
index 0000000..0169415
--- /dev/null
+++ b/lib/model/exerciselist.dart
@@ -0,0 +1,66 @@
+import 'package:f_logs/model/flog/flog.dart';
+import 'package:flutter/material.dart';
+import 'package:muesli_app/services/exceptions.dart';
+import 'package:muesli_app/services/request.dart';
+import 'package:muesli_app/model/exercise.dart';
+import 'package:muesli_app/model/exerciseresultlist.dart';
+import 'package:overlay_support/overlay_support.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:muesli_app/services/globals.dart' as globals;
+
+const String className = "ExerciseList";
+
+class ExerciseList {
+ List exercises;
+
+ ExerciseList({required this.exercises});
+
+ static Future fromJson(List json) async {
+ try {
+ if (json.isEmpty) {
+ return ExerciseList(exercises: []);
+ }
+ List exercises = [];
+ ExerciseResultList exerciseResultList =
+ await HttpRequest.getExerciseResultList(json[0]['id']);
+ if (exerciseResultList.exerciseResults.isNotEmpty) {
+ for (int i = 0; i < exerciseResultList.exerciseResults.length; i++) {
+ exercises.add(Exercise.fromJson(
+ json[i], exerciseResultList.exerciseResults[i]));
+ }
+ }
+ return ExerciseList(exercises: exercises);
+ } on NoServerConnectionException {
+ FLog.error(
+ className: className,
+ methodName: "fromJson",
+ text: "No connection to server.",
+ stacktrace: StackTrace.current);
+ showSimpleNotification(
+ Text(
+ AppLocalizations.of(globals.context).no_connection_to_server,
+ style: TextStyle(
+ fontFamily: "Inter",
+ color: Theme.of(globals.context).colorScheme.onErrorContainer),
+ ),
+ background: Theme.of(globals.context).colorScheme.errorContainer,
+ slideDismissDirection: DismissDirection.up,
+ duration: const Duration(seconds: 3),
+ );
+ return ExerciseList(exercises: []);
+ }
+ }
+
+ @override
+ String toString() {
+ String result = "ExerciseList:";
+ for (Exercise exercise in exercises) {
+ result += "\n\t${exercise.toString()}";
+ }
+ return result;
+ }
+
+ void add(Exercise exercise) {
+ exercises.add(exercise);
+ }
+}
diff --git a/lib/model/exerciseresult.dart b/lib/model/exerciseresult.dart
new file mode 100755
index 0000000..65bd32c
--- /dev/null
+++ b/lib/model/exerciseresult.dart
@@ -0,0 +1,16 @@
+class ExerciseResult {
+ double points;
+
+ ExerciseResult({required this.points});
+
+ factory ExerciseResult.fromJson(Map json) {
+ return ExerciseResult(
+ points: json['points'] ?? 0,
+ );
+ }
+
+ @override
+ String toString() {
+ return 'ExerciseResult{points: $points}';
+ }
+}
diff --git a/lib/model/exerciseresultlist.dart b/lib/model/exerciseresultlist.dart
new file mode 100755
index 0000000..6157028
--- /dev/null
+++ b/lib/model/exerciseresultlist.dart
@@ -0,0 +1,24 @@
+import 'package:muesli_app/model/exerciseresult.dart';
+
+class ExerciseResultList {
+ List exerciseResults;
+
+ ExerciseResultList({required this.exerciseResults});
+
+ factory ExerciseResultList.fromJson(List json) {
+ return ExerciseResultList(
+ exerciseResults: json.map((e) => ExerciseResult.fromJson(e)).toList(),
+ );
+ }
+
+ @override
+ String toString() {
+ String result = "ExerciseResultList:";
+ for (ExerciseResult exerciseResult in exerciseResults) {
+ result += "\n\t${exerciseResult.toString()}";
+ }
+ return result;
+ }
+
+ operator [](int index) => exerciseResults[index];
+}
diff --git a/lib/model/lecture.dart b/lib/model/lecture.dart
new file mode 100644
index 0000000..7915912
--- /dev/null
+++ b/lib/model/lecture.dart
@@ -0,0 +1,18 @@
+import 'package:muesli_app/model/user.dart';
+
+class Lecture {
+ int id;
+ String name;
+ String term;
+ String lecturer;
+ List assistants;
+ String url;
+
+ Lecture(
+ {required this.id,
+ required this.name,
+ required this.term,
+ required this.lecturer,
+ required this.assistants,
+ required this.url});
+}
diff --git a/lib/model/tutorial.dart b/lib/model/tutorial.dart
new file mode 100755
index 0000000..c60c41c
--- /dev/null
+++ b/lib/model/tutorial.dart
@@ -0,0 +1,27 @@
+import 'package:muesli_app/model/user.dart';
+import 'package:muesli_app/model/exam.dart';
+
+class Tutorial {
+ int id;
+ int lectureId;
+ String name;
+ User tutor;
+ String place;
+ String time;
+ List exams;
+
+ Tutorial({
+ required this.id,
+ required this.lectureId,
+ required this.name,
+ required this.tutor,
+ required this.place,
+ required this.time,
+ required this.exams,
+ });
+
+ @override
+ String toString() {
+ return 'Tutorial{id: $id, lectureId: $lectureId, name: $name, tutor: $tutor, place: $place, time: $time, exams: ${exams.toString()}}';
+ }
+}
diff --git a/lib/model/tutoriallist.dart b/lib/model/tutoriallist.dart
new file mode 100755
index 0000000..4cd515b
--- /dev/null
+++ b/lib/model/tutoriallist.dart
@@ -0,0 +1,13 @@
+import 'package:muesli_app/model/tutorial.dart';
+
+class TutorialList {
+ List tutorials;
+
+ TutorialList({
+ required this.tutorials,
+ });
+
+ factory TutorialList.fromJson(Map json) {
+ return TutorialList(tutorials: json['tutorials']);
+ }
+}
diff --git a/lib/model/user.dart b/lib/model/user.dart
new file mode 100755
index 0000000..7250c88
--- /dev/null
+++ b/lib/model/user.dart
@@ -0,0 +1,26 @@
+class User {
+ int id;
+ String name;
+ String lastName;
+ String email;
+
+ User({
+ required this.id,
+ required this.name,
+ required this.lastName,
+ required this.email,
+ });
+
+ factory User.fromJson(Map json) {
+ return User(
+ id: json['id'],
+ name: json['first_name'],
+ lastName: json['last_name'],
+ email: json['email'],
+ );
+ }
+
+ @override String toString() {
+ return 'User{id: $id, name: $name, lastName: $lastName, email: $email}';
+ }
+}
diff --git a/lib/services/exceptions.dart b/lib/services/exceptions.dart
new file mode 100644
index 0000000..4e258ab
--- /dev/null
+++ b/lib/services/exceptions.dart
@@ -0,0 +1,4 @@
+class NoServerConnectionException implements Exception {
+ String cause;
+ NoServerConnectionException(this.cause);
+}
\ No newline at end of file
diff --git a/lib/services/globals.dart b/lib/services/globals.dart
new file mode 100644
index 0000000..5efc734
--- /dev/null
+++ b/lib/services/globals.dart
@@ -0,0 +1,7 @@
+import 'package:flutter/material.dart';
+import 'package:package_info_plus/package_info_plus.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+late PackageInfo packageInfo;
+late BuildContext context;
+late SharedPreferences prefs;
\ No newline at end of file
diff --git a/lib/services/request.dart b/lib/services/request.dart
new file mode 100755
index 0000000..f421d30
--- /dev/null
+++ b/lib/services/request.dart
@@ -0,0 +1,404 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'package:f_logs/model/flog/flog.dart';
+import 'package:flutter/material.dart';
+import 'package:http/http.dart' as http;
+import 'package:muesli_app/services/exceptions.dart';
+import 'package:muesli_app/storage/storage.dart';
+import 'package:muesli_app/model/lecture.dart';
+import 'package:muesli_app/model/tutorial.dart';
+import 'package:muesli_app/model/exam.dart';
+import 'package:muesli_app/model/exerciselist.dart';
+import 'package:muesli_app/model/exerciseresultlist.dart';
+import 'package:muesli_app/model/user.dart';
+import 'package:overlay_support/overlay_support.dart';
+import 'package:url_launcher/url_launcher.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:muesli_app/services/globals.dart' as globals;
+
+const String apiUrl = "https://muesli.mathi.uni-heidelberg.de/api/v1";
+const String className = "HttpRequest";
+
+class HttpRequest {
+ const HttpRequest();
+
+ static Future authenticate(String username, String password) async {
+ try {
+ final response = await http.post(Uri.parse("$apiUrl/login"), body: {
+ "email": username,
+ "password": password,
+ });
+
+ switch (response.statusCode) {
+ case 200:
+ Map json = jsonDecode(response.body);
+ FLog.info(text: "Successfully authenticated.");
+ return json["token"];
+ case 404:
+ throw NoServerConnectionException('Failed to connect to server');
+ default:
+ throw Exception(
+ 'Failed to authenticate\nError code: ${response.statusCode}');
+ }
+ } on SocketException {
+ FLog.error(
+ className: className,
+ methodName: "authenticate",
+ text: "Failed to connect to server.",
+ stacktrace: StackTrace.current);
+ throw NoServerConnectionException('Failed to connect to server.');
+ }
+ }
+
+ static Future getUserData() async {
+ try {
+ SecureStorage secureStorage = SecureStorage();
+ final response = await http.get(Uri.parse("$apiUrl/whoami"), headers: {
+ "Authorization":
+ "Bearer ${await secureStorage.readSecureData('token')}",
+ });
+
+ if (response.statusCode == 200) {
+ FLog.info(text: "Successfully requested user data.");
+ return User.fromJson(jsonDecode(response.body));
+ } else {
+ throw Exception(
+ 'Failed to get user\nError code: ${response.statusCode}');
+ }
+ } on SocketException {
+ FLog.error(
+ className: className,
+ methodName: "getUserData",
+ text: "Failed to connect to server.",
+ stacktrace: StackTrace.current);
+ throw NoServerConnectionException('Failed to connect to server.');
+ } catch (e) {
+ FLog.error(
+ className: className,
+ methodName: "getUserData",
+ text: "Exception occured:",
+ exception: e,
+ stacktrace: StackTrace.current);
+ return User(id: 0, name: "", lastName: "", email: "");
+ }
+ }
+
+ static Future getLectureName(int id) async {
+ try {
+ SecureStorage secureStorage = SecureStorage();
+ final response =
+ await http.get(Uri.parse("$apiUrl/lectures/$id"), headers: {
+ "Authorization":
+ "Bearer ${await secureStorage.readSecureData('token')}",
+ });
+
+ if (response.statusCode == 200) {
+ FLog.info(text: "Successfully requested lecture name.");
+ Map json = jsonDecode(response.body);
+ return json["lecture"]["name"];
+ } else {
+ throw Exception(
+ 'Failed to get lecture name\nError code: ${response.statusCode}');
+ }
+ } on SocketException {
+ FLog.error(
+ className: className,
+ methodName: "getLectureName",
+ text: "Failed to connect to server.",
+ stacktrace: StackTrace.current);
+ throw NoServerConnectionException('Failed to connect to server.');
+ } catch (e) {
+ FLog.error(
+ className: className,
+ methodName: "getLectureName",
+ text: "Exception occured with lecture ID $id.",
+ exception: e,
+ stacktrace: StackTrace.current);
+ return "";
+ }
+ }
+
+ static Future> getTutorialList() async {
+ try {
+ SecureStorage secureStorage = SecureStorage();
+ final response = await http.get(Uri.parse("$apiUrl/tutorials"), headers: {
+ "Authorization":
+ "Bearer ${await secureStorage.readSecureData('token')}",
+ });
+
+ if (response.statusCode == 200) {
+ FLog.info(text: "Successfully requested tutorials.");
+ List json = jsonDecode(response.body);
+ return await Future.wait(
+ json.map((tutorial) => HttpRequest.getTutorial(tutorial['id'])));
+ } else {
+ throw Exception(
+ 'Failed to get tutorials\nError code: ${response.statusCode}');
+ }
+ } on SocketException {
+ FLog.error(
+ className: className,
+ methodName: "getLectureList",
+ text: "Failed to connect to server.",
+ stacktrace: StackTrace.current);
+ showSimpleNotification(
+ Text(
+ AppLocalizations.of(globals.context).no_connection_to_server,
+ style: const TextStyle(fontFamily: "Inter"),
+ ),
+ background: const Color(0xFF465770),
+ slideDismissDirection: DismissDirection.up,
+ duration: const Duration(seconds: 3),
+ );
+ } catch (e) {
+ FLog.error(
+ className: className,
+ methodName: "getExam",
+ text: "Exception occured.",
+ exception: e,
+ stacktrace: StackTrace.current);
+ }
+ return [];
+ }
+
+ static Future> getLectureList() async {
+ try {
+ SecureStorage secureStorage = SecureStorage();
+ final response = await http.get(Uri.parse("$apiUrl/lectures"), headers: {
+ "Authorization":
+ "Bearer ${await secureStorage.readSecureData('token')}",
+ });
+
+ if (response.statusCode == 200) {
+ FLog.info(text: "Successfully requested lectures.");
+ List json = jsonDecode(response.body);
+ List lectures = await Future.wait(
+ json.map((lecture) => HttpRequest.getLecture(lecture['id'])));
+ lectures.sort((a, b) => a.name.compareTo(b.name));
+ return lectures;
+ } else {
+ throw Exception(
+ 'Failed to get lectures\nError code: ${response.statusCode}');
+ }
+ } on SocketException {
+ FLog.error(
+ className: className,
+ methodName: "getLectureList",
+ text: "Failed to connect to server.",
+ stacktrace: StackTrace.current);
+ showSimpleNotification(
+ Text(
+ AppLocalizations.of(globals.context).no_connection_to_server,
+ style: TextStyle(
+ fontFamily: "Inter",
+ color: Theme.of(globals.context).colorScheme.onErrorContainer),
+ ),
+ background: Theme.of(globals.context).colorScheme.errorContainer,
+ slideDismissDirection: DismissDirection.up,
+ duration: const Duration(seconds: 3),
+ );
+ } catch (e) {
+ FLog.error(
+ className: className,
+ methodName: "getExam",
+ text: "Exception occured.",
+ exception: e,
+ stacktrace: StackTrace.current);
+ }
+ return [];
+ }
+
+ static Future getLecture(int id) async {
+ try {
+ SecureStorage secureStorage = SecureStorage();
+ final response =
+ await http.get(Uri.parse("$apiUrl/lectures/$id"), headers: {
+ "Authorization":
+ "Bearer ${await secureStorage.readSecureData('token')}",
+ });
+
+ if (response.statusCode == 200) {
+ FLog.info(text: "Successfully requested lecture.");
+ Map json = jsonDecode(response.body);
+
+ List assistants = [];
+ for (var assistant in json['lecture']['assistants']) {
+ assistants.add(User(
+ id: assistant['id'],
+ name: "${assistant['title']} ${assistant['first_name']}",
+ lastName: assistant['last_name'],
+ email: assistant['email'],
+ ));
+ }
+
+ return Lecture(
+ id: json['lecture']['id'],
+ name: json['lecture']['name'],
+ term: json['lecture']['term'],
+ lecturer: json['lecture']['lecturer'],
+ assistants: assistants,
+ url: json['lecture']['url'] == ""
+ ? "Keine Webseite angegeben"
+ : json['lecture']['url']);
+ } else {
+ throw Exception(
+ 'Failed to get lecture\nError code: ${response.statusCode}');
+ }
+ } on SocketException {
+ FLog.error(
+ className: className,
+ methodName: "getLecture",
+ text: "Failed to connect to server.",
+ stacktrace: StackTrace.current);
+ throw NoServerConnectionException('Failed to connect to server.');
+ } catch (e) {
+ FLog.error(
+ className: className,
+ methodName: "getLecture",
+ text: "Exception occured with lecture ID $id.",
+ exception: e,
+ stacktrace: StackTrace.current);
+ return Lecture(id: 0, name: "", term: "", lecturer: "", assistants: [], url: "");
+ }
+ }
+
+ static Future getTutorial(int id) async {
+ try {
+ SecureStorage secureStorage = SecureStorage();
+ final response =
+ await http.get(Uri.parse("$apiUrl/tutorials/$id"), headers: {
+ "Authorization":
+ "Bearer ${await secureStorage.readSecureData('token')}",
+ });
+
+ if (response.statusCode == 200) {
+ FLog.info(text: "Successfully requested tutorial.");
+ Map json = jsonDecode(response.body);
+ var export = json['exams'] as List;
+ var exams = await Future.wait(
+ export.map((exam) => HttpRequest.getExam(exam['id'])));
+
+ exams.sort((a, b) => a.id.compareTo(b.id));
+
+ return Tutorial(
+ id: json['id'],
+ lectureId: json['lecture_id'],
+ name: await HttpRequest.getLectureName(json['lecture_id']),
+ tutor: json['tutor'] != null
+ ? User.fromJson(json['tutor'])
+ : User(id: 0, name: "User", lastName: "not provided", email: ""),
+ place: json['place'],
+ time: json['time'],
+ exams: exams,
+ );
+ } else {
+ throw Exception(
+ 'Failed to get lecture\nError code: ${response.statusCode}');
+ }
+ } on SocketException {
+ FLog.error(
+ className: className,
+ methodName: "getTutorial",
+ text: "Failed to connect to server.",
+ stacktrace: StackTrace.current);
+ throw NoServerConnectionException('Failed to connect to server.');
+ } catch (e) {
+ FLog.error(
+ className: className,
+ methodName: "getTutorial",
+ text: "Exception occured with tutorial ID $id.",
+ exception: e,
+ stacktrace: StackTrace.current);
+ return Tutorial(id: 0, lectureId: 0, name: "", tutor: User(id: 0, name: "", lastName: "", email: ""), place: "", time: "", exams: []);
+ }
+ }
+
+ static Future getExam(int id) async {
+ try {
+ SecureStorage secureStorage = SecureStorage();
+ final response = await http.get(Uri.parse("$apiUrl/exams/$id"), headers: {
+ "Authorization":
+ "Bearer ${await secureStorage.readSecureData('token')}",
+ });
+
+ if (response.statusCode == 200) {
+ FLog.info(text: "Successfully requested exam.");
+ Map json = jsonDecode(response.body);
+ return Exam(
+ id: json['id'],
+ name: json['name'],
+ url: json['url'],
+ exercises: await ExerciseList.fromJson(json['exercises']),
+ );
+ } else {
+ throw Exception(
+ 'Failed to get exam\nError code: ${response.statusCode}');
+ }
+ } on SocketException {
+ FLog.error(
+ className: className,
+ methodName: "getExam",
+ text: "Failed to connect to server.",
+ stacktrace: StackTrace.current);
+ throw NoServerConnectionException('Failed to connect to server.');
+ } catch (e) {
+ FLog.error(
+ className: className,
+ methodName: "getExam",
+ text: "Exception occured with exam ID $id.",
+ exception: e,
+ stacktrace: StackTrace.current);
+ return Exam(id: 0, name: "", url: "", exercises: ExerciseList(exercises: []));
+ }
+ }
+
+ static Future getExerciseResultList(int id) async {
+ try {
+ SecureStorage secureStorage = SecureStorage();
+ final response = await http.get(
+ Uri.parse(
+ "$apiUrl/exercises/$id/${int.parse(await secureStorage.readSecureData('userId'))}"),
+ headers: {
+ "Authorization":
+ "Bearer ${await secureStorage.readSecureData('token')}",
+ });
+
+ if (response.statusCode == 200) {
+ FLog.info(text: "Successfully requested exercise results.");
+ return ExerciseResultList.fromJson(jsonDecode(response.body));
+ } else {
+ throw Exception(
+ 'Failed to get exercise\nError code: ${response.statusCode}');
+ }
+ } on SocketException {
+ FLog.error(
+ className: className,
+ methodName: "getExerciseResultList",
+ text: "Failed to connect to server.",
+ stacktrace: StackTrace.current);
+ throw NoServerConnectionException('Failed to connect to server.');
+ } catch (e) {
+ FLog.error(
+ className: className,
+ methodName: "getExerciseResultList",
+ text: "Exception occured with exercise results ID $id.",
+ exception: e,
+ stacktrace: StackTrace.current);
+ return ExerciseResultList(exerciseResults: []);
+ }
+ }
+
+ static launchURLBrowser(String url) async {
+ if (await canLaunchUrl(Uri.parse(url))) {
+ await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication);
+ FLog.info(text: "Successfully launched url.");
+ } else {
+ FLog.warning(
+ className: className,
+ methodName: "launchURLBrowser",
+ text: "Could not launch url $url.",
+ stacktrace: StackTrace.current);
+ }
+ }
+}
diff --git a/lib/storage/storage.dart b/lib/storage/storage.dart
new file mode 100755
index 0000000..7a92d92
--- /dev/null
+++ b/lib/storage/storage.dart
@@ -0,0 +1,17 @@
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+
+class SecureStorage {
+ final _storage = const FlutterSecureStorage();
+
+ Future writeSecureData(String key, String value) async {
+ await _storage.write(key: key, value: value);
+ }
+
+ Future readSecureData(String key) async {
+ return await _storage.read(key: key) ?? "";
+ }
+
+ Future deleteSecureData(String key) async {
+ await _storage.delete(key: key);
+ }
+}
diff --git a/lib/ui/pages/lecture.dart b/lib/ui/pages/lecture.dart
new file mode 100644
index 0000000..b031940
--- /dev/null
+++ b/lib/ui/pages/lecture.dart
@@ -0,0 +1,99 @@
+import 'package:f_logs/model/flog/flog.dart';
+import 'package:fading_edge_scrollview/fading_edge_scrollview.dart';
+import 'package:flutter/material.dart';
+import 'package:loading_animation_widget/loading_animation_widget.dart';
+import 'package:muesli_app/services/exceptions.dart';
+import 'package:muesli_app/services/request.dart';
+import 'package:muesli_app/model/lecture.dart' as models;
+import 'package:muesli_app/ui/widgets/lecture.dart';
+import 'package:overlay_support/overlay_support.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:muesli_app/services/globals.dart' as globals;
+
+const String className = "LecturePage";
+
+class LecturePage extends StatefulWidget {
+ const LecturePage({Key? key}) : super(key: key);
+
+ @override
+ State createState() => _LecturePageState();
+}
+
+class _LecturePageState extends State
+ with AutomaticKeepAliveClientMixin {
+ @override
+ bool get wantKeepAlive => true;
+
+ List lectureData = [];
+
+ final _controller = ScrollController();
+
+ @override
+ void initState() {
+ super.initState();
+
+ // use async call to get a build context during initState
+ try {
+ _lectures = FutureBuilder(
+ future: HttpRequest.getLectureList(),
+ builder: (context, AsyncSnapshot> snapshot) {
+ if (snapshot.hasData) {
+ lectureData = snapshot.data!;
+ return Padding(
+ padding: const EdgeInsets.only(left: 14, right: 14, bottom: 14),
+ child: FadingEdgeScrollView.fromSingleChildScrollView(
+ child: SingleChildScrollView(
+ controller: _controller,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Flexible(
+ child: ListView.builder(
+ itemBuilder: (context, index) => Padding(
+ padding: const EdgeInsets.only(bottom: 10.0),
+ child: Lecture(lecture: lectureData[index])),
+ itemCount: lectureData.length,
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ } else {
+ return LoadingAnimationWidget.fourRotatingDots(
+ color: Theme.of(context).colorScheme.onBackground, size: 45);
+ }
+ },
+ );
+ } on NoServerConnectionException {
+ _lectures = Container();
+ FLog.error(
+ className: className,
+ methodName: "fromJson",
+ text: "No connection to server.",
+ stacktrace: StackTrace.current);
+ showSimpleNotification(
+ Text(
+ AppLocalizations.of(globals.context).no_connection_to_server,
+ style: TextStyle(
+ fontFamily: "Inter",
+ color: Theme.of(globals.context).colorScheme.onErrorContainer),
+ ),
+ background: Theme.of(globals.context).colorScheme.errorContainer,
+ slideDismissDirection: DismissDirection.up,
+ duration: const Duration(seconds: 3),
+ );
+ }
+ }
+
+ late Widget _lectures;
+
+ @override
+ Widget build(BuildContext context) {
+ super.build(context);
+ return _lectures;
+ }
+}
diff --git a/lib/ui/pages/login.dart b/lib/ui/pages/login.dart
new file mode 100755
index 0000000..c9bc3bf
--- /dev/null
+++ b/lib/ui/pages/login.dart
@@ -0,0 +1,272 @@
+// ignore_for_file: use_build_context_synchronously
+
+import 'package:f_logs/model/flog/flog.dart';
+import 'package:flutter/material.dart';
+import 'package:loading_animation_widget/loading_animation_widget.dart';
+import 'package:muesli_app/services/exceptions.dart';
+import 'package:muesli_app/services/request.dart';
+import 'package:muesli_app/storage/storage.dart';
+import 'package:muesli_app/ui/pages/overview.dart';
+import 'package:muesli_app/ui/widgets/muesliappbar.dart';
+import 'package:overlay_support/overlay_support.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+const String className = "LoginPage";
+
+class LoginPage extends StatefulWidget {
+ const LoginPage({Key? key}) : super(key: key);
+
+ @override
+ State createState() => _LoginPageState();
+}
+
+class _LoginPageState extends State {
+ final TextEditingController _usernameController = TextEditingController();
+ final TextEditingController _passwordController = TextEditingController();
+
+ // validate username is an email address
+ bool _isUsernameValid(String username) {
+ return RegExp(
+ r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$")
+ .hasMatch(username);
+ }
+
+ bool _loading = false;
+
+ void _login(BuildContext context) async {
+ if (_isUsernameValid(_usernameController.text) &&
+ !(_passwordController.text == "")) {
+ FocusManager.instance.primaryFocus?.unfocus();
+ setState(() {
+ _loading = true;
+ });
+
+ final secureStorage = SecureStorage();
+ final prefs = await SharedPreferences.getInstance();
+ secureStorage.writeSecureData("username", _usernameController.text);
+ secureStorage.writeSecureData("password", _passwordController.text);
+ String token;
+
+ try {
+ token = await HttpRequest.authenticate(
+ await secureStorage.readSecureData("username"),
+ await secureStorage.readSecureData("password"),
+ );
+ secureStorage.writeSecureData("token", token);
+
+ final now = DateTime.now();
+
+ prefs.setInt(
+ "expireDate",
+ DateTime(now.year, now.month, now.day + 29)
+ .toUtc()
+ .millisecondsSinceEpoch);
+
+ setState(() {
+ _loading = false;
+ });
+
+ secureStorage.writeSecureData(
+ "userId", (await HttpRequest.getUserData()).id.toString());
+
+ Navigator.pushReplacement(context,
+ MaterialPageRoute(builder: ((context) => const OverviewPage())));
+ } on NoServerConnectionException {
+ setState(() {
+ _loading = false;
+ });
+
+ showSimpleNotification(
+ Text(
+ AppLocalizations.of(context).no_connection_to_server,
+ style: TextStyle(
+ fontFamily: "Inter",
+ color: Theme.of(context).colorScheme.onErrorContainer),
+ ),
+ background: Theme.of(context).colorScheme.errorContainer,
+ slideDismissDirection: DismissDirection.up,
+ duration: const Duration(seconds: 3),
+ );
+ } catch (e) {
+ setState(() {
+ _loading = false;
+ });
+
+ FLog.error(
+ className: className,
+ methodName: "_login",
+ text: "Exception occured:",
+ exception: e,
+ stacktrace: StackTrace.current);
+ }
+ } else {
+ FLog.info(text: "No email or password entered.");
+ showSimpleNotification(
+ Text(
+ AppLocalizations.of(context).no_email_or_password_entered,
+ style: TextStyle(
+ fontFamily: "Inter",
+ color: Theme.of(context).colorScheme.onErrorContainer),
+ ),
+ background: Theme.of(context).colorScheme.errorContainer,
+ slideDismissDirection: DismissDirection.up,
+ duration: const Duration(seconds: 3),
+ );
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Stack(
+ children: [
+ Scaffold(
+ resizeToAvoidBottomInset: false,
+ body: SafeArea(
+ child: Column(
+ children: [
+ MuesliAppBar(
+ children: [
+ Expanded(
+ child: Center(
+ child: Text(
+ AppLocalizations.of(context).login,
+ style: const TextStyle(
+ fontSize: 28,
+ fontWeight: FontWeight.bold,
+ fontFamily: "Inter"),
+ ),
+ ),
+ ),
+ ],
+ ),
+ SizedBox(
+ height: 150,
+ width: 150,
+ child: Image.asset("assets/cereal.png"),
+ ),
+ Padding(
+ padding:
+ const EdgeInsets.only(left: 20, right: 20, bottom: 20),
+ child: SingleChildScrollView(
+ child: Column(
+ children: [
+ Text(
+ AppLocalizations.of(context).login_description,
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 12,
+ fontFamily: "Inter",
+ fontWeight: FontWeight.bold,
+ color: Theme.of(context).colorScheme.onBackground,
+ ),
+ ),
+ const SizedBox(height: 10),
+ AutofillGroup(
+ child: Column(
+ children: [
+ TextField(
+ controller: _usernameController,
+ keyboardType: TextInputType.emailAddress,
+ autofillHints: const [AutofillHints.email],
+ decoration: InputDecoration(
+ labelText:
+ AppLocalizations.of(context).email_login,
+ labelStyle: TextStyle(
+ fontSize: 16,
+ fontFamily: "Inter",
+ fontWeight: FontWeight.bold,
+ color: Theme.of(context)
+ .colorScheme
+ .onBackground,
+ ),
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(10),
+ borderSide: BorderSide(
+ color: Theme.of(context)
+ .colorScheme
+ .onBackground)),
+ ),
+ ),
+ const SizedBox(height: 20),
+ TextField(
+ controller: _passwordController,
+ keyboardType: TextInputType.visiblePassword,
+ autofillHints: const [AutofillHints.password],
+ obscureText: true,
+ decoration: InputDecoration(
+ labelText: AppLocalizations.of(context)
+ .password_login,
+ labelStyle: TextStyle(
+ fontSize: 16,
+ fontFamily: "Inter",
+ fontWeight: FontWeight.bold,
+ color: Theme.of(context)
+ .colorScheme
+ .onBackground,
+ ),
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(10),
+ borderSide: BorderSide(
+ color: Theme.of(context)
+ .colorScheme
+ .onBackground)),
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 20),
+ GestureDetector(
+ onTapDown: (details) => _login(context),
+ child: Container(
+ width: 125,
+ height: 50,
+ decoration: BoxDecoration(
+ color: Theme.of(context)
+ .colorScheme
+ .secondaryContainer
+ .withOpacity(0.7),
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: Center(
+ child: Text(
+ AppLocalizations.of(context).execute_login,
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 16,
+ fontFamily: "Inter",
+ fontWeight: FontWeight.bold,
+ color: Theme.of(context)
+ .colorScheme
+ .onSecondaryContainer,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ _loading
+ ? Container(
+ decoration: BoxDecoration(
+ color:
+ Theme.of(context).colorScheme.background.withOpacity(0.7),
+ ),
+ child: Center(
+ child: LoadingAnimationWidget.fourRotatingDots(
+ color: Theme.of(context).colorScheme.onBackground,
+ size: 45),
+ ),
+ )
+ : Container(),
+ ],
+ );
+ }
+}
diff --git a/lib/ui/pages/overview.dart b/lib/ui/pages/overview.dart
new file mode 100755
index 0000000..ebfe18b
--- /dev/null
+++ b/lib/ui/pages/overview.dart
@@ -0,0 +1,131 @@
+import 'dart:math';
+import 'package:f_logs/f_logs.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:muesli_app/storage/storage.dart';
+import 'package:muesli_app/ui/pages/lecture.dart';
+import 'package:muesli_app/ui/pages/login.dart';
+import 'package:muesli_app/ui/pages/tutorial.dart';
+import 'package:muesli_app/ui/widgets/muesliappbar.dart';
+import 'package:muesli_app/ui/widgets/settings.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:muesli_app/services/globals.dart' as globals;
+
+class OverviewPage extends StatefulWidget {
+ const OverviewPage({Key? key}) : super(key: key);
+
+ @override
+ State createState() => _OverviewPageState();
+}
+
+class _OverviewPageState extends State
+ with AutomaticKeepAliveClientMixin {
+ @override
+ bool get wantKeepAlive => true;
+
+ SecureStorage secureStorage = SecureStorage();
+ int _currentPageIndex = 0;
+ final PageController _pageController = PageController(initialPage: 0);
+
+ @override
+ void dispose() {
+ _pageController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ super.build(context);
+ return Scaffold(
+ bottomNavigationBar: NavigationBar(
+ onDestinationSelected: (int index) {
+ setState(() {
+ _currentPageIndex = index;
+ _pageController.jumpToPage(index);
+ });
+ },
+ selectedIndex: _currentPageIndex,
+ destinations: [
+ NavigationDestination(
+ icon: const Icon(Icons.book_outlined),
+ selectedIcon: const Icon(Icons.book),
+ label: AppLocalizations.of(context).tutorials_navbar),
+ NavigationDestination(
+ icon: const Icon(Icons.summarize_outlined),
+ selectedIcon: const Icon(Icons.summarize),
+ label: AppLocalizations.of(context).lectures_navbar),
+ ],
+ ),
+ body: SafeArea(
+ bottom: false,
+ child: Column(
+ children: [
+ MuesliAppBar(
+ children: [
+ GestureDetector(
+ onTap: () {
+ secureStorage.deleteSecureData("token");
+ secureStorage.deleteSecureData("username");
+ secureStorage.deleteSecureData("password");
+ globals.prefs.remove("expireDate");
+
+ FLog.info(text: "Successcully logged out.");
+
+ Navigator.pushReplacement(
+ context,
+ MaterialPageRoute(
+ builder: (context) => const LoginPage(),
+ ));
+ },
+ child: Transform.rotate(
+ angle: pi,
+ child: SvgPicture.asset(
+ "assets/logout.svg",
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.onBackground,
+ BlendMode.srcIn),
+ width: 25,
+ height: 25,
+ ),
+ ),
+ ),
+ Text(
+ AppLocalizations.of(context).overview_header,
+ style: TextStyle(
+ fontSize: 28,
+ fontWeight: FontWeight.bold,
+ fontFamily: "Inter",
+ color: Theme.of(context).colorScheme.onBackground,
+ ),
+ ),
+ GestureDetector(
+ onTap: () {
+ FLog.info(text: "Open Settings Dialog.");
+ showDialog(
+ context: context,
+ builder: (BuildContext context) => const Settings());
+ },
+ child: SvgPicture.asset(
+ "assets/settings.svg",
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.onBackground,
+ BlendMode.srcIn),
+ width: 25,
+ height: 25,
+ ),
+ ),
+ ],
+ ),
+ Expanded(
+ child: PageView(
+ controller: _pageController,
+ physics: const NeverScrollableScrollPhysics(),
+ children: [TutorialPage(), const LecturePage()],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/ui/pages/tutorial.dart b/lib/ui/pages/tutorial.dart
new file mode 100644
index 0000000..17f9b75
--- /dev/null
+++ b/lib/ui/pages/tutorial.dart
@@ -0,0 +1,72 @@
+import 'package:flutter/material.dart';
+import 'package:loading_animation_widget/loading_animation_widget.dart';
+import 'package:muesli_app/services/request.dart';
+import 'package:muesli_app/storage/storage.dart';
+import 'package:muesli_app/model/tutorial.dart' as models;
+import 'package:muesli_app/ui/widgets/tutorial.dart' as widgets;
+import 'package:muesli_app/ui/widgets/tutorialsnapscrolllist.dart';
+
+class TutorialPage extends StatefulWidget {
+ TutorialPage({Key? key}) : super(key: key);
+
+ final SecureStorage secureStorage = SecureStorage();
+
+ @override
+ State createState() => _TutorialPageState();
+}
+
+class _TutorialPageState extends State
+ with AutomaticKeepAliveClientMixin {
+ @override
+ bool get wantKeepAlive => true;
+
+ late Widget _tutorials;
+
+ @override
+ void initState() {
+ super.initState();
+
+ _tutorials = FutureBuilder>(
+ future: HttpRequest.getTutorialList(),
+ builder: (context, AsyncSnapshot> snapshot) {
+ if (snapshot.hasData) {
+ tutorialData = snapshot.data!;
+ return TutorialScrollSnapList(
+ itemCount: tutorialData.length,
+ buildItemList: (context, index) => _buildItemList(context, index),
+ tutorialData: tutorialData,
+ noDataWidget: Container());
+ }
+ return LoadingAnimationWidget.fourRotatingDots(
+ color: Theme.of(context).colorScheme.onBackground, size: 45);
+ },
+ );
+ }
+
+ List tutorialData = [];
+
+ Widget _buildItemList(BuildContext context, int index) {
+ if (index == tutorialData.length) {
+ return Center(
+ child: LoadingAnimationWidget.fourRotatingDots(
+ color: Theme.of(context).colorScheme.onBackground, size: 45),
+ );
+ }
+ return SizedBox(
+ width: 340,
+ child: Padding(
+ padding: const EdgeInsets.only(left: 5, right: 5),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [widgets.Tutorial(tutorial: tutorialData[index])],
+ ),
+ ),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ super.build(context);
+ return _tutorials;
+ }
+}
diff --git a/lib/ui/widgets/card.dart b/lib/ui/widgets/card.dart
new file mode 100755
index 0000000..23836b1
--- /dev/null
+++ b/lib/ui/widgets/card.dart
@@ -0,0 +1,24 @@
+import 'package:flutter/material.dart';
+
+class Card extends StatelessWidget {
+ const Card({Key? key, this.child, this.height = 170, required this.color}) : super(key: key);
+
+ final Widget? child;
+
+ final double height;
+
+ final Color color;
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ height: height,
+ width: double.maxFinite,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(25),
+ color: color,
+ ),
+ child: child,
+ );
+ }
+}
diff --git a/lib/ui/widgets/dialogbox.dart b/lib/ui/widgets/dialogbox.dart
new file mode 100644
index 0000000..065354b
--- /dev/null
+++ b/lib/ui/widgets/dialogbox.dart
@@ -0,0 +1,116 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+
+class DialogBox extends StatelessWidget {
+ const DialogBox(
+ {Key? key,
+ required this.iconPath,
+ required this.children,
+ required this.buttonText,
+ required this.onPressed,
+ required this.title})
+ : super(key: key);
+
+ final String title;
+ final String iconPath;
+ final List children;
+ final String buttonText;
+ final Function onPressed;
+
+ @override
+ Widget build(BuildContext context) {
+ return Dialog(
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
+ elevation: 0,
+ backgroundColor: Colors.transparent,
+ child: Stack(
+ children: [
+ Container(
+ margin: const EdgeInsets.only(top: 28),
+ padding:
+ const EdgeInsets.only(left: 20, right: 20, bottom: 20, top: 30),
+ decoration: BoxDecoration(
+ shape: BoxShape.rectangle,
+ color: Theme.of(context)
+ .colorScheme
+ .secondaryContainer
+ .withOpacity(0.7),
+ borderRadius: BorderRadius.circular(25),
+ boxShadow: const [
+ BoxShadow(
+ color: Colors.black, offset: Offset(0, 10), blurRadius: 10),
+ ],
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ title,
+ style: TextStyle(
+ fontSize: 22,
+ color: Theme.of(context).colorScheme.onSecondaryContainer,
+ fontWeight: FontWeight.bold),
+ ),
+ const SizedBox(
+ height: 15,
+ ),
+ Column(
+ children: children,
+ ),
+ const SizedBox(
+ height: 22,
+ ),
+ Align(
+ alignment: Alignment.bottomRight,
+ child: GestureDetector(
+ onTapDown: (details) => onPressed(),
+ child: Container(
+ width: 100,
+ height: 40,
+ decoration: BoxDecoration(
+ color: const Color(0xFF465770),
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: Center(
+ child: Text(
+ buttonText,
+ textAlign: TextAlign.center,
+ style: const TextStyle(
+ fontSize: 16,
+ fontFamily: "Inter",
+ fontWeight: FontWeight.bold,
+ color: Colors.white,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ Positioned(
+ left: 20,
+ right: 20,
+ child: CircleAvatar(
+ backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
+ radius: 28,
+ child: ClipRRect(
+ borderRadius: const BorderRadius.all(Radius.circular(25)),
+ child: SvgPicture.asset(
+ iconPath,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context).colorScheme.onSecondaryContainer,
+ BlendMode.srcIn),
+ width: 35,
+ height: 35,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/ui/widgets/exam.dart b/lib/ui/widgets/exam.dart
new file mode 100755
index 0000000..e608afd
--- /dev/null
+++ b/lib/ui/widgets/exam.dart
@@ -0,0 +1,29 @@
+import 'package:flutter/material.dart';
+import 'package:muesli_app/model/exam.dart' as models;
+import 'package:muesli_app/ui/widgets/exerciselist.dart';
+
+class Exam extends StatelessWidget {
+ const Exam({Key? key, required this.exam}) : super(key: key);
+
+ final models.Exam exam;
+
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(exam.name.length > 20 ? "${exam.name.substring(0, 20)}..." : exam.name,
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ fontFamily: "Inter",
+ color: Theme.of(context).colorScheme.onSecondaryContainer.withOpacity(0.8))),
+ const SizedBox(
+ width: 30,
+ ),
+ ExerciseList(exercises: exam.exercises),
+ ],
+ );
+ }
+}
diff --git a/lib/ui/widgets/examlist.dart b/lib/ui/widgets/examlist.dart
new file mode 100755
index 0000000..97dc0fb
--- /dev/null
+++ b/lib/ui/widgets/examlist.dart
@@ -0,0 +1,83 @@
+import 'package:flutter/material.dart';
+import 'package:muesli_app/model/exam.dart';
+import 'package:muesli_app/ui/widgets/card.dart' as widgets;
+import 'package:muesli_app/ui/widgets/exam.dart' as widgets;
+import 'package:muesli_app/ui/widgets/icontextpair.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+
+class ExamList extends StatelessWidget {
+ const ExamList(
+ {Key? key,
+ required this.exams,
+ required this.percentage,
+ this.maxPoints = 0,
+ this.points = 0})
+ : super(key: key);
+
+ final List exams;
+ final double percentage;
+ final double maxPoints;
+ final double points;
+
+ @override
+ Widget build(BuildContext context) {
+ return Expanded(
+ child: Padding(
+ padding: const EdgeInsets.only(left: 20, right: 20, bottom: 10),
+ child: widgets.Card(
+ color: Theme.of(context).colorScheme.secondaryContainer.withOpacity(0.4),
+ height: 500,
+ child: Container(
+ margin:
+ const EdgeInsets.only(left: 16, right: 16, top: 10, bottom: 10),
+ child: exams.isNotEmpty
+ ? Column(
+ children: [
+ Stack(
+ children: [
+ ClipRRect(
+ borderRadius: BorderRadius.circular(20),
+ child: LinearProgressIndicator(
+ value: percentage,
+ minHeight: 20,
+ backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
+ valueColor: AlwaysStoppedAnimation(
+ Theme.of(context).colorScheme.onSecondaryContainer.withOpacity(0.6),
+ ),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.only(left: 10),
+ child: Text(
+ "$points/$maxPoints ${AppLocalizations.of(context).total_points_examlist} (${(percentage*100).toInt()}%)",
+ style: const TextStyle(
+ color: Colors.white, fontFamily: "Inter", fontWeight: FontWeight.bold),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 10),
+ Expanded(
+ child: SingleChildScrollView(
+ child: Column(
+ children: exams
+ .map((exam) => widgets.Exam(exam: exam))
+ .toList(),
+ ),
+ ),
+ ),
+ ],
+ )
+ : Center(
+ child: IconTextPair(
+ iconPath: "assets/sheet.svg",
+ text: AppLocalizations.of(context).no_exams_available,
+ direction: Axis.vertical,
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/ui/widgets/exercise.dart b/lib/ui/widgets/exercise.dart
new file mode 100755
index 0000000..7ef8d47
--- /dev/null
+++ b/lib/ui/widgets/exercise.dart
@@ -0,0 +1,22 @@
+import 'package:auto_size_text/auto_size_text.dart';
+import 'package:flutter/material.dart';
+import 'package:muesli_app/model/exercise.dart' as models;
+
+class Exercise extends StatelessWidget {
+ const Exercise({Key? key, required this.exercise}) : super(key: key);
+
+ final models.Exercise exercise;
+
+ @override
+ Widget build(BuildContext context) {
+ return AutoSizeText(
+ "${exercise.exerciseResult.points} / ${exercise.maxPoints}",
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ fontFamily: "Inter",
+ color: Theme.of(context).colorScheme.onSecondaryContainer.withOpacity(0.8),
+ ),
+ );
+ }
+}
diff --git a/lib/ui/widgets/exerciselist.dart b/lib/ui/widgets/exerciselist.dart
new file mode 100755
index 0000000..d875969
--- /dev/null
+++ b/lib/ui/widgets/exerciselist.dart
@@ -0,0 +1,21 @@
+import 'package:flutter/cupertino.dart';
+import 'package:muesli_app/model/exerciselist.dart' as models;
+import 'package:muesli_app/ui/widgets/exercise.dart' as widgets;
+
+class ExerciseList extends StatelessWidget {
+ const ExerciseList({Key? key, required this.exercises}) : super(key: key);
+
+ final models.ExerciseList exercises;
+
+ @override
+ Widget build(BuildContext context) {
+ return Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: exercises.exercises
+ .map((exercise) => widgets.Exercise(exercise: exercise))
+ .toList(),
+ ),
+ );
+ }
+}
diff --git a/lib/ui/widgets/icontextpair.dart b/lib/ui/widgets/icontextpair.dart
new file mode 100755
index 0000000..559e907
--- /dev/null
+++ b/lib/ui/widgets/icontextpair.dart
@@ -0,0 +1,46 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+
+class IconTextPair extends StatelessWidget {
+ const IconTextPair(
+ {Key? key,
+ required this.iconPath,
+ required this.text,
+ this.direction = Axis.horizontal})
+ : super(key: key);
+
+ final String iconPath;
+ final String text;
+ final Axis direction;
+
+ @override
+ Widget build(BuildContext context) {
+ return Wrap(
+ direction: direction,
+ crossAxisAlignment: WrapCrossAlignment.center,
+ spacing: 10,
+ children: [
+ SvgPicture.asset(
+ iconPath,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context)
+ .colorScheme
+ .onSecondaryContainer
+ .withOpacity(0.8),
+ BlendMode.srcIn),
+ width: 25,
+ height: 25,
+ ),
+ Text(text,
+ style: TextStyle(
+ fontSize: 16,
+ fontFamily: "Inter",
+ fontWeight: FontWeight.bold,
+ color: Theme.of(context)
+ .colorScheme
+ .onSecondaryContainer
+ .withOpacity(0.8))),
+ ],
+ );
+ }
+}
diff --git a/lib/ui/widgets/lecture.dart b/lib/ui/widgets/lecture.dart
new file mode 100644
index 0000000..4e7c5c4
--- /dev/null
+++ b/lib/ui/widgets/lecture.dart
@@ -0,0 +1,81 @@
+import 'package:auto_size_text/auto_size_text.dart';
+import 'package:flutter/material.dart';
+import 'package:muesli_app/services/request.dart';
+import 'package:muesli_app/model/lecture.dart' as models;
+import 'package:muesli_app/ui/widgets/card.dart' as widget;
+import 'package:muesli_app/ui/widgets/icontextpair.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+
+class Lecture extends StatelessWidget {
+ const Lecture({Key? key, required this.lecture}) : super(key: key);
+
+ final models.Lecture lecture;
+
+ @override
+ Widget build(BuildContext context) {
+ List assistants = [];
+ lecture.assistants.map((assistant) {
+ if (!lecture.lecturer.contains(assistant.lastName)) {
+ assistants.add("${assistant.name} ${assistant.lastName}");
+ }
+ }).toList();
+
+ return GestureDetector(
+ onTap: () {
+ HttpRequest.launchURLBrowser(lecture.url);
+ },
+ child: widget.Card(
+ color:
+ Theme.of(context).colorScheme.secondaryContainer.withOpacity(0.4),
+ child: Padding(
+ padding: const EdgeInsets.all(14),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(
+ width: MediaQuery.of(context).size.width,
+ child: AutoSizeText(
+ lecture.name,
+ maxLines: 2,
+ style: TextStyle(
+ fontSize: 22,
+ fontWeight: FontWeight.bold,
+ fontFamily: "Inter",
+ color: Theme.of(context)
+ .colorScheme
+ .onSecondaryContainer
+ .withOpacity(0.8),
+ ),
+ ),
+ ),
+ Expanded(
+ child: Wrap(
+ alignment: WrapAlignment.spaceEvenly,
+ direction: Axis.vertical,
+ children: [
+ IconTextPair(
+ iconPath: "assets/time.svg", text: lecture.term),
+ IconTextPair(
+ iconPath: "assets/person.svg",
+ text: lecture.lecturer.isEmpty
+ ? AppLocalizations.of(context).no_lecturer_lecture
+ : lecture.lecturer.length < 40
+ ? lecture.lecturer
+ : "${lecture.lecturer.substring(0, 40)}..."),
+ IconTextPair(
+ iconPath: "assets/multi_person.svg",
+ text: assistants.isEmpty
+ ? AppLocalizations.of(context).no_assistants_lecture
+ : assistants.join(", ").length < 35
+ ? assistants.join(", ")
+ : "${assistants.join(", ").substring(0, 35)}..."),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/ui/widgets/muesliappbar.dart b/lib/ui/widgets/muesliappbar.dart
new file mode 100755
index 0000000..edacfc2
--- /dev/null
+++ b/lib/ui/widgets/muesliappbar.dart
@@ -0,0 +1,27 @@
+import 'package:flutter/material.dart';
+import 'package:muesli_app/storage/storage.dart';
+
+class MuesliAppBar extends StatelessWidget {
+ MuesliAppBar({Key? key, required this.children}) : super(key: key);
+
+ final List children;
+ final SecureStorage secureStorage = SecureStorage();
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: children,
+ ),
+ ),
+ Divider(
+ color: Theme.of(context).colorScheme.onBackground,
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/ui/widgets/settings.dart b/lib/ui/widgets/settings.dart
new file mode 100644
index 0000000..5b14e3a
--- /dev/null
+++ b/lib/ui/widgets/settings.dart
@@ -0,0 +1,106 @@
+// ignore_for_file: use_build_context_synchronously
+
+import 'dart:io';
+import 'package:f_logs/model/flog/flog.dart';
+import 'package:flutter/material.dart';
+import 'package:muesli_app/services/request.dart';
+import 'package:muesli_app/ui/widgets/dialogbox.dart';
+import 'package:muesli_app/ui/widgets/settingsmenubutton.dart';
+import 'package:muesli_app/services/globals.dart' as globals;
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:overlay_support/overlay_support.dart';
+
+class Settings extends StatefulWidget {
+ const Settings({Key? key}) : super(key: key);
+
+ @override
+ State createState() => _SettingsState();
+}
+
+class _SettingsState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return DialogBox(
+ title: AppLocalizations.of(context).settings,
+ iconPath: "assets/settings.svg",
+ onPressed: () => Navigator.pop(context),
+ buttonText: AppLocalizations.of(context).save_settings,
+ children: [
+ Text(
+ "${AppLocalizations.of(context).available_soon}:\n${AppLocalizations.of(context).notifications_new_lectures}",
+ style: TextStyle(
+ fontFamily: "Inter",
+ color: Theme.of(context)
+ .colorScheme
+ .onSecondaryContainer
+ .withOpacity(0.8)),
+ ),
+ const SizedBox(height: 10),
+ SettingsMenuButton(
+ text: AppLocalizations.of(context).legal_notice_settings,
+ onTap: () => Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: ((context) => LicensePage(
+ applicationName: AppLocalizations.of(context).app_name,
+ applicationVersion: globals.packageInfo.version,
+ )),
+ ),
+ ),
+ ),
+ SettingsMenuButton(
+ text: AppLocalizations.of(context).github_settings,
+ onTap: () {
+ HttpRequest.launchURLBrowser(
+ "https://github.com/niels-beier/muesli_app");
+ }),
+ SettingsMenuButton(
+ text:
+ "${AppLocalizations.of(context).version_settings} ${globals.packageInfo.version}",
+ onTap: () {}),
+ SettingsMenuButton(
+ text: AppLocalizations.of(context).export_logs_settings,
+ onTap: () async {
+ File exported = await FLog.exportLogs();
+ if (Platform.isAndroid) {
+ showSimpleNotification(
+ Text(
+ "${AppLocalizations.of(context).exported_logs_success} ${exported.path}"),
+ duration: const Duration(seconds: 5));
+ FLog.info(text: "Exported logs.");
+ } else {
+ // TODO: Add share menu for exporting on iOS
+ }
+ }),
+ SettingsMenuButton(
+ text: AppLocalizations.of(context).privacy_settings,
+ onTap: () {
+ if (Platform.isAndroid) {
+ showDialog(
+ context: context,
+ builder: (_) => AlertDialog(
+ title: Text(AppLocalizations.of(context).privacy_settings),
+ content: Text(AppLocalizations.of(context).privacy_content),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(),
+ child: Text(
+ "OK",
+ style: TextStyle(
+ color: Theme.of(context)
+ .colorScheme
+ .onSecondaryContainer),
+ ),
+ ),
+ ],
+ backgroundColor:
+ Theme.of(context).colorScheme.secondaryContainer,
+ ),
+ );
+ }
+ }),
+ const Divider(),
+ ],
+ );
+ }
+}
diff --git a/lib/ui/widgets/settingsmenubutton.dart b/lib/ui/widgets/settingsmenubutton.dart
new file mode 100644
index 0000000..85ed536
--- /dev/null
+++ b/lib/ui/widgets/settingsmenubutton.dart
@@ -0,0 +1,22 @@
+import 'package:flutter/material.dart';
+
+class SettingsMenuButton extends StatelessWidget {
+ const SettingsMenuButton({Key? key, required this.text, required this.onTap})
+ : super(key: key);
+
+ final String text;
+ final Function onTap;
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: () => onTap(),
+ child: Column(
+ children: [
+ const Divider(),
+ Text(text, style: const TextStyle(fontFamily: "Inter"),),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/ui/widgets/tutorial.dart b/lib/ui/widgets/tutorial.dart
new file mode 100755
index 0000000..34e6abd
--- /dev/null
+++ b/lib/ui/widgets/tutorial.dart
@@ -0,0 +1,58 @@
+import 'package:auto_size_text/auto_size_text.dart';
+import 'package:flutter/material.dart';
+import 'package:muesli_app/model/tutorial.dart' as models;
+import 'package:muesli_app/ui/widgets/card.dart' as widget;
+import 'package:muesli_app/ui/widgets/icontextpair.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+
+class Tutorial extends StatelessWidget {
+ const Tutorial({Key? key, required this.tutorial}) : super(key: key);
+
+ final models.Tutorial tutorial;
+
+ @override
+ Widget build(BuildContext context) {
+ return widget.Card(
+ color: Theme.of(context).colorScheme.secondaryContainer.withOpacity(0.4),
+ child: Padding(
+ padding: const EdgeInsets.all(14),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(
+ width: MediaQuery.of(context).size.width,
+ child: AutoSizeText(
+ tutorial.name,
+ maxLines: 2,
+ style: TextStyle(
+ fontSize: 22,
+ fontWeight: FontWeight.bold,
+ fontFamily: "Inter",
+ color: Theme.of(context).colorScheme.onSecondaryContainer.withOpacity(0.8),
+ ),
+ ),
+ ),
+ Expanded(
+ flex: 1,
+ child: Wrap(
+ alignment: WrapAlignment.spaceEvenly,
+ direction: Axis.vertical,
+ children: [
+ IconTextPair(
+ iconPath: "assets/time.svg",
+ text: "${tutorial.time} ${AppLocalizations.of(context).o_clock_tutorial}"),
+ IconTextPair(
+ iconPath: "assets/person.svg",
+ text:
+ "${tutorial.tutor.name} ${tutorial.tutor.lastName}"),
+ IconTextPair(
+ iconPath: "assets/place.svg", text: tutorial.place.length > 30 ? "${tutorial.place.substring(0, 30)}..." : tutorial.place),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/ui/widgets/tutorialsnapscrolllist.dart b/lib/ui/widgets/tutorialsnapscrolllist.dart
new file mode 100644
index 0000000..0d44fc8
--- /dev/null
+++ b/lib/ui/widgets/tutorialsnapscrolllist.dart
@@ -0,0 +1,91 @@
+import 'package:flutter/material.dart';
+import 'package:muesli_app/model/tutorial.dart';
+import 'package:muesli_app/ui/widgets/examlist.dart';
+import 'dart:io' show Platform;
+
+import 'package:scroll_snap_list/scroll_snap_list.dart';
+
+// ignore: must_be_immutable
+class TutorialScrollSnapList extends StatefulWidget {
+ TutorialScrollSnapList(
+ {Key? key,
+ required this.buildItemList,
+ required this.itemCount,
+ required this.tutorialData, required this.noDataWidget})
+ : super(key: key);
+
+ final Widget Function(BuildContext, int) buildItemList;
+ final int itemCount;
+ final List tutorialData;
+ final Widget noDataWidget;
+
+ int _selectedIndex = 0;
+
+ @override
+ State createState() => _TutorialScrollSnapListState();
+}
+
+class _TutorialScrollSnapListState extends State {
+ final List _percentages = [];
+ final List _maxPoints = [];
+ final List _points = [];
+
+ @override
+ void initState() {
+ super.initState();
+ for (var tutorial in widget.tutorialData) {
+ double maxPoints = 0;
+ double points = 0;
+ for (var exam in tutorial.exams) {
+ for (var exercise in exam.exercises.exercises) {
+ maxPoints += exercise.maxPoints;
+ points += exercise.exerciseResult.points;
+ }
+ }
+ _percentages
+ .add(maxPoints != 0 && !maxPoints.isNaN ? points / maxPoints : 0);
+ _maxPoints.add(maxPoints);
+ _points.add(points);
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return widget.tutorialData.isNotEmpty
+ ? Column(
+ children: [
+ SizedBox(
+ height: 170,
+ child: ScrollSnapList(
+ itemBuilder: widget.buildItemList,
+ itemSize: 340,
+ onReachEnd: () {},
+ itemCount: widget.itemCount,
+ scrollPhysics:
+ Platform.isIOS ? const BouncingScrollPhysics() : null,
+ onItemFocus: (current) {
+ setState(() {
+ widget._selectedIndex = current;
+ });
+ },
+ ),
+ ),
+ const SizedBox(height: 10),
+ SizedBox(
+ width: 260,
+ child: Divider(
+ color: Theme.of(context).colorScheme.onBackground,
+ ),
+ ),
+ const SizedBox(height: 10),
+ ExamList(
+ exams: widget.tutorialData[widget._selectedIndex].exams,
+ percentage: _percentages[widget._selectedIndex],
+ maxPoints: _maxPoints[widget._selectedIndex],
+ points: _points[widget._selectedIndex],
+ ),
+ ],
+ )
+ : widget.noDataWidget;
+ }
+}
diff --git a/pubspec.lock b/pubspec.lock
new file mode 100644
index 0000000..e77273a
--- /dev/null
+++ b/pubspec.lock
@@ -0,0 +1,775 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ adaptive_action_sheet:
+ dependency: "direct main"
+ description:
+ name: adaptive_action_sheet
+ sha256: "2cf53889102f2f476d03da30ef4219a3199f1d9b9f7bf063e2b23cd5aa88ea02"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.2"
+ archive:
+ dependency: transitive
+ description:
+ name: archive
+ sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.3.6"
+ args:
+ dependency: transitive
+ description:
+ name: args
+ sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.0"
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.10.0"
+ auto_size_text:
+ dependency: "direct main"
+ description:
+ name: auto_size_text
+ sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ checked_yaml:
+ dependency: transitive
+ description:
+ name: checked_yaml
+ sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.2"
+ cli_util:
+ dependency: transitive
+ description:
+ name: cli_util
+ sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.5"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.17.0"
+ convert:
+ dependency: transitive
+ description:
+ name: convert
+ sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.1"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.2"
+ device_info_plus:
+ dependency: "direct main"
+ description:
+ name: device_info_plus
+ sha256: "1d6e5a61674ba3a68fb048a7c7b4ff4bebfed8d7379dbe8f2b718231be9a7c95"
+ url: "https://pub.dev"
+ source: hosted
+ version: "8.1.0"
+ device_info_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: device_info_plus_platform_interface
+ sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.0"
+ dynamic_color:
+ dependency: "direct main"
+ description:
+ name: dynamic_color
+ sha256: c4a508284b14ec4dda5adba2c28b2cdd34fbae1afead7e8c52cad87d51c5405b
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.6.2"
+ f_logs:
+ dependency: "direct main"
+ description:
+ name: f_logs
+ sha256: "8ccf733949dcc934d7c7425e94a973cee71af1d7675b94dd1fbd6cbe707a3ce7"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.1"
+ fading_edge_scrollview:
+ dependency: "direct main"
+ description:
+ name: fading_edge_scrollview
+ sha256: c25c2231652ce774cc31824d0112f11f653881f43d7f5302c05af11942052031
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.1"
+ ffi:
+ dependency: transitive
+ description:
+ name: ffi
+ sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.1"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.4"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_launcher_icons:
+ dependency: "direct main"
+ description:
+ name: flutter_launcher_icons
+ sha256: ce0e501cfc258907842238e4ca605e74b7fd1cdf04b3b43e86c43f3e40a1592c
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.11.0"
+ flutter_lints:
+ dependency: "direct dev"
+ description:
+ name: flutter_lints
+ sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.1"
+ flutter_localizations:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_secure_storage:
+ dependency: "direct main"
+ description:
+ name: flutter_secure_storage
+ sha256: "98352186ee7ad3639ccc77ad7924b773ff6883076ab952437d20f18a61f0a7c5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "8.0.0"
+ flutter_secure_storage_linux:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_linux
+ sha256: "0912ae29a572230ad52d8a4697e5518d7f0f429052fd51df7e5a7952c7efe2a3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.3"
+ flutter_secure_storage_macos:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_macos
+ sha256: "083add01847fc1c80a07a08e1ed6927e9acd9618a35e330239d4422cd2a58c50"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
+ flutter_secure_storage_platform_interface:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_platform_interface
+ sha256: b3773190e385a3c8a382007893d678ae95462b3c2279e987b55d140d3b0cb81b
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.1"
+ flutter_secure_storage_web:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_web
+ sha256: "42938e70d4b872e856e678c423cc0e9065d7d294f45bc41fc1981a4eb4beaffe"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ flutter_secure_storage_windows:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_windows
+ sha256: fc2910ec9b28d60598216c29ea763b3a96c401f0ce1d13cdf69ccb0e5c93c3ee
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
+ flutter_svg:
+ dependency: "direct main"
+ description:
+ name: flutter_svg
+ sha256: "97c5b291b4fd34ae4f55d6a4c05841d4d0ed94952e033c5a6529e1b47b4d2a29"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.2"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_web_plugins:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ http:
+ dependency: "direct main"
+ description:
+ name: http
+ sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.13.5"
+ http_parser:
+ dependency: transitive
+ description:
+ name: http_parser
+ sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.2"
+ image:
+ dependency: transitive
+ description:
+ name: image
+ sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.3.0"
+ intl:
+ dependency: "direct main"
+ description:
+ name: intl
+ sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.17.0"
+ js:
+ dependency: transitive
+ description:
+ name: js
+ sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.5"
+ json_annotation:
+ dependency: transitive
+ description:
+ name: json_annotation
+ sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.8.0"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.1"
+ loading_animation_widget:
+ dependency: "direct main"
+ description:
+ name: loading_animation_widget
+ sha256: "1901682600273a966c34cf44a85fc5355da92a8d08a8a43c11adc4e471993e3a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0+4"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.13"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.0"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.8.0"
+ overlay_support:
+ dependency: "direct main"
+ description:
+ name: overlay_support
+ sha256: fc39389bfd94e6985e1e13b2a88a125fc4027608485d2d4e2847afe1b2bb339c
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
+ package_info_plus:
+ dependency: "direct main"
+ description:
+ name: package_info_plus
+ sha256: "8df5ab0a481d7dc20c0e63809e90a588e496d276ba53358afc4c4443d0a00697"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.3"
+ package_info_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: package_info_plus_platform_interface
+ sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.1"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.8.2"
+ path_parsing:
+ dependency: transitive
+ description:
+ name: path_parsing
+ sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.1"
+ path_provider:
+ dependency: transitive
+ description:
+ name: path_provider
+ sha256: "04890b994ee89bfa80bf3080bfec40d5a92c5c7a785ebb02c13084a099d2b6f9"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.13"
+ path_provider_android:
+ dependency: transitive
+ description:
+ name: path_provider_android
+ sha256: "7623b7d4be0f0f7d9a8b5ee6879fc13e4522d4c875ab86801dee4af32b54b83e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.23"
+ path_provider_foundation:
+ dependency: transitive
+ description:
+ name: path_provider_foundation
+ sha256: eec003594f19fe2456ea965ae36b3fc967bc5005f508890aafe31fa75e41d972
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ path_provider_linux:
+ dependency: transitive
+ description:
+ name: path_provider_linux
+ sha256: "525ad5e07622d19447ad740b1ed5070031f7a5437f44355ae915ff56e986429a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.9"
+ path_provider_platform_interface:
+ dependency: transitive
+ description:
+ name: path_provider_platform_interface
+ sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.6"
+ path_provider_windows:
+ dependency: transitive
+ description:
+ name: path_provider_windows
+ sha256: "642ddf65fde5404f83267e8459ddb4556316d3ee6d511ed193357e25caa3632d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ petitparser:
+ dependency: transitive
+ description:
+ name: petitparser
+ sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.1.0"
+ platform:
+ dependency: transitive
+ description:
+ name: platform
+ sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
+ plugin_platform_interface:
+ dependency: transitive
+ description:
+ name: plugin_platform_interface
+ sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ pointycastle:
+ dependency: transitive
+ description:
+ name: pointycastle
+ sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.6.2"
+ process:
+ dependency: transitive
+ description:
+ name: process
+ sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.2.4"
+ scroll_snap_list:
+ dependency: "direct main"
+ description:
+ name: scroll_snap_list
+ sha256: "8009ad9030f680b93f3107b29b1ea31284d867aa4972868d2970c6d61fe9e59f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.9.1"
+ sdk_int:
+ dependency: "direct main"
+ description:
+ name: sdk_int
+ sha256: e685b4c98177d7d2a3d646c566e2458739bc208b87c3527e384e576ac321ef1a
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.0.1"
+ sembast:
+ dependency: transitive
+ description:
+ name: sembast
+ sha256: "4997717aa84f0622691815d7e2739988b7f7d3a463302fc878f7d5acfa748e96"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.4.0+6"
+ shared_preferences:
+ dependency: "direct main"
+ description:
+ name: shared_preferences
+ sha256: ee6257848f822b8481691f20c3e6d2bfee2e9eccb2a3d249907fcfb198c55b41
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.18"
+ shared_preferences_android:
+ dependency: transitive
+ description:
+ name: shared_preferences_android
+ sha256: a51a4f9375097f94df1c6e0a49c0374440d31ab026b59d58a7e7660675879db4
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.16"
+ shared_preferences_foundation:
+ dependency: transitive
+ description:
+ name: shared_preferences_foundation
+ sha256: "6b84fdf06b32bb336f972d373cd38b63734f3461ba56ac2ba01b56d052796259"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ shared_preferences_linux:
+ dependency: transitive
+ description:
+ name: shared_preferences_linux
+ sha256: d7fb71e6e20cd3dfffcc823a28da3539b392e53ed5fc5c2b90b55fdaa8a7e8fa
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ shared_preferences_platform_interface:
+ dependency: transitive
+ description:
+ name: shared_preferences_platform_interface
+ sha256: "824bfd02713e37603b2bdade0842e47d56e7db32b1dcdd1cae533fb88e2913fc"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ shared_preferences_web:
+ dependency: transitive
+ description:
+ name: shared_preferences_web
+ sha256: "6737b757e49ba93de2a233df229d0b6a87728cea1684da828cbc718b65dcf9d7"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.5"
+ shared_preferences_windows:
+ dependency: transitive
+ description:
+ name: shared_preferences_windows
+ sha256: bd014168e8484837c39ef21065b78f305810ceabc1d4f90be6e3b392ce81b46d
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.99"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.9.1"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.11.0"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ synchronized:
+ dependency: transitive
+ description:
+ name: synchronized
+ sha256: "33b31b6beb98100bf9add464a36a8dd03eb10c7a8cf15aeec535e9b054aaf04b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.1"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.4.16"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.1"
+ universal_io:
+ dependency: transitive
+ description:
+ name: universal_io
+ sha256: "06866290206d196064fd61df4c7aea1ffe9a4e7c4ccaa8fcded42dd41948005d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ url_launcher:
+ dependency: "direct main"
+ description:
+ name: url_launcher
+ sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.10"
+ url_launcher_android:
+ dependency: transitive
+ description:
+ name: url_launcher_android
+ sha256: "1f4d9ebe86f333c15d318f81dcdc08b01d45da44af74552608455ebdc08d9732"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.24"
+ url_launcher_ios:
+ dependency: transitive
+ description:
+ name: url_launcher_ios
+ sha256: c9cd648d2f7ab56968e049d4e9116f96a85517f1dd806b96a86ea1018a3a82e5
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.1"
+ url_launcher_linux:
+ dependency: transitive
+ description:
+ name: url_launcher_linux
+ sha256: e29039160ab3730e42f3d811dc2a6d5f2864b90a70fb765ea60144b03307f682
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.3"
+ url_launcher_macos:
+ dependency: transitive
+ description:
+ name: url_launcher_macos
+ sha256: "2dddb3291a57b074dade66b5e07e64401dd2487caefd4e9e2f467138d8c7eb06"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.3"
+ url_launcher_platform_interface:
+ dependency: transitive
+ description:
+ name: url_launcher_platform_interface
+ sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ url_launcher_web:
+ dependency: transitive
+ description:
+ name: url_launcher_web
+ sha256: "574cfbe2390666003c3a1d129bdc4574aaa6728f0c00a4829a81c316de69dd9b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.15"
+ url_launcher_windows:
+ dependency: transitive
+ description:
+ name: url_launcher_windows
+ sha256: "97c9067950a0d09cbd93e2e3f0383d1403989362b97102fbf446473a48079a4b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.4"
+ vector_graphics:
+ dependency: transitive
+ description:
+ name: vector_graphics
+ sha256: "254348b40251c995cf8301ac715486c8cfa0a93b7fdc4dbd495a30f04db1fb44"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
+ vector_graphics_codec:
+ dependency: transitive
+ description:
+ name: vector_graphics_codec
+ sha256: "143c290b762646c696c63be5d976bde7379ea892cb6868ddc5a17cbc56e71411"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
+ vector_graphics_compiler:
+ dependency: transitive
+ description:
+ name: vector_graphics_compiler
+ sha256: "3190cc26d9ebda686bafb9721bb6a74c6d358700f4fc978a0f2cba6912daff86"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ win32:
+ dependency: transitive
+ description:
+ name: win32
+ sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.3"
+ xdg_directories:
+ dependency: transitive
+ description:
+ name: xdg_directories
+ sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
+ xml:
+ dependency: transitive
+ description:
+ name: xml
+ sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.2.2"
+ xxtea:
+ dependency: transitive
+ description:
+ name: xxtea
+ sha256: edd57229638e16e88f4bb299737fe76a235d0f11da6df8573b3c7f28dec283ce
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
+ yaml:
+ dependency: transitive
+ description:
+ name: yaml
+ sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.1"
+sdks:
+ dart: ">=2.19.0-0 <3.0.0"
+ flutter: ">=3.7.0-0"
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100755
index 0000000..a09afaf
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,118 @@
+name: muesli_app
+description: A new Flutter project.
+
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# In Android, build-name is used as versionName while build-number used as versionCode.
+# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
+# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
+# Read more about iOS versioning at
+# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+version: 0.0.1-beta
+
+environment:
+ sdk: ">=2.15.1 <3.0.0"
+
+# Dependencies specify other packages that your package needs in order to work.
+# To automatically upgrade your package dependencies to the latest versions
+# consider running `flutter pub upgrade --major-versions`. Alternatively,
+# dependencies can be manually updated by changing the version numbers below to
+# the latest version available on pub.dev. To see which dependencies have newer
+# versions available, run `flutter pub outdated`.
+dependencies:
+ flutter:
+ sdk: flutter
+
+ http: ^0.13.4
+
+ flutter_secure_storage: ^8.0.0
+
+ auto_size_text: ^3.0.0
+
+ flutter_svg: ^2.0.2
+
+ scroll_snap_list: ^0.9.1
+
+ loading_animation_widget:
+
+ fading_edge_scrollview: ^3.0.0
+
+ url_launcher: ^6.1.2
+
+ adaptive_action_sheet: ^2.0.1
+
+ package_info_plus: ^3.0.3
+
+ overlay_support: ^2.0.1
+
+ dynamic_color: ^1.6.2
+
+ flutter_localizations:
+ sdk: flutter
+
+ intl: any
+
+ sdk_int: ^0.0.1
+ f_logs: ^2.0.1
+ shared_preferences: ^2.0.17
+ device_info_plus: ^8.1.0
+ flutter_launcher_icons: ^0.11.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+ # The "flutter_lints" package below contains a set of recommended lints to
+ # encourage good coding practices. The lint set provided by the package is
+ # activated in the `analysis_options.yaml` file located at the root of your
+ # package. See that file for information about deactivating specific lint
+ # rules and activating additional ones.
+ flutter_lints: ^2.0.1
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter.
+flutter:
+
+ # The following line ensures that the Material Icons font is
+ # included with your application, so that you can use the icons in
+ # the material Icons class.
+ uses-material-design: true
+ generate: true
+
+ assets:
+ - ./assets/
+
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/assets-and-images/#resolution-aware.
+
+ # For details regarding adding assets from package dependencies, see
+ # https://flutter.dev/assets-and-images/#from-packages
+
+ # To add custom fonts to your application, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts from package dependencies,
+ # see https://flutter.dev/custom-fonts/#from-packages