diff --git a/project/FrontEnd/.idea/workspace.xml b/project/FrontEnd/.idea/workspace.xml
new file mode 100644
index 00000000..96c1f7c7
--- /dev/null
+++ b/project/FrontEnd/.idea/workspace.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1697302631145
+
+
+ 1697302631145
+
+
+
+
\ No newline at end of file
diff --git a/project/FrontEnd/collaborative_science_platform/.gitignore b/project/FrontEnd/collaborative_science_platform/.gitignore
new file mode 100644
index 00000000..24476c5d
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/.gitignore
@@ -0,0 +1,44 @@
+# 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/
+
+# 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/project/FrontEnd/collaborative_science_platform/.metadata b/project/FrontEnd/collaborative_science_platform/.metadata
new file mode 100644
index 00000000..ab3e1c09
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/.metadata
@@ -0,0 +1,45 @@
+# 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 and should not be manually edited.
+
+version:
+ revision: "ead455963c12b453cdb2358cad34969c76daf180"
+ channel: "stable"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: ead455963c12b453cdb2358cad34969c76daf180
+ base_revision: ead455963c12b453cdb2358cad34969c76daf180
+ - platform: android
+ create_revision: ead455963c12b453cdb2358cad34969c76daf180
+ base_revision: ead455963c12b453cdb2358cad34969c76daf180
+ - platform: ios
+ create_revision: ead455963c12b453cdb2358cad34969c76daf180
+ base_revision: ead455963c12b453cdb2358cad34969c76daf180
+ - platform: linux
+ create_revision: ead455963c12b453cdb2358cad34969c76daf180
+ base_revision: ead455963c12b453cdb2358cad34969c76daf180
+ - platform: macos
+ create_revision: ead455963c12b453cdb2358cad34969c76daf180
+ base_revision: ead455963c12b453cdb2358cad34969c76daf180
+ - platform: web
+ create_revision: ead455963c12b453cdb2358cad34969c76daf180
+ base_revision: ead455963c12b453cdb2358cad34969c76daf180
+ - platform: windows
+ create_revision: ead455963c12b453cdb2358cad34969c76daf180
+ base_revision: ead455963c12b453cdb2358cad34969c76daf180
+
+ # 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/project/FrontEnd/collaborative_science_platform/Dockerfile b/project/FrontEnd/collaborative_science_platform/Dockerfile
new file mode 100644
index 00000000..cc754b17
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/Dockerfile
@@ -0,0 +1,33 @@
+# Install Operating system and dependencies
+FROM debian:latest AS build-env
+
+RUN apt-get update
+RUN apt-get install -y curl
+RUN apt-get install -y git
+RUN apt-get install -y wget
+RUN apt-get install -y unzip
+RUN apt-get install -y libstdc++6
+RUN apt-get install -y libglu1-mesa
+RUN apt-get install -y fonts-droid-fallback
+RUN apt-get install -y lib32stdc++6
+RUN apt-get install -y python3
+
+
+RUN git clone https://github.com/flutter/flutter.git /usr/local/flutter
+
+ENV PATH="/usr/local/flutter/bin:/usr/local/flutter/bin/cache/dart-sdk/bin:${PATH}"
+
+RUN flutter doctor -v
+
+RUN flutter channel master
+RUN flutter upgrade
+RUN flutter config --enable-web
+
+RUN mkdir /app/
+COPY . /app/
+WORKDIR /app/
+RUN flutter build web
+
+# Stage 2
+FROM nginx:1.21.1-alpine
+COPY --from=build-env /app/build/web /usr/share/nginx/html
\ No newline at end of file
diff --git a/project/FrontEnd/collaborative_science_platform/README.md b/project/FrontEnd/collaborative_science_platform/README.md
new file mode 100644
index 00000000..befe62b6
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/README.md
@@ -0,0 +1,55 @@
+# collaborative_science_platform
+
+A new Flutter project.
+
+## Running the Flutter App locally
+
+follow these steps:
+
+1. Make sure you have Flutter installed. If not, you can install it by following the official Flutter installation guide: [Flutter Installation](https://flutter.dev/docs/get-started/install).
+
+2. Navigate to your project directory:
+
+`cd your-path-to-project/collaborative_science_platform`
+
+3. Ensure you have the latest dependencies by running:
+
+`flutter pub get`
+
+4. Build and run the web version of your app with the following command:
+
+`flutter build web --release`
+
+`flutter run -d web`
+
+## Releasing an Android App
+
+1. Navigate to your project directory:
+
+`cd your-path-to-project/collaborative_science_platform`
+
+2. Build the release APK for Android using the following command:
+
+`flutter build apk --split-per-abi`
+
+This will generate the APK files in the `build/app/outputs/flutter-apk` directory.
+
+## Dockerization
+
+
+
+To create a docker image, run:
+
+
+
+`docker build -t .`
+
+
+
+To create a container from that image:
+
+
+`docker run -p 80:80 `
+
+
+After starting the container you can access the website at `http://localhost`
\ No newline at end of file
diff --git a/project/FrontEnd/collaborative_science_platform/analysis_options.yaml b/project/FrontEnd/collaborative_science_platform/analysis_options.yaml
new file mode 100644
index 00000000..0d290213
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/analysis_options.yaml
@@ -0,0 +1,28 @@
+# 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.dev/lints.
+ #
+ # 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/project/FrontEnd/collaborative_science_platform/android/.gitignore b/project/FrontEnd/collaborative_science_platform/android/.gitignore
new file mode 100644
index 00000000..6f568019
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/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/project/FrontEnd/collaborative_science_platform/android/app/build.gradle b/project/FrontEnd/collaborative_science_platform/android/app/build.gradle
new file mode 100644
index 00000000..aaaa15ba
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/android/app/build.gradle
@@ -0,0 +1,67 @@
+plugins {
+ id "com.android.application"
+ id "kotlin-android"
+ id "dev.flutter.flutter-gradle-plugin"
+}
+
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+ localPropertiesFile.withReader('UTF-8') { reader ->
+ localProperties.load(reader)
+ }
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+ flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+ flutterVersionName = '1.0'
+}
+
+android {
+ namespace "com.example.collaborative_science_platform"
+ 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 "com.example.collaborative_science_platform"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
+ minSdkVersion flutter.minSdkVersion
+ targetSdkVersion flutter.targetSdkVersion
+ versionCode flutterVersionCode.toInteger()
+ versionName flutterVersionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig signingConfigs.debug
+ }
+ }
+}
+
+flutter {
+ source '../..'
+}
+
+dependencies {}
diff --git a/project/FrontEnd/collaborative_science_platform/android/app/src/debug/AndroidManifest.xml b/project/FrontEnd/collaborative_science_platform/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 00000000..399f6981
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/android/app/src/main/AndroidManifest.xml b/project/FrontEnd/collaborative_science_platform/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..52ebfe12
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/android/app/src/main/kotlin/com/example/collaborative_science_platform/MainActivity.kt b/project/FrontEnd/collaborative_science_platform/android/app/src/main/kotlin/com/example/collaborative_science_platform/MainActivity.kt
new file mode 100644
index 00000000..b7f0254f
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/android/app/src/main/kotlin/com/example/collaborative_science_platform/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.example.collaborative_science_platform
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/drawable-v21/launch_background.xml b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 00000000..f74085f3
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/drawable/launch_background.xml b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 00000000..304732f8
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..db77bb4b
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..17987b79
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..09d43914
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..d5f1c8d3
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..4d6372ee
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/values-night/styles.xml b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 00000000..06952be7
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/values/styles.xml b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/values/styles.xml
new file mode 100644
index 00000000..cb1ef880
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/android/app/src/profile/AndroidManifest.xml b/project/FrontEnd/collaborative_science_platform/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 00000000..399f6981
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/android/build.gradle b/project/FrontEnd/collaborative_science_platform/android/build.gradle
new file mode 100644
index 00000000..f7eb7f63
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/android/build.gradle
@@ -0,0 +1,31 @@
+buildscript {
+ ext.kotlin_version = '1.7.10'
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:7.3.0'
+ 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')
+}
+
+tasks.register("clean", Delete) {
+ delete rootProject.buildDir
+}
diff --git a/project/FrontEnd/collaborative_science_platform/android/gradle.properties b/project/FrontEnd/collaborative_science_platform/android/gradle.properties
new file mode 100644
index 00000000..94adc3a3
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/project/FrontEnd/collaborative_science_platform/android/gradle/wrapper/gradle-wrapper.properties b/project/FrontEnd/collaborative_science_platform/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..3c472b99
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
diff --git a/project/FrontEnd/collaborative_science_platform/android/settings.gradle b/project/FrontEnd/collaborative_science_platform/android/settings.gradle
new file mode 100644
index 00000000..55c4ca8b
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/android/settings.gradle
@@ -0,0 +1,20 @@
+pluginManagement {
+ def flutterSdkPath = {
+ def properties = new Properties()
+ file("local.properties").withInputStream { properties.load(it) }
+ def flutterSdkPath = properties.getProperty("flutter.sdk")
+ assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+ return flutterSdkPath
+ }
+ settings.ext.flutterSdkPath = flutterSdkPath()
+
+ includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
+
+ plugins {
+ id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
+ }
+}
+
+include ":app"
+
+apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/project/FrontEnd/collaborative_science_platform/assets/images/.gitkeep b/project/FrontEnd/collaborative_science_platform/assets/images/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/project/FrontEnd/collaborative_science_platform/assets/images/gumball.jpg b/project/FrontEnd/collaborative_science_platform/assets/images/gumball.jpg
new file mode 100644
index 00000000..1a103eb5
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/assets/images/gumball.jpg differ
diff --git a/project/FrontEnd/collaborative_science_platform/assets/images/logo.svg b/project/FrontEnd/collaborative_science_platform/assets/images/logo.svg
new file mode 100644
index 00000000..309058b0
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/assets/images/logo.svg
@@ -0,0 +1,10 @@
+
diff --git a/project/FrontEnd/collaborative_science_platform/assets/images/logo_small.svg b/project/FrontEnd/collaborative_science_platform/assets/images/logo_small.svg
new file mode 100644
index 00000000..45fdec98
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/assets/images/logo_small.svg
@@ -0,0 +1,9 @@
+
diff --git a/project/FrontEnd/collaborative_science_platform/ios/.gitignore b/project/FrontEnd/collaborative_science_platform/ios/.gitignore
new file mode 100644
index 00000000..7a7f9873
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/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/project/FrontEnd/collaborative_science_platform/ios/Flutter/AppFrameworkInfo.plist b/project/FrontEnd/collaborative_science_platform/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 00000000..9625e105
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/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
+ 11.0
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Flutter/Debug.xcconfig b/project/FrontEnd/collaborative_science_platform/ios/Flutter/Debug.xcconfig
new file mode 100644
index 00000000..ec97fc6f
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Flutter/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "Generated.xcconfig"
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Flutter/Release.xcconfig b/project/FrontEnd/collaborative_science_platform/ios/Flutter/Release.xcconfig
new file mode 100644
index 00000000..c4855bfe
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Flutter/Release.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include "Generated.xcconfig"
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Podfile b/project/FrontEnd/collaborative_science_platform/ios/Podfile
new file mode 100644
index 00000000..fdcc671e
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Podfile
@@ -0,0 +1,44 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '11.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+ use_modular_headers!
+
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+ target 'RunnerTests' do
+ inherit! :search_paths
+ end
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings(target)
+ end
+end
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Podfile.lock b/project/FrontEnd/collaborative_science_platform/ios/Podfile.lock
new file mode 100644
index 00000000..b8ae62a0
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Podfile.lock
@@ -0,0 +1,41 @@
+PODS:
+ - Flutter (1.0.0)
+ - path_provider_foundation (0.0.1):
+ - Flutter
+ - FlutterMacOS
+ - share_plus (0.0.1):
+ - Flutter
+ - url_launcher_ios (0.0.1):
+ - Flutter
+ - webview_flutter_wkwebview (0.0.1):
+ - Flutter
+
+DEPENDENCIES:
+ - Flutter (from `Flutter`)
+ - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
+ - share_plus (from `.symlinks/plugins/share_plus/ios`)
+ - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
+ - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
+
+EXTERNAL SOURCES:
+ Flutter:
+ :path: Flutter
+ path_provider_foundation:
+ :path: ".symlinks/plugins/path_provider_foundation/darwin"
+ share_plus:
+ :path: ".symlinks/plugins/share_plus/ios"
+ url_launcher_ios:
+ :path: ".symlinks/plugins/url_launcher_ios/ios"
+ webview_flutter_wkwebview:
+ :path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
+
+SPEC CHECKSUMS:
+ Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
+ path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
+ share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
+ url_launcher_ios: 68d46cc9766d0c41dbdc884310529557e3cd7a86
+ webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f
+
+PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189
+
+COCOAPODS: 1.12.1
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.pbxproj b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..c0a758e3
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,722 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 54;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 02FF4CD218063384DCEAEAC8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D0A5A391ECD9161DC55995B /* Pods_Runner.framework */; };
+ 056A49E2460BDE34B5314BA8 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DCD2063BCD7C1F101D910DF /* Pods_RunnerTests.framework */; };
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
+ 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 PBXContainerItemProxy section */
+ 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 97C146E61CF9000F007C117D /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 97C146ED1CF9000F007C117D;
+ remoteInfo = Runner;
+ };
+/* End PBXContainerItemProxy 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 = ""; };
+ 1DCD2063BCD7C1F101D910DF /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 32C74E9729712222A8B5158E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
+ 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 4F1355D19F7DADF189B2E2FE /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
+ 55EEE95625690A5354939918 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ 6D0A5A391ECD9161DC55995B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 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 = ""; };
+ C9BCC3903ADDB5D03A645E3C /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
+ CAFFDAE927AE442777B510CE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
+ E413BBA2A85238B1F1CFB426 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 706AA10CEA944305A60DBF54 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 056A49E2460BDE34B5314BA8 /* Pods_RunnerTests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 97C146EB1CF9000F007C117D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 02FF4CD218063384DCEAEAC8 /* Pods_Runner.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 20ECD09BD6842EB873BA9DB3 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 6D0A5A391ECD9161DC55995B /* Pods_Runner.framework */,
+ 1DCD2063BCD7C1F101D910DF /* Pods_RunnerTests.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ 331C8082294A63A400263BE5 /* RunnerTests */ = {
+ isa = PBXGroup;
+ children = (
+ 331C807B294A618700263BE5 /* RunnerTests.swift */,
+ );
+ path = RunnerTests;
+ sourceTree = "";
+ };
+ 5927F5EA02CF07A776B8752A /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ E413BBA2A85238B1F1CFB426 /* Pods-Runner.debug.xcconfig */,
+ 55EEE95625690A5354939918 /* Pods-Runner.release.xcconfig */,
+ 32C74E9729712222A8B5158E /* Pods-Runner.profile.xcconfig */,
+ CAFFDAE927AE442777B510CE /* Pods-RunnerTests.debug.xcconfig */,
+ 4F1355D19F7DADF189B2E2FE /* Pods-RunnerTests.release.xcconfig */,
+ C9BCC3903ADDB5D03A645E3C /* Pods-RunnerTests.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
+ 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 */,
+ 331C8082294A63A400263BE5 /* RunnerTests */,
+ 5927F5EA02CF07A776B8752A /* Pods */,
+ 20ECD09BD6842EB873BA9DB3 /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 97C146EF1CF9000F007C117D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146EE1CF9000F007C117D /* Runner.app */,
+ 331C8081294A63A400263BE5 /* RunnerTests.xctest */,
+ );
+ 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 */
+ 331C8080294A63A400263BE5 /* RunnerTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
+ buildPhases = (
+ 676D866CBBD615188A0CD9B1 /* [CP] Check Pods Manifest.lock */,
+ 331C807D294A63A400263BE5 /* Sources */,
+ 331C807F294A63A400263BE5 /* Resources */,
+ 706AA10CEA944305A60DBF54 /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 331C8086294A63A400263BE5 /* PBXTargetDependency */,
+ );
+ name = RunnerTests;
+ productName = RunnerTests;
+ productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ 97C146ED1CF9000F007C117D /* Runner */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+ buildPhases = (
+ 121A7F993AC1A1FBF0251319 /* [CP] Check Pods Manifest.lock */,
+ 9740EEB61CF901F6004384FC /* Run Script */,
+ 97C146EA1CF9000F007C117D /* Sources */,
+ 97C146EB1CF9000F007C117D /* Frameworks */,
+ 97C146EC1CF9000F007C117D /* Resources */,
+ 9705A1C41CF9048500538489 /* Embed Frameworks */,
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ 8F1957FD121E5DDD4C7C3861 /* [CP] Embed Pods Frameworks */,
+ );
+ 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 = {
+ BuildIndependentTargetsInParallel = YES;
+ LastUpgradeCheck = 1430;
+ ORGANIZATIONNAME = "";
+ TargetAttributes = {
+ 331C8080294A63A400263BE5 = {
+ CreatedOnToolsVersion = 14.0;
+ TestTargetID = 97C146ED1CF9000F007C117D;
+ };
+ 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 */,
+ 331C8080294A63A400263BE5 /* RunnerTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 331C807F294A63A400263BE5 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 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 */
+ 121A7F993AC1A1FBF0251319 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
+ );
+ name = "Thin Binary";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+ };
+ 676D866CBBD615188A0CD9B1 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 8F1957FD121E5DDD4C7C3861 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 9740EEB61CF901F6004384FC /* Run Script */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ 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 */
+ 331C807D294A63A400263BE5 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 97C146EA1CF9000F007C117D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 97C146ED1CF9000F007C117D /* Runner */;
+ targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency 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 = 11.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 = com.example.collaborativeSciencePlatform;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Profile;
+ };
+ 331C8088294A63A400263BE5 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = CAFFDAE927AE442777B510CE /* Pods-RunnerTests.debug.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.collaborativeSciencePlatform.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ name = Debug;
+ };
+ 331C8089294A63A400263BE5 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 4F1355D19F7DADF189B2E2FE /* Pods-RunnerTests.release.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.collaborativeSciencePlatform.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ name = Release;
+ };
+ 331C808A294A63A400263BE5 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = C9BCC3903ADDB5D03A645E3C /* Pods-RunnerTests.profile.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.collaborativeSciencePlatform.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ 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 = 11.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 = 11.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 = com.example.collaborativeSciencePlatform;
+ 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 = com.example.collaborativeSciencePlatform;
+ 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 */
+ 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 331C8088294A63A400263BE5 /* Debug */,
+ 331C8089294A63A400263BE5 /* Release */,
+ 331C808A294A63A400263BE5 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 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/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..919434a6
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 00000000..f9b0d7c5
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 00000000..87131a09
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner.xcworkspace/contents.xcworkspacedata b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..21a3cc14
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 00000000..f9b0d7c5
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/AppDelegate.swift b/project/FrontEnd/collaborative_science_platform/ios/Runner/AppDelegate.swift
new file mode 100644
index 00000000..70693e4a
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/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/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..d36b1fab
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/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/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 00000000..dc9ada47
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 00000000..7353c41e
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 00000000..797d452e
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 00000000..6ed2d933
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 00000000..4cd7b009
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 00000000..fe730945
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 00000000..321773cd
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 00000000..797d452e
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 00000000..502f463a
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 00000000..0ec30343
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 00000000..0ec30343
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 00000000..e9f5fea2
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 00000000..84ac32ae
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 00000000..8953cba0
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 00000000..0467bf12
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 00000000..0bedcf2f
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/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/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 00000000..9da19eac
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 00000000..9da19eac
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 00000000..9da19eac
Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 00000000..89c2725b
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/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/project/FrontEnd/collaborative_science_platform/ios/Runner/Base.lproj/LaunchScreen.storyboard b/project/FrontEnd/collaborative_science_platform/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 00000000..f2e259c7
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Base.lproj/Main.storyboard b/project/FrontEnd/collaborative_science_platform/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 00000000..f3c28516
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Info.plist b/project/FrontEnd/collaborative_science_platform/ios/Runner/Info.plist
new file mode 100644
index 00000000..c93af6e0
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Runner/Info.plist
@@ -0,0 +1,54 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Collaborative Science Platform
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ collaborative_science_platform
+ 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
+
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+ io.flutter.embedded_views_preview
+
+
diff --git a/project/FrontEnd/collaborative_science_platform/ios/Runner/Runner-Bridging-Header.h b/project/FrontEnd/collaborative_science_platform/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 00000000..308a2a56
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/project/FrontEnd/collaborative_science_platform/ios/RunnerTests/RunnerTests.swift b/project/FrontEnd/collaborative_science_platform/ios/RunnerTests/RunnerTests.swift
new file mode 100644
index 00000000..86a7c3b1
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/ios/RunnerTests/RunnerTests.swift
@@ -0,0 +1,12 @@
+import Flutter
+import UIKit
+import XCTest
+
+class RunnerTests: XCTestCase {
+
+ func testExample() {
+ // If you add code to the Runner application, consider adding tests here.
+ // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
+ }
+
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/data/.gitkeep b/project/FrontEnd/collaborative_science_platform/lib/data/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/project/FrontEnd/collaborative_science_platform/lib/exceptions/auth_exceptions.dart b/project/FrontEnd/collaborative_science_platform/lib/exceptions/auth_exceptions.dart
new file mode 100644
index 00000000..92673738
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/exceptions/auth_exceptions.dart
@@ -0,0 +1,14 @@
+class NoUserFound implements Exception {
+ String message;
+ NoUserFound({this.message = "No User Found"});
+}
+
+class WrongPasswordException implements Exception {
+ String message;
+ WrongPasswordException({this.message = "Wrong Password"});
+}
+
+class UserExistException implements Exception {
+ String message;
+ UserExistException({this.message = "A user with that username already exists"});
+}
\ No newline at end of file
diff --git a/project/FrontEnd/collaborative_science_platform/lib/exceptions/node_details_exceptions.dart b/project/FrontEnd/collaborative_science_platform/lib/exceptions/node_details_exceptions.dart
new file mode 100644
index 00000000..b7324b59
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/exceptions/node_details_exceptions.dart
@@ -0,0 +1,16 @@
+class NodeDoesNotExist implements Exception {
+ String message;
+ NodeDoesNotExist({this.message = "Node Does Not Exist"});
+}
+
+// this exception is not used anywhere
+class ProofDoesNotExist implements Exception {
+ String message;
+ ProofDoesNotExist({this.message = "Proof Does Not Exist"});
+}
+
+// this exception is not used anywhere
+class TheoremDoesNotExist implements Exception {
+ String message;
+ TheoremDoesNotExist({this.message = "Theorem Does Not Exist"});
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/exceptions/profile_page_exceptions.dart b/project/FrontEnd/collaborative_science_platform/lib/exceptions/profile_page_exceptions.dart
new file mode 100644
index 00000000..7a4dcf8b
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/exceptions/profile_page_exceptions.dart
@@ -0,0 +1,4 @@
+class ProfileDoesNotExist implements Exception {
+ String message;
+ ProfileDoesNotExist({this.message = "Profile Does Not Exist"});
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/exceptions/search_exceptions.dart b/project/FrontEnd/collaborative_science_platform/lib/exceptions/search_exceptions.dart
new file mode 100644
index 00000000..216ab1a6
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/exceptions/search_exceptions.dart
@@ -0,0 +1,9 @@
+class WrongSearchTypeError implements Exception {
+ String message;
+ WrongSearchTypeError({this.message = "Wrong Search Type Error"});
+}
+
+class SearchError implements Exception {
+ String message;
+ SearchError({this.message = "Search Error"});
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/exceptions/workspace_exceptions.dart b/project/FrontEnd/collaborative_science_platform/lib/exceptions/workspace_exceptions.dart
new file mode 100644
index 00000000..b11732ff
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/exceptions/workspace_exceptions.dart
@@ -0,0 +1,56 @@
+class WorkspaceDoesNotExist implements Exception {
+ String message;
+ WorkspaceDoesNotExist({this.message = "Workspace Does Not Exist with the Given ID"});
+}
+
+class SendCollaborationRequestException implements Exception {
+ String message;
+ SendCollaborationRequestException({this.message = "Bad Request"});
+}
+
+class CreateWorkspaceException implements Exception {
+ String message;
+ CreateWorkspaceException(
+ {this.message = "Both workspace_id and workspace_title cannot be empty."});
+}
+
+class WorkspacePermissionException implements Exception {
+ String message;
+ WorkspacePermissionException(
+ {this.message = "You do not have permission to perform this action."});
+}
+
+class AddReferenceException implements Exception {
+ String message;
+ AddReferenceException({this.message = "Bad Request"});
+}
+
+class AddEntryException implements Exception {
+ String message;
+ AddEntryException({this.message = "Bad Request"});
+}
+
+class FinalizeWorkspaceException implements Exception {
+ String message;
+ FinalizeWorkspaceException({this.message = "Bad Request"});
+}
+
+class DeleteReferenceException implements Exception {
+ String message;
+ DeleteReferenceException({this.message = "Bad Request"});
+}
+
+class DeleteWorkspaceException implements Exception {
+ String message;
+ DeleteWorkspaceException({this.message = "Bad Request"});
+}
+
+class EditEntryException implements Exception {
+ String message;
+ EditEntryException({this.message = "Bad Request"});
+}
+
+class DeleteEntryException implements Exception {
+ String message;
+ DeleteEntryException({this.message = "Bad Request"});
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/extensions/.gitkeep b/project/FrontEnd/collaborative_science_platform/lib/extensions/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/project/FrontEnd/collaborative_science_platform/lib/helpers/date_to_string.dart b/project/FrontEnd/collaborative_science_platform/lib/helpers/date_to_string.dart
new file mode 100644
index 00000000..8858f96b
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/helpers/date_to_string.dart
@@ -0,0 +1,48 @@
+
+const Map months = {
+ DateTime.january: "Jan",
+ DateTime.february: "Feb",
+ DateTime.march: "Mar",
+ DateTime.april: "Apr",
+ DateTime.may: "May",
+ DateTime.june: "Jun",
+ DateTime.july: "Jul",
+ DateTime.august: "Aug",
+ DateTime.september: "Sep",
+ DateTime.october: "Oct",
+ DateTime.november: "Nov",
+ DateTime.december: "Dec",
+};
+
+String dateToString(DateTime dateTime) {
+ String? month = months[dateTime.month];
+ return "$month ${dateTime.day}, ${dateTime.year}";
+}
+
+String getDurationFromNow(DateTime dateTime) {
+ DateTime now = DateTime.now();
+ Duration duration = now.difference(dateTime);
+ if (duration.inDays~/365 > 1) {
+ return "${duration.inDays~/365} years ago";
+ } else if (duration.inDays~/365 == 1) {
+ return "1 year ago";
+ } else if (duration.inDays~/30 > 1) {
+ return "${duration.inDays~/30} months ago";
+ } else if (duration.inDays~/30 == 1) {
+ return "1 month ago";
+ } else if (duration.inDays > 1) {
+ return "${duration.inDays} days ago";
+ } else if (duration.inDays == 1) {
+ return "1 day ago";
+ } else if (duration.inHours > 1) {
+ return"${duration.inHours} hours ago";
+ } else if (duration.inHours == 1) {
+ return "1 hour ago";
+ } else if (duration.inMinutes > 1) {
+ return "${duration.inMinutes} minutes ago";
+ } else if (duration.inMinutes == 1) {
+ return "1 minute ago";
+ } else {
+ return "just now";
+ }
+}
\ No newline at end of file
diff --git a/project/FrontEnd/collaborative_science_platform/lib/helpers/node_helper.dart b/project/FrontEnd/collaborative_science_platform/lib/helpers/node_helper.dart
new file mode 100644
index 00000000..941be0d4
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/helpers/node_helper.dart
@@ -0,0 +1,20 @@
+import 'package:collaborative_science_platform/models/node_details_page/node_detailed.dart';
+import 'dart:convert';
+
+class NodeHelper {
+ // Type = true: all text, false: only first 500 characters
+ static getNodeContentLatex(NodeDetailed node, String type) {
+ if (node.theorem == null) {
+ return 'Theorem Content: No theorem';
+ }
+ String theorem = node.theorem!.theoremContent;
+ if (type == "long") {
+ //Theorem Content:
+ return utf8.decode(theorem.codeUnits);
+ }
+ if (theorem.length > 500) {
+ return '${utf8.decode(theorem.substring(0, 500).codeUnits)}...';
+ }
+ return utf8.decode(theorem.codeUnits);
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/helpers/search_helper.dart b/project/FrontEnd/collaborative_science_platform/lib/helpers/search_helper.dart
new file mode 100644
index 00000000..78b880f4
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/helpers/search_helper.dart
@@ -0,0 +1,6 @@
+import 'package:collaborative_science_platform/widgets/app_search_bar.dart';
+
+class SearchHelper {
+ static SearchType searchType = SearchType.theorem;
+ static SearchOption searchOption = SearchOption.exact;
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/main.dart b/project/FrontEnd/collaborative_science_platform/lib/main.dart
new file mode 100644
index 00000000..98a9f70e
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/main.dart
@@ -0,0 +1,80 @@
+import 'package:collaborative_science_platform/providers/auth.dart';
+import 'package:collaborative_science_platform/providers/profile_data_provider.dart';
+import 'package:collaborative_science_platform/providers/node_provider.dart';
+import 'package:collaborative_science_platform/providers/user_provider.dart';
+import 'package:collaborative_science_platform/providers/workspace_provider.dart';
+import 'package:collaborative_science_platform/services/screen_navigation.dart';
+import 'package:collaborative_science_platform/utils/constants.dart';
+import 'package:collaborative_science_platform/utils/router.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_portal/flutter_portal.dart';
+import 'package:flutter_web_plugins/url_strategy.dart';
+import 'package:provider/provider.dart';
+
+void main() {
+ configureApp();
+ runApp(const MyApp());
+}
+
+void configureApp() {
+ if (kIsWeb) {
+ setUrlStrategy(PathUrlStrategy());
+ }
+}
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+ @override
+ Widget build(BuildContext context) {
+ return MultiProvider(
+ providers: [
+ ChangeNotifierProvider(create: (context) => Auth()),
+ ChangeNotifierProvider(create: (context) => ScreenNavigation()),
+ ChangeNotifierProvider(create: (context) => ProfileDataProvider()),
+ ChangeNotifierProvider(create: (context) => NodeProvider()),
+ ChangeNotifierProvider(create: (context) => UserProvider()),
+ ChangeNotifierProvider(create: (context) => WorkspaceProvider()),
+ ],
+ // child: MaterialApp(
+ // debugShowCheckedModeBanner: false,
+ // title: Constants.appName,
+ // routes: {
+ // '/': (context) => const HomePage(),
+ // LoginPage.routeName: (context) => const LoginPage(),
+ // SignUpPage.routeName: (context) => const SignUpPage(),
+ // WorkspacesPage.routeName: (context) => const WorkspacesPage(),
+//
+ // ///ProfilePage.routeName: (context) => const ProfilePage(),
+ // GraphPage.routeName: (context) => const GraphPage(),
+ // NotificationPage.routeName: (context) => const NotificationPage(),
+ // AccountSettingsPage.routeName: (context) => const AccountSettingsPage(),
+ // PleaseLoginPage2.routeName: (context) => const PleaseLoginPage2(),
+ // NodeDetailsPage.routeName: (context) {
+ // final int nodeId = ModalRoute.of(context)!.settings.arguments as int;
+ // return NodeDetailsPage(nodeID: nodeId);
+ // },
+ // ProfilePage.routeName: (context) {
+ // final String email = ModalRoute.of(context)!.settings.arguments as String ?? "";
+ // return ProfilePage(email: email);
+ // },
+ // },
+ // navigatorKey: ScreenNavigation.navigatorKey,
+ // theme: ThemeData(
+ // colorScheme: ColorScheme.fromSeed(seedColor: AppColors.primaryColor),
+ // useMaterial3: true,
+ // ),
+ child: Portal(
+ child: MaterialApp.router(
+ routerConfig: router,
+ debugShowCheckedModeBanner: false,
+ title: Constants.appName,
+ theme: ThemeData(
+ colorScheme: ColorScheme.fromSeed(seedColor: Color.fromARGB(255, 85, 234, 145)),
+ useMaterial3: true,
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/account.dart b/project/FrontEnd/collaborative_science_platform/lib/models/account.dart
new file mode 100644
index 00000000..bccf1b26
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/account.dart
@@ -0,0 +1,26 @@
+import 'package:collaborative_science_platform/models/basic_user.dart';
+
+class Account {
+ BasicUser user;
+ String firstName;
+ String lastName;
+ String email;
+ String password;
+ String profilePictureURL;
+ String idDocumentURL;
+ DateTime registrationDate;
+ String aboutMe;
+ bool notificationsEnabled;
+
+ Account(
+ {required this.user,
+ required this.firstName,
+ required this.lastName,
+ required this.email,
+ required this.password,
+ required this.profilePictureURL,
+ required this.idDocumentURL,
+ required this.registrationDate,
+ required this.aboutMe,
+ required this.notificationsEnabled});
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/annotation.dart b/project/FrontEnd/collaborative_science_platform/lib/models/annotation.dart
new file mode 100644
index 00000000..fe20d8c4
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/annotation.dart
@@ -0,0 +1,25 @@
+import 'package:collaborative_science_platform/models/basic_user.dart';
+
+class Annotation {
+ int annotationID;
+ String annotationType;
+ String annotationVisibilityType;
+ BasicUser owner;
+ Object annotationLocation;
+ int startOffset;
+ int endOffset;
+ DateTime createdAt;
+ DateTime updatedAt;
+
+ Annotation({
+ required this.annotationID,
+ required this.annotationType,
+ required this.annotationVisibilityType,
+ required this.owner,
+ required this.annotationLocation,
+ required this.startOffset,
+ required this.endOffset,
+ required this.createdAt,
+ required this.updatedAt,
+ });
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/base_user.dart b/project/FrontEnd/collaborative_science_platform/lib/models/base_user.dart
new file mode 100644
index 00000000..485a46b5
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/base_user.dart
@@ -0,0 +1,11 @@
+abstract class BaseUser {
+ int sessionID;
+ DateTime logDate;
+ bool privacyPoliciesAccepted;
+
+ BaseUser({
+ required this.sessionID,
+ required this.logDate,
+ required this.privacyPoliciesAccepted,
+ });
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/basic_user.dart b/project/FrontEnd/collaborative_science_platform/lib/models/basic_user.dart
new file mode 100644
index 00000000..a8d635af
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/basic_user.dart
@@ -0,0 +1,22 @@
+class BasicUser {
+ int basicUserId;
+ String bio;
+ bool emailNotificationPreference;
+ bool showActivity;
+
+ BasicUser({
+ required this.basicUserId,
+ required this.bio,
+ required this.emailNotificationPreference,
+ required this.showActivity,
+ });
+ factory BasicUser.fromJson(Map jsonString) {
+ return BasicUser(
+ basicUserId: jsonString["basic_user_id"],
+ bio: jsonString["bio"],
+ emailNotificationPreference: jsonString["email_notification_preference"],
+ showActivity: jsonString["show_activity_preference"],
+ );
+ }
+
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/collaboration_request.dart b/project/FrontEnd/collaborative_science_platform/lib/models/collaboration_request.dart
new file mode 100644
index 00000000..cd72f014
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/collaboration_request.dart
@@ -0,0 +1,23 @@
+import 'package:collaborative_science_platform/models/request.dart';
+import 'package:collaborative_science_platform/models/status.dart';
+
+class CollaborationRequest extends Request {
+ int workspaceID;
+
+ CollaborationRequest({
+ required int requestID,
+ required int senderUserID,
+ required int receiverUserID,
+ required String title,
+ required String body,
+ required Status status,
+ required this.workspaceID,
+ }) : super(
+ requestID: requestID,
+ senderUserID: senderUserID,
+ receiverUserID: receiverUserID,
+ title: title,
+ body: body,
+ status: status,
+ );
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/contributor_user.dart b/project/FrontEnd/collaborative_science_platform/lib/models/contributor_user.dart
new file mode 100644
index 00000000..61ae20fd
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/contributor_user.dart
@@ -0,0 +1,11 @@
+class Contributor {
+ String name;
+ String surname;
+ String email;
+
+ Contributor({
+ required this.name,
+ required this.surname,
+ required this.email,
+ });
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/node.dart b/project/FrontEnd/collaborative_science_platform/lib/models/node.dart
new file mode 100644
index 00000000..2f3beea5
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/node.dart
@@ -0,0 +1,39 @@
+import 'package:collaborative_science_platform/models/user.dart';
+import 'package:intl/intl.dart';
+
+class Node {
+ int id;
+ String nodeTitle;
+ DateTime publishDate;
+ List contributors;
+ Node({
+ required this.contributors,
+ required this.id,
+ required this.nodeTitle,
+ required this.publishDate,
+ });
+
+ String get publishDateFormatted {
+ DateFormat formatter = DateFormat('dd-MM-yyyy');
+ return formatter.format(publishDate);
+ }
+
+ factory Node.fromJson(Map jsonString) {
+ var list = jsonString['authors'] as List;
+ List contributors = list.map((e) => User.fromJson(e)).toList();
+ return Node(
+ id: jsonString['id'],
+ nodeTitle: jsonString['title'],
+ publishDate: DateTime.parse(jsonString['date']),
+ contributors: contributors);
+ }
+ factory Node.fromJsonforNodeDetailPage(Map jsonString) {
+ var list = jsonString['contributors'] as List;
+ List contributors = list.map((e) => User.fromJsonforNodeDetailPage(e)).toList();
+ return Node(
+ id: jsonString['node_id'],
+ nodeTitle: jsonString['node_title'],
+ publishDate: DateTime.parse(jsonString['publish_date']),
+ contributors: contributors);
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/node_details_page/node_detailed.dart b/project/FrontEnd/collaborative_science_platform/lib/models/node_details_page/node_detailed.dart
new file mode 100644
index 00000000..6f52fe18
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/node_details_page/node_detailed.dart
@@ -0,0 +1,80 @@
+import 'package:collaborative_science_platform/models/node.dart';
+import 'package:collaborative_science_platform/models/node_details_page/proof.dart';
+import 'package:collaborative_science_platform/models/node_details_page/question.dart';
+import 'package:collaborative_science_platform/models/theorem.dart';
+import 'package:collaborative_science_platform/models/user.dart';
+
+import 'package:intl/intl.dart';
+
+class NodeDetailed {
+ int nodeId;
+ String nodeTitle;
+ DateTime? publishDate;
+ List contributors;
+ List proof;
+ Theorem? theorem;
+ List reviewers;
+ List references;
+ List citations;
+ bool isValid;
+ int noVisits;
+ List questions;
+
+ //List semanticTags;
+ //List wikiTags;
+ //List annotations;
+
+ NodeDetailed({
+ this.nodeId = 0,
+ this.nodeTitle = "",
+ this.contributors = const [],
+ this.proof = const [],
+ this.theorem,
+ this.publishDate,
+ this.reviewers = const [],
+ this.references = const [],
+ this.citations = const [],
+ this.isValid = true,
+ this.noVisits = 0,
+ this.questions = const [],
+ //required this.semanticTags,
+ //required this.wikiTags,
+ //required this.annotations,
+ });
+
+ String get publishDateFormatted {
+ DateFormat formatter = DateFormat('dd-MM-yyyy');
+ return formatter.format(publishDate!);
+ }
+
+ factory NodeDetailed.fromJson(Map jsonString) {
+ var referencesList = jsonString['from_referenced_nodes'] as List;
+ var citationsList = jsonString['to_referenced_nodes'] as List;
+ var contributorsList = jsonString['contributors'] as List;
+ //var reviewersList = jsonString['reviewers'] as List;
+ var proofsList = jsonString['proofs'] as List;
+ var theorem = Theorem.fromJson(jsonString['theorem']);
+ var questionsList = jsonString['question_set'] as List;
+ List references = referencesList.map((e) => Node.fromJsonforNodeDetailPage(e)).toList();
+ List citations = citationsList.map((e) => Node.fromJsonforNodeDetailPage(e)).toList();
+ List contributors =
+ contributorsList.map((e) => User.fromJsonforNodeDetailPage(e)).toList();
+ //List reviewers = reviewersList.map((e) => User.fromJsonforNodeDetailPage(e)).toList();
+ List proof = proofsList.map((e) => Proof.fromJson(e)).toList();
+ List questions = questionsList.map((e) => Question.fromJson(e)).toList();
+ return NodeDetailed(
+ citations: citations,
+ contributors: contributors,
+ isValid: jsonString['is_valid'],
+ nodeId: jsonString['node_id'],
+ nodeTitle: jsonString['node_title'],
+ noVisits: jsonString['num_visits'],
+ proof: proof,
+ publishDate: DateTime.parse(jsonString['publish_date']),
+ questions: questions,
+ references: references,
+ //reviewers: reviewers,
+ theorem: theorem,
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/node_details_page/proof.dart b/project/FrontEnd/collaborative_science_platform/lib/models/node_details_page/proof.dart
new file mode 100644
index 00000000..28e225dd
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/node_details_page/proof.dart
@@ -0,0 +1,28 @@
+class Proof {
+ //int proofID;
+ //String proofTitle;
+ String proofContent;
+ //bool isValid;
+ //bool isDisproof;
+ String publishDate;
+
+ Proof({
+ //required this.proofID,
+ //required this.proofTitle,
+ required this.proofContent,
+ //required this.isValid,
+ //required this.isDisproof,
+ required this.publishDate,
+ });
+
+ factory Proof.fromJson(Map jsonString) {
+ return Proof(
+ publishDate: jsonString['publish_date'],
+ //proofID: jsonString.containsKey('proof_id') ? jsonString['proof_id'] : "",
+ //proofTitle: jsonString.containsKey('proof_title') ? jsonString['proof_title'] : "",
+ proofContent: jsonString['proof_content'],
+ //isValid: jsonString.containsKey('is_valid') ? jsonString['is_valid'] : false,
+ //isDisproof: jsonString.containsKey('is_disproof') ? jsonString['is_disproof'] : false,
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/node_details_page/question.dart b/project/FrontEnd/collaborative_science_platform/lib/models/node_details_page/question.dart
new file mode 100644
index 00000000..51627119
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/node_details_page/question.dart
@@ -0,0 +1,31 @@
+import 'package:collaborative_science_platform/models/user.dart';
+
+class Question {
+ String content;
+ String createdAt;
+ User? asker;
+ String answer;
+ User? answerer;
+ String answeredAt;
+ Question({
+ required this.content,
+ required this.createdAt,
+ required this.answer,
+ required this.answeredAt,
+ required this.answerer,
+ required this.asker,
+ });
+ factory Question.fromJson(Map jsonString) {
+ return Question(
+ content: jsonString['question_content'] ?? "",
+ createdAt: jsonString['created_at'] ?? "",
+ answer: jsonString['answer_content'] ?? "",
+ answeredAt: jsonString['answered_at'] ?? "",
+ answerer: jsonString['answerer'] == null
+ ? null
+ : User.fromJsonforNodeDetailPage(jsonString['answerer']),
+ asker:
+ jsonString['asker'] == null ? null : User.fromJsonforNodeDetailPage(jsonString['asker']),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/notification.dart b/project/FrontEnd/collaborative_science_platform/lib/models/notification.dart
new file mode 100644
index 00000000..3b343594
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/notification.dart
@@ -0,0 +1,22 @@
+import 'package:collaborative_science_platform/models/basic_user.dart';
+
+class Notification {
+ int notificationID;
+ BasicUser sender;
+ BasicUser receiver;
+ String notificationBody;
+ DateTime notifiedAt;
+ bool readByUser;
+ String notificationType;
+ String notificationTitle;
+
+ Notification(
+ {required this.notificationID,
+ required this.sender,
+ required this.receiver,
+ required this.notificationBody,
+ required this.notifiedAt,
+ required this.readByUser,
+ required this.notificationType,
+ required this.notificationTitle});
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/profile_data.dart b/project/FrontEnd/collaborative_science_platform/lib/models/profile_data.dart
new file mode 100644
index 00000000..be633822
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/profile_data.dart
@@ -0,0 +1,77 @@
+import 'package:collaborative_science_platform/models/user.dart';
+
+class Node {
+ int id;
+ String nodeTitle;
+ String publishDate;
+ List contributors;
+ Node({
+ required this.contributors,
+ required this.id,
+ required this.nodeTitle,
+ required this.publishDate,
+ });
+ factory Node.fromJson(Map jsonString) {
+ var list = jsonString['authors'] as List;
+ List contributors = list.map((e) => User.fromJson(e)).toList();
+ return Node(
+ id: jsonString['id'],
+ nodeTitle: jsonString['title'],
+ publishDate: jsonString['date'],
+ contributors: contributors);
+ }
+}
+
+class ProfileData {
+ String name;
+ String surname;
+ String email;
+ String aboutMe;
+ List nodes;
+ List askedQuestionIDs;
+ List answeredQuestionIDs;
+ ProfileData(
+ {this.aboutMe = "",
+ this.email = "",
+ this.name = "",
+ this.surname = "",
+ this.nodes = const [],
+ this.askedQuestionIDs = const [],
+ this.answeredQuestionIDs = const []});
+ factory ProfileData.fromJson(Map jsonString) {
+ var list = jsonString['nodes'] as List;
+ List nodes = list.map((e) => Node.fromJson(e)).toList();
+ return ProfileData(
+ nodes: nodes,
+ name: jsonString['name'],
+ surname: jsonString['surname'],
+ aboutMe: jsonString['bio'],
+ );
+ }
+
+ static getLoremIpsum(int id) {
+ return ProfileData(
+ name: "Lorem",
+ surname: "Ipsum $id",
+ email: "loremipsum$id@email.com",
+ aboutMe:
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
+ nodes: [
+ Node(
+ id: 1,
+ nodeTitle: "Lorem Ipsum",
+ publishDate: "01.01.2021",
+ contributors: [
+ User(
+ firstName: "Lorem",
+ lastName: "Ipsum $id",
+ email: "loremipsum$id@email.com",
+ ),
+ ],
+ ),
+ ],
+ askedQuestionIDs: [1, 2, 3, 4, 5],
+ answeredQuestionIDs: [1, 2, 3, 4, 5],
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/proof.dart b/project/FrontEnd/collaborative_science_platform/lib/models/proof.dart
new file mode 100644
index 00000000..d57467e9
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/proof.dart
@@ -0,0 +1,17 @@
+class Proof {
+ int proofID;
+ String proofTitle;
+ String proofContent;
+ bool isValid;
+ bool isDisproof;
+ DateTime publishDate;
+
+ Proof({
+ required this.proofID,
+ required this.proofTitle,
+ required this.proofContent,
+ required this.isValid,
+ required this.isDisproof,
+ required this.publishDate,
+ });
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/question.dart b/project/FrontEnd/collaborative_science_platform/lib/models/question.dart
new file mode 100644
index 00000000..c75ffe16
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/question.dart
@@ -0,0 +1,20 @@
+import 'package:collaborative_science_platform/models/basic_user.dart';
+import 'package:collaborative_science_platform/models/contributor_user.dart';
+
+class Question {
+ int questionID;
+ BasicUser askedBy;
+ String questionContent;
+ String answer;
+ DateTime publishDate;
+ Contributor respondedBy;
+
+ Question({
+ required this.questionID,
+ required this.askedBy,
+ required this.questionContent,
+ required this.answer,
+ required this.publishDate,
+ required this.respondedBy,
+ });
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/request.dart b/project/FrontEnd/collaborative_science_platform/lib/models/request.dart
new file mode 100644
index 00000000..0c0bada6
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/request.dart
@@ -0,0 +1,18 @@
+import 'package:collaborative_science_platform/models/status.dart';
+
+abstract class Request {
+ int requestID;
+ int senderUserID;
+ int receiverUserID;
+ String title;
+ String body;
+ Status status;
+
+ Request(
+ {required this.requestID,
+ required this.senderUserID,
+ required this.receiverUserID,
+ required this.title,
+ required this.body,
+ required this.status});
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/review.dart b/project/FrontEnd/collaborative_science_platform/lib/models/review.dart
new file mode 100644
index 00000000..408097f7
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/review.dart
@@ -0,0 +1,19 @@
+/*
+import 'package:collaborative_science_platform/models/annotation.dart';
+import 'package:collaborative_science_platform/models/reviewer.dart';
+import 'package:collaborative_science_platform/models/status.dart';
+
+class Review {
+ Status status;
+ List annotations;
+ Reviewer reviewer;
+ List comments;
+
+ Review({
+ required this.status,
+ required this.annotations,
+ required this.reviewer,
+ required this.comments,
+ });
+}
+*/
\ No newline at end of file
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/review_request.dart b/project/FrontEnd/collaborative_science_platform/lib/models/review_request.dart
new file mode 100644
index 00000000..a827d894
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/review_request.dart
@@ -0,0 +1,28 @@
+/*
+import 'package:collaborative_science_platform/models/request.dart';
+import 'package:collaborative_science_platform/models/status.dart';
+import 'package:collaborative_science_platform/models/review.dart';
+
+class ReviewRequest extends Request {
+ int workspaceID;
+ Review review;
+
+ ReviewRequest({
+ required int requestID,
+ required int senderUserID,
+ required int receiverUserID,
+ required String title,
+ required String body,
+ required Status status,
+ required this.workspaceID,
+ required this.review,
+ }) : super(
+ requestID: requestID,
+ senderUserID: senderUserID,
+ receiverUserID: receiverUserID,
+ title: title,
+ body: body,
+ status: status,
+ );
+}
+*/
\ No newline at end of file
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/reviewer.dart b/project/FrontEnd/collaborative_science_platform/lib/models/reviewer.dart
new file mode 100644
index 00000000..9a2349ef
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/reviewer.dart
@@ -0,0 +1,34 @@
+/*
+import 'package:collaborative_science_platform/models/account.dart';
+import 'package:collaborative_science_platform/models/annotation.dart';
+import 'package:collaborative_science_platform/models/collaboration_request.dart';
+import 'package:collaborative_science_platform/models/contributor_user.dart';
+import 'package:collaborative_science_platform/models/notification.dart';
+import 'package:collaborative_science_platform/models/review_request.dart';
+import 'package:collaborative_science_platform/models/workspace.dart';
+
+class Reviewer extends Contributor {
+ List reviewRequestsGotten;
+
+ Reviewer({
+ required int sessionID,
+ required DateTime logDate,
+ required bool privacyPoliciesAccepted,
+ required Account account,
+ required List notifications,
+ required int userId,
+ required List annotations,
+ required List workspaces,
+ required List collaborationRequestsGotten,
+ required this.reviewRequestsGotten,
+ }) : super(
+ sessionID: sessionID,
+ logDate: logDate,
+ privacyPoliciesAccepted: privacyPoliciesAccepted,
+ account: account,
+ notifications: notifications,
+ userId: userId,
+ workspaces: workspaces,
+ collaborationRequestsGotten: collaborationRequestsGotten,
+ annotations: annotations);
+}*/
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/semantic_tag.dart b/project/FrontEnd/collaborative_science_platform/lib/models/semantic_tag.dart
new file mode 100644
index 00000000..8e078715
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/semantic_tag.dart
@@ -0,0 +1,19 @@
+class SemanticTag {
+ final String id;
+ final String label;
+ final String description;
+
+ SemanticTag({
+ required this.id,
+ required this.label,
+ required this.description,
+ });
+
+ factory SemanticTag.fromJson(Map json) {
+ return SemanticTag(
+ id: json['id'],
+ label: json['label'],
+ description: json['description'],
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/status.dart b/project/FrontEnd/collaborative_science_platform/lib/models/status.dart
new file mode 100644
index 00000000..cb9fa208
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/status.dart
@@ -0,0 +1,5 @@
+enum Status {
+ sended,
+ approved,
+ rejected,
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/tag.dart b/project/FrontEnd/collaborative_science_platform/lib/models/tag.dart
new file mode 100644
index 00000000..00105a60
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/tag.dart
@@ -0,0 +1,13 @@
+abstract class Tag {
+ int tagId;
+ String tagBody;
+ String tagLocation;
+ DateTime createdAt;
+
+ Tag({
+ required this.tagId,
+ required this.tagBody,
+ required this.tagLocation,
+ required this.createdAt,
+ });
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/theorem.dart b/project/FrontEnd/collaborative_science_platform/lib/models/theorem.dart
new file mode 100644
index 00000000..6c321825
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/theorem.dart
@@ -0,0 +1,16 @@
+class Theorem {
+ String theoremContent;
+ String publishDate;
+
+ Theorem(
+ {
+ this.theoremContent = "",
+ this.publishDate = ""});
+
+ factory Theorem.fromJson(Map jsonString) {
+ return Theorem(
+ publishDate: jsonString['publish_date'],
+ theoremContent: jsonString['theorem_content'],
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/user.dart b/project/FrontEnd/collaborative_science_platform/lib/models/user.dart
new file mode 100644
index 00000000..364cb638
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/user.dart
@@ -0,0 +1,31 @@
+class User {
+ int id;
+ String email;
+ String firstName;
+ String lastName;
+ String token;
+
+ User(
+ {this.id = 0,
+ this.token = '',
+ required this.email,
+ required this.firstName,
+ required this.lastName});
+
+ factory User.fromJson(Map jsonString) {
+ return User(
+ //id: jsonString['id'],
+ email: jsonString['username'],
+ firstName: jsonString['name'],
+ lastName: jsonString['surname'],
+ );
+ }
+ factory User.fromJsonforNodeDetailPage(Map jsonString) {
+ return User(
+ id: jsonString['id'],
+ email: jsonString['username'],
+ firstName: jsonString['first_name'],
+ lastName: jsonString['last_name'],
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/wiki_tag.dart b/project/FrontEnd/collaborative_science_platform/lib/models/wiki_tag.dart
new file mode 100644
index 00000000..74b5afe0
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/wiki_tag.dart
@@ -0,0 +1,6 @@
+import 'package:collaborative_science_platform/models/tag.dart';
+
+class WikiTag extends Tag {
+ WikiTag({required int tagId, required String tagBody, required String tagLocation, required DateTime createdAt})
+ : super(tagId: tagId, tagBody: tagBody, tagLocation: tagLocation, createdAt: createdAt);
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/entry.dart b/project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/entry.dart
new file mode 100644
index 00000000..2c3b235e
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/entry.dart
@@ -0,0 +1,42 @@
+import 'package:intl/intl.dart';
+
+class Entry {
+ bool isEditable;
+ bool isFinalEntry;
+ bool isProofEntry;
+ bool isTheoremEntry;
+ DateTime entryDate;
+ int entryId;
+ int entryNumber;
+ int index;
+ String content;
+
+ Entry({
+ required this.content,
+ required this.entryDate,
+ required this.entryId,
+ required this.entryNumber,
+ required this.index,
+ required this.isEditable,
+ required this.isFinalEntry,
+ required this.isProofEntry,
+ required this.isTheoremEntry,
+ });
+ String get publishDateFormatted {
+ DateFormat formatter = DateFormat('dd-MM-yyyy');
+ return formatter.format(entryDate);
+ }
+
+ factory Entry.fromJson(Map jsonString) {
+ return Entry(
+ isEditable: jsonString['is_editable'],
+ isFinalEntry: jsonString['is_final_entry'],
+ isProofEntry: jsonString['is_proof_entry'],
+ isTheoremEntry: jsonString['is_theorem_entry'],
+ entryDate: DateTime.parse(jsonString['entry_date']),
+ entryId: jsonString['entry_id'],
+ entryNumber: jsonString['entry_number'],
+ index: jsonString['entry_index'],
+ content: jsonString['content']);
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/workspace.dart b/project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/workspace.dart
new file mode 100644
index 00000000..de970972
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/workspace.dart
@@ -0,0 +1,57 @@
+import 'package:collaborative_science_platform/models/node.dart';
+import 'package:collaborative_science_platform/models/user.dart';
+import 'package:collaborative_science_platform/models/workspaces_page/entry.dart';
+
+enum WorkspaceStatus {finalized, workable, inReview, published, rejected}
+
+class Workspace {
+ int workspaceId;
+ String workspaceTitle;
+ List entries;
+ WorkspaceStatus status;
+ int numApprovals;
+ List contributors;
+ List pendingContributors;
+ List references;
+ Workspace({
+ required this.workspaceId,
+ required this.workspaceTitle,
+ required this.entries,
+ required this.status,
+ required this.numApprovals,
+ required this.contributors,
+ required this.pendingContributors,
+ required this.references,
+ });
+
+ factory Workspace.fromJson(Map jsonString) {
+ var entryList = jsonString['workspace_entries'] as List;
+ var contributorsList = jsonString['contributors'] as List;
+ var pendingContributorsList = jsonString['pending_contributors'] as List;
+ var referencesList = jsonString['references'] as List;
+
+ List entries = entryList.map((e) => Entry.fromJson(e)).toList();
+ List contributors =
+ contributorsList.map((e) => User.fromJsonforNodeDetailPage(e)).toList();
+ List pendingContributors =
+ pendingContributorsList.map((e) => User.fromJsonforNodeDetailPage(e)).toList();
+ List references = referencesList.map((e) => Node.fromJsonforNodeDetailPage(e)).toList();
+
+ String statusString = jsonString['status'];
+ WorkspaceStatus status = (statusString == "finalized") ? WorkspaceStatus.finalized
+ : (statusString == "workable") ? WorkspaceStatus.workable
+ : (statusString == "in_review") ? WorkspaceStatus.inReview
+ : (statusString == "published") ? WorkspaceStatus.published
+ : WorkspaceStatus.rejected;
+
+ return Workspace(
+ workspaceId: jsonString['workspace_id'],
+ workspaceTitle: jsonString['workspace_title'],
+ entries: entries,
+ status: status,
+ numApprovals: jsonString['num_approvals'],
+ contributors: contributors,
+ pendingContributors: pendingContributors,
+ references: references);
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/workspaces.dart b/project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/workspaces.dart
new file mode 100644
index 00000000..a2bc6e2f
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/workspaces.dart
@@ -0,0 +1,22 @@
+import 'package:collaborative_science_platform/models/workspaces_page/workspaces_object.dart';
+
+class Workspaces {
+ List workspaces;
+ List pendingWorkspaces;
+ Workspaces({
+ required this.workspaces,
+ required this.pendingWorkspaces,
+ });
+
+ factory Workspaces.fromJson(Map jsonString) {
+ var workspacesList = jsonString['workspaces'] as List;
+ var pendingWorkspacesList = jsonString['pending_workspaces'] as List;
+
+ List workspaces =
+ workspacesList.map((e) => WorkspacesObject.fromJson(e)).toList();
+
+ List pendingWorkspaces =
+ pendingWorkspacesList.map((e) => WorkspacesObject.fromJson(e)).toList();
+ return Workspaces(workspaces: workspaces, pendingWorkspaces: pendingWorkspaces);
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/workspaces_object.dart b/project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/workspaces_object.dart
new file mode 100644
index 00000000..04806c29
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/workspaces_object.dart
@@ -0,0 +1,17 @@
+class WorkspacesObject {
+ int workspaceId;
+ String workspaceTitle;
+ bool pending;
+ WorkspacesObject({
+ required this.workspaceId,
+ required this.workspaceTitle,
+ required this.pending,
+ });
+
+ factory WorkspacesObject.fromJson(Map jsonString) {
+ return WorkspacesObject(
+ workspaceId: jsonString['workspace_id'],
+ workspaceTitle: jsonString['workspace_title'],
+ pending: jsonString['pending']);
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/providers/auth.dart b/project/FrontEnd/collaborative_science_platform/lib/providers/auth.dart
new file mode 100644
index 00000000..75c77ca0
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/providers/auth.dart
@@ -0,0 +1,120 @@
+import 'dart:convert';
+
+import 'package:collaborative_science_platform/exceptions/auth_exceptions.dart';
+import 'package:collaborative_science_platform/models/basic_user.dart';
+import 'package:collaborative_science_platform/models/user.dart';
+import 'package:collaborative_science_platform/utils/constants.dart';
+import 'package:flutter/material.dart';
+import 'package:http/http.dart' as http;
+
+class Auth with ChangeNotifier {
+ User? user;
+ BasicUser? basicUser;
+ //User? user = User(email: "utkangezer@gmail.com", firstName: "utkan", lastName: "gezer");
+
+ bool get isSignedIn {
+ return user != null && user!.token.isNotEmpty;
+ }
+
+ Future login(String email, String password) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/login/");
+
+ final Map headers = {
+ "Accept": "application/json",
+ "content-type": "application/json"
+ };
+
+ final String body = json.encode({
+ 'username': email, //kararlaştırılacak
+ 'password': password,
+ });
+
+ try {
+ final response = await http.post(url, headers: headers, body: body);
+ if (response.statusCode == 200) {
+ final data = json.decode(response.body);
+ final token = data['token'];
+
+ Uri url = Uri.parse("${Constants.apiUrl}/get_authenticated_user/");
+ final tokenHeaders = {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ 'Authorization': "Token $token"
+ };
+
+ final tokenResponse = await http.get(url, headers: tokenHeaders);
+ if (tokenResponse.statusCode == 200) {
+ final userData = json.decode(tokenResponse.body);
+ user = User(
+ id: userData['id'],
+ email: userData['email'],
+ firstName: userData['first_name'],
+ lastName: userData['last_name'],
+ token: token);
+ } else {
+ throw Exception("Something has happened");
+ }
+ Uri urlBasicUser = Uri.parse("${Constants.apiUrl}/get_authenticated_basic_user/");
+
+ final basicUserResponse = await http.get(urlBasicUser, headers: tokenHeaders);
+
+ if (basicUserResponse.statusCode == 200) {
+ final basicUserData = json.decode(basicUserResponse.body);
+ basicUser = BasicUser.fromJson(basicUserData);
+ } else {
+ throw Exception("Something has happened");
+ }
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw WrongPasswordException(message: 'Your credentials are wrong');
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future signup(String name, String surname, String email, String password) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/signup/");
+
+ final Map headers = {'Content-Type': 'application/json; charset=UTF-8'};
+
+ final String body = json.encode({
+ 'username': email,
+ 'email': email,
+ 'first_name': name,
+ 'last_name': surname,
+ 'password': password,
+ 'password2': password,
+ });
+
+ final response = await http.post(url, headers: headers, body: body);
+
+ if (response.statusCode == 201) {
+ final data = json.decode(response.body);
+ user = User(
+ // TODO: fix this
+ id: data['id'],
+ email: data['email'],
+ firstName: data['first_name'],
+ lastName: data['last_name']);
+ try {
+ await login(email, password);
+ } catch (e) {
+ throw Exception("Something has happened");
+ }
+
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw UserExistException(message: 'A user with that username already exists');
+ } else {
+ throw Exception("Something has happened");
+ }
+ }
+
+ void logout() {
+ user = null;
+ notifyListeners();
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/providers/node_provider.dart b/project/FrontEnd/collaborative_science_platform/lib/providers/node_provider.dart
new file mode 100644
index 00000000..5532ab0e
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/providers/node_provider.dart
@@ -0,0 +1,165 @@
+import 'dart:convert';
+import 'package:collaborative_science_platform/exceptions/node_details_exceptions.dart';
+import 'package:collaborative_science_platform/exceptions/search_exceptions.dart';
+import 'package:collaborative_science_platform/models/node.dart';
+import 'package:collaborative_science_platform/models/node_details_page/node_detailed.dart';
+import 'package:collaborative_science_platform/models/semantic_tag.dart';
+import 'package:collaborative_science_platform/models/user.dart';
+import 'package:collaborative_science_platform/utils/constants.dart';
+import 'package:collaborative_science_platform/widgets/app_search_bar.dart';
+import 'package:flutter/material.dart';
+import 'package:http/http.dart' as http;
+
+class NodeProvider with ChangeNotifier {
+ final List _searchNodeResult = [];
+ final List _youMayLikeNodeResult = [];
+ NodeDetailed? nodeDetailed;
+
+ final List semanticTags = [];
+
+ void clearAll() {
+ nodeDetailed = null;
+ }
+
+ List get searchNodeResult {
+ return [..._searchNodeResult];
+ }
+
+ List get youMayLikeNodeResult {
+ return [..._youMayLikeNodeResult];
+ }
+
+ SemanticTag getSemanticTag(String label) {
+ return semanticTags.firstWhere((element) => element.label == label);
+ }
+
+ Future search(SearchType type, String query,
+ {bool random = false, bool semantic = false, bool suggestions = false}) async {
+ if (type == SearchType.author) {
+ throw WrongSearchTypeError();
+ }
+ String queryType = searchTypeToString[type]!;
+ if (random) {
+ queryType = "random";
+ }
+ if (semantic) {
+ queryType = "semantic";
+ }
+ Uri url = Uri.parse("${Constants.apiUrl}/search/?query=$query&type=$queryType");
+ final Map headers = {
+ "Accept": "application/json",
+ "content-type": "application/json"
+ };
+ try {
+ final response = await http.get(url, headers: headers);
+
+ if (response.statusCode == 200) {
+ final data = json.decode(response.body);
+ if (suggestions) {
+ _youMayLikeNodeResult.clear();
+ _youMayLikeNodeResult.addAll((data['nodes'] as List).map((node) => Node(
+ contributors: (node['authors'] as List)
+ .map((author) => User(
+ id: author['id'],
+ firstName: author['name'],
+ lastName: author['surname'],
+ email: author['username']))
+ .toList(),
+ id: node['id'],
+ nodeTitle: node['title'],
+ publishDate: DateTime.parse(node['date']),
+ )));
+ notifyListeners();
+ return;
+ }
+ _searchNodeResult.clear();
+ _searchNodeResult.addAll((data['nodes'] as List).map((node) => Node(
+ contributors: (node['authors'] as List)
+ .map((author) => User(
+ id: author['id'],
+ firstName: author['name'],
+ lastName: author['surname'],
+ email: author['username']))
+ .toList(),
+ id: node['id'],
+ nodeTitle: node['title'],
+ publishDate: DateTime.parse(node['date']),
+ )));
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw SearchError();
+ } else {
+ throw Exception("Error");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future semanticSuggestions(String keyword) async {
+ semanticTags.clear();
+ Uri url = Uri.parse("${Constants.apiUrl}/get_semantic_suggestion/?keyword=$keyword");
+ final Map headers = {
+ "Accept": "application/json",
+ "content-type": "application/json"
+ };
+ try {
+ final response = await http.get(url, headers: headers);
+ if (response.statusCode == 200) {
+ var data = json.decode(response.body);
+ data = data["suggestions"];
+ for (var element in data) {
+ semanticTags.add(SemanticTag.fromJson(element));
+ }
+ } else if (response.statusCode == 400) {
+ throw SearchError();
+ } else {
+ if (json.decode(response.body)["message"] == "There are no nodes with this semantic tag.") {
+ throw SearchError();
+ }
+ throw Exception("Error");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future getNode(int id) async {
+ clearAll();
+ Uri url = Uri.parse("${Constants.apiUrl}/get_node/");
+
+ if (id > -1) {
+ url = Uri.parse("${Constants.apiUrl}/get_node/?node_id=$id");
+ }
+
+ final Map headers = {
+ "Accept": "application/json",
+ "content-type": "application/json"
+ };
+ try {
+ final response = await http.get(url, headers: headers);
+ if (response.statusCode == 200) {
+ final data = json.decode(response.body);
+ nodeDetailed = NodeDetailed.fromJson(data);
+ notifyListeners();
+ } else if (response.statusCode == 404) {
+ throw NodeDoesNotExist();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (error) {
+ rethrow;
+ }
+ }
+
+ Future getNodeSuggestions() async {
+ await search(SearchType.theorem, "", random: true, suggestions: true);
+ }
+
+ Map searchTypeToString = {
+ SearchType.theorem: "node",
+ SearchType.author: "author",
+ SearchType.by: "by",
+ SearchType.both: "all"
+ };
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/providers/profile_data_provider.dart b/project/FrontEnd/collaborative_science_platform/lib/providers/profile_data_provider.dart
new file mode 100644
index 00000000..fc6a4758
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/providers/profile_data_provider.dart
@@ -0,0 +1,33 @@
+import 'dart:convert';
+import 'package:collaborative_science_platform/exceptions/profile_page_exceptions.dart';
+import 'package:collaborative_science_platform/models/profile_data.dart';
+import 'package:collaborative_science_platform/utils/constants.dart';
+import 'package:flutter/material.dart';
+import 'package:http/http.dart' as http;
+
+class ProfileDataProvider with ChangeNotifier {
+ ProfileData? profileData;
+
+ Future getData(String email) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/get_profile_info/?mail=$email");
+ final Map headers = {
+ "Accept": "application/json",
+ "content-type": "application/json"
+ };
+ try {
+ final response = await http.get(url, headers: headers);
+ if (response.statusCode == 200) {
+ final data = json.decode(response.body);
+ profileData = ProfileData.fromJson(data);
+ profileData!.email = email;
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw ProfileDoesNotExist();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/providers/settings_provider.dart b/project/FrontEnd/collaborative_science_platform/lib/providers/settings_provider.dart
new file mode 100644
index 00000000..f122e1da
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/providers/settings_provider.dart
@@ -0,0 +1,57 @@
+import 'dart:convert';
+import 'package:collaborative_science_platform/models/user.dart';
+import 'package:collaborative_science_platform/utils/constants.dart';
+import 'package:flutter/material.dart';
+import 'package:http/http.dart' as http;
+
+class SettingsProvider with ChangeNotifier {
+ Future changePassword(User? user, String oldPass, String newPass) async {
+ final Map header = {
+ "Accept": "application/json",
+ "content-type": "application/json",
+ 'Authorization': "Token"// ${user!.token}",
+ };
+
+ try {
+ final response = await http.put(
+ Uri.parse("${Constants.apiUrl}/change_password/"),
+ headers: header,
+ body: jsonEncode(
+ {
+ 'old_password': oldPass,
+ 'new_password': newPass,
+ },
+ ),
+ );
+ print(response.statusCode);
+ return response.statusCode;
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future changePreferences(User? user, String bio, bool sendNotification, bool showActivity) async {
+ final Map header = {
+ "Accept": "application/json",
+ "content-type": "application/json",
+ 'Authorization': "Token"// ${user!.token}",
+ };
+
+ try {
+ final response = await http.put(
+ Uri.parse("${Constants.apiUrl}/change_profile_settings/"),
+ headers: header,
+ body: jsonEncode(
+ {
+ 'bio': bio,
+ 'email_notification_preference': sendNotification.toString(),
+ 'show_activity_preference': showActivity.toString()
+ },
+ ),
+ );
+ print(response.statusCode);
+ } catch (e) {
+ rethrow;
+ }
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/providers/user_provider.dart b/project/FrontEnd/collaborative_science_platform/lib/providers/user_provider.dart
new file mode 100644
index 00000000..a2d243f1
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/providers/user_provider.dart
@@ -0,0 +1,58 @@
+import 'dart:convert';
+import 'package:collaborative_science_platform/exceptions/search_exceptions.dart';
+import 'package:collaborative_science_platform/models/profile_data.dart';
+import 'package:collaborative_science_platform/utils/constants.dart';
+import 'package:collaborative_science_platform/widgets/app_search_bar.dart';
+import 'package:flutter/material.dart';
+import 'package:http/http.dart' as http;
+
+class UserProvider with ChangeNotifier {
+ final List _searchUserResult = [];
+
+ List get searchUserResult {
+ return [..._searchUserResult];
+ }
+
+ Future search(SearchType type, String query) async {
+ _searchUserResult.clear();
+ if (type == SearchType.theorem || type == SearchType.by) {
+ throw WrongSearchTypeError();
+ }
+
+ String queryType = searchTypeToString[type]!;
+ Uri url =
+ Uri.parse("${Constants.apiUrl}/search/?query=$query&type=$queryType");
+ final Map headers = {
+ "Accept": "application/json",
+ "content-type": "application/json"
+ };
+ try {
+ final response = await http.get(url, headers: headers);
+
+ if (response.statusCode == 200) {
+ final data = json.decode(response.body);
+
+ _searchUserResult.addAll(
+ (data['authors'] as List).map((author) => ProfileData(
+ name: author['name'],
+ surname: author['surname'],
+ email: author['username'],
+ )));
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw SearchError();
+ } else {
+ throw Exception("Error");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+}
+
+Map searchTypeToString = {
+ SearchType.theorem: "node",
+ SearchType.author: "author",
+ SearchType.by: "by",
+ SearchType.both: "all"
+};
diff --git a/project/FrontEnd/collaborative_science_platform/lib/providers/workspace_provider.dart b/project/FrontEnd/collaborative_science_platform/lib/providers/workspace_provider.dart
new file mode 100644
index 00000000..4d15c556
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/providers/workspace_provider.dart
@@ -0,0 +1,362 @@
+import 'dart:convert';
+import 'package:collaborative_science_platform/exceptions/workspace_exceptions.dart';
+import 'package:collaborative_science_platform/models/workspaces_page/workspace.dart';
+import 'package:collaborative_science_platform/models/workspaces_page/workspaces.dart';
+import 'package:collaborative_science_platform/utils/constants.dart';
+import 'package:flutter/material.dart';
+import 'package:http/http.dart' as http;
+
+class WorkspaceProvider with ChangeNotifier {
+ Workspaces? workspaces;
+ Workspace? workspace;
+
+ void clearAll() {
+ workspace = null;
+ workspaces = null;
+ }
+
+ Future getUserWorkspaces(int id, String token) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/get_user_workspaces/?user_id=$id");
+ final Map headers = {
+ "Authorization": "Token $token",
+ "content-type": "application/json"
+ };
+ try {
+ final response = await http.get(url, headers: headers);
+ if (response.statusCode == 200) {
+ final data = json.decode(response.body);
+ workspaces = Workspaces.fromJson(data);
+ notifyListeners();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (error) {
+ rethrow;
+ }
+ }
+
+ Future getWorkspaceById(int id, String token) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/get_workspace/?workspace_id=$id");
+ final Map headers = {
+ "Authorization": "Token $token",
+ "content-type": "application/json"
+ };
+ try {
+ final response = await http.get(url, headers: headers);
+ if (response.statusCode == 200) {
+ final data = json.decode(response.body);
+ workspace = Workspace.fromJson(data);
+ notifyListeners();
+ } else if (response.statusCode == 404) {
+ throw WorkspaceDoesNotExist();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (error) {
+ rethrow;
+ }
+ }
+
+ Future sendCollaborationRequest(
+ int senderId, int receiverId, String title,
+ String requestBody, int workspaceId, String token) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/send_collab_req/");
+
+ final Map headers = {
+ "Authorization": "Token $token",
+ "content-type": "application/json"
+ };
+
+ final String body = json.encode({
+ 'sender': senderId,
+ 'receiver': receiverId,
+ 'title': title,
+ 'body': requestBody,
+ 'workspace': workspaceId
+ });
+
+ try {
+ final response = await http.post(url, headers: headers, body: body);
+ if (response.statusCode == 200) {
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw SendCollaborationRequestException();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future updateRequest(int id, String status, String token) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/update_req");
+
+ final Map headers = {
+ "Authorization": "Token $token",
+ "content-type": "application/json"
+ };
+
+ final String body = json.encode({'id': id, 'status': status});
+
+ try {
+ final response = await http.put(url, headers: headers, body: body);
+ if (response.statusCode == 200) {
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw SendCollaborationRequestException();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future createWorkspace(String title, String token) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/workspace_post/?format=json");
+
+ final Map headers = {
+ "Authorization": "Token $token",
+ "content-type": "application/json"
+ };
+
+ final String body = json.encode({
+ 'workspace_title': title,
+ });
+
+ try {
+ final response = await http.post(url, headers: headers, body: body);
+ if (response.statusCode == 200) {
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw CreateWorkspaceException();
+ } else if (response.statusCode == 403) {
+ throw WorkspacePermissionException();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future addSemanticTags(int id, String token, List semanticTags) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/workspace_post/");
+
+ final Map headers = {
+ "Authorization": token,
+ "content-type": "application/json"
+ };
+
+ final String body = json.encode({
+ 'workspace_id': id,
+ 'semantic_tags': semanticTags,
+ });
+
+ try {
+ final response = await http.post(url, headers: headers, body: body);
+ if (response.statusCode == 200) {
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw CreateWorkspaceException();
+ } else if (response.statusCode == 403) {
+ throw WorkspacePermissionException();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future updateWorkspaceTitle(int id, String token, String title) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/workspace_post/");
+
+ final Map headers = {
+ "Authorization": token,
+ "content-type": "application/json"
+ };
+
+ final String body = json.encode({
+ 'workspace_id': id,
+ 'workspace_title': title,
+ });
+
+ try {
+ final response = await http.post(url, headers: headers, body: body);
+ if (response.statusCode == 200) {
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw CreateWorkspaceException();
+ } else if (response.statusCode == 403) {
+ throw WorkspacePermissionException();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future addReference(int workspaceId, int nodeId, String token) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/add_reference/");
+
+ final Map headers = {
+ "Authorization": "Token $token",
+ "content-type": "application/json"
+ };
+
+ final String body = json.encode({
+ 'workspace_id': workspaceId,
+ 'node_id': nodeId,
+ });
+
+ try {
+ final response = await http.post(url, headers: headers, body: body);
+ if (response.statusCode == 200) {
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw AddReferenceException();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future addEntry(String content, int workspaceId, String token) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/add_entry/");
+
+ final Map headers = {
+ "Authorization": "Token $token",
+ "content-type": "application/json"
+ };
+
+ final String body = json.encode({
+ 'workspace_id': workspaceId,
+ 'entry_content': content,
+ });
+
+ try {
+ final response = await http.post(url, headers: headers, body: body);
+ if (response.statusCode == 200) {
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw AddEntryException();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future finalizeWorkspace(int workspaceId, String token) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/finalize_workspace/");
+
+ final Map headers = {
+ "Authorization": "Token $token",
+ "content-type": "application/json"
+ };
+
+ final String body = json.encode({
+ 'workspace_id': workspaceId,
+ });
+
+ try {
+ final response = await http.post(url, headers: headers, body: body);
+ if (response.statusCode == 200) {
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw FinalizeWorkspaceException();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future deleteReference(int workspaceId, int nodeId, String token) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/delete_reference/");
+
+ final Map headers = {
+ "Authorization": "Token $token",
+ "content-type": "application/json"
+ };
+
+ final String body = json.encode({
+ 'workspace_id': workspaceId,
+ 'node_id': nodeId,
+ });
+
+ try {
+ final response = await http.post(url, headers: headers, body: body);
+ if (response.statusCode == 200) {
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw DeleteReferenceException();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future editEntry(String content, int entryId, String token) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/edit_entry/");
+
+ final Map headers = {
+ "Authorization": "Token $token",
+ "content-type": "application/json"
+ };
+
+ final String body = json.encode({
+ 'entry_id': entryId,
+ 'content': content,
+ });
+
+ try {
+ final response = await http.post(url, headers: headers, body: body);
+ if (response.statusCode == 200) {
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw EditEntryException();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future deleteEntry(int entryId, int workspaceId, String token) async {
+ Uri url = Uri.parse("${Constants.apiUrl}/add_entry/");
+
+ final Map headers = {
+ "Authorization": "Token $token",
+ "content-type": "application/json"
+ };
+
+ final String body = json.encode({
+ 'workspace_id': workspaceId,
+ 'entry_id': entryId,
+ });
+
+ try {
+ final response = await http.post(url, headers: headers, body: body);
+ if (response.statusCode == 200) {
+ notifyListeners();
+ } else if (response.statusCode == 400) {
+ throw DeleteEntryException();
+ } else {
+ throw Exception("Something has happened");
+ }
+ } catch (e) {
+ rethrow;
+ }
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/login_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/login_page.dart
new file mode 100644
index 00000000..60ed6f8d
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/login_page.dart
@@ -0,0 +1,259 @@
+import 'package:collaborative_science_platform/exceptions/auth_exceptions.dart';
+import 'package:collaborative_science_platform/providers/auth.dart';
+import 'package:collaborative_science_platform/screens/auth_screens/signup_page.dart';
+import 'package:collaborative_science_platform/screens/home_page/home_page.dart';
+import 'package:collaborative_science_platform/utils/colors.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:collaborative_science_platform/widgets/app_button.dart';
+import 'package:collaborative_science_platform/widgets/app_text_field.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:provider/provider.dart';
+import 'package:go_router/go_router.dart';
+
+class LoginPage extends StatefulWidget {
+ static const routeName = '/login';
+ const LoginPage({super.key});
+
+ @override
+ State createState() => _LoginPageState();
+}
+
+class _LoginPageState extends State {
+ final emailController = TextEditingController();
+ final passwordController = TextEditingController();
+
+ final emailFocusNode = FocusNode();
+ final passwordFocusNode = FocusNode();
+
+ bool obscuredPassword = true;
+ bool error = false;
+ bool isLoading = false;
+ bool buttonState = false;
+
+ String errorMessage = "";
+
+ @override
+ void dispose() {
+ emailController.dispose();
+ passwordController.dispose();
+ emailFocusNode.dispose();
+ passwordFocusNode.dispose();
+ super.dispose();
+ }
+
+ Future authenticate() async {
+ if (!validate()) {
+ return false;
+ }
+ try {
+ final auth = Provider.of(context, listen: false);
+ setState(() {
+ isLoading = true;
+ });
+ await auth.login(emailController.text, passwordController.text);
+ } on WrongPasswordException {
+ setState(() {
+ error = true;
+ errorMessage = "Username or password is wrong.";
+ });
+ } catch (e) {
+ setState(() {
+ error = true;
+ errorMessage = "Something went wrong.";
+ });
+ } finally {
+ setState(() {
+ isLoading = false;
+ });
+ }
+ return error ? false : true;
+ }
+
+ bool validate() {
+ if (emailController.text.isEmpty || passwordController.text.isEmpty) {
+ setState(() {
+ error = true;
+ errorMessage = "All fields are mandatory.";
+ });
+ return false;
+ } else {
+ setState(() {
+ error = false;
+ errorMessage = "";
+ });
+ }
+ return true;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ width: Responsive.isMobile(context) ? MediaQuery.of(context).size.width : 600,
+ padding: const EdgeInsets.only(top: 40.0, right: 16, left: 16),
+ child: SingleChildScrollView(
+ // To avoid Render Pixel Overflow
+ scrollDirection: Axis.vertical,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ MouseRegion(
+ cursor: SystemMouseCursors.click,
+ child: GestureDetector(
+ onTap: () {
+ context.go(HomePage.routeName);
+ },
+ child: Container(
+ color: Colors.transparent,
+ child: SvgPicture.asset(
+ "assets/images/logo.svg",
+ width: 394.0,
+ height: 120.0,
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(height: 40.0), //to add space
+ AppTextField(
+ controller: emailController,
+ focusNode: emailFocusNode,
+ hintText: 'Email',
+ obscureText: false,
+ color: error && emailController.text.isEmpty
+ ? AppColors.dangerColor
+ : AppColors.primaryColor,
+ prefixIcon: const Icon(Icons.person),
+ height: 64.0,
+ onChanged: (_) {
+ if (emailController.text.isEmpty || passwordController.text.isEmpty) {
+ setState(() {
+ buttonState = false;
+ });
+ } else {
+ setState(() {
+ buttonState = true;
+ });
+ }
+ },
+ ),
+ const SizedBox(height: 8.0),
+ AppTextField(
+ controller: passwordController,
+ focusNode: passwordFocusNode,
+ hintText: 'Password',
+ obscureText: obscuredPassword,
+ color: error && passwordController.text.isEmpty
+ ? AppColors.dangerColor
+ : AppColors.primaryColor,
+ prefixIcon: const Icon(Icons.lock),
+ suffixIcon: IconButton(
+ onPressed: () {
+ setState(() {
+ obscuredPassword = !obscuredPassword; //eye icon to work
+ });
+ },
+ icon: obscuredPassword
+ ? const Icon(Icons.visibility)
+ : const Icon(Icons.visibility_off),
+ ),
+ height: 64.0,
+ onChanged: (_) {
+ if (emailController.text.isEmpty || passwordController.text.isEmpty) {
+ setState(() {
+ buttonState = false;
+ });
+ } else {
+ setState(() {
+ buttonState = true;
+ });
+ }
+ },
+ ),
+ if (error) //all error messages
+ Padding(
+ padding: const EdgeInsets.only(top: 8.0),
+ child: SelectableText(
+ errorMessage,
+ style: const TextStyle(color: AppColors.dangerColor),
+ ),
+ ),
+ const SizedBox(height: 10.0),
+ SingleChildScrollView(
+ // To avoid Render Pixel Overflow
+ scrollDirection: Axis.horizontal,
+ child: Row(
+ children: [
+ const SizedBox(width: 16.0),
+ MouseRegion(
+ cursor: SystemMouseCursors.click,
+ child: GestureDetector(
+ onTap: () {/* Direct user to the password recovery page */},
+ child: const Text(
+ "Forgot your password?",
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ color: AppColors.hyperTextColor,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 20.0),
+ AppButton(
+ onTap: () async {
+ if (await authenticate() && mounted) {
+ // Navigate to home page if authentication is successful
+ context.go(HomePage.routeName);
+ }
+ },
+ text: "Log in",
+ height: 64,
+ isLoading: isLoading,
+ isActive: buttonState,
+ ),
+ const SizedBox(height: 10.0),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Flexible(
+ child: SelectableText(
+ "Don't have an account?",
+ maxLines: 1,
+ style: TextStyle(
+ color: Colors.grey.shade700,
+ ),
+ ),
+ ),
+ const SizedBox(width: 4.0),
+ MouseRegion(
+ cursor: SystemMouseCursors.click,
+ child: GestureDetector(
+ onTap: () {
+ context.go(SignUpPage.routeName);
+ },
+ child: const Text(
+ "Sign up now",
+ style: TextStyle(
+ fontWeight: FontWeight.bold, color: AppColors.hyperTextColor),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/please_login_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/please_login_page.dart
new file mode 100644
index 00000000..0d8e0d8d
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/please_login_page.dart
@@ -0,0 +1,48 @@
+import 'package:collaborative_science_platform/screens/auth_screens/widgets/please_login_signup.dart';
+import 'package:collaborative_science_platform/screens/auth_screens/widgets/please_login_prompts.dart';
+import 'package:collaborative_science_platform/screens/home_page/widgets/home_page_appbar.dart';
+import 'package:collaborative_science_platform/screens/page_with_appbar/page_with_appbar.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:flutter/material.dart';
+
+class PleaseLoginPage extends StatelessWidget {
+ static const routeName = '/please-login';
+ final String? pageType;
+ const PleaseLoginPage({
+ super.key,
+ this.pageType,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return PageWithAppBar(
+ appBar: const HomePageAppBar(),
+ child: SizedBox(
+ width: Responsive.getGenericPageWidth(context),
+ child: SingleChildScrollView(
+ child: Column(
+ children: [
+ const Padding(
+ padding: EdgeInsets.fromLTRB(15.0, 0.0, 15.0, 0.0),
+ child: Divider(height: 40.0),
+ ),
+ if (pageType == "notifications") NotificationExplanation(),
+ if (pageType == "workspaces") WorkspaceExplanation(),
+ if (pageType == "profile") ProfileExplanation(),
+ if (pageType != null)
+ Padding(
+ padding: EdgeInsets.fromLTRB(15.0, 0.0, 15.0, 0.0),
+ child: Divider(height: 40.0),
+ ),
+ PleaseLoginSignup(),
+ const Padding(
+ padding: EdgeInsets.fromLTRB(15.0, 0.0, 15.0, 0.0),
+ child: Divider(height: 40.0),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/signup_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/signup_page.dart
new file mode 100644
index 00000000..d019f6d6
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/signup_page.dart
@@ -0,0 +1,350 @@
+import 'package:collaborative_science_platform/exceptions/auth_exceptions.dart';
+import 'package:collaborative_science_platform/providers/auth.dart';
+import 'package:collaborative_science_platform/screens/auth_screens/login_page.dart';
+import 'package:collaborative_science_platform/screens/auth_screens/widgets/strong_password_checks.dart';
+import 'package:collaborative_science_platform/screens/home_page/home_page.dart';
+import 'package:collaborative_science_platform/utils/colors.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:collaborative_science_platform/widgets/app_button.dart';
+import 'package:collaborative_science_platform/widgets/app_text_field.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:provider/provider.dart';
+import 'package:go_router/go_router.dart';
+
+class SignUpPage extends StatefulWidget {
+ static const routeName = '/signup';
+ const SignUpPage({super.key});
+
+ @override
+ State createState() => _SignUpPageState();
+}
+
+class _SignUpPageState extends State {
+ final emailController = TextEditingController();
+ final passwordController = TextEditingController();
+ final confirmPasswordController = TextEditingController();
+ final nameController = TextEditingController();
+ final surnameController = TextEditingController();
+
+ final emailFocusNode = FocusNode();
+ final passwordFocusNode = FocusNode();
+ final confirmPasswordFocusNode = FocusNode();
+ final nameFocusNode = FocusNode();
+ final surnameFocusNode = FocusNode();
+
+ bool buttonState = false;
+ bool isLoading = false;
+
+ bool obscuredPassword = true;
+ bool error = false;
+
+ bool passwordMatchError = false;
+ bool weakPasswordError = false;
+
+ String errorMessage = "";
+
+ @override
+ void dispose() {
+ emailController.dispose();
+ passwordController.dispose();
+ nameController.dispose();
+ confirmPasswordController.dispose();
+ surnameController.dispose();
+ emailFocusNode.dispose();
+ passwordFocusNode.dispose();
+ nameFocusNode.dispose();
+ confirmPasswordFocusNode.dispose();
+ surnameFocusNode.dispose();
+ super.dispose();
+ }
+
+ Future authenticate() async {
+ if (!validate()) {
+ return false;
+ }
+ try {
+ final auth = Provider.of(context, listen: false);
+ setState(() {
+ isLoading = true;
+ });
+ await auth.signup(nameController.text, surnameController.text, emailController.text,
+ passwordController.text);
+ } on UserExistException {
+ setState(() {
+ error = true;
+ errorMessage = "A user with that username already exists";
+ });
+ } catch (e) {
+ setState(() {
+ error = true;
+ errorMessage = "Something went wrong!";
+ });
+ } finally {
+ setState(() {
+ isLoading = false;
+ });
+ }
+ return error ? false : true;
+ }
+
+ void validateStrongPassword() {
+ if (StrongPasswordChecks.passedAllPasswordCriteria(
+ passwordController.text, confirmPasswordController.text) &&
+ nameController.text.isNotEmpty &&
+ surnameController.text.isNotEmpty &&
+ emailController.text.isNotEmpty) {
+ setState(() {
+ buttonState = true;
+ });
+ } else {
+ setState(() {
+ buttonState = false;
+ });
+ }
+ }
+
+ bool validate() {
+ if (nameController.text.isEmpty ||
+ surnameController.text.isEmpty ||
+ emailController.text.isEmpty ||
+ passwordController.text.isEmpty ||
+ confirmPasswordController.text.isEmpty) {
+ setState(() {
+ error = true;
+ errorMessage = "All fields are mandatory!";
+ });
+ return false;
+ } else if (!StrongPasswordChecks.passedAllPasswordCriteria(
+ passwordController.text, confirmPasswordController.text)) {
+ setState(() {
+ error = true;
+ weakPasswordError = true;
+ errorMessage = "Your password is not strong enough!";
+ });
+ return false;
+ } else if (passwordController.text != confirmPasswordController.text) {
+ setState(() {
+ error = true;
+ passwordMatchError = true;
+ errorMessage = "Passwords do not match!";
+ });
+ return false;
+ } else {
+ setState(() {
+ error = false;
+ weakPasswordError = false;
+ passwordMatchError = false;
+ errorMessage = "";
+ });
+ }
+ return true;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ width: Responsive.isMobile(context) ? MediaQuery.of(context).size.width : 600,
+ padding: const EdgeInsets.only(top: 40.0, right: 16, left: 16),
+ child: SingleChildScrollView(
+ // To avoid Render Pixel Overflow
+ scrollDirection: Axis.vertical,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ MouseRegion(
+ cursor: SystemMouseCursors.click,
+ child: GestureDetector(
+ onTap: () {
+ context.go(HomePage.routeName);
+ },
+ child: Container(
+ color: Colors.transparent,
+ child: SvgPicture.asset(
+ "assets/images/logo.svg",
+ width: 394.0,
+ height: 120.0,
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(height: 40.0),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ Expanded(
+ child: AppTextField(
+ controller: nameController,
+ focusNode: nameFocusNode,
+ hintText: 'Name',
+ color: error && nameController.text.isEmpty
+ ? AppColors.dangerColor
+ : AppColors.primaryColor,
+ obscureText: false,
+ prefixIcon: const Icon(Icons.person),
+ height: 64.0,
+ onChanged: (_) {
+ validateStrongPassword();
+ },
+ ),
+ ),
+ const SizedBox(width: 10.0),
+ Expanded(
+ child: AppTextField(
+ controller: surnameController,
+ focusNode: surnameFocusNode,
+ hintText: 'Surname',
+ color: error && surnameController.text.isEmpty
+ ? AppColors.dangerColor
+ : AppColors.primaryColor,
+ obscureText: false,
+ prefixIcon: const Icon(Icons.person),
+ height: 64.0,
+ onChanged: (_) {
+ validateStrongPassword();
+ },
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 10.0),
+ AppTextField(
+ controller: emailController,
+ focusNode: emailFocusNode,
+ hintText: 'Email',
+ color: error && emailController.text.isEmpty
+ ? AppColors.dangerColor
+ : AppColors.primaryColor,
+ obscureText: false,
+ prefixIcon: const Icon(Icons.mail),
+ height: 64.0,
+ onChanged: (_) {
+ validateStrongPassword();
+ },
+ ),
+ const SizedBox(height: 10.0),
+ AppTextField(
+ controller: passwordController,
+ focusNode: passwordFocusNode,
+ hintText: 'Password',
+ color: error &&
+ (passwordMatchError ||
+ passwordController.text.isEmpty ||
+ weakPasswordError)
+ ? AppColors.dangerColor
+ : AppColors.primaryColor,
+ obscureText: obscuredPassword,
+ prefixIcon: const Icon(Icons.lock),
+ suffixIcon: IconButton(
+ onPressed: () {
+ setState(() {
+ obscuredPassword = !obscuredPassword;
+ });
+ },
+ icon: obscuredPassword
+ ? const Icon(Icons.visibility)
+ : const Icon(Icons.visibility_off),
+ ),
+ height: 64.0,
+ onChanged: (_) {
+ validateStrongPassword();
+ },
+ ),
+ const SizedBox(height: 10.0),
+ if (passwordController.text.isNotEmpty)
+ StrongPasswordChecks(
+ password: passwordController.text,
+ confirmPassword: confirmPasswordController.text,
+ ),
+ const SizedBox(height: 10.0),
+ AppTextField(
+ controller: confirmPasswordController,
+ focusNode: confirmPasswordFocusNode,
+ hintText: 'Confirm Password',
+ color: error && (passwordMatchError || confirmPasswordController.text.isEmpty)
+ ? AppColors.dangerColor
+ : AppColors.primaryColor,
+ obscureText: obscuredPassword,
+ prefixIcon: const Icon(Icons.lock),
+ suffixIcon: IconButton(
+ onPressed: () {
+ setState(() {
+ obscuredPassword = !obscuredPassword;
+ });
+ },
+ icon: obscuredPassword
+ ? const Icon(Icons.visibility)
+ : const Icon(Icons.visibility_off),
+ ),
+ height: 64.0,
+ onChanged: (_) {
+ validateStrongPassword();
+ },
+ ),
+ const SizedBox(height: 10.0),
+ if (error)
+ Padding(
+ padding: const EdgeInsets.only(top: 8.0),
+ child: SelectableText(
+ errorMessage,
+ style: const TextStyle(color: AppColors.dangerColor),
+ ),
+ ),
+ const SizedBox(height: 10.0),
+ AppButton(
+ onTap: () async {
+ if (await authenticate() && mounted) {
+ // Navigate to home page if authentication is successful
+ context.go(HomePage.routeName);
+ }
+ },
+ text: "Sign Up",
+ height: 64,
+ isActive: buttonState,
+ isLoading: isLoading,
+ ),
+ const SizedBox(height: 10.0),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Flexible(
+ child: SelectableText(
+ "Already have an account?",
+ style: TextStyle(
+ color: Colors.grey.shade700,
+ ),
+ ),
+ ),
+ const SizedBox(width: 4.0),
+ MouseRegion(
+ cursor: SystemMouseCursors.click,
+ child: GestureDetector(
+ onTap: () {
+ context.go(LoginPage.routeName);
+ },
+ child: const Text(
+ "Log in",
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ color: AppColors.hyperTextColor,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/login_page_appbar.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/login_page_appbar.dart
new file mode 100644
index 00000000..2fa103a9
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/login_page_appbar.dart
@@ -0,0 +1,19 @@
+import 'package:collaborative_science_platform/screens/page_with_appbar/widgets/app_bar_logo.dart';
+import 'package:flutter/material.dart';
+
+class LoginPageAppBar extends StatelessWidget {
+ const LoginPageAppBar({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ AppBarLogo(
+ logoPath: 'assets/images/logo.svg',
+ height: 60.0,
+ ),
+ ],
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/please_login_prompts.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/please_login_prompts.dart
new file mode 100644
index 00000000..f2d5cb73
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/please_login_prompts.dart
@@ -0,0 +1,140 @@
+import 'package:collaborative_science_platform/utils/colors.dart';
+import 'package:collaborative_science_platform/utils/text_styles.dart';
+import 'package:flutter/material.dart';
+
+class NotificationExplanation extends StatelessWidget {
+ const NotificationExplanation({
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ margin: const EdgeInsets.fromLTRB(15.0, 0.0, 15.0, 0.0),
+ child: Text(
+ "Stay connected and informed with notifications! Up-to-date on the activities related to your interests and contributions.",
+ style: TextStyles.title4.copyWith(fontSize: 24),
+ ),
+ ),
+ Container(
+ margin: const EdgeInsets.fromLTRB(15.0, 5.0, 15.0, 0.0),
+ child: const Text(
+ "including:",
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ color: AppColors.secondaryDarkColor,
+ ),
+ ),
+ ),
+ SizedBox(height: 8.0), // Add space between texts
+ Container(
+ margin: const EdgeInsets.fromLTRB(15.0, 0.0, 15.0, 0.0),
+ child: Text(
+ '''
+\u2022 Get notified when your questions receive replies, fostering knowledge exchange.
+\u2022 Stay informed when your contributions are reviewed with valuable feedback.
+\u2022 Embrace collaboration! Receive alerts for collaboration requests.
+\u2022 Stay connected when users ask questions about your contributed nodes
+\u2022 Customize your preferences to enhance engagement on our platform!
+ ''',
+ style: TextStyles.bodyBlack,
+ ),
+ ),
+ ],
+ );
+ }
+}
+
+class WorkspaceExplanation extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ margin: const EdgeInsets.fromLTRB(15.0, 0.0, 15.0, 0.0),
+ child: Text(
+ "Workspaces empower your collaborative work, providing a dynamic, flexible and all-in-one hub for your contributions.",
+ style: TextStyles.title4.copyWith(fontSize: 24),
+ ),
+ ),
+ Container(
+ margin: const EdgeInsets.fromLTRB(15.0, 5.0, 15.0, 0.0),
+ child: Text(
+ "including:",
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ color: AppColors.secondaryDarkColor,
+ ),
+ ),
+ ),
+ SizedBox(height: 8.0), // Add space between texts
+ Container(
+ margin: const EdgeInsets.fromLTRB(15.0, 0.0, 15.0, 0.0),
+ child: Text(
+ '''
+\u2022 Send collaboration requests to contributors of your choice.
+\u2022 Add and edit entries collaboratively.
+\u2022 Effortlessly cite references in your work.
+\u2022 Exclusive visibility, ensuring a private space for your team.
+\u2022 Create different workspaces for various projects with diverse contributors.
+ ''',
+ style: TextStyles.bodyBlack,
+ ),
+ ),
+ ],
+ );
+ }
+}
+
+class ProfileExplanation extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ margin: const EdgeInsets.fromLTRB(15.0, 0.0, 15.0, 0.0),
+ child: Text(
+ "Signing up brings a host of benefits",
+ style: TextStyles.title4.copyWith(fontSize: 24),
+ ),
+ ),
+ Container(
+ margin: const EdgeInsets.fromLTRB(15.0, 5.0, 15.0, 0.0),
+ child: Text(
+ "including:",
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ color: AppColors.secondaryDarkColor,
+ ),
+ ),
+ ),
+ SizedBox(height: 8.0), // Add space between texts
+ Container(
+ margin: const EdgeInsets.fromLTRB(15.0, 0.0, 15.0, 0.0),
+ child: Text(
+ '''
+\u2022 Personalize your profile for easy identification by others.
+\u2022 Showcase your activity on your profile.
+\u2022 Explore and connect with like-minded individuals in similar activities.
+\u2022 Enable others to find you easily and engage in meaningful collaborations.
+\u2022 Edit your profile information effortlessly.
+\u2022 Change your password with ease.
+ ''',
+ style: TextStyles.bodyBlack,
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/please_login_signup.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/please_login_signup.dart
new file mode 100644
index 00000000..51f81617
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/please_login_signup.dart
@@ -0,0 +1,75 @@
+import 'package:collaborative_science_platform/screens/auth_screens/login_page.dart';
+import 'package:collaborative_science_platform/screens/auth_screens/signup_page.dart';
+import 'package:collaborative_science_platform/utils/colors.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+class PleaseLoginSignup extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ SelectableText("To be able to see this page, please login!"),
+ const SizedBox(height: 5),
+ MouseRegion(
+ cursor: SystemMouseCursors.click,
+ child: GestureDetector(
+ onTap: () => context.go(LoginPage.routeName),
+ child: Container(
+ height: 40.0,
+ width: 160,
+ decoration: BoxDecoration(
+ color: Colors.blue,
+ borderRadius: BorderRadius.circular(5.0),
+ ),
+ child: const Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ 'Login',
+ style: TextStyle(
+ color: Colors.white,
+ fontWeight: FontWeight.bold,
+ fontSize: 16.0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(height: 10),
+ const SelectableText("If you don't have an account please"),
+ const SizedBox(height: 5),
+ MouseRegion(
+ cursor: SystemMouseCursors.click,
+ child: GestureDetector(
+ onTap: () => context.go(SignUpPage.routeName),
+ child: Container(
+ height: 40.0,
+ width: 160,
+ decoration: BoxDecoration(
+ color: AppColors.primaryColor,
+ borderRadius: BorderRadius.circular(5.0),
+ ),
+ child: const Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ 'Signup',
+ style: TextStyle(
+ color: Colors.white,
+ fontWeight: FontWeight.bold,
+ fontSize: 16.0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/strong_password_checks.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/strong_password_checks.dart
new file mode 100644
index 00000000..80f70f1e
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/strong_password_checks.dart
@@ -0,0 +1,70 @@
+import 'package:flutter/material.dart';
+
+class StrongPasswordChecks extends StatelessWidget {
+ static const int minPasswordLength = 8;
+ final String password;
+ final String confirmPassword;
+
+ const StrongPasswordChecks({
+ super.key,
+ required this.password,
+ required this.confirmPassword,
+ });
+
+ static bool passedAllPasswordCriteria(String password, String confirmPassword) {
+ bool minLengthIsMet = password.length >= minPasswordLength;
+ bool atLeastOneLowerCaseIsPresent = RegExp(r'[a-z]').hasMatch(password);
+ bool atLeastOneUpperCaseIsPresent = RegExp(r'[A-Z]').hasMatch(password);
+ bool atLeastOneNumberIsPresent = RegExp(r'\d').hasMatch(password);
+ bool atLeastOneSpecialCharacterIsPresent =
+ RegExp(r'[!@#$%^&*/()_\-+{}\[\]:;<>,.?~\\|]').hasMatch(password);
+ return minLengthIsMet &&
+ atLeastOneLowerCaseIsPresent &&
+ atLeastOneUpperCaseIsPresent &&
+ atLeastOneNumberIsPresent &&
+ atLeastOneSpecialCharacterIsPresent &&
+ password == confirmPassword;
+ }
+
+ Widget conditionWidget(String message, bool conditionIsMet) {
+ return SingleChildScrollView(
+ // To avoid Render Pixel Overflow
+ scrollDirection: Axis.horizontal,
+ child: Row(
+ children: [
+ (conditionIsMet)
+ ? const Icon(Icons.check, color: Colors.green)
+ : const Icon(Icons.close, color: Colors.red),
+ const SizedBox(width: 6.0),
+ SelectableText(
+ message,
+ style: TextStyle(
+ color: (conditionIsMet) ? Colors.green : Colors.red,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ bool minLengthIsMet = password.length >= minPasswordLength;
+ bool atLeastOneLowerCaseIsPresent = RegExp(r'[a-z]').hasMatch(password);
+ bool atLeastOneUpperCaseIsPresent = RegExp(r'[A-Z]').hasMatch(password);
+ bool atLeastOneNumberIsPresent = RegExp(r'\d').hasMatch(password);
+ bool atLeastOneSpecialCharacterIsPresent =
+ RegExp(r'[!@#$%^&*/()_\-+{}\[\]:;<>,.?~\\|]').hasMatch(password);
+
+ return Column(
+ children: [
+ conditionWidget("At least $minPasswordLength characters long", minLengthIsMet),
+ conditionWidget("At least one lowercase letter", atLeastOneLowerCaseIsPresent),
+ conditionWidget("At least one uppercase letter", atLeastOneUpperCaseIsPresent),
+ conditionWidget("At least one number", atLeastOneNumberIsPresent),
+ conditionWidget("At least one special character", atLeastOneSpecialCharacterIsPresent),
+ conditionWidget("Passwords do not match", password == confirmPassword),
+ ],
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/builder_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/builder_page.dart
new file mode 100644
index 00000000..3dd7dd38
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/builder_page.dart
@@ -0,0 +1,59 @@
+// import 'package:collaborative_science_platform/screens/home_page/home_page.dart';
+// import 'package:collaborative_science_platform/screens/home_page/home_page_appbar.dart';
+// import 'package:collaborative_science_platform/screens/notifications_page.dart';
+// import 'package:collaborative_science_platform/screens/page_with_appbar.dart';
+// import 'package:collaborative_science_platform/screens/profile_page/profile_options.dart';
+// import 'package:collaborative_science_platform/screens/graph_page.dart';
+// import 'package:collaborative_science_platform/screens/workspaces_page.dart';
+// import 'package:collaborative_science_platform/services/screen_navigation.dart';
+// import 'package:flutter/material.dart';
+// import 'package:provider/provider.dart';
+
+// class BuilderPage extends StatelessWidget {
+// const BuilderPage({super.key});
+
+// @override
+// Widget build(BuildContext context) {
+// return PageWithAppBar(
+// appBar: const HomePageAppBar(),
+// navigator: Navigator(
+// //key: Provider.of(context).navigatorKey,
+// initialRoute: HomePage.routeName,
+// onGenerateRoute: _onGenerateRoute,
+// ),
+// child: const SizedBox(),
+// );
+// }
+
+// // Widget getCurrentPage(BuildContext context) {
+// // final ScreenNavigation screenNavigation = Provider.of(context);
+// // switch (screenNavigation.selectedTab) {
+// // case ScreenTab.home:
+// // html.window.history.pushState({}, '', HomePage.routeName);
+// // return const HomePage();
+// // case ScreenTab.graph:
+// // html.window.history.pushState({}, '', ProfilePage.routeName);
+// // return const ProfilePage();
+// // case ScreenTab.workspace:
+// // html.window.history.pushState({}, '', WorkspacesPage.routeName);
+// // return const WorkspacesPage();
+// // }
+// // }
+// }
+
+// Route _onGenerateRoute(RouteSettings settings) {
+// switch (settings.name) {
+// case HomePage.routeName:
+// return MaterialPageRoute(builder: (context) => const HomePage(), settings: settings);
+// case GraphPage.routeName:
+// return MaterialPageRoute(builder: (context) => const GraphPage(), settings: settings);
+// case WorkspacesPage.routeName:
+// return MaterialPageRoute(builder: (context) => const WorkspacesPage(), settings: settings);
+// case NotificationPage.routeName:
+// return MaterialPageRoute(builder: (context) => const NotificationPage(), settings: settings);
+// case ProfileOptions.routeName:
+// return MaterialPageRoute(builder: (context) => const ProfileOptions(), settings: settings);
+// default:
+// return MaterialPageRoute(builder: (context) => const HomePage(), settings: settings);
+// }
+// }
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/graph_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/graph_page.dart
new file mode 100644
index 00000000..7ddc7a84
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/graph_page.dart
@@ -0,0 +1,112 @@
+import 'package:collaborative_science_platform/exceptions/node_details_exceptions.dart';
+import 'package:collaborative_science_platform/models/node_details_page/node_detailed.dart';
+import 'package:collaborative_science_platform/providers/node_provider.dart';
+import 'package:collaborative_science_platform/screens/graph_page/mobile_graph_page.dart';
+import 'package:collaborative_science_platform/screens/graph_page/web_graph_page.dart';
+import 'package:collaborative_science_platform/screens/home_page/widgets/home_page_appbar.dart';
+import 'package:collaborative_science_platform/screens/page_with_appbar/page_with_appbar.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:provider/provider.dart';
+
+class GraphPage extends StatefulWidget {
+ static const routeName = '/graph';
+ final int nodeId;
+ const GraphPage({super.key, this.nodeId = -1});
+
+ @override
+ State createState() => _GraphPageState();
+}
+
+class _GraphPageState extends State {
+ NodeDetailed node = NodeDetailed();
+ bool _isFirstTime = true;
+ bool isLoading = false;
+ bool error = false;
+ String errorMessage = "";
+
+ @override
+ void didChangeDependencies() {
+ if (_isFirstTime) {
+ getNode();
+ _isFirstTime = false;
+ }
+ super.didChangeDependencies();
+ }
+
+ @override
+ void initState() {
+ if (kIsWeb) {
+ BrowserContextMenu.disableContextMenu();
+ }
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ if (kIsWeb) {
+ BrowserContextMenu.enableContextMenu();
+ }
+ super.dispose();
+ }
+
+ void getNode() async {
+ try {
+ final nodeProvider = Provider.of(context, listen: false);
+ setState(() {
+ error = false;
+ isLoading = true;
+ });
+ if (nodeProvider.nodeDetailed != null) {
+ if (nodeProvider.nodeDetailed!.nodeId == widget.nodeId) {
+ setState(() {
+ node = nodeProvider.nodeDetailed!;
+ });
+ return;
+ }
+ }
+ await nodeProvider.getNode(widget.nodeId);
+ setState(() {
+ node = nodeProvider.nodeDetailed!;
+ });
+ } on NodeDoesNotExist {
+ setState(() {
+ error = true;
+ errorMessage = NodeDoesNotExist().message;
+ });
+ } catch (e) {
+ setState(() {
+ error = true;
+ errorMessage = "Something went wrong!";
+ });
+ } finally {
+ setState(() {
+ isLoading = false;
+ });
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ if (isLoading || error) {
+ return PageWithAppBar(
+ appBar: const HomePageAppBar(),
+ child: Center(
+ child: isLoading
+ ? const CircularProgressIndicator()
+ : Text(
+ errorMessage,
+ style: const TextStyle(color: Colors.red),
+ textAlign: TextAlign.center,
+ )),
+ );
+ } else {
+ return Responsive(
+ mobile: MobileGraphPage(node: node),
+ desktop: WebGraphPage(node: node),
+ );
+ }
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/mobile_graph_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/mobile_graph_page.dart
new file mode 100644
index 00000000..dc75ac86
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/mobile_graph_page.dart
@@ -0,0 +1,202 @@
+import 'package:carousel_slider/carousel_slider.dart';
+import 'package:collaborative_science_platform/models/node.dart';
+import 'package:collaborative_science_platform/models/node_details_page/node_detailed.dart';
+import 'package:collaborative_science_platform/screens/graph_page/widgets/graph_page_node_card.dart';
+import 'package:collaborative_science_platform/screens/home_page/widgets/home_page_appbar.dart';
+import 'package:collaborative_science_platform/screens/home_page/widgets/home_page_node_card.dart';
+import 'package:collaborative_science_platform/screens/page_with_appbar/page_with_appbar.dart';
+import 'package:collaborative_science_platform/screens/workspace_page/mobile_workspace_page/widget/subsection_title.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:collaborative_science_platform/screens/graph_page/graph_page.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/node_details_page.dart';
+
+class MobileGraphPage extends StatefulWidget {
+ final NodeDetailed node;
+ final bool isLoading;
+ const MobileGraphPage({
+ super.key,
+ required this.node,
+ this.isLoading = false,
+ });
+
+ @override
+ State createState() {
+ return _MobileGraphPageState();
+ }
+}
+
+class _MobileGraphPageState extends State {
+ int current = 1;
+ final CarouselController controller = CarouselController();
+
+ // void getReferences() {
+ // setState(() {
+ // areReferencesLoading = true;
+ // });
+ // references = List.generate(
+ // 10,
+ // (index) => SmallNode(
+ // nodeId: index + 1,
+ // nodeTitle: "Reference ${index + 1}",
+ // contributors: [
+ // Contributor(
+ // name: "Contributor Name ${index + 1}",
+ // surname: "Contributor Surname ${index + 1}",
+ // email: "contributor${index + 1}@mail.com"),
+ // ],
+ // publishDate: DateTime(1590, 12, 12),
+ // ),
+ // );
+ // setState(() {
+ // areReferencesLoading = false;
+ // });
+ // }
+
+ // void getReferents() {
+ // setState(() {
+ // areReferentsLoading = true;
+ // });
+ // referents = List.generate(
+ // 10,
+ // (index) => SmallNode(
+ // nodeId: index + 1,
+ // nodeTitle: "Referent ${index + 1}",
+ // contributors: [
+ // Contributor(
+ // name: "Contributor Name ${index + 1}",
+ // surname: "Contributor Surname ${index + 1}",
+ // email: "contributor${index + 1}@mail.com"),
+ // ],
+ // publishDate: DateTime(1990, 12, 12),
+ // ),
+ // );
+ // setState(() {
+ // areReferentsLoading = false;
+ // });
+ // }
+
+ Widget referencesCardList() {
+ // pre
+ return Column(
+ children: [
+ const SubSectionTitle(title: "References"),
+ ListView.builder(
+ shrinkWrap: true,
+ padding: const EdgeInsets.all(3),
+ itemCount: widget.node.references.length,
+ itemBuilder: (context, index) => HomePageNodeCard(
+ smallNode: widget.node.references[index],
+ onTap: () {
+ context.push("${GraphPage.routeName}/${widget.node.citations[index].id}");
+ },
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget referentsCardList() {
+ return Column(
+ children: [
+ const SubSectionTitle(title: "Referents"),
+ ListView.builder(
+ shrinkWrap: true,
+ padding: const EdgeInsets.all(3),
+ itemCount: widget.node.citations.length,
+ itemBuilder: (context, index) => HomePageNodeCard(
+ smallNode: widget.node.citations[index],
+ onTap: () {
+ context.push("${GraphPage.routeName}/${widget.node.citations[index].id}");
+ },
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget slidingPages(BuildContext context) {
+ List subpages = [
+ !widget.isLoading ? referencesCardList() : const Center(child: CircularProgressIndicator()),
+ Column(
+ children: [
+ const SubSectionTitle(title: "Theorem"),
+ GraphPageNodeCard(
+ node: widget.node,
+ onTap: () {
+ context.push("${NodeDetailsPage.routeName}/${widget.node.nodeId}");
+ },
+ ),
+ ],
+ ),
+ !widget.isLoading ? referentsCardList() : const Center(child: CircularProgressIndicator()),
+ ];
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Expanded(
+ child: Center(
+ child: SizedBox(
+ height: MediaQuery.of(context).size.height,
+ width: MediaQuery.of(context).size.width,
+ child: CarouselSlider(
+ carouselController: controller,
+ items: subpages,
+ options: CarouselOptions(
+ scrollPhysics: const ScrollPhysics(),
+ autoPlay: false,
+ viewportFraction: 1.0,
+ enableInfiniteScroll: false,
+ initialPage: current,
+ enlargeCenterPage: true,
+ enlargeStrategy: CenterPageEnlargeStrategy.zoom,
+ enlargeFactor: 0.3,
+ onPageChanged: (index, reason) {
+ setState(() {
+ current = index;
+ });
+ },
+ ),
+ ),
+ ),
+ ),
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: subpages.asMap().entries.map((entry) {
+ return GestureDetector(
+ onTap: () => controller.animateToPage(entry.key),
+ child: Container(
+ width: 12.0,
+ height: 12.0,
+ margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 6.0),
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: (current == entry.key) ? Colors.indigo[700] : Colors.indigo[200]),
+ ),
+ );
+ }).toList(),
+ ),
+ ],
+ );
+ }
+
+ Node createSmallNode(NodeDetailed node) {
+ return Node(
+ id: node.nodeId,
+ nodeTitle: node.nodeTitle,
+ contributors: node.contributors,
+ publishDate: node.publishDate!,
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return PageWithAppBar(
+ isScrollable: false,
+ appBar: const HomePageAppBar(),
+ child: slidingPages(context),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/web_graph_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/web_graph_page.dart
new file mode 100644
index 00000000..e6b77696
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/web_graph_page.dart
@@ -0,0 +1,77 @@
+import 'package:collaborative_science_platform/models/node_details_page/node_detailed.dart';
+import 'package:collaborative_science_platform/screens/graph_page/widgets/graph_page_node_card.dart';
+import 'package:collaborative_science_platform/screens/graph_page/widgets/node_list.dart';
+import 'package:collaborative_science_platform/screens/home_page/widgets/home_page_appbar.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/node_details_page.dart';
+import 'package:collaborative_science_platform/screens/page_with_appbar/page_with_appbar.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+class WebGraphPage extends StatefulWidget {
+ final NodeDetailed node;
+
+ const WebGraphPage({
+ super.key,
+ required this.node,
+ });
+
+ @override
+ State createState() => _WebGraphPageState();
+}
+
+class _WebGraphPageState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return PageWithAppBar(
+ appBar: const HomePageAppBar(),
+ pageColor: Colors.grey.shade200,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ widget.node.references.isEmpty
+ ? const Center(
+ child: Text(
+ "No references",
+ style: TextStyle(
+ fontSize: 18.0,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ )
+ : Flexible(
+ flex: 2,
+ child: NodeList(
+ nodes: widget.node.references,
+ title: "References",
+ width: MediaQuery.of(context).size.width / 3.2),
+ ),
+ Flexible(
+ flex: 6,
+ child: GraphPageNodeCard(
+ node: widget.node,
+ onTap: () => context.go('${NodeDetailsPage.routeName}/${widget.node.nodeId}')),
+ ),
+ widget.node.citations.isEmpty
+ ? const Center(
+ child: Text(
+ "No citations",
+ style: TextStyle(
+ fontSize: 18.0,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ )
+ : Flexible(
+ flex: 2,
+ child: NodeList(
+ nodes: widget.node.citations,
+ title: "Citations",
+ width: MediaQuery.of(context).size.width / 5,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/graph_node.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/graph_node.dart
new file mode 100644
index 00000000..3bca316f
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/graph_node.dart
@@ -0,0 +1,83 @@
+import 'package:collaborative_science_platform/helpers/date_to_string.dart';
+import 'package:collaborative_science_platform/models/node.dart';
+import 'package:collaborative_science_platform/models/user.dart';
+import 'package:flutter/material.dart';
+import 'dart:convert';
+
+class GraphNodeCard extends StatefulWidget {
+ final Node node;
+ final Color? color;
+ final Function() onTap;
+
+ const GraphNodeCard({
+ Key? key,
+ required this.node,
+ this.color,
+ required this.onTap,
+ }) : super(key: key);
+
+ @override
+ State createState() => _GraphNodeCardState();
+}
+
+class _GraphNodeCardState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return InkWell(
+ onTap: widget.onTap,
+ customBorder: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(2.0),
+ ),
+ child: Card(
+ elevation: 4,
+ child: Padding(
+ padding: const EdgeInsets.all(12.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SelectableText(
+ utf8.decode(widget.node.nodeTitle.codeUnits),
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 16.0,
+ ),
+ ),
+ const SizedBox(height: 8.0), // Increased spacing
+ SelectableText(
+ getContributorsText(widget.node.contributors),
+ style: const TextStyle(
+ fontWeight: FontWeight.w500,
+ fontSize: 12.0,
+ color: Colors.grey,
+ ),
+ ),
+ const SizedBox(height: 8.0),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ SelectableText(
+ getDurationFromNow(widget.node.publishDate),
+ style: const TextStyle(
+ color: Colors.grey,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ const Icon(
+ Icons.arrow_forward,
+ color: Colors.grey,
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ String getContributorsText(List contributors) {
+ return contributors
+ .map((user) => "${user.firstName} ${user.lastName} (${user.email})")
+ .join(",\n");
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/graph_node_popup.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/graph_node_popup.dart
new file mode 100644
index 00000000..c0c1ff3a
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/graph_node_popup.dart
@@ -0,0 +1,171 @@
+// node_details_popup.dart
+
+import 'dart:ui';
+
+import 'package:collaborative_science_platform/exceptions/node_details_exceptions.dart';
+import 'package:collaborative_science_platform/helpers/node_helper.dart';
+import 'package:collaborative_science_platform/models/node_details_page/node_detailed.dart';
+import 'package:collaborative_science_platform/providers/node_provider.dart';
+import 'package:collaborative_science_platform/screens/graph_page/graph_page.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/node_details_page.dart';
+import 'package:collaborative_science_platform/widgets/annotation_text.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_tex/flutter_tex.dart';
+import 'package:collaborative_science_platform/helpers/date_to_string.dart';
+import 'package:go_router/go_router.dart';
+import 'package:provider/provider.dart';
+import 'dart:convert';
+
+class NodeDetailsPopup extends StatefulWidget {
+ final int nodeId;
+
+ const NodeDetailsPopup({Key? key, required this.nodeId}) : super(key: key);
+
+ @override
+ State createState() => _NodeDetailsPopupState();
+}
+
+class _NodeDetailsPopupState extends State {
+ NodeDetailed node = NodeDetailed();
+ bool isLoading = false;
+ bool error = false;
+ String errorMessage = "";
+ bool _isFirstTime = true;
+
+ @override
+ void didChangeDependencies() {
+ if (_isFirstTime) {
+ getNode();
+ _isFirstTime = false;
+ }
+ super.didChangeDependencies();
+ }
+
+ void getNode() async {
+ try {
+ final nodeProvider = Provider.of(context, listen: false);
+ setState(() {
+ error = false;
+ isLoading = true;
+ });
+ if (nodeProvider.nodeDetailed != null) {
+ if (nodeProvider.nodeDetailed!.nodeId == widget.nodeId) {
+ setState(() {
+ node = nodeProvider.nodeDetailed!;
+ isLoading = false;
+ });
+ return;
+ }
+ }
+ await nodeProvider.getNode(widget.nodeId);
+ setState(() {
+ node = nodeProvider.nodeDetailed!;
+ isLoading = false;
+ });
+ } on NodeDoesNotExist {
+ setState(() {
+ isLoading = false;
+ error = true;
+ errorMessage = "Node does not exist!";
+ });
+ } catch (e) {
+ setState(() {
+ isLoading = false;
+ error = true;
+ errorMessage = "Something went wrong!";
+ });
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ if (isLoading) {
+ return const Center(
+ child: CircularProgressIndicator(),
+ );
+ } else {
+ return BackdropFilter(
+ filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
+ child: AlertDialog(
+ title: AnnotationText(
+ utf8.decode(node.nodeTitle.codeUnits),
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 18.0,
+ ),
+ ),
+ content: Container(
+ decoration: const BoxDecoration(
+ border: Border(
+ top: BorderSide(width: 1.5, color: Colors.grey),
+ bottom: BorderSide(width: 1.5, color: Colors.grey),
+ ),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(12.0),
+ child: SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ TeXView(
+ renderingEngine: const TeXViewRenderingEngine.katex(),
+ child: TeXViewDocument(
+ NodeHelper.getNodeContentLatex(node, "short"),
+ ),
+ ),
+ const SizedBox(height: 6.0),
+ Text(
+ node.contributors
+ .map((user) => "${user.firstName} ${user.lastName} (${user.email})")
+ .join(", "),
+ style: const TextStyle(
+ fontWeight: FontWeight.w600,
+ fontSize: 10.0,
+ color: Colors.grey,
+ ),
+ ),
+ const SizedBox(height: 2.0),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ Text(
+ getDurationFromNow(node.publishDate!),
+ style: const TextStyle(
+ color: Colors.grey,
+ fontSize: 10,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ actions: [
+ ElevatedButton(
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ child: const Text('Close'),
+ ),
+ ElevatedButton(
+ onPressed: () {
+ context.pushReplacement('${NodeDetailsPage.routeName}/${node.nodeId}');
+ },
+ child: const Text('Go to Node View Page'),
+ ),
+ ElevatedButton(
+ onPressed: () {
+ context.pushReplacement('${GraphPage.routeName}/${node.nodeId}');
+ },
+ child: const Text('Go to Graph Page'),
+ ),
+ ],
+ ),
+ );
+ }
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/graph_page_node_card.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/graph_page_node_card.dart
new file mode 100644
index 00000000..78cdd1a8
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/graph_page_node_card.dart
@@ -0,0 +1,108 @@
+import 'package:collaborative_science_platform/helpers/date_to_string.dart';
+import 'package:collaborative_science_platform/helpers/node_helper.dart';
+import 'package:collaborative_science_platform/models/node_details_page/node_detailed.dart';
+import 'package:collaborative_science_platform/widgets/annotation_text.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_tex/flutter_tex.dart';
+import 'dart:convert';
+
+class GraphPageNodeCard extends StatelessWidget {
+ final NodeDetailed node;
+ final Color? color;
+ final Function() onTap;
+
+ const GraphPageNodeCard({
+ super.key,
+ required this.node,
+ this.color,
+ required this.onTap,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return SingleChildScrollView(
+ child: Column(
+ children: [
+ Card(
+ elevation: 4.0,
+ margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ child: InkWell(
+ onTap: onTap, // Navigate to the screen of the Node
+ customBorder: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Container(
+ decoration: const BoxDecoration(
+ border: Border(
+ bottom: BorderSide(width: 2.0, color: Colors.grey),
+ ),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.only(bottom: 8.0),
+ child: AnnotationText(
+ utf8.decode(node.nodeTitle.codeUnits),
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 18.0,
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(height: 8.0),
+ Container(
+ padding: const EdgeInsets.all(8.0), // Add padding inside the box
+ child: TeXView(
+ renderingEngine: const TeXViewRenderingEngine.katex(),
+ child: TeXViewDocument(
+ NodeHelper.getNodeContentLatex(node, "long"),
+ ),
+ ),
+ ),
+ const SizedBox(height: 20.0),
+ Column(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ node.contributors
+ .map((user) =>
+ "${user.firstName} ${user.lastName} (${user.email})")
+ .join("\n"),
+ style: const TextStyle(
+ fontWeight: FontWeight.w400,
+ fontSize: 14.0,
+ color: Colors.black54,
+ ),
+ ),
+ SelectableText(
+ getDurationFromNow(node.publishDate!),
+ style: const TextStyle(
+ color: Colors.grey,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ],
+ ),
+ ],
+ )
+ ],
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/node_list.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/node_list.dart
new file mode 100644
index 00000000..8d1174c3
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/node_list.dart
@@ -0,0 +1,77 @@
+import 'package:collaborative_science_platform/models/node.dart';
+import 'package:collaborative_science_platform/screens/graph_page/widgets/graph_node_popup.dart';
+import 'package:collaborative_science_platform/screens/home_page/widgets/home_page_node_card.dart';
+import 'package:flutter/material.dart';
+
+class NodeList extends StatefulWidget {
+ final String title;
+ final List nodes;
+ final Color? color;
+ final double width;
+
+ const NodeList({
+ Key? key,
+ required this.nodes,
+ required this.title,
+ required this.width,
+ this.color,
+ }) : super(key: key);
+
+ @override
+ State createState() => _NodeListState();
+}
+
+class _NodeListState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ const SizedBox(height: 5),
+ Card(
+ color: Colors.white,
+ elevation: 2.0,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(4.0),
+ ),
+ child: ExpansionTile(
+ shape: const Border(),
+ tilePadding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 0.0),
+ title: Center(
+ child: Padding(
+ padding: const EdgeInsets.all(2.0),
+ child: Text(
+ widget.title,
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 18.0,
+ ),
+ ),
+ ),
+ ),
+ children: [
+ // Display the list of nodes
+ Column(
+ children: widget.nodes.map((node) {
+ return Container(
+ margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
+ child: HomePageNodeCard(
+ smallNode: node, onTap: () => _showNodeDetailsPopup(context, node)),
+ );
+ }).toList(),
+ ),
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+
+ void _showNodeDetailsPopup(BuildContext context, Node node) {
+ showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return NodeDetailsPopup(nodeId: node.id);
+ },
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/home_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/home_page.dart
new file mode 100644
index 00000000..00de7d9f
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/home_page.dart
@@ -0,0 +1,155 @@
+import 'package:collaborative_science_platform/exceptions/search_exceptions.dart';
+import 'package:collaborative_science_platform/helpers/search_helper.dart';
+import 'package:collaborative_science_platform/models/semantic_tag.dart';
+import 'package:collaborative_science_platform/providers/node_provider.dart';
+import 'package:collaborative_science_platform/providers/user_provider.dart';
+import 'package:collaborative_science_platform/screens/home_page/mobile_home_page.dart';
+import 'package:collaborative_science_platform/widgets/app_search_bar.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class HomePage extends StatefulWidget {
+ static const routeName = '/';
+ const HomePage({super.key});
+
+ @override
+ State createState() => _HomePageState();
+}
+
+class _HomePageState extends State {
+ final searchBarFocusNode = FocusNode();
+ bool searchBarActive = false;
+ bool error = false;
+ bool isLoading = false;
+ bool firstSearch = false;
+
+ bool _firstTime = true;
+ String errorMessage = "";
+
+ @override
+ void didChangeDependencies() {
+ if (_firstTime) {
+ randomNodes();
+ _firstTime = false;
+ }
+ super.didChangeDependencies();
+ }
+
+ @override
+ void dispose() {
+ searchBarFocusNode.dispose();
+ super.dispose();
+ }
+
+ void randomNodes() async {
+ try {
+ final nodeProvider = Provider.of(context, listen: false);
+ setState(() {
+ isLoading = true;
+ });
+ await nodeProvider.search(SearchType.both, "", random: true);
+ } on WrongSearchTypeError {
+ setState(() {
+ error = true;
+ errorMessage = WrongSearchTypeError().message;
+ });
+ } on SearchError {
+ setState(() {
+ error = true;
+ errorMessage = SearchError().message;
+ });
+ } catch (e) {
+ setState(() {
+ error = true;
+ errorMessage = "Something went wrong!";
+ });
+ } finally {
+ setState(() {
+ isLoading = false;
+ });
+ }
+ }
+
+ void search(String text) async {
+ if (text.isEmpty) return;
+ if (text.length < 4) return;
+ SearchType searchType = SearchHelper.searchType;
+ try {
+ final userProvider = Provider.of(context, listen: false);
+ final nodeProvider = Provider.of(context, listen: false);
+ setState(() {
+ isLoading = true;
+ firstSearch = true;
+ });
+ if (searchType == SearchType.author) {
+ await userProvider.search(searchType, text);
+ } else if (searchType == SearchType.both) {
+ await userProvider.search(searchType, text);
+ await nodeProvider.search(searchType, text);
+ } else {
+ await nodeProvider.search(searchType, text);
+ }
+ } on WrongSearchTypeError {
+ setState(() {
+ error = true;
+ errorMessage = WrongSearchTypeError().message;
+ });
+ } on SearchError {
+ setState(() {
+ error = true;
+ errorMessage = SearchError().message;
+ });
+ } catch (e) {
+ setState(() {
+ error = true;
+ errorMessage = "Something went wrong!";
+ });
+ } finally {
+ setState(() {
+ isLoading = false;
+ });
+ }
+ }
+
+ void semanticSearch(SemanticTag tag) async {
+ SearchType searchType = SearchHelper.searchType;
+ try {
+ final nodeProvider = Provider.of(context, listen: false);
+ setState(() {
+ isLoading = true;
+ });
+ await nodeProvider.search(searchType, tag.id, semantic: true);
+ } on WrongSearchTypeError {
+ setState(() {
+ error = true;
+ errorMessage = WrongSearchTypeError().message;
+ });
+ } on SearchError {
+ setState(() {
+ error = true;
+ errorMessage = SearchError().message;
+ });
+ } catch (e) {
+ setState(() {
+ error = true;
+ errorMessage = "Something went wrong!";
+ });
+ } finally {
+ setState(() {
+ isLoading = false;
+ });
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return MobileHomePage(
+ searchBarFocusNode: searchBarFocusNode,
+ onSearch: search,
+ onSemanticSearch: semanticSearch,
+ isLoading: isLoading,
+ error: error,
+ errorMessage: errorMessage,
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/mobile_home_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/mobile_home_page.dart
new file mode 100644
index 00000000..9d860e7e
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/mobile_home_page.dart
@@ -0,0 +1,83 @@
+import 'package:collaborative_science_platform/helpers/search_helper.dart';
+import 'package:collaborative_science_platform/providers/node_provider.dart';
+import 'package:collaborative_science_platform/providers/user_provider.dart';
+import 'package:collaborative_science_platform/screens/home_page/widgets/home_page_appbar.dart';
+import 'package:collaborative_science_platform/screens/home_page/widgets/node_cards.dart';
+import 'package:collaborative_science_platform/screens/home_page/widgets/user_cards.dart';
+import 'package:collaborative_science_platform/screens/page_with_appbar/page_with_appbar.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:collaborative_science_platform/widgets/app_search_bar.dart';
+import 'package:collaborative_science_platform/widgets/search_bar_extended.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class MobileHomePage extends StatelessWidget {
+ final FocusNode searchBarFocusNode;
+ final Function onSearch;
+ final Function onSemanticSearch;
+ final bool isLoading;
+ final bool error;
+ final String errorMessage;
+
+ const MobileHomePage({
+ super.key,
+ required this.searchBarFocusNode,
+ required this.onSearch,
+ required this.onSemanticSearch,
+ required this.isLoading,
+ required this.error,
+ required this.errorMessage,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final userProvider = Provider.of(context);
+ final nodeProvider = Provider.of(context);
+ return PageWithAppBar(
+ appBar: const HomePageAppBar(),
+ child: SingleChildScrollView(
+ primary: false,
+ scrollDirection: Axis.vertical,
+ child: SizedBox(
+ width: Responsive.getGenericPageWidth(context),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Padding(
+ padding: const EdgeInsets.fromLTRB(10.0, 16.0, 8.0, 0.0),
+ child: Responsive(
+ mobile:
+ SearchBarExtended(exactSearch: onSearch, semanticSearch: onSemanticSearch),
+ desktop:
+ SearchBarExtended(exactSearch: onSearch, semanticSearch: onSemanticSearch)),
+ ),
+ Padding(
+ padding: const EdgeInsets.only(top: 10.0),
+ child: isLoading
+ ? const Padding(
+ padding: EdgeInsets.only(top: 10.0),
+ child: Center(
+ child: CircularProgressIndicator(),
+ ),
+ )
+ : error
+ ? SelectableText(
+ errorMessage,
+ style: const TextStyle(color: Colors.red),
+ textAlign: TextAlign.center,
+ )
+ : (SearchHelper.searchType == SearchType.author)
+ ? UserCards(
+ userList: userProvider.searchUserResult,
+ )
+ : NodeCards(
+ nodeList: nodeProvider.searchNodeResult,
+ )),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/home_page_appbar.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/home_page_appbar.dart
new file mode 100644
index 00000000..7fb7be06
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/home_page_appbar.dart
@@ -0,0 +1,41 @@
+import 'package:collaborative_science_platform/screens/notifications_page/notifications_page.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:collaborative_science_platform/screens/page_with_appbar/widgets/app_bar_button.dart';
+import 'package:collaborative_science_platform/screens/page_with_appbar/widgets/profile_menu.dart';
+import 'package:collaborative_science_platform/screens/page_with_appbar/widgets/app_bar_logo.dart';
+import 'package:collaborative_science_platform/screens/page_with_appbar/widgets/top_navigation_bar.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+class HomePageAppBar extends StatelessWidget {
+ const HomePageAppBar({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Responsive(
+ mobile: const TopNavigationBar(),
+ desktop: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ const AppBarLogo(height: 50.0),
+ const TopNavigationBar(),
+ Row(children: [
+ if (!Responsive.isMobile(context))
+ AppBarButton(
+ icon: Icons.notifications,
+ text: "Notifications",
+ onPressed: () {
+ context.push(NotificationPage.routeName);
+ }),
+ const SizedBox(width: 10.0),
+ const ProfileMenu(),
+ ]),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/home_page_node_card.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/home_page_node_card.dart
new file mode 100644
index 00000000..7bd7e852
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/home_page_node_card.dart
@@ -0,0 +1,81 @@
+import 'package:collaborative_science_platform/helpers/date_to_string.dart';
+import 'package:collaborative_science_platform/models/node.dart';
+import 'package:collaborative_science_platform/utils/colors.dart';
+import 'package:flutter/material.dart';
+
+class HomePageNodeCard extends StatelessWidget {
+ final Node smallNode;
+ final Color? color;
+ final Function() onTap;
+
+ const HomePageNodeCard({
+ super.key,
+ required this.smallNode,
+ this.color,
+ required this.onTap,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Card(
+ elevation: 1.0,
+ margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ child: InkWell(
+ onTap: onTap,
+ customBorder: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ MouseRegion(
+ cursor: SystemMouseCursors.click,
+ child: InkWell(
+ onTap: onTap,
+ child: Text(
+ smallNode.nodeTitle,
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 14.0,
+ color: AppColors.primaryDarkColor,
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(height: 8.0),
+ SelectableText(
+ smallNode.contributors
+ .map((user) => "${user.firstName} ${user.lastName} (${user.email})")
+ .join(", "),
+ style: const TextStyle(
+ fontWeight: FontWeight.w600,
+ fontSize: 10.0,
+ color: Colors.grey,
+ ),
+ ),
+ const SizedBox(height: 8.0),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ Text(
+ getDurationFromNow(smallNode.publishDate),
+ style: const TextStyle(
+ color: Colors.grey,
+ fontSize: 10,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/home_page_user_card.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/home_page_user_card.dart
new file mode 100644
index 00000000..bdf5cf6b
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/home_page_user_card.dart
@@ -0,0 +1,89 @@
+import 'package:collaborative_science_platform/models/profile_data.dart';
+import 'package:collaborative_science_platform/utils/colors.dart';
+import 'package:flutter/material.dart';
+
+class HomePageUserCard extends StatelessWidget {
+ final ProfileData profileData;
+ final Function() onTap;
+ final Color color;
+ final String? profilePagePath;
+
+ const HomePageUserCard({
+ super.key,
+ required this.profileData,
+ required this.onTap,
+ required this.color,
+ this.profilePagePath,
+ });
+
+ // Remove this function when it is no longer needed
+ Widget profilePhoto() {
+ return CircleAvatar(
+ radius: 48.0,
+ backgroundColor: AppColors.primaryColor,
+ backgroundImage: profilePagePath != null ? AssetImage(profilePagePath!) : null,
+ child: profilePagePath == null
+ ? const Icon(
+ Icons.person,
+ size: 36.0,
+ color: Colors.white,
+ )
+ : null,
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Card(
+ elevation: 2.0, // Reduced elevation for a subtle shadow
+ margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(12.0),
+ ),
+ child: InkWell(
+ onTap: onTap, // Navigate to the Profile Page of the User
+ customBorder: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(12.0),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Row(
+ children: [
+ profilePhoto(),
+ const SizedBox(width: 16.0),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SelectableText(
+ "${profileData.name} ${profileData.surname}",
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 18.0,
+ ),
+ ),
+ const SizedBox(height: 8.0),
+ SelectableText(
+ profileData.email,
+ style: const TextStyle(
+ fontWeight: FontWeight.w500,
+ fontSize: 16.0,
+ color: Colors.grey,
+ ),
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ profileData.aboutMe,
+ maxLines: 3,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/node_cards.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/node_cards.dart
new file mode 100644
index 00000000..aa529b62
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/node_cards.dart
@@ -0,0 +1,40 @@
+import 'package:collaborative_science_platform/models/node.dart';
+import 'package:collaborative_science_platform/screens/home_page/widgets/home_page_node_card.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/node_details_page.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+class NodeCards extends StatelessWidget {
+ final List nodeList;
+
+ const NodeCards({
+ super.key,
+ required this.nodeList,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.only(top: 8.0),
+ child: nodeList.isEmpty
+ ? const Center(
+ child: SelectableText("No results found."),
+ )
+ : ListView.builder(
+ padding: const EdgeInsets.all(0),
+ physics: const NeverScrollableScrollPhysics(),
+ scrollDirection: Axis.vertical,
+ shrinkWrap: true,
+ itemCount: nodeList.length,
+ itemBuilder: (context, index) {
+ return HomePageNodeCard(
+ smallNode: nodeList[index],
+ onTap: () {
+ context.push('${NodeDetailsPage.routeName}/${nodeList[index].id}');
+ },
+ );
+ },
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/user_cards.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/user_cards.dart
new file mode 100644
index 00000000..c3d87275
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/user_cards.dart
@@ -0,0 +1,45 @@
+import 'package:collaborative_science_platform/models/profile_data.dart';
+import 'package:collaborative_science_platform/screens/home_page/widgets/home_page_user_card.dart';
+import 'package:collaborative_science_platform/screens/profile_page/profile_page.dart';
+import 'package:collaborative_science_platform/utils/colors.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+class UserCards extends StatelessWidget {
+ final List userList;
+
+ const UserCards({
+ super.key,
+ required this.userList,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.only(top: 8.0),
+ child: userList.isEmpty
+ ? const Center(
+ child: SelectableText("No results found."),
+ )
+ : ListView.builder(
+ padding: const EdgeInsets.all(0),
+ physics: const NeverScrollableScrollPhysics(),
+ scrollDirection: Axis.vertical,
+ shrinkWrap: true,
+ itemCount: userList.length,
+ itemBuilder: (context, index) {
+ final String email = userList[index].email;
+ final String encodedEmail = Uri.encodeComponent(email);
+ return HomePageUserCard(
+ profileData: userList[index],
+ onTap: () {
+ context.push('${ProfilePage.routeName}/$encodedEmail');
+ },
+ color: AppColors.primaryLightColor,
+ profilePagePath: "assets/images/gumball.jpg",
+ );
+ },
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/node_details_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/node_details_page.dart
new file mode 100644
index 00000000..a8a6e10d
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/node_details_page.dart
@@ -0,0 +1,195 @@
+import 'package:collaborative_science_platform/exceptions/node_details_exceptions.dart';
+import 'package:collaborative_science_platform/models/node_details_page/node_detailed.dart';
+import 'package:collaborative_science_platform/providers/node_provider.dart';
+import 'package:collaborative_science_platform/screens/home_page/widgets/home_page_appbar.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/widgets/contributors_list_view.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/widgets/node_details.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/widgets/you_may_like.dart';
+import 'package:collaborative_science_platform/screens/page_with_appbar/page_with_appbar.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:provider/provider.dart';
+
+class NodeDetailsPage extends StatefulWidget {
+ static const routeName = '/node';
+ //final arguments = (ModalRoute.of(context)?.settings.arguments ?? {}) as Map;
+ final int nodeID;
+ const NodeDetailsPage({super.key, required this.nodeID});
+
+ @override
+ State createState() => _NodeDetailsPageState();
+}
+
+class _NodeDetailsPageState extends State {
+ ScrollController controller1 = ScrollController();
+ ScrollController controller2 = ScrollController();
+ bool _isFirstTime = true;
+ NodeDetailed node = NodeDetailed();
+
+ bool error = false;
+ String errorMessage = "";
+
+ bool isLoading = false;
+
+ @override
+ void didChangeDependencies() {
+ if (_isFirstTime) {
+ getNodeDetails();
+ _isFirstTime = false;
+ }
+ super.didChangeDependencies();
+ }
+
+ void getNodeDetails() async {
+ try {
+ final nodeDetailsProvider = Provider.of(context);
+ setState(() {
+ error = false;
+ isLoading = true;
+ });
+ await nodeDetailsProvider.getNode(widget.nodeID);
+
+ setState(() {
+ node = (nodeDetailsProvider.nodeDetailed ?? {} as NodeDetailed);
+ });
+ } on NodeDoesNotExist {
+ setState(() {
+ error = true;
+ errorMessage = NodeDoesNotExist().message;
+ });
+ } catch (e) {
+ setState(() {
+ error = true;
+ errorMessage = "Something went wrong!";
+ });
+ } finally {
+ setState(() {
+ isLoading = false;
+ });
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return PageWithAppBar(
+ appBar: const HomePageAppBar(),
+ pageColor: Colors.grey.shade200,
+ child: isLoading
+ ? Container(
+ padding: const EdgeInsets.only(top: 32),
+ decoration: const BoxDecoration(color: Colors.white),
+ child: const Center(
+ child: CircularProgressIndicator(),
+ ),
+ )
+ : error
+ ? SelectableText(
+ errorMessage,
+ style: const TextStyle(color: Colors.red),
+ textAlign: TextAlign.center,
+ )
+ : Responsive.isDesktop(context)
+ ? WebNodeDetails(node: node)
+ : NodeDetails(
+ node: node,
+ controller: controller2,
+ ),
+ );
+ }
+}
+
+class WebNodeDetails extends StatefulWidget {
+ final NodeDetailed node;
+
+ const WebNodeDetails({super.key, required this.node});
+
+ @override
+ State createState() => _WebNodeDetailsState();
+}
+
+class _WebNodeDetailsState extends State {
+ final ScrollController controller1 = ScrollController();
+ final ScrollController controller2 = ScrollController();
+ bool _isFirstTime = true;
+ bool error = false;
+ bool isLoading = false;
+
+ @override
+ void didChangeDependencies() {
+ if (_isFirstTime) {
+ getNodeSuggestions();
+ _isFirstTime = false;
+ }
+ super.didChangeDependencies();
+ }
+
+ void getNodeSuggestions() async {
+ try {
+ final nodeDetailsProvider = Provider.of(context);
+ setState(() {
+ error = false;
+ isLoading = true;
+ });
+ await nodeDetailsProvider.getNodeSuggestions();
+ } on NodeDoesNotExist {
+ setState(() {
+ error = true;
+ });
+ } catch (e) {
+ setState(() {
+ error = true;
+ });
+ } finally {
+ setState(() {
+ isLoading = false;
+ });
+ }
+ }
+
+ @override
+ void dispose() {
+ controller1.dispose();
+ controller2.dispose();
+ BrowserContextMenu.enableContextMenu();
+ super.dispose();
+ }
+
+ @override
+ void initState() {
+ BrowserContextMenu.disableContextMenu();
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 32),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Contributors(
+ contributors: widget.node.contributors, //widget.inputNode.contributors,
+ controller: controller1,
+ ),
+ const SizedBox(width: 12),
+ Flexible(
+ child: NodeDetails(
+ node: widget.node,
+ controller: controller2,
+ ),
+ ),
+ const SizedBox(width: 12),
+ SizedBox(
+ height: MediaQuery.of(context).size.height - 100,
+ child: YouMayLike(
+ isLoading: isLoading,
+ error: error,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/contributors_list_view.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/contributors_list_view.dart
new file mode 100644
index 00000000..8a2c737e
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/contributors_list_view.dart
@@ -0,0 +1,68 @@
+import 'package:collaborative_science_platform/models/user.dart';
+import 'package:collaborative_science_platform/screens/profile_page/profile_page.dart';
+import 'package:collaborative_science_platform/utils/colors.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:collaborative_science_platform/utils/text_styles.dart';
+import 'package:collaborative_science_platform/widgets/card_container.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+class Contributors extends StatelessWidget {
+ final List contributors;
+ final ScrollController controller;
+ const Contributors({super.key, required this.contributors, required this.controller});
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.only(top: 16),
+ child: Column(
+ children: [
+ const Text(
+ "Contributors",
+ style: TextStyle(
+ color: AppColors.secondaryDarkColor,
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ SizedBox(
+ width:
+ Responsive.isDesktop(context) ? Responsive.desktopPageWidth / 4 : double.infinity,
+ //decoration: BoxDecoration(color: Colors.grey[200]),
+ child: ListView.builder(
+ controller: controller,
+ scrollDirection: Axis.vertical,
+ shrinkWrap: true,
+ padding: const EdgeInsets.all(8),
+ itemCount: contributors.length,
+ itemBuilder: (BuildContext context, int index) {
+ return Padding(
+ padding: const EdgeInsets.all(2),
+ child: CardContainer(
+ onTap: () {
+ final String email = contributors[index].email;
+ final String encodedEmail = Uri.encodeComponent(email);
+ context.push('${ProfilePage.routeName}/$encodedEmail');
+ },
+ child: Column(
+ children: [
+ SelectableText(
+ "${contributors[index].firstName} ${contributors[index].lastName}",
+ style: TextStyles.title4,
+ ),
+ SelectableText(
+ contributors[index].email,
+ style: TextStyles.bodyGrey,
+ )
+ ],
+ ),
+ ),
+ );
+ }),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/node_details.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/node_details.dart
new file mode 100644
index 00000000..e1a9a948
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/node_details.dart
@@ -0,0 +1,208 @@
+import 'package:collaborative_science_platform/helpers/node_helper.dart';
+import 'package:collaborative_science_platform/models/node_details_page/node_detailed.dart';
+import 'package:collaborative_science_platform/screens/graph_page/graph_page.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/widgets/contributors_list_view.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/widgets/node_details_tab_bar.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/widgets/proof_list_view.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/widgets/questions_list_view.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/widgets/references_list_view.dart';
+import 'package:collaborative_science_platform/services/share_page.dart';
+import 'package:collaborative_science_platform/utils/text_styles.dart';
+import 'package:collaborative_science_platform/widgets/annotation_text.dart';
+import 'package:collaborative_science_platform/widgets/app_button.dart';
+import 'package:collaborative_science_platform/widgets/card_container.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_tex/flutter_tex.dart';
+import 'package:go_router/go_router.dart';
+import 'dart:convert';
+
+class NodeDetails extends StatefulWidget {
+ final NodeDetailed node;
+ final ScrollController controller;
+ const NodeDetails({
+ super.key,
+ required this.node,
+ required this.controller,
+ });
+
+ @override
+ State createState() => _NodeDetailsState();
+}
+
+class _NodeDetailsState extends State {
+ int currentIndex = 0;
+
+ void updateIndex(int index) {
+ setState(() {
+ currentIndex = index;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: BoxDecoration(
+ color: Colors.grey[200],
+ ),
+ width: Responsive.isDesktop(context)
+ ? Responsive.desktopPageWidth * 0.8
+ : Responsive.getGenericPageWidth(context),
+ height: MediaQuery.of(context).size.height - 60,
+ child: SingleChildScrollView(
+ primary: false,
+ scrollDirection: Axis.vertical,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: CardContainer(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Padding(
+ padding: Responsive.isDesktop(context)
+ ? const EdgeInsets.all(70.0)
+ : const EdgeInsets.all(10.0),
+ child: AnnotationText(utf8.decode(widget.node.nodeTitle.codeUnits),
+ textAlign: TextAlign.center, style: TextStyles.title2)),
+ Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
+ Column(
+ children: [
+ SelectableText.rich(
+ TextSpan(children: [
+ const TextSpan(
+ text: "published on ",
+ style: TextStyles.bodyGrey,
+ ),
+ TextSpan(
+ text: widget.node.publishDateFormatted,
+ style: TextStyles.bodyBlack,
+ )
+ ]),
+ ),
+ ],
+ ),
+ Column(
+ children: [
+ Row(
+ children: [
+ SizedBox(
+ width: 110,
+ child: AppButton(
+ text: "Graph",
+ height: 40,
+ icon: const Icon(
+ CupertinoIcons.square_grid_3x2,
+ size: 16,
+ color: Colors.white,
+ ),
+ type: "secondary",
+ onTap: () {
+ context.push('${GraphPage.routeName}/${widget.node.nodeId}');
+ }),
+ ),
+ const SizedBox(width: 10),
+ SizedBox(
+ width: 110,
+ child: AppButton(
+ text: "Share",
+ icon: const Icon(
+ Icons.share,
+ size: 16,
+ color: Colors.white,
+ ),
+ height: 40,
+ type: "primary",
+ onTap: () => SharePage.shareNodeView(widget.node),
+ ),
+ ),
+ ],
+ )
+ ],
+ ),
+ ]),
+ ],
+ )),
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: NodeDetailsTabBar(
+ callback: updateIndex,
+ ),
+ ),
+ if (currentIndex == 0)
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: Container(
+ width: Responsive.desktopPageWidth,
+ decoration: BoxDecoration(color: Colors.grey[200]),
+ child: CardContainer(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: TeXView(
+ renderingEngine: const TeXViewRenderingEngine.katex(),
+ child: TeXViewDocument(
+ NodeHelper.getNodeContentLatex(widget.node, "long")))),
+ SelectableText.rich(
+ textAlign: TextAlign.start,
+ TextSpan(children: [
+ const TextSpan(
+ text: "published on ",
+ style: TextStyles.bodyGrey,
+ ),
+ TextSpan(
+ text: widget.node.publishDateFormatted,
+ style: TextStyles.bodyBlack,
+ )
+ ]),
+ ),
+ ],
+ )),
+ )),
+ if (currentIndex == 1)
+ //proofs
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: ProofListView(proof: widget.node.proof),
+ ),
+ if (currentIndex == 2)
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: ReferencesView(nodes: widget.node.references, ref: true),
+ ),
+ if (currentIndex == 3)
+ //citations
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: ReferencesView(nodes: widget.node.citations),
+ ),
+ if (currentIndex == 4)
+ //Q/A
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: QuestionsView(questions: widget.node.questions),
+ ),
+ if (currentIndex == 5)
+ //contributors
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: Contributors(
+ contributors: widget.node.contributors, controller: widget.controller)),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+bool containsMathExpression(String text) {
+ // Check if the text contains the '$' symbol indicating a mathematical expression
+ return text.contains(r'$');
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/node_details_nav_bar_item.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/node_details_nav_bar_item.dart
new file mode 100644
index 00000000..948e901e
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/node_details_nav_bar_item.dart
@@ -0,0 +1,83 @@
+import 'package:collaborative_science_platform/utils/colors.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:flutter/material.dart';
+
+class NavigationBarItem extends StatefulWidget {
+ final Function callback;
+ final int index;
+ final String text;
+ final IconData icon;
+ final bool isSelected;
+ const NavigationBarItem({
+ required this.callback,
+ required this.icon,
+ required this.index,
+ required this.isSelected,
+ required this.text,
+ super.key,
+ });
+
+ @override
+ State createState() => _NavigationBarItemState();
+}
+
+class _NavigationBarItemState extends State {
+ bool isHovering = false;
+ @override
+ Widget build(BuildContext context) {
+ return MouseRegion(
+ cursor: SystemMouseCursors.click,
+ onEnter: (event) => setState(() => isHovering = true),
+ onExit: (event) => setState(() => isHovering = false),
+ child: GestureDetector(
+ onTap: () => widget.callback(widget.index),
+ child: Container(
+ color: Colors.transparent,
+ child: Column(children: [
+ Padding(
+ padding: const EdgeInsets.all(8),
+ child: Column(
+ children: [
+ Center(
+ child: Row(
+ children: [
+ Icon(
+ widget.icon,
+ color: widget.isSelected
+ ? AppColors.secondaryColor
+ : isHovering
+ ? Colors.indigo[200]
+ : Colors.grey[700],
+ ),
+ if (!Responsive.isMobile(context))
+ Padding(
+ padding: const EdgeInsets.only(top: 4.0),
+ child: Text(
+ widget.text,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: widget.isSelected
+ ? FontWeight.w700
+ : isHovering
+ ? FontWeight.w600
+ : FontWeight.w500,
+ color: widget.isSelected
+ ? AppColors.secondaryColor
+ : isHovering
+ ? Colors.indigo[200]
+ : Colors.grey[700],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ )
+ ]),
+ ),
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/node_details_tab_bar.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/node_details_tab_bar.dart
new file mode 100644
index 00000000..21e2f77d
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/node_details_tab_bar.dart
@@ -0,0 +1,79 @@
+import 'package:collaborative_science_platform/screens/node_details_page/widgets/node_details_nav_bar_item.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:collaborative_science_platform/widgets/card_container.dart';
+import 'package:flutter/material.dart';
+
+class NodeDetailsTabBar extends StatefulWidget {
+ final Function callback;
+ const NodeDetailsTabBar({
+ super.key,
+ required this.callback,
+ });
+ @override
+ State createState() => _NodeDetailsTabBarState();
+}
+
+class _NodeDetailsTabBarState extends State {
+ int currentIndex = 0;
+
+ void updateIndex(int newIndex) {
+ setState(() {
+ currentIndex = newIndex;
+ });
+ widget.callback(newIndex);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return CardContainer(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ NavigationBarItem(
+ callback: updateIndex,
+ icon: Icons.my_library_books,
+ index: 0,
+ text: "Theorem",
+ isSelected: currentIndex == 0,
+ ),
+ NavigationBarItem(
+ callback: updateIndex,
+ icon: Icons.manage_search,
+ index: 1,
+ text: "Proofs",
+ isSelected: currentIndex == 1,
+ ),
+ NavigationBarItem(
+ callback: updateIndex,
+ icon: Icons.import_contacts,
+ index: 2,
+ text: "References",
+ isSelected: currentIndex == 2,
+ ),
+ NavigationBarItem(
+ callback: updateIndex,
+ icon: Icons.format_quote,
+ index: 3,
+ text: "Citations",
+ isSelected: currentIndex == 3,
+ ),
+ NavigationBarItem(
+ callback: updateIndex,
+ icon: Icons.question_answer,
+ index: 4,
+ isSelected: currentIndex == 4,
+ text: "Q/A",
+ ),
+ if (!Responsive.isDesktop(context))
+ NavigationBarItem(
+ callback: updateIndex,
+ icon: Icons.people,
+ index: 5,
+ isSelected: currentIndex == 5,
+ text: "Contributors",
+ ),
+ ],
+ ));
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/proof_list_view.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/proof_list_view.dart
new file mode 100644
index 00000000..148714fd
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/proof_list_view.dart
@@ -0,0 +1,78 @@
+import 'package:collaborative_science_platform/models/node_details_page/proof.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:collaborative_science_platform/utils/text_styles.dart';
+import 'package:collaborative_science_platform/widgets/card_container.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_tex/flutter_tex.dart';
+import 'dart:convert';
+
+class ProofListView extends StatelessWidget {
+ final List proof;
+ const ProofListView({super.key, required this.proof});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ width: Responsive.desktopPageWidth,
+ decoration: BoxDecoration(color: Colors.grey[200]),
+ child: ListView.builder(
+ scrollDirection: Axis.vertical,
+ shrinkWrap: true,
+ padding: const EdgeInsets.all(8),
+ itemCount: proof.length,
+ itemBuilder: (BuildContext context, int index) {
+ return Padding(
+ padding: const EdgeInsets.all(5),
+ child: CardContainer(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // Text(
+ // proof[index].isDisproof ? "Disproof" : "Proof",
+ // style: TextStyles.bodyGrey,
+ // textAlign: TextAlign.start,
+ // ),
+ // Text(
+ // proof[index].proofTitle,
+ // style: TextStyles.title4,
+ // textAlign: TextAlign.start,
+ // ),
+ TeXView(
+ renderingEngine: const TeXViewRenderingEngine.katex(),
+ child: TeXViewDocument(utf8.decode(proof[index].proofContent.codeUnits))),
+
+ // Row(
+ // mainAxisAlignment: MainAxisAlignment.end,
+ // crossAxisAlignment: CrossAxisAlignment.end,
+ // children: [
+ // Icon(
+ // proof[index].isValid ? Icons.check : Icons.clear,
+ // color: proof[index].isValid ? AppColors.successColor : AppColors.dangerColor,
+ // ),
+ // Text(
+ // proof[index].isValid ? "valid" : "invalid",
+ // style: TextStyles.bodyGrey,
+ // textAlign: TextAlign.end,
+ // ),
+ // ],
+ // ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ Text(
+ proof[index].publishDate.toString(),
+ style: TextStyles.bodyGrey,
+ textAlign: TextAlign.end,
+ )
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/questions_list_view.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/questions_list_view.dart
new file mode 100644
index 00000000..c57d1069
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/questions_list_view.dart
@@ -0,0 +1,61 @@
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:collaborative_science_platform/utils/text_styles.dart';
+import 'package:collaborative_science_platform/widgets/card_container.dart';
+import 'package:flutter/material.dart';
+
+import '../../../models/node_details_page/question.dart';
+
+class QuestionsView extends StatelessWidget {
+ final List questions;
+ const QuestionsView({super.key, required this.questions});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ width: Responsive.desktopPageWidth,
+ decoration: BoxDecoration(color: Colors.grey[200]),
+ child: ListView.builder(
+ scrollDirection: Axis.vertical,
+ shrinkWrap: true,
+ padding: const EdgeInsets.all(8),
+ itemCount: questions.length,
+ itemBuilder: (BuildContext context, int index) {
+ if (Responsive.isDesktop(context)) {
+ return Padding(
+ padding: const EdgeInsets.all(5),
+ child: CardContainer(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SelectableText(
+ "Q: ${questions[index].content}",
+ style: TextStyles.title4black,
+ textAlign: TextAlign.start,
+ ),
+ SelectableText(
+ "asked by ${questions[index].asker} at ${questions[index].createdAt}",
+ style: TextStyles.bodyGrey,
+ textAlign: TextAlign.end,
+ ),
+ SelectableText(
+ "A: ${questions[index].answer}",
+ style: TextStyles.bodyBlack,
+ textAlign: TextAlign.start,
+ ),
+ SelectableText(
+ "answered by ${questions[index].answerer} at ${questions[index].answeredAt}",
+ style: TextStyles.bodyGrey,
+ textAlign: TextAlign.end,
+ ),
+ ],
+ ),
+ ),
+ );
+ } else {
+ return const SizedBox();
+ }
+ }),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/references_list_view.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/references_list_view.dart
new file mode 100644
index 00000000..c541343f
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/references_list_view.dart
@@ -0,0 +1,63 @@
+import 'package:collaborative_science_platform/models/node.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/node_details_page.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:collaborative_science_platform/utils/text_styles.dart';
+import 'package:collaborative_science_platform/widgets/card_container.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+class ReferencesView extends StatelessWidget {
+ final List nodes;
+ final bool ref;
+ const ReferencesView({super.key, required this.nodes, this.ref = false});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ width: Responsive.desktopPageWidth,
+ decoration: BoxDecoration(color: Colors.grey[200]),
+ child: ListView.builder(
+ scrollDirection: Axis.vertical,
+ shrinkWrap: true,
+ padding: const EdgeInsets.all(8),
+ itemCount: nodes.length,
+ itemBuilder: (BuildContext context, int index) {
+ return Padding(
+ padding: const EdgeInsets.all(5),
+ child: CardContainer(
+ onTap: () {
+ context.push("${NodeDetailsPage.routeName}/${nodes[index].id}");
+ },
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SelectableText(
+ nodes[index].nodeTitle,
+ onTap: () => context.push("${NodeDetailsPage.routeName}/${nodes[index].id}"),
+ style: TextStyles.title4,
+ textAlign: TextAlign.start,
+ ),
+ SelectableText(
+ nodes[index]
+ .contributors
+ .map((e) => "by ${e.firstName} ${e.lastName}")
+ .join(", "),
+ onTap: () => context.push("${NodeDetailsPage.routeName}/${nodes[index].id}"),
+ style: TextStyles.bodyGrey,
+ textAlign: TextAlign.start,
+ ),
+ SelectableText(
+ nodes[index].publishDateFormatted,
+ onTap: () => context.push("${NodeDetailsPage.routeName}/${nodes[index].id}"),
+ style: TextStyles.bodyGrey,
+ textAlign: TextAlign.start,
+ ),
+ ],
+ ),
+ ),
+ );
+ }),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/suggestion_node_card.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/suggestion_node_card.dart
new file mode 100644
index 00000000..18506d4b
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/suggestion_node_card.dart
@@ -0,0 +1,63 @@
+import 'package:collaborative_science_platform/helpers/date_to_string.dart';
+import 'package:collaborative_science_platform/models/node.dart';
+import 'package:collaborative_science_platform/utils/colors.dart';
+import 'package:flutter/material.dart';
+
+class SuggestionNodeCard extends StatelessWidget {
+ final Node smallNode;
+ final Color? color;
+ final Function() onTap;
+
+ const SuggestionNodeCard({
+ super.key,
+ required this.smallNode,
+ this.color,
+ required this.onTap,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Card(
+ elevation: 1.0,
+ margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
+ color: Colors.white,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ child: InkWell(
+ onTap: onTap, // Navigate to the screen of the Node
+ customBorder: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SelectableText(
+ smallNode.nodeTitle,
+ onTap: onTap,
+ style: const TextStyle(
+ fontWeight: FontWeight.bold, fontSize: 10.0, color: AppColors.primaryDarkColor),
+ ),
+ const SizedBox(height: 8.0),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ Text(
+ getDurationFromNow(smallNode.publishDate),
+ style: const TextStyle(
+ color: Colors.grey,
+ fontSize: 8,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/you_may_like.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/you_may_like.dart
new file mode 100644
index 00000000..03027701
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/you_may_like.dart
@@ -0,0 +1,68 @@
+import 'package:collaborative_science_platform/models/node.dart';
+import 'package:collaborative_science_platform/providers/node_provider.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/node_details_page.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/widgets/suggestion_node_card.dart';
+import 'package:collaborative_science_platform/utils/colors.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:provider/provider.dart';
+
+class YouMayLike extends StatelessWidget {
+ final bool isLoading;
+ final bool error;
+ const YouMayLike({super.key, required this.isLoading, required this.error});
+
+ @override
+ Widget build(BuildContext context) {
+ final List nodes = Provider.of(context).youMayLikeNodeResult;
+
+ return Container(
+ padding: const EdgeInsets.only(top: 16),
+ width: 300,
+ decoration: const BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.all(Radius.circular(10)),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ const Text(
+ "You may also like",
+ style: TextStyle(
+ color: AppColors.secondaryDarkColor,
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const SizedBox(
+ height: 10,
+ ),
+ error
+ ? const Text(
+ "Something went wrong!",
+ style: TextStyle(color: Colors.red),
+ )
+ : isLoading
+ ? const Center(
+ child: CircularProgressIndicator(),
+ )
+ : nodes.isEmpty
+ ? const Text("No nodes found")
+ : Expanded(
+ child: ListView.builder(
+ shrinkWrap: true,
+ itemCount: nodes.length,
+ itemBuilder: (context, index) {
+ return SuggestionNodeCard(
+ smallNode: nodes[index],
+ onTap: () {
+ context.push('${NodeDetailsPage.routeName}/${nodes[index].id}');
+ });
+ },
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/notifications_page/notifications_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/notifications_page/notifications_page.dart
new file mode 100644
index 00000000..b9b8ef5d
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/notifications_page/notifications_page.dart
@@ -0,0 +1,14 @@
+import 'package:collaborative_science_platform/screens/home_page/widgets/home_page_appbar.dart';
+import 'package:collaborative_science_platform/screens/page_with_appbar/page_with_appbar.dart';
+import 'package:flutter/material.dart';
+
+class NotificationPage extends StatelessWidget {
+ static const routeName = '/notifications';
+ const NotificationPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const PageWithAppBar(
+ appBar: HomePageAppBar(), child: Text("Notifications")); // Profile Page Content
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/page_with_appbar.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/page_with_appbar.dart
new file mode 100644
index 00000000..62f6c772
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/page_with_appbar.dart
@@ -0,0 +1,62 @@
+import 'package:flutter/material.dart';
+
+/// A widget for creating a page with a customizable app bar and content.
+///
+/// This widget allows you to create a page with an app bar and content area.
+/// The app bar can be customized using the [appBar] parameter, and the content
+/// can be set using the [child] parameter. You can also specify the background
+/// color of the content area using the [pageColor] parameter and control
+/// whether the content is scrollable with the [isScrollable] parameter.
+class PageWithAppBar extends StatelessWidget {
+ final Widget child;
+ final Widget appBar;
+ final Color pageColor;
+ final bool isScrollable;
+ final Navigator? navigator;
+ final FloatingActionButton? floatingActionButton;
+
+ /// Creates a [PageWithAppBar] widget.
+ ///
+ /// The [child] parameter represents the content to be displayed below the app bar.
+ /// The [appBar] parameter is a widget that serves as the app bar.
+ /// The [pageColor] parameter specifies the background color of the content area (default: Colors.white).
+ /// The [isScrollable] parameter indicates whether the content is scrollable (default: true).
+ const PageWithAppBar(
+ {required this.child,
+ required this.appBar,
+ this.pageColor = Colors.white,
+ this.isScrollable = true,
+ this.navigator,
+ this.floatingActionButton,
+ super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return SafeArea(
+ child: SelectionArea(
+ child: Scaffold(
+ backgroundColor: Colors.white,
+ resizeToAvoidBottomInset: true,
+ floatingActionButton: floatingActionButton,
+ body: SingleChildScrollView(
+ physics:
+ isScrollable ? const BouncingScrollPhysics() : const NeverScrollableScrollPhysics(),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ appBar,
+ Divider(
+ height: 0,
+ thickness: 2,
+ color: Colors.grey[300],
+ ),
+ child,
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/app_bar_button.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/app_bar_button.dart
new file mode 100644
index 00000000..5370e0d4
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/app_bar_button.dart
@@ -0,0 +1,39 @@
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:flutter/material.dart';
+
+class AppBarButton extends StatelessWidget {
+ final Function() onPressed;
+ final IconData icon;
+ final String text;
+ const AppBarButton({super.key, required this.icon, required this.text, required this.onPressed});
+
+ @override
+ Widget build(BuildContext context) {
+ return Responsive(mobile: mobile(), desktop: mobile());
+ }
+
+ Widget desktop() {
+ return ElevatedButton(
+ onPressed: onPressed,
+ style: ElevatedButton.styleFrom(
+ elevation: 0,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8),
+ ),
+ backgroundColor: Colors.grey[100],
+ padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6),
+ ),
+ child: Row(
+ children: [
+ Text(text),
+ const SizedBox(width: 3),
+ Icon(icon),
+ ],
+ ),
+ );
+ }
+
+ Widget mobile() {
+ return IconButton(onPressed: onPressed, icon: Icon(icon));
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/app_bar_logo.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/app_bar_logo.dart
new file mode 100644
index 00000000..74a3accc
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/app_bar_logo.dart
@@ -0,0 +1,32 @@
+import 'package:collaborative_science_platform/screens/home_page/home_page.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:go_router/go_router.dart';
+
+class AppBarLogo extends StatelessWidget {
+ final String logoPath;
+ final double height;
+
+ const AppBarLogo({
+ this.logoPath = 'assets/images/logo_small.svg',
+ required this.height,
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return MouseRegion(
+ cursor: SystemMouseCursors.click,
+ child: GestureDetector(
+ onTap: () => context.go(HomePage.routeName),
+ child: Container(
+ color: Colors.transparent,
+ child: SvgPicture.asset(
+ logoPath,
+ height: height,
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/profile_menu.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/profile_menu.dart
new file mode 100644
index 00000000..e895fa44
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/profile_menu.dart
@@ -0,0 +1,104 @@
+import 'package:collaborative_science_platform/providers/auth.dart';
+import 'package:collaborative_science_platform/screens/auth_screens/login_page.dart';
+import 'package:collaborative_science_platform/screens/auth_screens/signup_page.dart';
+import 'package:collaborative_science_platform/screens/profile_page/profile_page.dart';
+import 'package:collaborative_science_platform/services/screen_navigation.dart';
+import 'package:collaborative_science_platform/screens/page_with_appbar/widgets/app_bar_button.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:go_router/go_router.dart';
+
+class ProfileMenu extends StatelessWidget {
+ const ProfileMenu({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final auth = Provider.of(context);
+ return auth.isSignedIn ? AuthenticatedProfileMenu() : UnAuthenticatedProfileMenu();
+ }
+}
+
+class AuthenticatedProfileMenu extends StatelessWidget {
+ final GlobalKey> _popupMenu = GlobalKey();
+ AuthenticatedProfileMenu({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final auth = Provider.of(context);
+ return PopupMenuButton(
+ key: _popupMenu,
+ position: PopupMenuPosition.under,
+ color: Colors.grey[200],
+ onSelected: (String result) async {
+ switch (result) {
+ case 'profile':
+ Provider.of(context, listen: false)
+ .setSelectedTab(ScreenTab.profile, context);
+ final String encodedEmail = Uri.encodeComponent(auth.user!.email);
+ context.push('${ProfilePage.routeName}/$encodedEmail');
+ break;
+ case 'logout':
+ auth.logout();
+ break;
+ default:
+ }
+ },
+ child: AppBarButton(
+ icon: CupertinoIcons.chevron_down,
+ text: Provider.of(context).user!.firstName,
+ onPressed: () => _popupMenu.currentState!.showButtonMenu(),
+ ),
+ itemBuilder: (BuildContext context) => >[
+ const PopupMenuItem(
+ value: 'profile',
+ child: Text("Profile"),
+ ),
+ const PopupMenuItem(
+ value: 'logout',
+ child: Text("Logout"),
+ )
+ ],
+ );
+ }
+}
+
+class UnAuthenticatedProfileMenu extends StatelessWidget {
+ final GlobalKey> _popupMenu = GlobalKey();
+ UnAuthenticatedProfileMenu({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return PopupMenuButton(
+ key: _popupMenu,
+ position: PopupMenuPosition.under,
+ color: Colors.grey[200],
+ onSelected: (String result) async {
+ switch (result) {
+ case 'signin':
+ context.go(LoginPage.routeName);
+ break;
+ case 'signup':
+ context.go(SignUpPage.routeName);
+ break;
+ default:
+ }
+ },
+ child: AppBarButton(
+ icon: CupertinoIcons.chevron_down,
+ text: "Sign In",
+ onPressed: () => _popupMenu.currentState!.showButtonMenu(),
+ ),
+ itemBuilder: (BuildContext context) => >[
+ const PopupMenuItem(
+ value: 'signin',
+ child: Text("Sign In"),
+ ),
+ const PopupMenuItem(
+ value: 'signup',
+ child: Text("Sign Up"),
+ )
+ ],
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/top_navigation_bar.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/top_navigation_bar.dart
new file mode 100644
index 00000000..0907a802
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/top_navigation_bar.dart
@@ -0,0 +1,162 @@
+import 'package:collaborative_science_platform/providers/auth.dart';
+import 'package:collaborative_science_platform/services/screen_navigation.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class TopNavigationBar extends StatelessWidget {
+ const TopNavigationBar({
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final ScreenNavigation screenNavigation = Provider.of(context);
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ NavigationBarItem(
+ icon: Icons.dashboard,
+ value: ScreenTab.home,
+ text: "Home",
+ isSelected: screenNavigation.selectedTab == ScreenTab.home,
+ ),
+ NavigationBarItem(
+ icon: Icons.graphic_eq,
+ value: ScreenTab.graph,
+ text: "Graph",
+ isSelected: screenNavigation.selectedTab == ScreenTab.graph,
+ ),
+ NavigationBarItem(
+ icon: Icons.workspaces,
+ value: ScreenTab.workspaces,
+ isSelected: screenNavigation.selectedTab == ScreenTab.workspaces,
+ text: "Workspaces",
+ ),
+ if (Responsive.isMobile(context))
+ NavigationBarItem(
+ icon: Icons.notifications,
+ value: ScreenTab.notifications,
+ isSelected: screenNavigation.selectedTab == ScreenTab.notifications,
+ text: "Notifications",
+ ),
+ if (Responsive.isMobile(context))
+ NavigationBarItem(
+ icon: Icons.person,
+ value: ScreenTab.profile,
+ isSelected: screenNavigation.selectedTab == ScreenTab.profile,
+ text: "Profile",
+ ),
+ ],
+ );
+ }
+}
+
+class NavigationBarItem extends StatefulWidget {
+ final ScreenTab value;
+ final String text;
+ final IconData icon;
+ final bool isSelected;
+ const NavigationBarItem({
+ required this.icon,
+ required this.value,
+ required this.isSelected,
+ required this.text,
+ super.key,
+ });
+
+ @override
+ State createState() => _NavigationBarItemState();
+}
+
+class _NavigationBarItemState extends State {
+ bool isHovering = false;
+ @override
+ Widget build(BuildContext context) {
+ return MouseRegion(
+ cursor: SystemMouseCursors.click,
+ onEnter: (event) => setState(() => isHovering = true),
+ onExit: (event) => setState(() => isHovering = false),
+ child: GestureDetector(
+ onTap: () {
+ ScreenTab selected = widget.value;
+ if (selected == ScreenTab.profile) {
+ String userEmail = Provider.of(context, listen: false).user?.email ?? "";
+ Provider.of(context, listen: false)
+ .setSelectedTab(selected, context, email: Uri.decodeComponent(userEmail));
+ return;
+ } else {
+ Provider.of(context, listen: false).setSelectedTab(selected, context);
+ }
+ },
+ child: Container(
+ padding: const EdgeInsets.only(top: 16),
+ color: isHovering ? Colors.grey[300] : Colors.transparent,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Padding(
+ padding: (Responsive.isMobile(context))
+ ? const EdgeInsets.symmetric(horizontal: 0, vertical: 0)
+ : const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
+ child: Column(
+ children: [
+ Center(
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Icon(
+ widget.icon,
+ size: isHovering ? 32 : 28.0,
+ color: widget.isSelected
+ ? Colors.indigo[600]
+ : isHovering
+ ? Colors.indigo[200]
+ : Colors.grey[700],
+ ),
+ if (!Responsive.isMobile(context))
+ Padding(
+ padding: const EdgeInsets.only(top: 4.0),
+ child: Text(
+ widget.text,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: widget.isSelected
+ ? FontWeight.w700
+ : isHovering
+ ? FontWeight.w600
+ : FontWeight.w500,
+ color: widget.isSelected
+ ? Colors.indigo[600]
+ : isHovering
+ ? Colors.indigo[200]
+ : Colors.grey[700],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ //const SizedBox(height: 12),
+ /* Container(
+ color: widget.isSelected ? Colors.indigo[600] : Colors.transparent,
+ height: 4,
+ width: 150,
+ ) */
+ ],
+ ),
+ ),
+ const SizedBox(height: 8),
+ Container(
+ color: widget.isSelected ? Colors.indigo[600] : Colors.transparent,
+ height: 5,
+ width: MediaQuery.of(context).size.width / 5,
+ ),
+ ]),
+ ),
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/account_settings_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/account_settings_page.dart
new file mode 100644
index 00000000..78119d29
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/account_settings_page.dart
@@ -0,0 +1,18 @@
+import 'package:collaborative_science_platform/screens/page_with_appbar/page_with_appbar.dart';
+import 'package:collaborative_science_platform/screens/profile_page/widgets/account_settings_form.dart';
+import 'package:collaborative_science_platform/widgets/simple_app_bar.dart';
+import 'package:flutter/material.dart';
+
+class AccountSettingsPage extends StatelessWidget {
+ static const routeName = '/account-settings';
+ const AccountSettingsPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const PageWithAppBar(
+ appBar: SimpleAppBar(title: "Account Settings"),
+ child: AccountSettingsForm(),
+ );
+ }
+}
+
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/change_password_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/change_password_page.dart
new file mode 100644
index 00000000..c006ac87
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/change_password_page.dart
@@ -0,0 +1,20 @@
+//import 'package:collaborative_science_platform/models/user.dart';
+//import 'package:collaborative_science_platform/screens/page_with_appbar/page_with_appbar.dart';
+//import 'package:collaborative_science_platform/screens/profile_page/widgets/change_password_form.dart';
+//import 'package:collaborative_science_platform/widgets/simple_app_bar.dart';
+//import 'package:flutter/material.dart';
+//
+//class ChangePasswordPage extends StatelessWidget {
+// final User user;
+// static const routeName = '/change-password';
+// const ChangePasswordPage({super.key, required this.user});
+//
+// @override
+// Widget build(BuildContext context) {
+// return const PageWithAppBar(
+// appBar: SimpleAppBar(title: "Account Settings"),
+// child: ChangePasswordForm(user: widget.user),
+// );
+// }
+//}
+
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/profile_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/profile_page.dart
new file mode 100644
index 00000000..adc8b502
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/profile_page.dart
@@ -0,0 +1,338 @@
+import 'package:collaborative_science_platform/exceptions/profile_page_exceptions.dart';
+import 'package:collaborative_science_platform/models/profile_data.dart';
+import 'package:collaborative_science_platform/models/user.dart';
+import 'package:collaborative_science_platform/providers/auth.dart';
+import 'package:collaborative_science_platform/providers/profile_data_provider.dart';
+import 'package:collaborative_science_platform/screens/home_page/widgets/home_page_appbar.dart';
+import 'package:collaborative_science_platform/screens/node_details_page/node_details_page.dart';
+import 'package:collaborative_science_platform/screens/page_with_appbar/page_with_appbar.dart';
+import 'package:collaborative_science_platform/screens/profile_page/widgets/about_me.dart';
+import 'package:collaborative_science_platform/screens/profile_page/widgets/desktop_edit_profile_button.dart';
+import 'package:collaborative_science_platform/screens/profile_page/widgets/logout_button.dart';
+import 'package:collaborative_science_platform/screens/profile_page/widgets/mobile_edit_profile_button.dart';
+import 'package:collaborative_science_platform/screens/profile_page/widgets/profile_activity_tabbar.dart';
+import 'package:collaborative_science_platform/screens/profile_page/widgets/profile_node_card.dart';
+import 'package:collaborative_science_platform/screens/profile_page/widgets/question_activity.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:collaborative_science_platform/widgets/card_container.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:go_router/go_router.dart';
+
+class ProfilePage extends StatefulWidget {
+ static const routeName = '/profile';
+ final String email;
+
+ const ProfilePage({super.key, required this.email});
+
+ @override
+ State createState() => _ProfilePageState();
+}
+
+// TODO: add optional parameter to ProfilePage to get others profileData
+class _ProfilePageState extends State {
+ ProfileData profileData = ProfileData();
+ int noWorks = 0;
+ bool error = false;
+ String errorMessage = "";
+ bool isLoading = false;
+
+ bool _isFirstTime = true;
+
+ int currentIndex = 0;
+
+ void updateIndex(int index) {
+ setState(() {
+ currentIndex = index;
+ });
+ }
+
+ @override
+ void didChangeDependencies() {
+ if (_isFirstTime) {
+ try {
+ getUserData();
+ } catch (e) {
+ setState(() {
+ error = true;
+ errorMessage = "Something went wrong!";
+ });
+ }
+ _isFirstTime = false;
+ }
+ super.didChangeDependencies();
+ }
+
+ void getUserData() async {
+ try {
+ if (widget.email != "") {
+ final profileDataProvider = Provider.of(context);
+ setState(() {
+ isLoading = true;
+ });
+ await profileDataProvider.getData(widget.email);
+ setState(() {
+ profileData = (profileDataProvider.profileData ?? {} as ProfileData);
+ noWorks = profileData.nodes.length;
+ });
+ } else {
+ final User user = Provider.of(context).user!;
+ final profileDataProvider = Provider.of(context);
+ await profileDataProvider.getData(user.email);
+ setState(() {
+ profileData = (profileDataProvider.profileData ?? {} as ProfileData);
+ noWorks = profileData.nodes.length;
+ });
+ }
+ } on ProfileDoesNotExist {
+ setState(() {
+ error = true;
+ errorMessage = ProfileDoesNotExist().message;
+ });
+ } catch (e) {
+ setState(() {
+ error = true;
+ errorMessage = "Something went wrong!";
+ });
+ } finally {
+ setState(() {
+ isLoading = false;
+ });
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final User? user = Provider.of(context).user;
+ if (user == null) {
+ // guest can see profile pages
+ } else if (user.email == profileData.email) {
+ // own profile page, should be editible
+ return PageWithAppBar(
+ appBar: const HomePageAppBar(),
+ pageColor: Colors.grey.shade200,
+ child: Responsive(
+ mobile: SingleChildScrollView(
+ child: SizedBox(
+ width: Responsive.getGenericPageWidth(context),
+ child: isLoading
+ ? Container(
+ decoration: const BoxDecoration(color: Colors.white),
+ child: const Center(
+ child: CircularProgressIndicator(),
+ ),
+ )
+ : error
+ ? SelectableText(
+ errorMessage,
+ style: const TextStyle(color: Colors.red),
+ textAlign: TextAlign.center,
+ )
+ : Column(
+ children: [
+ AboutMe(
+ aboutMe: profileData.aboutMe,
+ email: profileData.email,
+ name: profileData.name,
+ surname: profileData.surname,
+ noWorks: noWorks,
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 10),
+ child: Row(
+ children: [
+ const Expanded(child: MobileEditProfileButton()),
+ Expanded(child: LogOutButton())
+ ],
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: ProfileActivityTabBar(
+ callback: updateIndex,
+ ),
+ ),
+ if (currentIndex == 0)
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: CardContainer(
+ child: ListView.builder(
+ padding: const EdgeInsets.all(0),
+ scrollDirection: Axis.vertical,
+ shrinkWrap: true,
+ itemCount: profileData.nodes.length,
+ itemBuilder: (context, index) {
+ return ProfileNodeCard(
+ profileNode: profileData.nodes.elementAt(index),
+ onTap: () {
+ context.push(
+ '${NodeDetailsPage.routeName}/${profileData.nodes.elementAt(index).id}');
+ },
+ );
+ },
+ ),
+ ),
+ ),
+ if (currentIndex == 1)
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: CardContainer(
+ child: SizedBox(
+ height: 400,
+ child: QuestionActivity(),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ desktop: SingleChildScrollView(
+ child: SizedBox(
+ width: Responsive.getGenericPageWidth(context),
+ child: isLoading
+ ? Container(
+ decoration: const BoxDecoration(color: Colors.white),
+ padding: const EdgeInsets.only(top: 20),
+ child: const Center(
+ child: CircularProgressIndicator(),
+ ),
+ )
+ : error
+ ? SelectableText(
+ errorMessage,
+ style: const TextStyle(color: Colors.red),
+ textAlign: TextAlign.center,
+ )
+ : Column(
+ children: [
+ AboutMe(
+ aboutMe: profileData.aboutMe,
+ email: profileData.email,
+ name: profileData.name,
+ surname: profileData.surname,
+ noWorks: noWorks,
+ ),
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: DesktopEditProfileButton(),
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: ProfileActivityTabBar(
+ callback: updateIndex,
+ ),
+ ),
+ if (currentIndex == 0)
+ CardContainer(
+ child: ListView.builder(
+ padding: const EdgeInsets.all(0),
+ scrollDirection: Axis.vertical,
+ shrinkWrap: true,
+ itemCount: profileData.nodes.length,
+ itemBuilder: (context, index) {
+ return ProfileNodeCard(
+ profileNode: profileData.nodes.elementAt(index),
+ onTap: () {
+ context.push(
+ '${NodeDetailsPage.routeName}/${profileData.nodes.elementAt(index).id}');
+ },
+ );
+ },
+ ),
+ ),
+ if (currentIndex == 1)
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: CardContainer(
+ child: SizedBox(
+ height: 400,
+ child: QuestionActivity(),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ // others profile page, will be same both on desktop and mobile
+ return PageWithAppBar(
+ appBar: const HomePageAppBar(),
+ pageColor: Colors.grey.shade200,
+ child: SingleChildScrollView(
+ child: SizedBox(
+ width: Responsive.getGenericPageWidth(context),
+ child: isLoading
+ ? Container(
+ decoration: const BoxDecoration(color: Colors.white),
+ padding: const EdgeInsets.only(top: 20),
+ child: const Center(
+ child: CircularProgressIndicator(),
+ ),
+ )
+ : error
+ ? SelectableText(
+ errorMessage,
+ style: const TextStyle(color: Colors.red),
+ textAlign: TextAlign.center,
+ )
+ : Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ AboutMe(
+ aboutMe: profileData.aboutMe,
+ email: profileData.email,
+ name: profileData.name,
+ surname: profileData.surname,
+ noWorks: noWorks,
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: ProfileActivityTabBar(
+ callback: updateIndex,
+ ),
+ ),
+ if (currentIndex == 0)
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: CardContainer(
+ child: ListView.builder(
+ padding: const EdgeInsets.all(0),
+ physics:
+ const NeverScrollableScrollPhysics(), // Prevents a conflict with SingleChildScrollView
+ scrollDirection: Axis.vertical,
+ shrinkWrap: true,
+ itemCount: profileData.nodes.length,
+ itemBuilder: (context, index) {
+ return ProfileNodeCard(
+ profileNode: profileData.nodes.elementAt(index),
+ onTap: () {
+ context.push(
+ '${NodeDetailsPage.routeName}/${profileData.nodes.elementAt(index).id}');
+ },
+ );
+ },
+ ),
+ ),
+ ),
+ if (currentIndex == 1)
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: CardContainer(
+ child: SizedBox(
+ height: 400,
+ child: QuestionActivity(),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/about_me.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/about_me.dart
new file mode 100644
index 00000000..77c7e73b
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/about_me.dart
@@ -0,0 +1,106 @@
+import 'package:collaborative_science_platform/utils/colors.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:flutter/material.dart';
+
+class AboutMe extends StatelessWidget {
+ final String email;
+ final String name;
+ final String surname;
+ final int noWorks;
+ final String aboutMe;
+ const AboutMe(
+ {super.key,
+ required this.email,
+ required this.name,
+ required this.surname,
+ required this.noWorks,
+ required this.aboutMe});
+
+ @override
+ Widget build(BuildContext context) {
+ return SizedBox(
+ width: MediaQuery.of(context).size.width,
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Row(
+ children: [
+ SelectableText(
+ "$name $surname",
+ style: const TextStyle(
+ color: AppColors.primaryDarkColor,
+ fontWeight: FontWeight.bold,
+ fontSize: 40,
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 10,
+ ),
+ Row(
+ children: [
+ SizedBox(
+ width: Responsive.isMobile(context)
+ ? MediaQuery.of(context).size.width * 0.9
+ : MediaQuery.of(context).size.width * 0.5,
+ child: SelectableText(
+ aboutMe,
+ style: const TextStyle(
+ fontWeight: FontWeight.normal,
+ fontSize: 20,
+ ),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 10,
+ ),
+ Row(
+ children: [
+ const Icon(
+ Icons.mail,
+ color: AppColors.secondaryColor,
+ size: 20,
+ ),
+ const SizedBox(
+ width: 10,
+ ),
+ SelectableText(
+ email,
+ style: const TextStyle(
+ fontSize: 20,
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 20,
+ ),
+ Row(
+ children: [
+ SelectableText(
+ "Published works: $noWorks",
+ style: const TextStyle(
+ fontWeight: FontWeight.normal,
+ fontSize: 20,
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 10,
+ ),
+ const Divider(
+ color: AppColors.tertiaryColor,
+ )
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/about_me_edit.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/about_me_edit.dart
new file mode 100644
index 00000000..dd920e71
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/about_me_edit.dart
@@ -0,0 +1,37 @@
+import 'package:collaborative_science_platform/utils/colors.dart';
+import 'package:flutter/material.dart';
+
+class AboutMeEdit extends StatelessWidget {
+ final TextEditingController controller;
+ const AboutMeEdit(this.controller, {super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ padding: const EdgeInsets.symmetric(horizontal: 5),
+ height: 180,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(4),
+ border: Border.all(
+ color: AppColors.primaryColor,
+ ),
+ color: Colors.white,
+ ),
+ child: TextField(
+ autocorrect: false,
+ keyboardType: TextInputType.multiline,
+ minLines: 1,
+ maxLines: 10,
+ controller: controller,
+ cursorColor: AppColors.primaryColor,
+ textAlignVertical: TextAlignVertical.top,
+ style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w400),
+ decoration: const InputDecoration(
+ hintText: "Type your description",
+ hintStyle: TextStyle(fontSize: 12, fontWeight: FontWeight.w400),
+ border: InputBorder.none,
+ ),
+ ),
+ );
+ }
+}
diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/account_settings_form.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/account_settings_form.dart
new file mode 100644
index 00000000..e0165940
--- /dev/null
+++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/account_settings_form.dart
@@ -0,0 +1,184 @@
+import 'package:collaborative_science_platform/models/profile_data.dart';
+import 'package:collaborative_science_platform/models/user.dart';
+import 'package:collaborative_science_platform/providers/auth.dart';
+import 'package:collaborative_science_platform/providers/settings_provider.dart';
+import 'package:collaborative_science_platform/screens/profile_page/widgets/about_me_edit.dart';
+import 'package:collaborative_science_platform/screens/profile_page/widgets/change_password_form.dart';
+import 'package:collaborative_science_platform/utils/colors.dart';
+import 'package:collaborative_science_platform/utils/responsive/responsive.dart';
+import 'package:collaborative_science_platform/utils/text_styles.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class AccountSettingsForm extends StatefulWidget {
+ const AccountSettingsForm({super.key});
+
+ @override
+ State createState() => _AccountSettingsFormState();
+}
+
+class _AccountSettingsFormState extends State {
+ ProfileData profileData = ProfileData();
+ final passwordController = TextEditingController();
+ final aboutMeController = TextEditingController();
+
+ final passwordFocusNode = FocusNode();
+ final aboutMeFocusNode = FocusNode();
+
+ bool isSwitched = false;
+ bool isSwitched2 = false;
+ bool error = false;
+ String message = "";
+
+ @override
+ void dispose() {
+ passwordController.dispose();
+ aboutMeController.dispose();
+ passwordFocusNode.dispose();
+ aboutMeFocusNode.dispose();
+ super.dispose();
+ }
+
+ void changePreff() async {
+ try {
+ final User? user = Provider.of